diff --git a/licence.txt b/LICENCE.txt
similarity index 100%
rename from licence.txt
rename to LICENCE.txt
diff --git a/README.md b/README.md
index cda4897e208c55ec8ff3a2d7a67e5454380690a5..3cc84a35c0f680b71459928e018e4a454ba8b3c5 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,83 @@
 # dlc_flytracker
 
+Python package for the tracking the 3d kinematics of animals (currently only flies)  build around [DeepLabCut](https://github.com/DeepLabCut/DeepLabCut)
 
+![???image](/paths/to/image.png)
 
+## Introduction
+The package allow the fitting of an animal (chosen) 3d skeleton to the tracking results from DeepLabCut.
 
+The usual steps:
+- **Post-processing of video recordings (at least 2 cameras):** image enhancement, dynamic cropping around moving animal and the stitching of multiple camera views (to improve tracking with DeepLabCut)
+- **Define the joints of the animal skeleton** that will be used for the tracking. You can use a previously made skeleton (e.g. flies), or make your own from body modules (see the section ??? below to see an example).
+- **(Outside this package) Training of a DeepLabCut (DLC) network:** usually done using the DLC user interface. Require to manually label 100-200 images. See the section ??? below to see an example, and how DLC settings can be tweaked in order to get good tracking results with stitched images.
+- **Run DLC tracking on the full dataset**
+- **Load the DLC results in dlc_flytracker**
+- **Reconstruct 3d coordinates of the skeleton joints** from their 2d coordinates tracked with DeepLabCut
+- **Optimize fit the 3d skeleton** to the 2d/3d coordinates of the joints
+- **Post-processing of the kinematics parameters** 
+
+
+## Citation
+???
+
+
+## Getting Started
+### Prerequisite
+dlc_flytracker requires `deeplabcut`, `tensorflow` and other common packages like `numpy`, `scipy`, `matplotlib`. For a full list, you can check the file [requirements.txt](requirements.txt)
+
+### Installing
+???
+
+
+## Usage
+Here the main steps introduced earlier are explained in details with examples.
+
+### Post-processing of video recordings 
+??? (at least 2 cameras): 
+- **Image enhancement:** ???
+- **Dynamic cropping around moving animal:** ???
+- **Stitching of multiple camera views:** ??? (to improve tracking with DeepLabCut)
+
+![???figure showing post-processing steps](/paths/to/image.png)
+
+
+### Build animal skeleton
+???
+
+#### in DeepLabCut
+???
+
+#### in dlc_flytracker
+???
+
+### Training a network in DeepLabCut
+- ??? using the DLC user interface. Require to manually label 100-200 images. See the section ??? below to see an example, and how DLC settings can be tweaked in order to get good tracking results with stitched images. 
+
+### Run DLC tracking on the full dataset
+???
+
+### Load the DLC results in dlc_flytracker
+???
+
+### Reconstruct 3d coordinates of the skeleton joints from their 2d coordinates tracked with DeepLabCut
+???
+
+### Optimize fit the 3d skeleton to the 2d/3d coordinates of the joints
+???
+
+### Post-processing of the kinematics parameters
+???
+
+## Notes
+### Running the tests
+To run tests locally, type:
+```bash
+> pytest dlc_flytracker
+```
+
+
+## License
+
+This project utilizes the [LGPL LICENSE](LICENCE.txt). 
+Allows developers and companies to use and integrate a software component released under the LGPL into their own (even proprietary) software without being required by the terms of a strong copyleft license to release the source code of their own components. However, any developer who modifies an LGPL-covered component is required to make their modified version available under the same LGPL license.
\ No newline at end of file
diff --git a/cam/__init__.py b/cam/__init__.py
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/cam/calib.py b/cam/calib.py
deleted file mode 100644
index 118bdddb8b3db29294dec0573bc85b5c7e2ded47..0000000000000000000000000000000000000000
--- a/cam/calib.py
+++ /dev/null
@@ -1,372 +0,0 @@
-"""
-I found this code at https://www.mail-archive.com/floatcanvas@mithis.com/msg00513.html
-(author: Marcos Duarte) and I made several small modifications (me: Antoine Cribellier)
-
-
-Camera calibration and point reconstruction based on direct linear transformation (DLT).
-
-The fundamental problem here is to find a mathematical relationship between the 
- coordinates  of a 3D point and its projection onto the images plane. The DLT
- (a linear apporximation to this problem) is derived from modelling the object 
- and its projection on the images plane as a pinhole camera situation.
-In simplistic terms, using the pinhole camera model, it can be found by similar
- triangles the following relation between the images coordinates (u,v) and the 3D
- point (X,Y,Z):
-   [ u ]   [ L1  L2  L3  L4 ] [ X ]
-   [ v ] = [ L5  L6  L7  L8 ] [ Y ]
-   [ 1 ]   [ L9 L10 L11 L12 ] [ Z ]
-                              [ 1 ]
-The matrix L is kwnown as the camera matrix or camera projection matrix. For a
- 2D point (X,Y), the last column of the matrix doesn't exist. In fact, the L12 
- term (or L9 for 2D DLT) is not independent from the other parameters and then 
- there are only 11 (or 8 for 2D DLT) independent parameters in the DLT to be 
- determined.
-
-DLT is typically used in two steps: 1. camera calibration and 2. object (point)
- reconstruction.
-The camera calibration step consists in digitizing points with known coordiantes
- in the real space.
-At least 4 points are necessary for the calibration of a plane (2D DLT) and at 
- least 6 points for the calibration of a volume (3D DLT). For the 2D DLT, at least
- one view of the object (points) must be entered. For the 3D DLT, at least 2 
- different views of the object (points) must be entered.
-These coordinates (from the object and images(s)) are inputed to the calib
- algorithm which  estimates the camera parameters (8 for 2D DLT and 11 for 3D DLT).
-With these camera parameters and with the camera(s) at the same position of the
- calibration step,  we now can reconstruct the real position of any point inside 
- the calibrated space (area for 2D DLT and volume for the 3D DLT) from the point 
- position(s) viewed by the same fixed camera(s).
-
-This code can perform 2D or 3D DLT with any number of views (cameras).
-For 3D DLT, at least two views (cameras) are necessary.
-
-There are more accurate (but more complex) algorithms for camera calibration that
- also consider lens distortion. For example, OpenCV and Tsai softwares have been
- ported to Python. However, DLT is classic, simple, and effective (fast) for 
- most applications.
-
-About DLT, see: http://kwon3d.com/theory/dlt/html
-
-This code is based on different implementations and teaching material on DLT 
- found in the internet.
-"""
-
-import numpy as np
-
-
-def calib(nd, xyz, uv):
-    """
-    Camera calibration by DLT using known object points and their images points.
-
-    This code performs 2D or 3D DLT camera calibration with any number of views (cameras).
-    For 3D DLT, at least two views (cameras) are necessary.
-
-    The coordinates (x,y,z and u,v) are given as columns and the different points as rows.
-    For the 2D DLT (object planar space), only the first 2 columns (x and y) are used.
-    There must be at least 6 calibration points for the 3D DLT and 4 for the 2D 
-
-    Args:
-        nd: the number of dimensions of the object space: 3 for 3D DLT and 2 for 2D 
-        xyz: the coordinates in the object 3D or 2D space of the calibration points (3*N).
-        uv: the coordinates in the images 2D space of these calibration points (2*N).
-
-    Returns:
-        L: array of the 8 or 11 parameters of the calibration matrix (9*1 or 12*1)
-        err: error of the DLT (mean residual of the DLT transformation in units of camera coordinates).
-    """
-
-    # Convert all variables to numpy array:
-    xyz = np.asarray(xyz)
-    uv = np.asarray(uv)
-    # number of points:
-    nb_pts = xyz.shape[0]
-    # Check the parameters:
-    if uv.shape[0] != nb_pts:
-        raise ValueError('xyz (%d points) and uv (%d points) have different number of points.' % (nb_pts, uv.shape[0]))
-    if (nd == 2 and xyz.shape[1] != 2) or (nd == 3 and xyz.shape[1] != 3):
-        raise ValueError('Incorrect number of coordinates (%d) for %dD DLT (it should be %d).' % (xyz.shape[1], nd, nd))
-    if nd == 3 and nb_pts < 6 or nd == 2 and nb_pts < 4:
-        raise ValueError(
-            '%dD DLT requires at least %d calibration points. Only %d points were entered.' % (nd, 2 * nd, nb_pts))
-
-    # Normalize the data to improve the DLT quality (DLT is dependent of the system of coordinates).
-    # This is relevant when there is a considerable perspective distortion.
-    # Normalization: mean position at origin and mean distance equals to 1 at each direction.
-    Txyz, xyzn = Normalization(nd, xyz)
-    Tuv, uvn = Normalization(2, uv)
-
-    A = []
-    if nd == 2:  # 2D DLT
-        for i in range(nb_pts):
-            x, y = xyzn[i, 0], xyzn[i, 1]
-            u, v = uvn[i, 0], uvn[i, 1]
-            A.append([x, y, 1, 0, 0, 0, -u * x, -u * y, -u])
-            A.append([0, 0, 0, x, y, 1, -v * x, -v * y, -v])
-    elif nd == 3:  # 3D DLT
-        for i in range(nb_pts):
-            x, y, z = xyzn[i, 0], xyzn[i, 1], xyzn[i, 2]
-            u, v = uvn[i, 0], uvn[i, 1]
-            A.append([x, y, z, 1, 0, 0, 0, 0, -u * x, -u * y, -u * z, -u])
-            A.append([0, 0, 0, 0, x, y, z, 1, -v * x, -v * y, -v * z, -v])
-
-    # convert A to array
-    A = np.asarray(A)
-    # Find the 11 (or 8 for 2D DLT) parameters:
-    U, S, Vh = np.linalg.svd(A)
-    # The parameters are in the last line of Vh and normalize them:
-    L = Vh[-1, :] / Vh[-1, -1]
-    # Camera projection matrix:
-    H = L.reshape(3, nd + 1)
-    # Denormalization:
-    H = np.dot(np.dot(np.linalg.pinv(Tuv), H), Txyz);
-    H = H / H[-1, -1]
-    L = H.flatten('C')  # was L = H.flatten(0)
-    # Mean error of the DLT (mean residual of the DLT transformation in units of camera coordinates):
-    uv2 = np.dot(H, np.concatenate((xyz.T, np.ones((1, xyz.shape[0])))))
-    uv2 = uv2 / uv2[2, :]
-    # mean distance:
-    err = np.sqrt(np.mean(np.sum((uv2[0:2, :].T - uv) ** 2, 1)))
-
-    return L, err
-
-
-def find2d(nc, Ls, xyz):
-    """
-    Find 2D reprojection of object point to images point based on the DLT parameters.
-
-    Args:
-        nc: number of cameras (views) used.
-        Ls: (array type) are the camera calibration parameters of each camera (is the output of calib function).
-        The Ls parameters are given as columns and the Ls for different cameras as rows (nc*9 or nc*12).
-        xyz: coordinates in the object 3D or 2D space of the calibration points (3*N).
-
-    Returns:
-        uv: coordinates in the images 2D space of these calibration points.
-        The coordinates (x,y,z and u,v) are given as columns and the different points as rows.
-        For the 2D DLT (object planar space), only the first 2 columns (x and y) are used.
-        There must be at least 6 calibration points for the 3D DLT and 4 for the 2D 
-    """
-
-    # Convert Ls to array:
-    Ls = np.asarray(Ls)
-
-    if nc == 1:
-        H = Ls.reshape((3, 4))
-
-        uv = np.dot(H, np.concatenate((xyz.T, np.ones((1, xyz.shape[0])))))
-        uv = uv / uv[2, :]
-        uvs = uv[0:2, :].T
-
-    else:
-        uvs = np.array([])
-        for i in range(nc):
-            L = Ls[i, :]
-            H = L.reshape((3, 4))
-
-            uv = np.dot(H, np.concatenate((xyz.T, np.ones((1, xyz.shape[0])))))
-            uv = uv / uv[2, :]
-            uv = uv[0:2, :].T
-
-            uvs = np.vstack([uvs, uv]) if uvs.size else uv
-
-    return uvs
-
-
-def recon3D(nd, nc, Ls, uvs):
-    """
-    Reconstruction of object point from images point(s) based on the DLT parameters.
-
-    This code performs 2D or 3D DLT point reconstruction with any number of views (cameras).
-    For 3D DLT, at least two views (cameras) are necessary.
-
-    Args:
-        nd: number of dimensions of the object space: 3 for 3D DLT and 2 for 2D 
-        nc: number of cameras (views) used.
-        Ls: (array type) are the camera calibration parameters of each camera (is the output of calib function).
-        The Ls parameters are given as columns and the Ls for different cameras as rows (nc*9 or nc*12).
-        uvs: coordinates of the point in the images 2D space of each camera
-        The coordinates of the point are given as columns and the different views as rows (2*N).
-
-    Returns:
-        xyz: point coordinates in space (3*N)
-    """
-
-    # Convert Ls to array:
-    Ls = np.asarray(Ls)
-
-    # Check the parameters:
-    if np.isnan(uvs).any():
-        raise ValueError("uvs shouldn't contain NaN!")
-    if Ls.ndim == 1 and nc != 1:
-        raise ValueError(
-            'Number of views (%d) and number of sets of camera calibration parameters (1) are different.' % (nc))
-    if Ls.ndim > 1 and nc != Ls.shape[0]:
-        raise ValueError(
-            'Number of views (%d) and number of sets of camera calibration parameters (%d) are different.' % (
-            nc, Ls.shape[0]))
-    if nd == 3 and Ls.ndim == 1:
-        raise ValueError('At least two sets of camera calibration parameters are needed for 3D point reconstruction.')
-
-    if nc == 1:  # 2D and 1 camera (view), the simplest (and fastest) case
-        # One could calculate inv(H) and input that to the code to speed up things if needed.
-        # (If there is only 1 camera, this transformation is all Floatcanvas2 might need)
-        Hinv = np.linalg.inv(Ls.reshape(3, 3))
-        # Point coordinates in space:
-        xyz = np.dot(Hinv, [uvs[0], uvs[1], 1])
-        xyz = xyz[0:2] / xyz[2]
-
-    else:
-        M = []
-        for i in range(nc):
-            L = Ls[i, :]
-            u, v = uvs[i][0], uvs[i][1]  # this indexing works for both list and numpy array
-            if nd == 2:
-                M.append([L[0] - u * L[6], L[1] - u * L[7], L[2] - u * L[8]])
-                M.append([L[3] - v * L[6], L[4] - v * L[7], L[5] - v * L[8]])
-            elif nd == 3:
-                M.append([L[0] - u * L[8], L[1] - u * L[9], L[2] - u * L[10], L[3] - u * L[11]])
-                M.append([L[4] - v * L[8], L[5] - v * L[9], L[6] - v * L[10], L[7] - v * L[11]])
-
-
-        # Find the xyz coordinates:
-        U, S, Vh = np.linalg.svd(np.asarray(M))
-        # Point coordinates in space:
-        xyz = Vh[-1, 0:-1] / Vh[-1, -1]
-
-    return xyz
-
-
-def Normalization(nd, x):
-    """
-    Normalization of coordinates (centroid to the origin and mean distance of sqrt(2 or 3).
-
-    Args:
-        nd: number of dimensions (2 for 2D; 3 for 3D)
-        x: data to be normalized (directions at different columns and points at rows)
-
-    Returns:
-        Tr: the transformation matrix (translation plus scaling)
-        x: the transformed data
-    """
-
-    x = np.asarray(x)
-    m, s = np.mean(x, 0), np.std(x)
-    if nd == 2:
-        Tr = np.array([[s, 0, m[0]], [0, s, m[1]], [0, 0, 1]])
-    else:
-        Tr = np.array([[s, 0, 0, m[0]], [0, s, 0, m[1]], [0, 0, s, m[2]], [0, 0, 0, 1]])
-
-    Tr = np.linalg.inv(Tr)
-    x = np.dot(Tr, np.concatenate((x.T, np.ones((1, x.shape[0])))))
-    x = x[0:nd, :].T
-
-    return Tr, x
-
-
-if __name__ == '__main__':
-    # Tests of DLTx
-    print('')
-    print('Test of camera calibration and point reconstruction based on direct linear transformation (DLT).')
-    print('3D (x, y, z) coordinates (in cm) of the corner of a cube (the measurement error is at least 0.2 cm):')
-    xyz = [[0, 0, 0], [0, 12.3, 0], [14.5, 12.3, 0], [14.5, 0, 0], [0, 0, 14.5], [0, 12.3, 14.5], [14.5, 12.3, 14.5],
-           [14.5, 0, 14.5]]
-    print(np.asarray(xyz))
-    print('2D (u, v) coordinates (in pixels) of 4 different views of the cube:')
-    uv1 = [[1302, 1147], [1110, 976], [1411, 863], [1618, 1012], [1324, 812], [1127, 658], [1433, 564], [1645, 704]]
-    uv2 = [[1094, 1187], [1130, 956], [1514, 968], [1532, 1187], [1076, 854], [1109, 647], [1514, 659], [1523, 860]]
-    uv3 = [[1073, 866], [1319, 761], [1580, 896], [1352, 1016], [1064, 545], [1304, 449], [1568, 557], [1313, 668]]
-    uv4 = [[1205, 1511], [1193, 1142], [1601, 1121], [1631, 1487], [1157, 1550], [1139, 1124], [1628, 1100],
-           [1661, 1520]]
-    print('uv1:')
-    print(np.asarray(uv1))
-    print('uv2:')
-    print(np.asarray(uv2))
-    print('uv3:')
-    print(np.asarray(uv3))
-    print('uv4:')
-    print(np.asarray(uv4))
-
-    print('')
-    print('Use 4 views to perform a 3D calibration of the camera with 8 points of the cube:')
-    nd = 3
-    nc = 4
-    L1, err1 = calib(nd, xyz, uv1)
-    print('Camera calibration parameters based on view #1:')
-    print(L1)
-    print('Error of the calibration of view #1 (in pixels):')
-    print(err1)
-    L2, err2 = calib(nd, xyz, uv2)
-    print('Camera calibration parameters based on view #2:')
-    print(L2)
-    print('Error of the calibration of view #2 (in pixels):')
-    print(err2)
-    L3, err3 = calib(nd, xyz, uv3)
-    print('Camera calibration parameters based on view #3:')
-    print(L3)
-    print('Error of the calibration of view #3 (in pixels):')
-    print(err3)
-    L4, err4 = calib(nd, xyz, uv4)
-    print('Camera calibration parameters based on view #4:')
-    print(L4)
-    print('Error of the calibration of view #4 (in pixels):')
-    print(err4)
-    xyz1234 = np.zeros((len(xyz), 3))
-    L1234 = [L1, L2, L3, L4]
-    for i in range(len(uv1)):
-        xyz1234[i, :] = recon3D(nd, nc, L1234, [uv1[i], uv2[i], uv3[i], uv4[i]])
-    print('Reconstruction of the same 8 points based on 4 views and the camera calibration parameters:')
-    print(xyz1234)
-    print('Mean error of the point reconstruction using the DLT (error in cm):')
-    print(np.mean(np.sqrt(np.sum((np.array(xyz1234) - np.array(xyz)) ** 2, 1))))
-
-    print('')
-    print('Test of the 2D DLT')
-    print('2D (x, y) coordinates (in cm) of the corner of a square (the measurement error is at least 0.2 cm):')
-    xy = [[0, 0], [0, 12.3], [14.5, 12.3], [14.5, 0]]
-    print(np.asarray(xy))
-    print('2D (u, v) coordinates (in pixels) of 2 different views of the square:')
-    uv1 = [[1302, 1147], [1110, 976], [1411, 863], [1618, 1012]]
-    uv2 = [[1094, 1187], [1130, 956], [1514, 968], [1532, 1187]]
-    print('uv1:')
-    print(np.asarray(uv1))
-    print('uv2:')
-    print(np.asarray(uv2))
-    print('')
-    print('Use 2 views to perform a 2D calibration of the camera with 4 points of the square:')
-    nd = 2
-    nc = 2
-    L1, err1 = calib(nd, xy, uv1)
-    print('Camera calibration parameters based on view #1:')
-    print(L1)
-    print('Error of the calibration of view #1 (in pixels):')
-    print(err1)
-    L2, err2 = calib(nd, xy, uv2)
-    print('Camera calibration parameters based on view #2:')
-    print(L2)
-    print('Error of the calibration of view #2 (in pixels):')
-    print(err2)
-    xy12 = np.zeros((len(xy), 2))
-    L12 = [L1, L2]
-    for i in range(len(uv1)):
-        xy12[i, :] = recon3D(nd, nc, L12, [uv1[i], uv2[i]])
-    print('Reconstruction of the same 4 points based on 2 views and the camera calibration parameters:')
-    print(xy12)
-    print('Mean error of the point reconstruction using the DLT (error in cm):')
-    print(np.mean(np.sqrt(np.sum((np.array(xy12) - np.array(xy)) ** 2, 1))))
-
-    print('')
-    print('Use only one view to perform a 2D calibration of the camera with 4 points of the square:')
-    nd = 2
-    nc = 1
-    L1, err1 = calib(nd, xy, uv1)
-    print('Camera calibration parameters based on view #1:')
-    print(L1)
-    print('Error of the calibration of view #1 (in pixels):')
-    print(err1)
-    xy1 = np.zeros((len(xy), 2))
-    for i in range(len(uv1)):
-        xy1[i, :] = recon3D(nd, nc, L1, uv1[i])
-    print('Reconstruction of the same 4 points based on one view and the camera calibration parameters:')
-    print(xy1)
-    print('Mean error of the point reconstruction using the DLT (error in cm):')
-    print(np.mean(np.sqrt(np.sum((np.array(xy1) - np.array(xy)) ** 2, 1))))
diff --git a/camera/__init__.py b/camera/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e5be388838716757e15b8c07a8e8cf4629092b20
--- /dev/null
+++ b/camera/__init__.py
@@ -0,0 +1 @@
+import camera.calib as calib
diff --git a/camera/calib.py b/camera/calib.py
new file mode 100644
index 0000000000000000000000000000000000000000..09c5ab70f12d2d396b43ddf780e0c2c980c2baee
--- /dev/null
+++ b/camera/calib.py
@@ -0,0 +1,460 @@
+"""
+I (Antoine Cribellier) found this code at https://www.mail-archive.com/floatcanvas@mithis.com/msg00513.html
+(author: Marcos Duarte)
+I made various modification to clarify things and added functions (read_dlt_coefs, save_dlt_coefs, gen_dlt_coefs)
+Also the functions are now working use 8 or 11 DLT coefficients instead of 9 or 12 before (the last one was always 1)
+
+
+Camera calibration and point reconstruction based on direct linear transformation (DLT).
+
+The fundamental problem here is to find a mathematical relationship between the 
+ coordinates  of a 3D point and its projection onto the images plane. The DLT
+ (a linear apporximation to this problem) is derived from modelling the object 
+ and its projection on the images plane as a pinhole camera situation.
+In simplistic terms, using the pinhole camera model, it can be found by similar
+ triangles the following relation between the images coordinates (u,v) and the 3D
+ point (X,Y,Z):
+   [ u ]   [ L1  L2  L3  L4 ] [ X ]
+   [ v ] = [ L5  L6  L7  L8 ] [ Y ]
+   [ 1 ]   [ L9 L10 L11 L12 ] [ Z ]
+                              [ 1 ]
+The matrix dlt_coef is kwnown as the camera matrix or camera projection matrix. For a
+ 2D point (X,Y), the last column of the matrix doesn't exist. In fact, the L12 
+ term (or L9 for 2D DLT) is not independant from the other parameters and then
+ there are only 11 (or 8 for 2D DLT) independent parameters in the DLT to be 
+ determined.
+
+DLT is typically used in two steps: 1. camera calibration and 2. object (point)
+ reconstruction.
+The camera calibration step consists in digitizing points with known coordiantes
+ in the real space.
+At least 4 points are necessary for the calibration of a plane (2D DLT) and at 
+ least 6 points for the calibration of a volume (3D DLT). For the 2D DLT, at least
+ one view of the object (points) must be entered. For the 3D DLT, at least 2 
+ different views of the object (points) must be entered.
+These coordinates (from the object and images(s)) are inputed to the calib
+ algorithm which  estimates the camera parameters (8 for 2D DLT and 11 for 3D DLT).
+With these camera parameters and with the camera(s) at the same position of the
+ calibration step,  we now can reconstruct the real position of any point inside 
+ the calibrated space (area for 2D DLT and volume for the 3D DLT) from the point 
+ position(s) viewed by the same fixed camera(s).
+
+This code can perform 2D or 3D DLT with any number of views (cameras).
+For 3D DLT, at least two views (cameras) are necessary.
+
+There are more accurate (but more complex) algorithms for camera calibration that
+ also consider lens distortion. For example, OpenCV and Tsai softwares have been
+ ported to Python. However, DLT is classic, simple, and effective (fast) for 
+ most applications.
+
+About DLT, see: http://kwon3d.com/theory/dlt/html
+
+This code is based on different implementations and teaching material on DLT 
+ found in the internet.
+"""
+from typing import List
+
+import numpy as np
+from matplotlib import pyplot as plt
+
+
+def calib(nb_dim, xyz, uv):
+    """
+    Camera calibration by DLT using known object points and their images points.
+
+    This code performs 2D or 3D DLT camera calibration with any number of views (cameras).
+    For 3D DLT, at least two views (cameras) are necessary.
+
+    The coordinates (x,y,z and u,v) are given as columns and the different points as rows.
+    For the 2D DLT (object planar space), only the first 2 columns (x and y) are used.
+    There must be at least 6 calibration points for the 3D DLT and 4 for the 2D 
+
+    Args:
+        nb_dim: the number of dimensions of the object space: 3 for 3D DLT and 2 for 2D 
+        xyz: the coordinates in the object 3D or 2D space of the calibration points (3*N).
+        uv: the coordinates in the images 2D space of these calibration points (2*N).
+
+    Returns:
+        dlt_coef: array of the 8 or 11 parameters of the calibration matrix (8*1 or 11*1)
+        repro_err: error of the DLT (mean residual of the DLT transformation in units of camera coordinates).
+    """
+
+    # Convert all variables to numpy array:
+    xyz = np.asarray(xyz)
+    uv = np.asarray(uv)
+
+    # Number of points:
+    nb_pts = xyz.shape[0]
+
+    # Check the parameters:
+    if uv.shape[0] != nb_pts:
+        raise ValueError('xyz (%d points) and uv (%d points) have different number of points.' % (nb_pts, uv.shape[0]))
+
+    if (nb_dim == 2 and xyz.shape[1] != 2) or (nb_dim == 3 and xyz.shape[1] != 3):
+        raise ValueError('Incorrect number of coordinates (%d) for %dD DLT (it should be %d).' % (xyz.shape[1], nb_dim, nb_dim))
+
+    if nb_dim == 3 and nb_pts < 6 or nb_dim == 2 and nb_pts < 4:
+        raise ValueError(
+            '%dD DLT requires at least %d calibration points. Only %d points were entered.' % (nb_dim, 2 * nb_dim, nb_pts))
+
+    # Normalize the data to improve the DLT quality (DLT is dependent of the system of coordinates).
+    # This is relevant when there is a considerable perspective distortion.
+    # Normalization: mean position at origin and mean distance equals to 1 at each direction.
+    Txyz, xyzn = normalization(nb_dim, xyz)
+    Tuv, uvn = normalization(2, uv)
+
+    A = []
+    if nb_dim == 2:  # 2D DLT
+        for i in range(nb_pts):
+            x, y = xyzn[i, 0], xyzn[i, 1]
+            u, v = uvn[i, 0], uvn[i, 1]
+            A.append([x, y, 1, 0, 0, 0, -u * x, -u * y, -u])
+            A.append([0, 0, 0, x, y, 1, -v * x, -v * y, -v])
+    elif nb_dim == 3:  # 3D DLT
+        for i in range(nb_pts):
+            x, y, z = xyzn[i, 0], xyzn[i, 1], xyzn[i, 2]
+            u, v = uvn[i, 0], uvn[i, 1]
+            A.append([x, y, z, 1, 0, 0, 0, 0, -u * x, -u * y, -u * z, -u])
+            A.append([0, 0, 0, 0, x, y, z, 1, -v * x, -v * y, -v * z, -v])
+
+    # Convert A to array
+    A = np.asarray(A)
+
+    # Find the 11 (or 8 for 2D DLT) parameters:
+    U, S, Vh = np.linalg.svd(A)
+
+    # The parameters are in the last line of Vh and normalize them:
+    dlt_coef = Vh[-1, :] / Vh[-1, -1]
+
+    # Camera projection matrix:
+    H = dlt_coef.reshape(3, nb_dim + 1)
+
+    # De-normalization:
+    H = np.dot(np.dot(np.linalg.pinv(Tuv), H), Txyz);
+    H = H / H[-1, -1]
+    dlt_coef = H.flatten('C')  # was dlt_coef = H.flatten(0)
+
+    dlt_coef = dlt_coef[:-1]  # to remove last coefficient as it's not independent from the other (should always be 1.0)
+
+    # Mean error of the DLT (mean residual of the DLT transformation in units of camera coordinates):
+    uv2 = np.dot(H, np.concatenate((xyz.T, np.ones((1, xyz.shape[0])))))
+    uv2 = uv2 / uv2[2, :]
+
+    # Mean distance:
+    repro_err = np.sqrt(np.mean(np.sum((uv2[0:2, :].T - uv) ** 2, 1)))
+
+    return dlt_coef, repro_err
+
+
+def find2d(nb_cam, dlt_coefs, xyz):
+    """
+    Find 2D reprojection of object point to images point based on the DLT parameters.
+
+    Args:
+        nb_cam: number of cameras (views) used.
+        dlt_coefs: (array type) are the camera calibration parameters of each camera (is the output of calib function).
+        The DLT coefficients parameters are given as columns with the cameras as rows (nb_cam*8 or nb_cam*11).
+        xyz: coordinates in the object 3D or 2D space of the calibration points (3*N).
+
+    Returns:
+        uv: coordinates in the images 2D space of these calibration points.
+        The coordinates (x,y,z and u,v) are given as columns and the different points as rows.
+        For the 2D DLT (object planar space), only the first 2 columns (x and y) are used.
+        There must be at least 6 calibration points for the 3D DLT and 4 for the 2D 
+    """
+
+    dlt_coefs = np.asarray(dlt_coefs)   # Convert dlt_coefs to array:
+
+    if nb_cam == 1:
+        dlt_coefs = np.append(dlt_coefs, 1.0)  # to allow reshaping to 3*4 matrix
+        H = dlt_coefs.reshape((3, 4))
+
+        uv = np.dot(H, np.concatenate((xyz.T, np.ones((1, xyz.shape[0])))))
+        uv = uv / uv[2, :]
+        uvs = uv[0:2, :].T
+
+    else:
+        uvs = np.array([])
+        for i in range(nb_cam):
+            dlt_coef = dlt_coefs[i, :]
+            dlt_coef = np.append(dlt_coef, 1.0)  # to allow reshaping to 3*4 matrix
+
+            H = dlt_coef.reshape((3, 4))
+
+            uv = np.dot(H, np.concatenate((xyz.T, np.ones((1, xyz.shape[0])))))
+            uv = uv / uv[2, :]
+            uv = uv[0:2, :].T
+
+            uvs = np.vstack([uvs, uv]) if uvs.size else uv
+
+    return uvs
+
+
+def recon3d(nb_dim, nb_cam, dlt_coefs, uvs):
+    """
+    Reconstruction of object point from images point(s) based on the DLT parameters.
+
+    This code performs 2D or 3D DLT point reconstruction with any number of views (cameras).
+    For 3D DLT, at least two views (cameras) are necessary.
+
+    Args:
+        nb_dim: number of dimensions of the object space: 3 for 3D DLT and 2 for 2D 
+        nb_cam: number of cameras (views) used.
+        dlt_coefs: (array type) are the camera calibration parameters of each camera (is the output of calib function).
+        The dlt_coefs parameters are given as columns with the cameras as rows (nb_cam*8 or nb_cam*11).
+        uvs: coordinates of the point in the images 2D space of each camera
+        The coordinates of the point are given as columns and the different views as rows (2*N).
+
+    Returns:
+        xyz: point coordinates in space (3*N)
+    """
+
+    dlt_coefs = np.asarray(dlt_coefs)  # Convert dlt_coefs to array:
+
+    # Check the parameters:
+    if np.isnan(uvs).any():
+        raise ValueError("uvs shouldn't contain NaN!")
+
+    if dlt_coefs.ndim == 1 and nb_cam != 1:
+        raise ValueError(
+            'Number of views (%d) and number of sets of camera calibration parameters (1) are different.' % (nb_cam))
+
+    if dlt_coefs.ndim > 1 and nb_cam != dlt_coefs.shape[0]:
+        raise ValueError(
+            'Number of views (%d) and number of sets of camera calibration parameters (%d) are different.' % (
+                nb_cam, dlt_coefs.shape[0]))
+
+    if nb_dim == 3 and dlt_coefs.ndim == 1:
+        raise ValueError('At least two sets of camera calibration parameters are needed for 3D point reconstruction.')
+
+    if nb_cam == 1:  # 2D and 1 camera (view), the simplest (and fastest) case
+        dlt_coefs = np.append(dlt_coefs, 1.0)  # to allow reshaping to 3*3 matrices
+
+        # One could calculate inv(H) and input that to the code to speed up things if needed.
+        # (If there is only 1 camera, this transformation is all Floatcanvas2 might need)
+        Hinv = np.linalg.inv(dlt_coefs.reshape(3, 3))
+
+        # Point coordinates in space:
+        xyz = np.dot(Hinv, [uvs[0], uvs[1], 1])
+        xyz = xyz[0:2] / xyz[2]
+
+    else:
+        M = []
+        for i in range(nb_cam):
+            dlt_coef = dlt_coefs[i, :]
+            dlt_coef = np.append(dlt_coef, 1.0)
+
+            u, v = uvs[i][0], uvs[i][1]  # this indexing works for both list and numpy array
+            if nb_dim == 2:
+                M.append([dlt_coef[0] - u * dlt_coef[6], dlt_coef[1] - u * dlt_coef[7], dlt_coef[2] - u * dlt_coef[8]])
+                M.append([dlt_coef[3] - v * dlt_coef[6], dlt_coef[4] - v * dlt_coef[7], dlt_coef[5] - v * dlt_coef[8]])
+
+            elif nb_dim == 3:
+                M.append([dlt_coef[0] - u * dlt_coef[8], dlt_coef[1] - u * dlt_coef[9], dlt_coef[2] - u * dlt_coef[10], dlt_coef[3] - u * dlt_coef[11]])
+                M.append([dlt_coef[4] - v * dlt_coef[8], dlt_coef[5] - v * dlt_coef[9], dlt_coef[6] - v * dlt_coef[10], dlt_coef[7] - v * dlt_coef[11]])
+
+        # Find the xyz coordinates:
+        U, S, Vh = np.linalg.svd(np.asarray(M))
+
+        # Point coordinates in space:
+        xyz = Vh[-1, 0:-1] / Vh[-1, -1]
+
+    return xyz
+
+
+def normalization(nb_dim, x):
+    """
+    Normalization of coordinates (centroid to the origin and mean distance of sqrt(2 or 3).
+
+    Args:
+        nb_dim: number of dimensions (2 for 2D; 3 for 3D)
+        x: data to be normalized (directions at different columns and points at rows)
+
+    Returns:
+        Tr: the transformation matrix (translation plus scaling)
+        x: the transformed data
+    """
+
+    x = np.asarray(x)
+    m, s = np.mean(x, 0), np.std(x)
+    if nb_dim == 2:
+        Tr = np.array([[s, 0, m[0]], [0, s, m[1]], [0, 0, 1]])
+    else:
+        Tr = np.array([[s, 0, 0, m[0]], [0, s, 0, m[1]], [0, 0, s, m[2]], [0, 0, 0, 1]])
+
+    Tr = np.linalg.inv(Tr)
+    x = np.dot(Tr, np.concatenate((x.T, np.ones((1, x.shape[0])))))
+    x = x[0:nb_dim, :].T
+
+    return Tr, x
+
+
+def read_dlt_coefs(csv_path: str) -> List[List[float]]:
+    """
+    Read DLT coefficients from .csv file
+
+    Args:
+        csv_path: path of the .csv file where the DLT (11) coefficients (along rows) for all camera (along column) will be written
+
+    Returns:
+        dlt_coefs: List with DLT coefficients (11 * nb_cam)
+    """
+
+    dlt_csv = np.genfromtxt(csv_path, delimiter=',', skip_header=0, names=True)
+    assert len(dlt_csv) == 11
+
+    nb_cam = len(dlt_csv[0])
+    dlt_csv.dtype.names = ['cam{0}'.format(camn) for camn in range(1, nb_cam + 1)]
+
+    dlt_coefs = np.zeros((nb_cam, 11))
+    for i, camn in enumerate(range(1, nb_cam + 1)):
+        dlt_coefs[i] = np.array(dlt_csv['cam{0}'.format(camn)])
+
+    return dlt_coefs
+
+
+def save_dlt_coefs(dlt_coefs: List[List[float]], csv_path: str) -> None:
+    """
+    Save DLT coefficients in .csv file
+
+    Args:
+        dlt_coefs: List with DLT coefficients (11 * nb_cam)
+        csv_path: path of the .csv file with DLT (11) coefficients (along rows) for all camera (along column)
+    """
+
+    assert len(dlt_coefs[0]) == 11
+
+    nb_cam = len(dlt_coefs)
+    header = ['cam{0}'.format(camn) for camn in range(1, nb_cam + 1)]
+    header = ','.join(header)
+
+    np.savetxt(csv_path, np.c_[dlt_coefs].T, delimiter=',', header=header)
+
+
+def gen_dlt_coef(obj_coord: List[List[float]], img_pts: List[List[float]]) -> List[List[float]] and List[List[float]]:
+    """
+    Generate DLT coefficients for one camera
+
+    Args:
+        obj_coord: known 3d coordinates of the objects (size: nb_obj*3)
+        img_pts: 2d coordinates for all objects for one camera (size: nb_obj*2)
+
+    Returns:
+        dlt_coef: List of DLT coefficient (11*1) for one camera
+        mean_repro_err: mean reprojcetion error in pixel (or Root-mean-square deviation)
+    """
+
+    dlt_coef, repro_err = calib(3, obj_coord, img_pts)
+
+    return dlt_coef, repro_err
+
+
+def gen_dlt_coefs(obj_coord: List[List[float]], img_pts: List[List[float]],
+                  plot_error_analysis: bool = False) -> List[str]:
+    """
+    Generate DLT coefficients for multiple cameras
+
+    Args:
+        obj_coord: known 3d coordinates of the objects (size: nb_obj*3)
+        img_pts: 2d coordinates for all objects for multiple cameras (size: nb_cam*nb_obj*3)
+        plot_error_analysis: Whether or not will do an error analysis
+        (check if not using each 2d point will impact overall calibration error)
+
+    Returns:
+        dlt_coef: List of DLT coefficient (11*nb_cam) for all cameras
+        mean_repro_err: mean reprojection errors in pixel (or Root-mean-square deviation) for all cameras
+    """
+
+    if plot_error_analysis: repro_errs_analysis = {}
+
+    nb_cam = len(obj_coord)
+
+    dlt_coefs = np.zeros((nb_cam, 11))
+    repro_errs = np.zeros((nb_cam))
+    for i, camn in enumerate(range(1, nb_cam + 1)):
+
+        dlt_coefs[i], repro_errs[i] = gen_dlt_coef(obj_coord[i], img_pts[i])
+        print('>> The reprojection error of cam{0} is {1} pixels'.format(camn, repro_errs[i]))
+
+        if plot_error_analysis:
+            repro_errs_analysis[camn] = np.zeros(len(img_pts[i].T[0]))
+
+            for ind_pt in range(len(img_pts[i].T[0])):
+                img_pts_calib_wo_pt = np.array([[s for j, s in enumerate(img_pts[i].T[0]) if j is not ind_pt],
+                                                [s for j, s in enumerate(img_pts[i].T[0]) if j is not ind_pt]]).T
+                obj_coord_calib_wo_pt = np.array([[s for j, s in enumerate(obj_coord[i].T[0]) if j is not ind_pt],
+                                                  [s for j, s in enumerate(obj_coord[i].T[1]) if j is not ind_pt],
+                                                  [s for j, s in enumerate(obj_coord[i].T[2]) if j is not ind_pt]]).T
+
+                _, repro_errs_analysis[camn][ind_pt] = gen_dlt_coef(obj_coord_calib_wo_pt, img_pts_calib_wo_pt)
+
+    if plot_error_analysis:
+        # Plot all rmse without one point
+
+        for camn in range(1, nb_cam + 1):
+            fig = plt.figure('Error analysis (cam{0})'.format(camn))
+            ax = fig.add_subplot(111)
+            ax.scatter(list(range(1, len(repro_errs_analysis[camn]) +1)), repro_errs_analysis[camn])
+            ax.set_xlabel('x (m)')
+            ax.set_ylabel('y (m)')
+            plt.show()
+
+    return dlt_coefs, repro_errs
+
+
+def gen_dlt_coefs_from_paths(xyz_path: str, xy_path: str,
+                             points_to_remove: List[int] = [], plot_error_analysis: bool = False,
+                             from_matlab: bool = True, img_height: int = None) -> List[str]:
+    """
+    Generate DLT coefficients for multiple cameras from the paths of csv files with known 3d coordinates of the objects
+    and 2d coordinates for all objects for multiple cameras
+
+    Args:
+        xyz_path: Path of a csv file containing known 3d coordinates of the objects (size: nb_obj*3)
+        xy_path: Path of a csv file containing 2d coordinates for all objects for multiple cameras (size: nb_cam*nb_obj*3)
+        points_to_remove: List of indexes of points that need to be ignored for the computation of the DLT coefficients
+        plot_error_analysis: Whether or not will do an error analysis
+        (check if not using each 2d point will impact overall calibration error)
+
+        from_matlab: Whether or not to convert 2d coordinates to the coordinate system used here
+        img_height: Height of the images of all cameras (assume the same for all),
+                    only needed when converting 2d coordinates from matlab
+
+    Returns:
+        dlt_coef: List of DLT coefficient (11*nb_cam) for all cameras
+        mean_repro_err: mean reprojection errors in pixel (or Root-mean-square deviation) for all cameras
+    """
+
+    coord_calib_csv = np.genfromtxt(xyz_path, delimiter=',', skip_header=0, names=True)
+    pts_calib_csv = np.genfromtxt(xy_path, delimiter=',', skip_header=0, names=True)
+
+    coord_calib = {'x': np.array([s for i, s in enumerate(coord_calib_csv['x']) if i+1 not in points_to_remove]),
+                   'y': np.array([s for i, s in enumerate(coord_calib_csv['y']) if i+1 not in points_to_remove]),
+                   'z': np.array([s for i, s in enumerate(coord_calib_csv['z']) if i+1 not in points_to_remove])}
+
+    nb_cam = int(len(pts_calib_csv[0])/2)
+
+    img_pts = [[]] * nb_cam
+    obj_coord = [[]] * nb_cam
+    for i, camn in enumerate(range(1, nb_cam + 1)):
+        x_pts = np.array([s for i, s in enumerate(pts_calib_csv['cam{0}_X'.format(camn)]) if i+1 not in points_to_remove])
+        y_pts = np.array([s for i, s in enumerate(pts_calib_csv['cam{0}_Y'.format(camn)]) if i+1 not in points_to_remove])
+
+        if from_matlab:  # Will transform coordinate (from Matlab) system using img_size
+            if img_height is None:
+                raise ValueError('Need to set image height in order to convert coordinate system from matlab')
+
+            assert img_height >= np.nanmax(y_pts)
+
+            img_pts[i] = np.array([x_pts, img_height - y_pts], np.float32).T
+
+        else:
+            img_pts[i] = np.array([x_pts, y_pts], np.float32).T
+
+        index = ~np.isnan(img_pts[i]).any(axis=1)  # Remove nan
+        img_pts[i] = img_pts[i][index]
+
+        obj_coord[i] = np.array([coord_calib['x'][index], coord_calib['y'][index], coord_calib['z'][index]]).T
+
+    return gen_dlt_coefs(obj_coord, img_pts, plot_error_analysis=plot_error_analysis)
+
diff --git a/data/calib/examples/mosquito_escape/20200306_xypts.csv b/data/calib/examples/mosquito_escape/20200306_xypts.csv
deleted file mode 100644
index 014a4b5eb1c2b441036cb31720c74994cb0b8cfd..0000000000000000000000000000000000000000
--- a/data/calib/examples/mosquito_escape/20200306_xypts.csv
+++ /dev/null
@@ -1,24 +0,0 @@
-cam1_X,cam1_Y,cam2_X,cam2_Y,cam3_X,cam3_Y
-nan,nan,nan,nan,nan,nan
-nan,nan,nan,nan,102,82
-nan,nan,nan,nan,438,526
-nan,nan,nan,nan,nan,nan
-53,752,nan,nan,nan,nan
-232,280,nan,nan,nan,nan
-510,838,nan,nan,1005,860
-758,610,60,272,938,626
-nan,nan,nan,nan,nan,nan
-nan,nan,nan,nan,986,926
-nan,nan,nan,nan,621,362
-nan,nan,nan,nan,nan,nan
-625,816,nan,nan,nan,nan
-424,378,901,626,157,398
-nan,nan,668,849,nan,nan
-273,633,406,781,590,634
-560,109,189,497,784,121
-nan,nan,329,166,nan,nan
-973,218,645,56,378,218
-nan,nan,829,61,160,1014
-822,81,nan,nan,10,114
-nan,nan,813,396,nan,nan
-530,506,514,521,498,512
diff --git a/data/calib/examples/mosquito_escapes/20200215_DLTcoefs-py.csv b/data/calib/examples/mosquito_escapes/20200215_DLTcoefs-py.csv
new file mode 100644
index 0000000000000000000000000000000000000000..01959d6bb5290367ca3d3c4fdcfae194879d0205
--- /dev/null
+++ b/data/calib/examples/mosquito_escapes/20200215_DLTcoefs-py.csv
@@ -0,0 +1,12 @@
+# cam1,cam2,cam3
+-8.262424931591261839e+02,9.535763357450576223e+03,-6.658637507848138739e+02
+9.342390484220992221e+03,-1.984440135562464402e-01,9.057492290578740722e+03
+8.325857030634652745e+01,-7.371305819268055757e+02,5.808225753247376133e+02
+4.249044568903008781e+02,5.215101083947636198e+02,4.523248219467138824e+02
+-6.867280605283698378e+02,3.447013951805126197e+01,-6.963120258685201406e+03
+8.485001368738440419e+01,-9.541215128903701043e+03,-1.041321374154470725e+01
+-9.354011898208420462e+03,-6.532590573919845838e+02,-5.877425922192175676e+03
+5.378205057715513249e+02,6.051492814375513944e+02,5.088074918326084344e+02
+-1.494755255760544133e+00,5.309336695878297263e-03,-1.178273065522193352e+00
+-1.958966279489277040e-02,-5.772301911740258806e-02,-2.172570887228763518e-03
+1.168411603741917847e-02,-1.494943508532696530e+00,1.080794810979094134e+00
diff --git a/data/calib/examples/mosquito_escapes/20200306_DLTcoefs-py.csv b/data/calib/examples/mosquito_escapes/20200306_DLTcoefs-py.csv
new file mode 100644
index 0000000000000000000000000000000000000000..d4d6c617ec8a36ab5ca50741fb55763f85ef1fa9
--- /dev/null
+++ b/data/calib/examples/mosquito_escapes/20200306_DLTcoefs-py.csv
@@ -0,0 +1,12 @@
+# cam1,cam2,cam3
+-1.141261605371704945e+03,1.328673407525115726e+04,-1.191831435894136303e+04
+1.309940896417702243e+04,-8.778595600402001153e+00,-1.019424781667450475e+03
+6.738506807383616604e+01,-8.981925484222546174e+02,-2.997667235750116177e+01
+3.842798595065022482e+02,5.319070289962245397e+02,4.773508932664243503e+02
+-1.116010261322133829e+03,-8.034049187175665452e+01,2.938439556082694253e+02
+-1.992891489952654638e+01,1.318195592211345684e+04,-1.005860989597624894e+03
+1.296662062858988247e+04,-1.082397032455340195e+03,1.188050139878365735e+04
+4.698677169860810636e+02,3.559214627515071925e+02,4.653246378138107957e+02
+-1.984822484276100418e+00,-6.581836073099980866e-02,2.467466773754960763e-01
+1.491439211012067223e-01,-1.480609988735411831e-01,-2.038602110252764277e+00
+-1.617512824958341477e-01,-1.859361725271246879e+00,-2.534475051759991215e-01
diff --git a/data/calib/20200306_xypts.csv b/data/calib/examples/mosquito_escapes/20200306_xypts.csv
similarity index 100%
rename from data/calib/20200306_xypts.csv
rename to data/calib/examples/mosquito_escapes/20200306_xypts.csv
diff --git a/data/calib/examples/mosquito_escapes/to_delete/20200215_DLTcoefs-py.csv b/data/calib/examples/mosquito_escapes/to_delete/20200215_DLTcoefs-py.csv
new file mode 100644
index 0000000000000000000000000000000000000000..01959d6bb5290367ca3d3c4fdcfae194879d0205
--- /dev/null
+++ b/data/calib/examples/mosquito_escapes/to_delete/20200215_DLTcoefs-py.csv
@@ -0,0 +1,12 @@
+# cam1,cam2,cam3
+-8.262424931591261839e+02,9.535763357450576223e+03,-6.658637507848138739e+02
+9.342390484220992221e+03,-1.984440135562464402e-01,9.057492290578740722e+03
+8.325857030634652745e+01,-7.371305819268055757e+02,5.808225753247376133e+02
+4.249044568903008781e+02,5.215101083947636198e+02,4.523248219467138824e+02
+-6.867280605283698378e+02,3.447013951805126197e+01,-6.963120258685201406e+03
+8.485001368738440419e+01,-9.541215128903701043e+03,-1.041321374154470725e+01
+-9.354011898208420462e+03,-6.532590573919845838e+02,-5.877425922192175676e+03
+5.378205057715513249e+02,6.051492814375513944e+02,5.088074918326084344e+02
+-1.494755255760544133e+00,5.309336695878297263e-03,-1.178273065522193352e+00
+-1.958966279489277040e-02,-5.772301911740258806e-02,-2.172570887228763518e-03
+1.168411603741917847e-02,-1.494943508532696530e+00,1.080794810979094134e+00
diff --git a/data/calib/examples/mosquito_escapes/to_delete/20200215_DLTcoefs.csv b/data/calib/examples/mosquito_escapes/to_delete/20200215_DLTcoefs.csv
new file mode 100644
index 0000000000000000000000000000000000000000..9511de80799a51971e6282067042ab6a81564ab5
--- /dev/null
+++ b/data/calib/examples/mosquito_escapes/to_delete/20200215_DLTcoefs.csv
@@ -0,0 +1,11 @@
+-805.02,9557.8,-647.01
+9352.5,7.9159,9044
+74.967,-740.34,591.92
+424.71,522.15,452.16
+-791.52,-35.483,5810.1
+-95.262,9509.3,-13.818
+9352.5,-855.76,6980.8
+485.57,419.18,515.86
+-1.4474,-0.017694,-1.1411
+-0.0088429,-0.015916,-0.026959
+-0.014362,-1.4501,1.1051
diff --git a/data/calib/20200305_DLTcoefs-py.csv b/data/calib/examples/mosquito_escapes/to_delete/20200305_DLTcoefs-py.csv
similarity index 100%
rename from data/calib/20200305_DLTcoefs-py.csv
rename to data/calib/examples/mosquito_escapes/to_delete/20200305_DLTcoefs-py.csv
diff --git a/data/calib/20200306_DLTcoefs-py.csv b/data/calib/examples/mosquito_escapes/to_delete/20200306_DLTcoefs-py.csv
similarity index 100%
rename from data/calib/20200306_DLTcoefs-py.csv
rename to data/calib/examples/mosquito_escapes/to_delete/20200306_DLTcoefs-py.csv
diff --git a/data/calib/20200311_DLTcoefs-py.csv b/data/calib/examples/mosquito_escapes/to_delete/20200311_DLTcoefs-py.csv
similarity index 100%
rename from data/calib/20200311_DLTcoefs-py.csv
rename to data/calib/examples/mosquito_escapes/to_delete/20200311_DLTcoefs-py.csv
diff --git a/data/calib/20200311_xypts.csv b/data/calib/examples/mosquito_escapes/to_delete/20200311_xypts.csv
similarity index 100%
rename from data/calib/20200311_xypts.csv
rename to data/calib/examples/mosquito_escapes/to_delete/20200311_xypts.csv
diff --git a/data/calib/20200401_1531_DLTcoefs-py.csv b/data/calib/examples/mosquito_escapes/to_delete/20200401_1531_DLTcoefs-py.csv
similarity index 100%
rename from data/calib/20200401_1531_DLTcoefs-py.csv
rename to data/calib/examples/mosquito_escapes/to_delete/20200401_1531_DLTcoefs-py.csv
diff --git a/data/calib/20200401_1531_xypts.csv b/data/calib/examples/mosquito_escapes/to_delete/20200401_1531_xypts.csv
similarity index 100%
rename from data/calib/20200401_1531_xypts.csv
rename to data/calib/examples/mosquito_escapes/to_delete/20200401_1531_xypts.csv
diff --git a/data/calib/20200615_DLTcoefs-py.csv b/data/calib/examples/mosquito_escapes/to_delete/20200615_DLTcoefs-py.csv
similarity index 100%
rename from data/calib/20200615_DLTcoefs-py.csv
rename to data/calib/examples/mosquito_escapes/to_delete/20200615_DLTcoefs-py.csv
diff --git a/data/calib/20200615_DLTcoefs.csv b/data/calib/examples/mosquito_escapes/to_delete/20200615_DLTcoefs.csv
similarity index 100%
rename from data/calib/20200615_DLTcoefs.csv
rename to data/calib/examples/mosquito_escapes/to_delete/20200615_DLTcoefs.csv
diff --git a/data/calib/20200615_xypts-rot.csv b/data/calib/examples/mosquito_escapes/to_delete/20200615_xypts-rot.csv
similarity index 100%
rename from data/calib/20200615_xypts-rot.csv
rename to data/calib/examples/mosquito_escapes/to_delete/20200615_xypts-rot.csv
diff --git a/data/calib/20200615_xypts.csv b/data/calib/examples/mosquito_escapes/to_delete/20200615_xypts.csv
similarity index 100%
rename from data/calib/20200615_xypts.csv
rename to data/calib/examples/mosquito_escapes/to_delete/20200615_xypts.csv
diff --git a/data/calib/20200623_DLTcoefs.csv b/data/calib/examples/mosquito_escapes/to_delete/20200623_DLTcoefs.csv
similarity index 100%
rename from data/calib/20200623_DLTcoefs.csv
rename to data/calib/examples/mosquito_escapes/to_delete/20200623_DLTcoefs.csv
diff --git a/data/calib/20200623_xypts-rot.csv b/data/calib/examples/mosquito_escapes/to_delete/20200623_xypts-rot.csv
similarity index 100%
rename from data/calib/20200623_xypts-rot.csv
rename to data/calib/examples/mosquito_escapes/to_delete/20200623_xypts-rot.csv
diff --git a/data/calib/20200623_xypts.csv b/data/calib/examples/mosquito_escapes/to_delete/20200623_xypts.csv
similarity index 100%
rename from data/calib/20200623_xypts.csv
rename to data/calib/examples/mosquito_escapes/to_delete/20200623_xypts.csv
diff --git a/data/calib/xyz_calibration_device_big.csv b/data/calib/examples/mosquito_escapes/to_delete/xyz_calibration_device_big.csv
similarity index 100%
rename from data/calib/xyz_calibration_device_big.csv
rename to data/calib/examples/mosquito_escapes/to_delete/xyz_calibration_device_big.csv
diff --git a/data/calib/xyz_calibration_device_small.csv b/data/calib/examples/mosquito_escapes/to_delete/xyz_calibration_device_small.csv
similarity index 100%
rename from data/calib/xyz_calibration_device_small.csv
rename to data/calib/examples/mosquito_escapes/to_delete/xyz_calibration_device_small.csv
diff --git a/data/calib/examples/mosquito_escape/xyz_calibration_device.csv b/data/calib/examples/mosquito_escapes/xyz_calibration_device.csv
similarity index 100%
rename from data/calib/examples/mosquito_escape/xyz_calibration_device.csv
rename to data/calib/examples/mosquito_escapes/xyz_calibration_device.csv
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010001.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010001.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..8540072cabeb62484866c8277c5edc542b6c8ef5
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010001.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010051.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010051.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..ca26986d1730cb5f9e4c91fc572e83b1a1a6e171
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010051.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010101.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010101.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..40dece84a99917361625149bce1e2e2dda52939c
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010101.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010151.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010151.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..cc52bb1b7e0a6bd6e8bf17be6ece2e0e4b3d259b
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010151.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010201.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010201.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c790644b27cfc656d175f745ceacaae8b50f9c7e
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010201.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010251.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010251.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..366fea0f5550a7e9b529c22db636bd96dc155622
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010251.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010301.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010301.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..5e70b2f8fee48a3dfeb2624d58d017fdf1d0a9a5
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010301.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010351.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010351.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..f0063e3707a8c6ad8d5fb0e41c1bd2494c2f3964
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010351.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010401.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010401.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..6f321f3909be553bef3c04f804cab4711d3259a3
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010401.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010451.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010451.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..38c3cf12267953095830ab8c88af9243bc32833d
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010451.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010501.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010501.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..915309d1afaba3245a38a6bf62865229d61b30af
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010501.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010551.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010551.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..5cee961a2f03e2592b3eb3b980251991b0df003e
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010551.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010601.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010601.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b89d1a7a3034ea1d498edac720edf3d722b949be
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010601.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010651.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010651.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..0fc176212ae3c76ad5aa8a348c0ed5a19a14fa3f
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010651.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010701.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010701.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..54e1eb1b314f37886cf221fc9499c517bf898edf
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010701.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010751.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010751.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..6b788f3c72013d96048da9dcaa1ef056699872ac
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010751.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010801.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010801.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..6e46acb6c58d3a623686e5de218120b730a4cec0
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010801.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010851.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010851.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..7b35b1dc3fb64a69a079faa4158e1d18c9573abe
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010851.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010901.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010901.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..5b126dd0083c61f6fbab36da98e48b4c984c30ee
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010901.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010951.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010951.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b799d94e72b303345774bea842008ba9e32d417e
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759010951.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011001.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011001.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..55263b4dfae45415026910f2e553d9142509cc83
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011001.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011051.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011051.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..4d58967b1bf3d576ce2f37a939a976fce5ce1c27
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011051.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011101.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011101.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..3f4e9484d46ba91f22ebb369d77aa15b4fd52fb9
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011101.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011151.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011151.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..de2dd0bafb8fe55e4c99228a9aacd3819d79d38f
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011151.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011201.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011201.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..955cc9d0d793b608a58f5107727d69d840050567
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011201.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011251.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011251.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..614bb3fe81fc77f60018eab387c1f19ffc6a388b
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011251.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011301.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011301.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..82940ae16884df0ed76ee36a047b57b393d05945
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011301.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011351.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011351.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..f803995e7f77c2b54e475959b31c35b8e6dd1c0b
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011351.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011401.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011401.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..0e078fd95c2f37fa486f4e434d8c7d5af461a7c2
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011401.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011451.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011451.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..9e867f54cdabf0e3390c5f5c65b7ba6c5b047ea4
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011451.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011501.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011501.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c934e8c8fdf4db864054cc2ebf9b6098a6e7dc4e
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011501.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011551.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011551.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..1bef9288880447ac1f73ca8e8ded5ccd96874cf8
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011551.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011601.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011601.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..4082cc7affc677dd1d7a3a12cb1292f6fb452b71
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011601.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011651.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011651.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..aaf5c500508598adf8e1c0ac8761486ce6d00d37
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011651.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011701.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011701.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..22531340927812f7c97948e05c33dfd890d6e9eb
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011701.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011751.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011751.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..f752ae296ed525e25abb37fc118cb85c9f4b334f
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011751.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011801.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011801.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..71d2819e90a7ba1b00581127d1066ced9388696f
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011801.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011851.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011851.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..7e14f9e2447ec11a5f8677ae27769d3e65030916
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011851.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011901.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011901.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..bbec2966ba8f0707553572c9b91a673563778d9a
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011901.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011951.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011951.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..6b2b3dafa122eb025d0f593f7ad07b71176fe48d
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759011951.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012001.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012001.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..7eac4df44dc6e676d00aed2e8976c4107be58c02
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012001.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012051.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012051.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..59be7b352da90de5edaeedcc1e6d332cd20d1319
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012051.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012101.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012101.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..8140606cd718a225c1be852a6aa6e7701b09491b
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012101.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012151.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012151.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..e650ecc44c04b415c8eb180d9384ec313ce3e6f5
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012151.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012201.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012201.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..4c1f2f8747d4973828a1689790c10cae6a420178
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012201.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012251.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012251.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..13711d48bc95140534b67f02021916089f71d0a4
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012251.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012301.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012301.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..4b7e10459a408a0cd19333fee269fca078c86549
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012301.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012351.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012351.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..12a5bcd893d25af62d378658737667aecc7aca8c
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012351.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012401.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012401.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..3e20c382d691940ae51f3a823f1574c7d1e9f1b6
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012401.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012451.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012451.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..faf33b0f77ed50a5607bd227ad47ff893ce53e90
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012451.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012501.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012501.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..0e1bd68c03252a673e16b0d0c282e6f567b83fd0
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012501.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012551.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012551.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..52229f12f079bed5f8d78dcc52bf6a2befaf5af1
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012551.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012601.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012601.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..a5caf7a4dd8805dd3a29377d25aedd5b4060d5a9
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012601.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012651.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012651.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..dbf89b0b3db7a41df6cc0ee6803e9a4d6f23fdd3
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012651.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012701.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012701.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c6b42841a769ca27639245ef6763b42432e910cb
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012701.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012751.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012751.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..6615753827421571013455261a63ca436ae412cf
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012751.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012801.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012801.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..14eafe8617124ec2cf7309cc0eb005d3d0275993
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012801.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012851.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012851.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..6a99333aab2c6e808a1c60b7aae7fefd9d753265
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012851.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012901.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012901.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..3a43bf571d8af6c3b8a797821d8aeee4497f2b2c
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012901.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012951.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012951.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..8ffb8457038e7fba93bff4ccb9fcda25a43f8f06
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759012951.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013001.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013001.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..7843af1d10ade00e95ad26d283b6732f7669ea60
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013001.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013051.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013051.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b8a532f2f6e25f6976128d8c084c5b2110d7702d
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013051.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013101.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013101.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..4b5e5058a1bf48ae91b24aaaa7dc9338a6dc4624
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013101.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013151.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013151.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..12cde6f365c98c0b185d7833213cf668700c992e
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013151.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013201.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013201.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..d908f499ceee08fdb96b102d47465773fe611917
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013201.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013251.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013251.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..82a1ed51f77b4292a537250069d90cf3eb49401d
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013251.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013301.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013301.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..660a6492f683f89a2bea993cfc3b1bc76418ced0
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013301.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013351.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013351.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..5d39ecf7322115c8006da569c56bc44fc0baf41c
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013351.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013401.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013401.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..f8832919819ba154ec960ac0f3a14136cef63fbc
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013401.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013451.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013451.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..440dc2fc34c9b162654c2a53c5a7f3894a2c885a
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013451.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013501.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013501.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..9a35e1ce36999c19f098317b3488d8954e093706
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013501.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013551.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013551.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b6856444bc93f5fd66d4b5982e7e6817522e2796
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013551.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013601.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013601.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b8fe5b3e4d034f48870a55b23c33286c6f92a5c2
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013601.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013651.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013651.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..496520b9275095c78a57209604224ded5cd830f8
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013651.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013701.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013701.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..91d44d2cd9cd363bbbfbf35a8e0b24f2c9051c7a
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013701.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013751.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013751.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c1cad8841010a3ee6a0efe96ed91e65196edbb33
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013751.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013801.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013801.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..8d6774467e468cb89aafea59718d9cb40618ef35
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013801.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013851.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013851.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..45af8bdb5c97da1e92ba220796a1ce75e9370061
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013851.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013901.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013901.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..2511d01730c6d53fcf00cd4f0c78e27601d610e3
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013901.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013951.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013951.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..3f1e97072d9529782d6310be82e024013b96b6b4
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759013951.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014001.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014001.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..572cbb9ffa92e650d9bc4b3067d0692f4c40503f
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014001.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014051.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014051.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..1c72aa60105869290db293cb596106d5ef644ffb
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014051.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014101.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014101.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..52818872dafcbc27f941e76913dc285efbed448a
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014101.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014151.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014151.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..1fb78dcbc2f1f91c3562e9764efaec14eb723d3b
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014151.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014201.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014201.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..92b3e083ca9eb852186840738d8788d8358f040d
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014201.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014251.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014251.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..e5475d9d785cbb6d7737de507c2f17a9a85ff7b7
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014251.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014301.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014301.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..7e5a84bc70c0a97285a9025c6e3a7335b62e3609
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014301.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014351.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014351.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..336d7b0ab1177eb90a7b438f7572e1efbc224862
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014351.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014401.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014401.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..07f15fb414d070e5f84ebc226b7e928e8866453b
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014401.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014451.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014451.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..f34dd175af6f6f2a307e7fce90aca65d298f4d2b
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014451.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014501.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014501.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..17e4250afd8cd115528df8e539d03b6b4c18e8c3
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014501.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014551.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014551.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..71c531198f74c2e9355383b21c5c4beb47230ce5
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014551.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014601.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014601.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..2e189c67a5b2954fd09c5b07056c26991fab2399
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014601.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014651.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014651.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c7b48d0ba248eada5f1a14c73fec04e96c050840
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014651.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014701.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014701.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..475abf063e55b6a1157cdef1983f15351ea69a47
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014701.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014751.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014751.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..3ad6f3cc0fba858faf54bba23976e7d64811840c
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014751.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014801.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014801.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..bc4b1b55369eb5785d9a6dee6b80a91dbcdc0b42
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014801.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014851.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014851.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..6f2bec8b27864f18c0b8ff2b91bf9c4742b54b21
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014851.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014901.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014901.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..7bf8a4c8afa2341712c40b5f15500d715deefa7a
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014901.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014951.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014951.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..991074c180ed06229138a427736784e71a3f4e42
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759014951.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759015001.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759015001.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..742b39e14db9db72df3056c8c1bff357677a61f8
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759015001.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759015051.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759015051.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..adb14a64cdd3b8c19e93a784aa8328676dadeff2
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759015051.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759015101.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759015101.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..2345628c3edba8241d0c51e0c8f4e356584e52c6
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759015101.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759015151.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759015151.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..fdbfa7bee4a5f0b62f6c75d68d72304bd74df457
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759015151.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759015201.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759015201.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..09d572eb55dd958be9eb52aea477ad718676f693
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759015201.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759015251.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759015251.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..fb690fd43d8df6dacef2cbbbbba6448e3dc87917
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759015251.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759015301.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759015301.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..31dcb1cec245806dfe3486e760dc532f9a0a018d
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759015301.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759015351.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759015351.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..d07d56f5c11a30ac2319b87ffd2e1d50cc370906
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759015351.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759015401.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759015401.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..d08f867283d179b6b8c825fd32e09498e40a8be1
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759015401.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759015451.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759015451.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..786fdc777183da37d86e92f9cb3e95bec650b702
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-sample_binned/cam1_20200216_0759015451.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013501.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013501.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..5ccb573c9d1e02d185a0cf3150dddde8d2fb9194
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013501.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013502.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013502.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..5eea156a690ff2fc173762ee94d1fe248d0023d0
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013502.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013503.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013503.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..0fa0a1d04cac18607751f54e233985aa9c27d3e2
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013503.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013504.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013504.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..5c2847aadb0e6853fdd7c3aefd197253f90c6cd4
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013504.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013505.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013505.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..3650c53d5348755704e19c5367923e94b0b816fb
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013505.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013506.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013506.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..44d9442f5ce9e3b36db2d818398141f0c6e0fe16
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013506.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013507.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013507.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..84a85c0359cb7acf5ed3b8bfba20c58fc009edc3
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013507.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013508.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013508.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..21b081f54967b3bf1abf5659537d5aad0ded95a7
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013508.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013509.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013509.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..9ab4ca7a385acbcf25d4fb1f7209b4c30bf03732
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013509.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013510.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013510.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..f6f9a0324a396e689977510f5a25b7665ce43f9a
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013510.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013511.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013511.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..dae1a8d516a1d4f6f8044b8c3267d04fcabd7645
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013511.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013512.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013512.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c70f059561dfce5dd19200f946efefb2776da448
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013512.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013513.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013513.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..5591a203d832f153fb4f70282e99126bfb926f2a
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013513.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013514.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013514.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..6f079e230be201f0333b5d984c044662669bfe27
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013514.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013515.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013515.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..60fe7164828d2786feee52066310c41ea42d22a6
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013515.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013516.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013516.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b786156a0d0d7f1f68dac5ee5b76ddecaef42a87
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013516.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013517.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013517.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..3389cd69804cf0180c170a683571023427e0b081
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013517.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013518.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013518.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..4e79e03725fb8a393267f8e9629fb2e6f290fb0b
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013518.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013519.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013519.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..446886f0d650ebbd4792d04dcd863efea4278ecc
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013519.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013520.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013520.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..1effd4da987913a562bf64551dab236397e35a00
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013520.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013521.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013521.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..34948eb4858506befbf10f0edc42d10acd46303f
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013521.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013522.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013522.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..55ef3d44fc634bd52951f94ce76e6ced606be92f
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013522.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013523.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013523.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b72cca997538c6379c32ef07cc831c30dd267557
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013523.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013524.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013524.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..2a4e9787f608738256c470ef542febd3c47ee1d3
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013524.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013525.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013525.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..2f200a60aa6d5852f5d37b1c8f3c1fe9598d913f
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013525.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013526.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013526.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..3d37a98bae0a671c48b6e7baae094163db280c81
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013526.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013527.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013527.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..98716f257753cdfb7a645e83443331505bf5c725
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013527.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013528.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013528.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..0c563270bc3b0459d833399e0bef6ad960d56739
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013528.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013529.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013529.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..65531eeb4d1288ac750b3ae8c623117cbc5c68e7
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013529.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013530.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013530.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..02d3c3c25ba195c65556c11dc201d24d1e680e8c
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013530.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013531.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013531.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b03df6243c821980e9001159f7368e61554c3331
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013531.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013532.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013532.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..3ca7f1c6a362fdcf59bdfdd9a30f1e33bb502e1c
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013532.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013533.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013533.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..3fdc0cab6df67f279e5db97cd190ff7e1ba52fae
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013533.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013534.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013534.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..341e2f1b38a7367968449f04e054f7c2803c9c36
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013534.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013535.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013535.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..d7dec3921d9e7759dd18bb71f101e4b5a9afe8ce
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013535.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013536.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013536.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..f65e4dfed0e1d9be2b6c38dff7314f121ca51826
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013536.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013537.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013537.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..087eb947170932faaf6b1ec7a3d13cf932eec27f
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013537.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013538.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013538.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..bb75901b805eae9d6dd6149dbd803b7b80df9f29
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013538.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013539.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013539.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..5b855ba8e76c66f0ca340d5435437983ad7537ff
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013539.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013540.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013540.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..ebf82545fcbb9680c8702e3d866b2d84cd7f4c2c
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013540.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013541.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013541.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..67b9d4d7c0e9b5429df0bab9bc8dd13c4e22b020
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013541.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013542.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013542.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..fb8fb23684f92d2a1d3424d2b02af9ceb26a3d3c
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013542.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013543.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013543.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..016b4595b2c49842fbb091136583fef32d33e10a
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013543.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013544.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013544.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c1bf5f7cc9e31cff228fe3f9f400a13d05001706
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013544.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013545.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013545.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..0355c03c565dbf41edb9af443286f073a8acfde9
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013545.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013546.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013546.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..cd12dab915a73891ccc4a607c2d0531a45754f50
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013546.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013547.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013547.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..e693cc0ceb1e7adb3763a1fc70cff4b914ac71b4
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013547.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013548.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013548.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..cb4f18b89fcdd69c9da0e207c79c94fc27fbb02a
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013548.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013549.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013549.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..147b448ba0d1421973df58fed3b485988bae9867
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013549.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013550.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013550.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..422d52d79ac5f82cf591f38785cde14bb09257c0
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013550.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013551.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013551.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..8e4b49d66d6d942813657f2a1f74685e8c785103
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013551.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013552.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013552.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..21b2b413605310fea83322f8198a810663cd33c0
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013552.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013553.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013553.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..f4826b70bde3dde6c05ca86973280cefa35eca27
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013553.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013554.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013554.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..574b545c826afce716ad9b581831597625197ecc
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013554.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013555.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013555.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..773d23ebcbaad539ae5bf8e1a6394f09086448c7
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013555.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013556.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013556.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..439df3ddd57adafa6a91acc4291401110d4be016
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013556.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013557.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013557.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..0abaf0e69613dfabfe2e0906c09d6ddb3ce51520
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013557.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013558.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013558.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..e94a2492fbefa36bbf82ba5b1cec4bec5ef412ba
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013558.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013559.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013559.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..7bb732f78088895477daf1afc4402ceeaebdf7a4
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013559.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013560.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013560.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..aa1646a366f79337333d165154288196d0bf3a4e
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013560.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013561.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013561.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..301373578edc82e79039b566c3547cb9052d7795
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013561.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013562.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013562.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..e6428fec3370eee40c35a61a21c9fc18318f3b6a
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013562.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013563.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013563.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..192cff7383eaeaebf925a307d7d4138ccb62804d
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013563.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013564.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013564.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..6e281777667c7cd3dd99edfb37708b2245b8a44a
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013564.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013565.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013565.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..02abfe3c8e451276f2af1bf419adec624ac62483
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013565.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013566.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013566.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..82c34fe7aba22f0fa05ea74e9d5443bcee9ef5ca
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013566.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013567.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013567.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..96796fe824cb98415c3065699f5ef83864de15d2
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013567.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013568.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013568.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c124c8aea95b09dd154d30d3442bce2ba1767111
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013568.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013569.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013569.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..2e14e022cd9d52a6758a36c433fb48a4a2a0add1
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013569.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013570.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013570.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..048758d1b71827e31cf9cfde28299ab08ee185d0
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013570.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013571.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013571.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..a4b06043e34cd9a72c37dec8af31e94328ef95b0
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013571.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013572.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013572.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c6510a4222506d424ce5e5a794742dcd15f6ca0b
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013572.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013573.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013573.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..46dd2c53b11966bd379794ba7d64b887a956ca9e
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013573.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013574.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013574.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..db8332724e21c7db6b26cf081616cfe2a9f2b7f0
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013574.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013575.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013575.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c3511614e461d7e0125645179329e22f15789f42
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013575.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013576.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013576.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..1289e6ec2e035524c22c25bb7cbdd35063ffa7e6
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013576.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013577.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013577.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..7ecb9fc63ae79579869dbcc11badcb95f762d352
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013577.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013578.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013578.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..1d27077b8a2d46fe79a2dc492ed656f8a1008cc2
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013578.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013579.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013579.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..e82f1fadd4ffcab78b1d043d0d38f969e324e344
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013579.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013580.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013580.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b1180f0dd9a9e9009932e9a97bc28a1b3af5c09e
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013580.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013581.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013581.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..0d6097cb900c1b6a5f4e84cee808ba55e217abde
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013581.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013582.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013582.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..38f57d3d1ac6ae7ebc32189081fd26f42ba2fba9
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013582.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013583.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013583.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..a7b48990a0722291599de80936f6b41a7e2913b4
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013583.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013584.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013584.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..337ed896c63f380cc50b4ea490fd44ca0a9f3dc2
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013584.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013585.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013585.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..9cb5e7d9213d9907422942907ec4900066361726
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013585.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013586.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013586.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..d29bf4fffa7e39b62e126b6966d06391180907a0
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013586.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013587.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013587.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..5e8069dbdd7cdcf60cca80a1c4731becdd71beef
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013587.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013588.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013588.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..043ca63748edfb302db4ed2dd1bc3aae0c027307
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013588.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013589.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013589.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..4f3aa2351ae5d43255b698a8a492a37a12ddd48e
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013589.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013590.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013590.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..6ec3949947f02cc8d399e880f441faf7104dea45
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013590.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013591.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013591.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..d91d4921bdbf7234ef7fb0ed65a45352ad7cb954
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013591.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013592.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013592.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..cc77ff93f18d327d96af7b27e90d88a746669346
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013592.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013593.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013593.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..d855478dcb2d76ea45c2ab9e3559e240a6d07430
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013593.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013594.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013594.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..2f7db7b5b253cf0bb764e1cd2104a420ff1c2157
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013594.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013595.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013595.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..59b517efdacb48361bcfacbee836c52b7c2a7507
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013595.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013596.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013596.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..8cef1527a2e8d474bc91d366b2035f84922c728a
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013596.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013597.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013597.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b1c15dfcf36379d97f822545ddbf72880a360335
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013597.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013598.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013598.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..cdadb815023e6fb7c2da617418405ab5455d7aa0
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013598.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013599.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013599.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c8003b06ca6e5ab97f5c0546bb51da4214fb3722
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013599.jpg differ
diff --git a/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013600.jpg b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013600.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..a030a59bc04c3db8426f1202e648321e5b4def1e
Binary files /dev/null and b/data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013600.jpg differ
diff --git a/data/examples/mosquito_escape/cam1_20200303_030117/cam1_20200303_030117-2d_points.csv b/data/examples/mosquito_escapes/cam1/20200303_030117/cam1_20200303_030117-2d_points.csv
similarity index 100%
rename from data/examples/mosquito_escape/cam1_20200303_030117/cam1_20200303_030117-2d_points.csv
rename to data/examples/mosquito_escapes/cam1/20200303_030117/cam1_20200303_030117-2d_points.csv
diff --git a/data/examples/mosquito_escape/cam1_20200303_030117/cam1_20200303_030117-obj1-2d_points.csv b/data/examples/mosquito_escapes/cam1/20200303_030117/cam1_20200303_030117-obj1-2d_points.csv
similarity index 100%
rename from data/examples/mosquito_escape/cam1_20200303_030117/cam1_20200303_030117-obj1-2d_points.csv
rename to data/examples/mosquito_escapes/cam1/20200303_030117/cam1_20200303_030117-obj1-2d_points.csv
diff --git a/data/examples/mosquito_escape/cam1_20200303_030117/cam1_20200303_030117-obj1-3d_track.csv b/data/examples/mosquito_escapes/cam1/20200303_030117/cam1_20200303_030117-obj1-3d_track.csv
similarity index 100%
rename from data/examples/mosquito_escape/cam1_20200303_030117/cam1_20200303_030117-obj1-3d_track.csv
rename to data/examples/mosquito_escapes/cam1/20200303_030117/cam1_20200303_030117-obj1-3d_track.csv
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050001.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050001.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..5ddcb8bfa18e37b4d108461dc86751041c3275b6
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050001.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050051.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050051.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b7c7ed1a628b59de179f4058489b73b20955f874
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050051.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050101.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050101.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..e8f34e6f1d9e8adb416f357a8b010f337e9301d6
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050101.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050151.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050151.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..ead87ef1d7dddbf0d80ba2161bd105c696d635c8
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050151.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050201.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050201.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..af2e77ee6768577a66164fabf8895cfe52df5e9e
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050201.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050251.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050251.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..0513d1f57a4374da60fe8092313472a876ba146c
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050251.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050301.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050301.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c5d9644c426ce9b4d0b42b35621f8f399a5b84fd
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050301.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050351.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050351.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..95e3dc4314bbd04a9277da1059399d242f5a7292
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050351.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050401.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050401.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..d2e1b36a5f0fd19100e2e07a4c0fe2260616ac43
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050401.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050451.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050451.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..0ed8bfc97579a28d6407236dfaec5e864ac6ae9a
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050451.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050501.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050501.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..f5a859d3f083dc659c45488f846c461c00c093aa
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050501.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050551.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050551.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..0a12cf1fe62e203e935ce315a7da668128adda11
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050551.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050601.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050601.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..db44efa1738a0118447afc332c5715d9d3065ba9
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050601.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050651.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050651.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..79d8c4944fa5946aefd3c524e3caf7567694cb76
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050651.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050701.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050701.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..902732982b90cd90d2eb4127ce5cacc7a08aa758
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050701.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050751.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050751.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..4766e05bd8d18c8038a6b6cc888e6b2e23ec221c
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050751.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050801.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050801.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..5a34b8e5d1a791198880b5d553b1118195000357
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050801.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050851.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050851.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..cb4438c418d91b4bff64742a6aade057afedb49b
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050851.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050901.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050901.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..49b263494429479732247cb47d5559b91318c147
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050901.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050951.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050951.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..53d5a89791f4a2db6ac7c926a0273dc9a19e2a48
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759050951.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051001.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051001.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..f15ca701366d5fcbbcb538f172c80dd7d66fa4a7
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051001.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051051.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051051.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..d6937c290f6305fdc6d6adb91dd7b0e39b04e619
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051051.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051101.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051101.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..66f3f02023977b948ad16b358d679f48fded4685
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051101.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051151.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051151.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..31203dbc715ae421c5813a1dd3356c828adeb7fb
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051151.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051201.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051201.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..10c87a301efaf2bea0b7afea723ccf2cae373c7e
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051201.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051251.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051251.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..8131dbc12b88df01504cb452249b6c5c7eafa3e9
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051251.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051301.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051301.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..27a6b815cea811b4dd7e87c4f8f4bd8734ce1851
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051301.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051351.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051351.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..2b68ef2e26144dbe24672644f469cd3c05641633
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051351.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051401.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051401.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..9dbf5c9ab71e56008a8ea8fe56068afa2714b949
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051401.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051451.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051451.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..fb231b4e83424267afe828e9ef4fcfe892b2403a
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051451.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051501.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051501.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..ca339807326ffec23a3a57779ca3cfd9398322d2
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051501.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051551.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051551.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..ba09ba77730020f6968287569ceba9f5d0aeb9d3
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051551.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051601.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051601.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..7c8d2ab755ccd6b2acbc6aeff7f37b3aaf93a39b
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051601.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051651.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051651.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c8e5e7a0e903ccea32cddc89481249065f06220b
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051651.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051701.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051701.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..f575e4bd9d6e9c130952fd78fb97b91925af5e46
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051701.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051751.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051751.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c6523557b6fcb605f86e886b722147cb59e65655
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051751.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051801.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051801.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..cd8bdcffda543009c1f65aedebd2eb630b67d014
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051801.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051851.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051851.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..ea60d40e07a9572284aac546e121aeeb4c15a374
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051851.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051901.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051901.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..a5e106eee95109b85dccc18033f870f6ba9c5bce
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051901.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051951.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051951.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..7ad2063d31ee3258b9834322ded4af8c5db7056b
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759051951.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052001.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052001.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..341643a30bad8bfca60c98f15c3f7f5fcd5f364a
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052001.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052051.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052051.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..15a72dd823b439d4cb0d2133f00b406d2744f2f6
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052051.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052101.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052101.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..1a162e355720f7ef7eb1383e4b608cebaba9730b
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052101.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052151.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052151.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b9683c0e8363625d3b1ebf05303135610e156820
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052151.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052201.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052201.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..bc5ab73f14fc8332423e4bfe98427b9de7cc4016
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052201.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052251.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052251.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..4def47763251f7a1293af53b8fe3ebeae872d795
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052251.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052301.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052301.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..d3581c3385c5c6e1b460b07e83d465ef59042206
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052301.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052351.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052351.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..46d5bc81f9c47c60c3b04d6cbcf822e94becce83
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052351.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052401.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052401.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..49b492a5e9b61de5ea926837d066fe532abfca6a
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052401.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052451.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052451.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..7f2b72ff25637dcea67d231734a3bfc3057181ab
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052451.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052501.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052501.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..8166f59d8455a50d83fc0e162b31069bafcc22c6
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052501.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052551.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052551.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..fcb53ee5106bc43ace6f483ccbab1f779e54eec7
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052551.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052601.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052601.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..11b6db1771363182ff507cbb28225e8cfa82a810
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052601.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052651.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052651.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..9f7191effd010093cf53148e680666dfe36350a4
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052651.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052701.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052701.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..16341d14a6b8b089498f247118cacdd3c104d6fd
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052701.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052751.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052751.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..456bd4586d1dc018d34aa1b913efec5913961a14
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052751.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052801.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052801.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..53d1e314dbdf6f35478d502ab0ee08ee31a6b3c5
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052801.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052851.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052851.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b9ffda5730e3d9849806bb4a34e437cae05485f4
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052851.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052901.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052901.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..8bbf9c051c51f8f4270e923e48bc8424f8f9d63b
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052901.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052951.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052951.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..06eb3ec1ab1515f7526d8395e2364f776c67a67f
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759052951.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053001.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053001.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..6f49364ab77d193ad125210817bfbe77e2e4a44e
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053001.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053051.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053051.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..d82006351242219f6e3a112f8e9f6346ff16ba06
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053051.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053101.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053101.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..ea5985cb3feeaf399db56088e1d6b80d6a06af66
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053101.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053151.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053151.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..cbe87dc576271ea5966248e087e607ba945edfc1
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053151.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053201.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053201.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..340b3d3ad9aaed7728804b743ac6c467117715b0
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053201.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053251.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053251.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..37cefc8b53fa87547f6e281c871688d902e415df
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053251.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053301.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053301.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..73a00cc2afbe180e6f52751b9d214724e87317d6
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053301.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053351.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053351.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..9fab5aecb643712a2e539ae7314c01cdb0c085d8
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053351.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053401.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053401.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..991567530b1c71ede4bdb62f0a2d5a8df7c3a76a
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053401.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053451.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053451.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..0be1980be3af15923f809db8b101a63dfcc6d8ea
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053451.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053501.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053501.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..2aefe9f70be10d6f73f914b52ec64b291addf82c
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053501.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053551.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053551.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..aa3862065b753aaf8e84d78e652eae7a5910f99e
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053551.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053601.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053601.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..bc8dd050dace59b9efd5c05f6e796325e06fe0c8
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053601.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053651.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053651.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..d138a39fcf5babc4c3219cb63d9020dd75cf9729
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053651.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053701.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053701.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..4fecbb146ef80a11f50da1cc625926f25acc6891
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053701.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053751.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053751.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..94da7603388a6732336ef48de63adf971c8cf06b
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053751.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053801.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053801.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..d450b4eb48c7eb9f9ca87aca2232b83401fa01d7
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053801.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053851.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053851.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c6d5882976af342676b4b2e013a4ccc512db3a9c
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053851.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053901.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053901.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..22d0b133f912992b422a94d6fbd8ab6919393139
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053901.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053951.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053951.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..63b1a5662481d2302c0b8249b7b9817acfcdcd52
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759053951.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054001.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054001.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c7a254470d914ddc43bc5d1eb5254a8e197801f9
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054001.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054051.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054051.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c71663eafe1a2b23a42125bbe5a99f2ef4721fae
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054051.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054101.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054101.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..1578e99a39359345c9e9c6ecb89129663df69d5f
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054101.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054151.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054151.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..f525aa43f0409482f9749f3142457137b4406a30
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054151.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054201.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054201.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..6fbe0d01c50465077c2c91c7d501e5cd62fe457a
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054201.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054251.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054251.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..43c106a7197a8a5c765de66f12433371b1321809
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054251.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054301.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054301.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..69f7eacdb5c62a4ec567a180aa7231e0e93a0cbc
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054301.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054351.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054351.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..4b74c1e90500d50d186e5a3bfa1f474452bfbc3f
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054351.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054401.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054401.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..df5ee580d84ae28f5dc3a45cfad10ec7381e07ee
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054401.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054451.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054451.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..fa7b3936474e043e51c1b6a185440e137f1f3a18
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054451.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054501.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054501.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..fcbd7bef6cb3ac8b82858effcdfb4cd9ae897094
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054501.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054551.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054551.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..982e6a9de5d8bbfce98ff807088d1d4393ef31c8
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054551.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054601.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054601.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..4120cebeccc6a3a3f5f7d652b9e06e12749190c1
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054601.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054651.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054651.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..0dbfc4a2468ba21a1b28c93b719f6f58cdf7da47
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054651.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054701.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054701.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..03d7b110ceff32519977f3eaab5d7ea00d68967d
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054701.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054751.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054751.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..918118f9ae790e840789f4d5022fe86264057611
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054751.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054801.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054801.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..e3c3ee340a5ed33a11cdb6971bf5ee3413042974
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054801.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054851.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054851.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..668a8af280d4de42378c9f082a166bee6ad90cfe
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054851.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054901.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054901.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..3daff121200864cd7386ef9758e6832b59adee35
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054901.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054951.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054951.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..38d6d472e2343b0fa539fa2cc8dd7aab2c25043c
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759054951.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759055001.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759055001.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..3b64103d9e24f4de0dca580a3697add5ece7c633
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759055001.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759055051.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759055051.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..358f5d234a8ad2a75e03de1c644851ddba157dcd
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759055051.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759055101.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759055101.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c4bf4c120579c00f65b743e1cbe639c231b50ab9
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759055101.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759055151.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759055151.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c828a36b5fa63d1636bc9cf1abff17b2892d85c7
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759055151.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759055201.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759055201.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..1ed46669094fe5b5cf8f499757426002a30e4c7c
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759055201.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759055251.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759055251.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..59747f5b1b82fe69a7b7436aee2cae95ea7955c4
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759055251.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759055301.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759055301.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..2313893551e1229045865af183be8a3857874941
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759055301.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759055351.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759055351.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..8e6211452edb9b87371b67d00adcdf18b4fb4a42
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759055351.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759055401.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759055401.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..193825f4c1bb0e9dddd83776a7fc4b543db13269
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759055401.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759055451.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759055451.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..fc00d746c2e255ee58302d9efd5834987efc5e55
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-sample_binned/cam2_20200216_0759055451.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053501.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053501.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c6ffda3e8c3ed1f6a02261992bf4d6eda64c3d5d
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053501.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053502.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053502.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..d0f41d85f753123c053010b627a278476f332774
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053502.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053503.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053503.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..ea3260d82156e9584cca1d5bc6f022958b001a59
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053503.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053504.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053504.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..3f6dba431c167de702161bfd3ca0d92272ab56a4
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053504.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053505.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053505.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..ee35347b632392dca2b1724394572b5b80775b34
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053505.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053506.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053506.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..cc516c93e6b028e0645f34450a44f15d376b5a68
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053506.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053507.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053507.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..3f97e17819a1972641a9e83ee11c2c55af2ab10b
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053507.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053508.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053508.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..ee911976a26b849e1fab47d2f64b501cdd88cc2a
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053508.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053509.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053509.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..81e13041392ea3e5bcbce85cae25bad400ea69a7
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053509.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053510.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053510.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..07f07a8b3c0790aab975aa69bd1fdad0bb732daf
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053510.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053511.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053511.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..074513c252331afb9d56f534cc1c6489ce29793f
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053511.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053512.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053512.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..4e85e0f4dbffd891eceed54cff75396811202eb9
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053512.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053513.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053513.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..93fe2b5cad37a0e2442a343a99723a5b04d7de65
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053513.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053514.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053514.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..41894240ec1a8d51f20c0a4a0c875a7b5d1ae77d
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053514.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053515.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053515.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..afdd7491222a5d616030ff89d26cd5cc5ba3c5d9
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053515.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053516.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053516.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..117e9bb3466031d07954a79de6ea8437a5ca5248
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053516.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053517.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053517.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..40188df3cdd280bc76a74637f98d0b1876cdbd06
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053517.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053518.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053518.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..2a8daffa797bad37062e97724fd632565cc61328
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053518.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053519.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053519.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..99ebf238072449c3b32297fe6cb50d8d22e8e875
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053519.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053520.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053520.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c1fc173d6ee2a6e3343b65710c11668ea91e9300
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053520.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053521.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053521.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..190ca6a15480274e56fe9232ec457df609495235
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053521.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053522.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053522.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..4bdf29c2a610c89bb57befcc2ce497abac194d15
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053522.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053523.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053523.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..62f608b105fb7b3e4fc21ece16c62814c282b459
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053523.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053524.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053524.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..9b11dd751b4176cac50325413c4a95b25c789b53
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053524.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053525.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053525.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..1a765c281541a9da19e2f8f26bb2f74e792c7df5
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053525.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053526.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053526.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..6dcc8dc6f38cb6a677a719b40b9a46f31065c5cc
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053526.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053527.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053527.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..caa4bdf7ae39ad42c8f3bd47a43702f86e67dd08
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053527.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053528.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053528.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..40a867ad90fd7c48090be8d719541bb5754d80f6
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053528.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053529.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053529.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..424323080ecb746ec5dcf518f8802c21f7a0fd08
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053529.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053530.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053530.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..5aa46504c7e53d13340c62caf12c11848ebcfb9e
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053530.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053531.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053531.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..47e66dddb49dc5ed434200abc9e626fde98e5279
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053531.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053532.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053532.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..d29e19730728e9f7a24fc447734dddc961c16caf
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053532.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053533.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053533.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b4df1dc5ef007d4e4a5109790505ac5c5e523265
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053533.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053534.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053534.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..878597506bbcd40d86a2ac0f207329857a21ba3f
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053534.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053535.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053535.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..68cb95a08aea3988e2a15bcfcdf264a67e49633a
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053535.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053536.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053536.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..d56845454154f7b63b36c3ea37f9313f1116e3f0
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053536.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053537.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053537.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..f74dd007eb9d757f7af157918281005decfbb376
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053537.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053538.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053538.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..d7b47391dcb2d0f2a958ad38d4171382214bf710
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053538.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053539.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053539.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..fa8f959aa06be00a32b9bd79556d56c8e8749d20
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053539.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053540.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053540.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..2a2e8c507af822e457a5b29f1da7f9a81bb8e9dd
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053540.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053541.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053541.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..63a4ee7eb91f68ade68442547dba65ab43424a5d
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053541.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053542.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053542.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..6e1fafd1f901ac0c9e5c0740351ec7ee14314140
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053542.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053543.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053543.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..5cd573d787976e5a8ebb10568833700b3e470527
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053543.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053544.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053544.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..9bb980e956203612574ec2f549e4ea0a36f4e342
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053544.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053545.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053545.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..4cf1c4279e08f4d6f20a33d78fe125f7a3532ae5
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053545.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053546.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053546.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..ca56e0f2d5dde6f6511d117d0db0db8e4207e5a4
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053546.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053547.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053547.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..67deed3de3349be60a02d5890a1c774cc8181ca4
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053547.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053548.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053548.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..212f7f17c21730b2e0677500270babc563c07297
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053548.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053549.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053549.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..9cac7f9761edb897602b88b5bfe16cb088fbf0c0
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053549.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053550.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053550.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..9a2d195fdf4307cb6e2a0d293359c533cfb235ec
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053550.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053551.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053551.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..8faaf7191193af03d054da9d09d758093091b4ec
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053551.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053552.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053552.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..245d50ec171baefa165c53031774b8c2ebd50509
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053552.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053553.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053553.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..74fb449b41c8ed733885f1c81326981572f9070c
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053553.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053554.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053554.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..fdc3e6f78b4e4911676ec4732caa84fe38d6fa2e
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053554.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053555.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053555.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..6119414ff961ce62f3897c9f8f5bc8551d33f990
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053555.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053556.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053556.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..805600ec70f56072091634cadf5f155501c6f996
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053556.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053557.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053557.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..3c20b49a263b5647d58a6dfd6595bda2317faf6f
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053557.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053558.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053558.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..46c32cf3f1d375af91388cc05a0f590113c54d93
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053558.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053559.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053559.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..fcf874733ab311f5124da53a656641f8f6ee11b7
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053559.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053560.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053560.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..5c21d74fec6b55d0ee843c5a6f30bcbdd9f2e000
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053560.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053561.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053561.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..7f20b39b56d3bcc2395f397ccf6d7de3dd2c9ccc
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053561.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053562.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053562.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..cd81a8b2388a59815ad034cdc0f790cd1493e4e6
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053562.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053563.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053563.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..cc27bc3fa66fa3fe3e207fe3b9945b752043ee60
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053563.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053564.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053564.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..ad773c34c1365983f6db31506218661ef9571208
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053564.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053565.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053565.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..fa654fc5fb95072609efba765bd77d797fddcf04
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053565.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053566.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053566.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..e2c3bef05451fd613d40fd5de5d7f2f3fac76ad5
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053566.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053567.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053567.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..2c0f475c8ad660a7756eedc4090097318b70b4fe
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053567.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053568.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053568.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..dd6db56ee7f7df971c98147afd6226b0c1ee791b
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053568.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053569.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053569.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..d5070793c41abd98d9a19dcea4b1a980ca9a737d
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053569.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053570.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053570.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..6b5ce30082a6a738681ae9ffc32d425c844cef08
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053570.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053571.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053571.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..baaa0e3231009654ce88bf2f73cd634557a8c9fc
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053571.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053572.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053572.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..2d7ffe9ea92d413933660614aeb958ce5aea2649
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053572.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053573.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053573.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b6aed129d9bb13cb79300f6d2368b2ee18f0c70d
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053573.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053574.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053574.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..8580bd3308592099465965f28c454849590d1bf4
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053574.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053575.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053575.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..a0bea23af44b4e2e162c30bf5394489e2fac8450
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053575.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053576.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053576.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c72c8bd6ed9ac55aa4fc369d904c801f2bab1fdb
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053576.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053577.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053577.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..368aaa46ea972cec14893d0ed331a5979c8fc9a7
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053577.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053578.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053578.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..20b43d93cc6e2b26db07886d130779df1bdd7bc8
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053578.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053579.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053579.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..da9f08ea05b07e3c21f552269501e70d113aa4c3
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053579.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053580.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053580.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..f71455f44554b8550c858e055fcb1351a3813452
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053580.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053581.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053581.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..93cb9699b730f43ae813c094549c844d503e1bbc
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053581.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053582.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053582.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..8f6a89c63fd8b6ee1b23cb41b6c9c170feefb821
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053582.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053583.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053583.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..600a9549c64dcb287a09e5cf4cac41302f23f413
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053583.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053584.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053584.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..3626c9c33175a246cd6c04072b02b2a9f8ebed70
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053584.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053585.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053585.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..26ced330d62ca7f706cea70598ca004436d66332
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053585.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053586.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053586.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..e7a1ce2bb0f721ca5cae5b3f5582791ebff427ee
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053586.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053587.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053587.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..cc76311fe4612d58be5c3ed06207240b9731706f
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053587.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053588.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053588.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..e2b6fda74c5d42c7c2adb7541aca5c1c636fc53c
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053588.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053589.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053589.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..36b0397a6832de7e05495455a61b096c941cb955
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053589.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053590.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053590.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..9246c59f836d7157cdc74e9e4d0e563f30307d89
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053590.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053591.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053591.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..4641a6d28c333e4fb5fa88b1c8c0ddef42e758b7
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053591.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053592.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053592.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..a051279e52030786793ccb0d2f7c44f2bb380dc5
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053592.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053593.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053593.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..50f3d7de1317f6bcd34f7dda2299412559fd0b32
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053593.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053594.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053594.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..7fcac497408e345922e2940989085f052ecedc76
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053594.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053595.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053595.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..763e1e58c01db9a5191b64a87b89cbf1eda7788a
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053595.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053596.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053596.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..fec64844009948341baafbedf01690a5a7a674be
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053596.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053597.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053597.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..6679c1b1ac574fcb77e8ce0ab4cf7d02b82b1f3b
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053597.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053598.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053598.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..3f9bf9608c55daa4eca0df2ad00542ae9ce2efd8
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053598.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053599.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053599.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..11acc9d6e750d72456f235d07127c92ae63f2578
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053599.jpg differ
diff --git a/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053600.jpg b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053600.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..fac34bbc8dbafaf69f34c3db6fdcd5a2a7bda7da
Binary files /dev/null and b/data/examples/mosquito_escapes/cam2/20200216_075905-selection/cam2_20200216_0759053600.jpg differ
diff --git a/data/examples/mosquito_escape/cam2_20200303_030120/cam2_20200303_030120-2d_points.csv b/data/examples/mosquito_escapes/cam2/20200303_030120/cam2_20200303_030120-2d_points.csv
similarity index 100%
rename from data/examples/mosquito_escape/cam2_20200303_030120/cam2_20200303_030120-2d_points.csv
rename to data/examples/mosquito_escapes/cam2/20200303_030120/cam2_20200303_030120-2d_points.csv
diff --git a/data/examples/mosquito_escape/cam2_20200303_030120/cam2_20200303_030120-obj1-2d_points.csv b/data/examples/mosquito_escapes/cam2/20200303_030120/cam2_20200303_030120-obj1-2d_points.csv
similarity index 100%
rename from data/examples/mosquito_escape/cam2_20200303_030120/cam2_20200303_030120-obj1-2d_points.csv
rename to data/examples/mosquito_escapes/cam2/20200303_030120/cam2_20200303_030120-obj1-2d_points.csv
diff --git a/data/examples/mosquito_escape/cam2_20200303_030120/cam2_20200303_030120-obj1-3d_track.csv b/data/examples/mosquito_escapes/cam2/20200303_030120/cam2_20200303_030120-obj1-3d_track.csv
similarity index 100%
rename from data/examples/mosquito_escape/cam2_20200303_030120/cam2_20200303_030120-obj1-3d_track.csv
rename to data/examples/mosquito_escapes/cam2/20200303_030120/cam2_20200303_030120-obj1-3d_track.csv
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050001.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050001.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..0d113628e6d340d4f2aaabe362efe6de5fecdd2c
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050001.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050051.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050051.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..1a3bdfb69d5c927f5591455d5270f26a7647fea0
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050051.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050101.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050101.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..7d4c313f569c6241db0d75b106b43ec00f486075
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050101.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050151.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050151.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..8451e964fabc653d0397b895e623adee942472db
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050151.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050201.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050201.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c7f529577eaa6eb8fffb507addb85090ace3ed4c
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050201.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050251.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050251.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..9b912549b8953079db0dd71f6848dc49d4d34b23
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050251.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050301.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050301.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..80360d5127c531368f40cec9cc6c3e31c1149221
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050301.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050351.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050351.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..fa7d29d9813db6481ef07781dd3ac317fd633f50
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050351.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050401.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050401.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..64ce03523aa66eacc48bb53e0c7e501214b152d9
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050401.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050451.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050451.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c1b07d802e0a50b236eb9e117451092b81b6308e
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050451.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050501.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050501.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..7a353decbdd9f3780412273d57dfcb4feeddaae0
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050501.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050551.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050551.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..de531b63f4e3873e0a75a39372de9df0aabc8a7e
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050551.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050601.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050601.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..33ee7e370141aa87c3b5646cf1acb6d16b14a004
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050601.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050651.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050651.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..7db1508d9c51c090bcbd18a3ead34ca1f8f4f9ea
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050651.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050701.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050701.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..69366d144fec614d2c3ee5e9ecb059cd19fbe89d
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050701.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050751.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050751.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..45a55fabca21f97711b30a34548d05941c094aa3
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050751.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050801.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050801.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..74f8be46c8b79a702620b0158a4b07cd5a936d82
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050801.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050851.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050851.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..2d87d36fc92625c0d77e49de7009262bb175ee84
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050851.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050901.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050901.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..52f07dde1dbaad622054880397d7f27c87ae4355
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050901.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050951.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050951.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..d9ae8232d588321ef5de55fcf6ebba85ab4cb137
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759050951.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051001.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051001.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..87855ebf3d749cfe6b39b8c69ce66d2198a66eec
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051001.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051051.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051051.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..5cf243b81dbfb183cf8f1c55ce738e0fa88f12a7
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051051.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051101.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051101.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..a66803496cdd9b06d50f3896370430be81124831
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051101.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051151.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051151.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..7ace06023771658fc0682dae6a5d5e4c4ce1bea2
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051151.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051201.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051201.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..55b92da86b77cbf53011a042aebc185ee28aa965
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051201.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051251.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051251.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..1b5bf0dd9858c70e877d885913deb38a3b15aefb
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051251.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051301.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051301.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..de39d85545a077b7040d5a129655b70f4d5731dc
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051301.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051351.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051351.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..4116237fba4e6fe69d1c46fe7cd274e7d02b36a5
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051351.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051401.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051401.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..51105ed3214c45eac9f8c69ef88516be84969e65
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051401.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051451.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051451.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..44330e6bf821dde4aa2eeb46cf4bb7199b06bd69
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051451.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051501.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051501.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..7ccb95635c5e5969be356a30b5c8e0a77a4eced2
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051501.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051551.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051551.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c44bba56f7baf252e1ec5240f2aaba90f9bce8da
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051551.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051601.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051601.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..74442ea8caf2ee48bb408a33c4314d5db75b9c24
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051601.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051651.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051651.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..39803d5b47d4667ecbedf47178d28f54aeeca031
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051651.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051701.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051701.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..ad6b2c274abee647361586db090a391381bf5877
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051701.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051751.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051751.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..3743003b083ad781d9ba2eb5d63736c801901b82
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051751.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051801.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051801.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..87d485fd1f096dd4cff7655b66b88c28bfd29226
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051801.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051851.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051851.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..7e5daedcf896cac03511f125465b676f8062216a
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051851.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051901.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051901.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..0ad6ebea856a3dea978b51790e845cba78a6e035
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051901.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051951.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051951.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..29f91977b574a21b6e100a85ce84cb21a2dc81fa
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759051951.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052001.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052001.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..707a6100d443105edd20bc0417540e43beaa72f8
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052001.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052051.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052051.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..38594a9df693f389c3546dbf3cace9f14b693f79
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052051.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052101.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052101.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..cdb7f69fb97fcb7e376383de249cb5602c1e976b
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052101.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052151.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052151.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..9e1fe0c0839f41b8c4f0c158438fd6c15c4cbfe4
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052151.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052201.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052201.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b5fd1aa435eb810866a347afe0f822c597907b39
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052201.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052251.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052251.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..0da5ddbe20177e77794887368de547cadae0b062
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052251.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052301.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052301.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..cb5aa933485146a1dce303d1ac503e4f81d91783
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052301.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052351.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052351.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..941c838c81eb27e9dd0867dea2a403d49a9fd8ea
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052351.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052401.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052401.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..ee2cf733b2ce02f70fd9cda45973b63dec98b3ad
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052401.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052451.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052451.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..336682cc6faf91f92904ff058dca655064fb9f70
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052451.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052501.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052501.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..cf93d81a4e7893ce5c99eb03619a86e71ba58559
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052501.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052551.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052551.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..315176fe4fdefe36b675918dec51ca6ff5afd429
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052551.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052601.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052601.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..5f083d7f538eaecf531a275f865b11cbe08f388e
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052601.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052651.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052651.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..4be4cd53d4ddea2d7b24d6cc6407af6d9fa0f235
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052651.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052701.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052701.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b783635a03ce677e52bca3bc08203d168ade06ef
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052701.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052751.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052751.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..37de30692c7e9e10b52d1b05d7485f4c9283757b
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052751.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052801.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052801.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..4f7956f6e0b9c8e088538396e180cca099ae57b6
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052801.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052851.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052851.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..5e18d458c71da3f07b4bcae6172297c869cf0b99
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052851.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052901.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052901.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..243a7633c587f761dbe21153ed22fbefa7d3d345
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052901.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052951.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052951.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..7ba865a9896895ac541488155aea4b961259bfc8
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759052951.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053001.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053001.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c6ec9576ed72bc46622f40ae76ebec97a6481687
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053001.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053051.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053051.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..55b8d665c14b84eb44953fec41dda1cde49633a2
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053051.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053101.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053101.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..7ffa50b62fb0017b6776c49835cbfcccb502b40e
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053101.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053151.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053151.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..169919e81ed2287dab936d30347d9533dd630a26
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053151.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053201.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053201.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..7d9a8a9b8ccbe283238286cefe615b27e2935877
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053201.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053251.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053251.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..84d41bb6391af34f4477b1a505a9ec7fc6b9cf8c
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053251.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053301.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053301.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..36550d26ebb9022b70801b21b2102d5eb655ad7d
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053301.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053351.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053351.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..21334cbdf9c994e5ca3687f4c101dd1322a2e3ba
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053351.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053401.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053401.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..94e9cfb9dfe04b376631934d37b9c92e2e35ad45
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053401.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053451.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053451.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..ce17b216560ef35ab54740824ba3835e84b57458
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053451.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053501.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053501.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..81aa969cdfa609c413dacc80c2c2bc285e676db3
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053501.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053551.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053551.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..78ab45cda72c92de72bb3fe6e135db3033e3aeb8
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053551.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053601.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053601.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..dc94a583ee89a530b12df824d7276ec33e210d2a
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053601.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053651.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053651.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..9787a51aa1074696d23c150ddb318c471161519a
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053651.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053701.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053701.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..477413eb052e273fe800dd1a9425b8140e2b30f1
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053701.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053751.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053751.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..f47b67f397e353bc6fd2156fba2537c8850441fc
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053751.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053801.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053801.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c746acf76a96d1a1bda1d1aee937ebf84f8fe03d
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053801.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053851.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053851.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..aa2f4e08e8da115a944a0a65f6c2cc16c61b7fa7
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053851.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053901.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053901.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..725f639502304bfbf9524389d35248da1882c21a
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053901.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053951.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053951.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..178b382cabb76df730451e9858da28ef9739e409
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759053951.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054001.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054001.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..f52d29c1f3df9775d073672676a229216aab3b57
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054001.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054051.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054051.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..3604ff003b1a954f987dc68a42efb44bb7d0f569
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054051.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054101.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054101.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..88a5db812ea3fcaa5f058efbdc3d892fd6d827ab
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054101.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054151.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054151.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..126b9dc096950e0cc729e7d25cf35ecd91906dbf
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054151.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054201.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054201.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..011f010d16ab3e7a740cdee58fcb8fe6f84674f5
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054201.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054251.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054251.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..8062e64894b815394529bb7a94413bf96331cb5a
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054251.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054301.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054301.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..e5b22235f2bc7c7d8c5c5db0ee239adc0fc2af6d
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054301.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054351.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054351.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..30bd8215a3203e45cc24dfc6ec36a0849b6da4ec
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054351.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054401.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054401.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b385fffcd899c2aa9ec58a06898161bf129cf722
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054401.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054451.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054451.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..4e24fcbf2e8c1a27150eaf2739e7456752060659
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054451.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054501.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054501.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..8db25b50c8d8021a22deb5994ddad3b8014b6149
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054501.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054551.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054551.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..d00e3ecf845bbe86fb2edec993134e51ec17554d
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054551.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054601.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054601.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..04f164eb71f332be35fee0ed7891c729b6f50215
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054601.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054651.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054651.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..048237e05bdc4fbd54a3e0b8e6a3677b40414ef9
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054651.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054701.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054701.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..4657e490f09f52db021006ebcf45230d3d14eb51
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054701.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054751.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054751.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..5602797f1155a309db8e6e5910a5f44819ef14b8
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054751.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054801.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054801.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..2a355aa7a9230755c07d9291198cca5da6875f45
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054801.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054851.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054851.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..27728bed943bd3913f6c0490c060f710a582c051
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054851.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054901.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054901.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..7a6af4911a84e9a195438433b058c1a56cc1b3b9
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054901.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054951.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054951.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..bc5ee8302e7297d5d6bd8bd0fa10ecbe3ac2468c
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759054951.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759055001.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759055001.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..9dbd2aa95eaa98b87179a0a3605ea302f0d42ad7
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759055001.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759055051.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759055051.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..58d84f9e1f72ebea6cec198666e226e1527156e0
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759055051.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759055101.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759055101.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..0908cc816c39bcba0d14b4fffaee1d98dd1ddfa2
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759055101.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759055151.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759055151.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..bb59131578ca6d42f5054bd3828210591830bf46
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759055151.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759055201.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759055201.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..e8e1c3393eaf27fb857d49c5346f1f8bb7a552e6
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759055201.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759055251.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759055251.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..812384dd7e6e79d22c17e005383bb51ebdb4340c
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759055251.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759055301.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759055301.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..f0f9997cf95bdb332c27728041b4147d650164d2
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759055301.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759055351.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759055351.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..674384972f740c5b35a965d17b37a8ee62f58b0a
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759055351.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759055401.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759055401.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..9f28821ef34ff0834f5384fb13eb3ad8ae1f2c68
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759055401.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759055451.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759055451.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b24029d70f414ea39512ec0691f25ad67ec529a7
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-sample_binned/cam3_20200216_0759055451.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053501.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053501.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..627581df02b44b1aae77d43fd2426a2f8b3d2a18
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053501.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053502.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053502.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..5edc13742382a0c4588ca7731a95e11c41410e9e
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053502.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053503.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053503.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..1a783dd89333cc08c0b8d31aa8e8f5779bbb6ef9
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053503.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053504.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053504.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..d5f1e1ca4d74297e34312b98bcc02b950b2e0eaa
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053504.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053505.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053505.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..22b62e67fd77e56f7a2aaec374298ddc449f4d42
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053505.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053506.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053506.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..3d0832a495cf242aa8ccb2414998bf6b91e6d69e
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053506.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053507.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053507.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..9b925cd6f96371e218075637a670f771b205d90e
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053507.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053508.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053508.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..dda70b282dae54032a6581f78e9ae046547e8730
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053508.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053509.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053509.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..adb371fd7f25648b9dab08542602637e2f19601a
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053509.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053510.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053510.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..1863d5629607baa4c65dfa14c474288a1e89694f
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053510.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053511.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053511.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..4fa92f2210e45684c293968e15acb33c146a232e
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053511.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053512.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053512.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..701d17714541960da1264d5bb095cd31676a5d46
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053512.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053513.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053513.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..ae705e6bae3e67ccfd35aff2b0fd3e53c0c2d718
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053513.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053514.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053514.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..7c18e5c425c97356a639b10299fa63b7aaec18d3
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053514.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053515.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053515.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..86538e388b870a13e2321c7df90d2b6f67ace566
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053515.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053516.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053516.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..394c9bc3d4585d682bed0a3cf24784d338b31902
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053516.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053517.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053517.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..d3f9d655751d18109958f49c25bdca9b4eb1f269
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053517.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053518.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053518.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c701409053e6b3969f264020bd1de2575fbd4f51
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053518.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053519.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053519.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..34b376a45b026b5d4eda6619892677f2680934e8
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053519.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053520.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053520.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..8d64104e6e934864da371dde68d7ca0409849f5a
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053520.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053521.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053521.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..9b5a1230791084219cc63edab23da5bca52706d7
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053521.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053522.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053522.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..88bedf169aea8ce02a1900bd8f342423d9a87db9
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053522.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053523.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053523.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..d877ef879aa4a79896e8f3c05e468a28c8d5646e
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053523.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053524.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053524.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..7b91f0435bdfe41288df253920ee4e5727f02c0c
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053524.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053525.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053525.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..18b6268586728b73c83a90cc0ef63d3e31faff3b
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053525.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053526.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053526.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..8549e87dd3aedc5576752bd43a58706dd8f98514
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053526.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053527.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053527.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c09e315eeeb8bcdc6aeae1d42ccee581e28482df
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053527.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053528.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053528.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..313881b35e2bca63a4256900f5652cbdaa7d38f4
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053528.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053529.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053529.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..e0b3225073c1b898733cb9e64d3bdc43a042c54c
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053529.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053530.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053530.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..636d9379f1efec6a97bf72f07a7d4c7cc315e5a7
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053530.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053531.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053531.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..70937f50cb4f4524151f4089add2a78a4c82e4f3
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053531.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053532.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053532.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b56a82314a5f73fa202c39bb8e623882227d885b
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053532.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053533.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053533.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b4114072f5613d3554f6d735c2fd3d9609c769f4
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053533.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053534.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053534.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..985dd92a69dc523010e590764c04361dca3688a8
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053534.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053535.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053535.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..0f05bf45c9910a1fd57f5ff992034b548612797e
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053535.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053536.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053536.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..ab9bfcc041f6327791cb900b92f60268c1a6b783
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053536.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053537.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053537.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..92cca89ebbdac9cd9d7ddab97ab3e1b6c5d78831
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053537.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053538.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053538.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..851395a8a634e0cd2ec427a8c822e182ccf1b2d8
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053538.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053539.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053539.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..0d387a8aeafd5f73ae468c8893f8670ccfed1ce3
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053539.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053540.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053540.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..65ffd737b25917790f1b38efb375c7a4deda3c3e
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053540.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053541.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053541.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..606f4ede5ef35888064cfecfb26d6d94132d3665
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053541.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053542.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053542.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b2c89a8748fa86bb3fc79b063881d3de78e5c84b
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053542.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053543.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053543.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c1ef448f03dc2db58e80249a5c6c2efc55923cb1
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053543.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053544.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053544.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c31cdbd6f1069eb8b2046e577212ef5750f333bf
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053544.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053545.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053545.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..ca848047127906d55ae2f0c2237d32ec402e1886
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053545.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053546.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053546.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..78807448de0d0809c2d4122ab1f3886f63f7910c
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053546.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053547.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053547.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b9728a1375efdc2a6869dd0cbfd7b39358bd5c84
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053547.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053548.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053548.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..af8b291a0c86986729bba0af98344c5133186ee0
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053548.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053549.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053549.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..8fe7d7f950f723fb63e9c16c3b6c95d8116991f2
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053549.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053550.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053550.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..5558827f2bdff8b74b771beb50bf4c1d9349adf7
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053550.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053551.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053551.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..dae2609c5d4704248980cdab381fb853816260c6
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053551.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053552.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053552.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..be318ccf9c418decf92f984744b5dafa7d683449
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053552.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053553.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053553.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..f106a1b0899f495420ffbb08109263f65a5f7701
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053553.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053554.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053554.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..73b96373dd455924503c8e3c3921f608286be81b
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053554.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053555.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053555.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..08e63dc674114b7df8d6df41c4eb44523a8c9717
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053555.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053556.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053556.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..4936b7132710bde6b3296680ecc81a7b283ad85b
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053556.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053557.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053557.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..e02b242f44ac99894605d03a3e7a8c400181d670
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053557.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053558.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053558.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b3d8f9a120c969bdcd1abc63d11683c1a7b98016
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053558.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053559.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053559.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..43ce72a5c2efd7d374437f3f35be9d068d2a3694
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053559.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053560.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053560.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..6c224c8f99a16b543b93cfdfda1d033185837218
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053560.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053561.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053561.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..02713887f784affc7e891ca44dcfc83088e487dd
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053561.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053562.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053562.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..cf2da61bf5b70069370ea3e2c2bb6222f81a3f7d
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053562.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053563.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053563.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..d15c14d574f2d334164920fe591326ee2b0bd452
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053563.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053564.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053564.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..cc2051c76cc69d257890c5955f09a4bc06cbe4ab
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053564.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053565.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053565.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..320536fd159f317674138e193c77f5371330f6e9
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053565.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053566.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053566.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..04baacd157486967a8759b89605c2403641f3035
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053566.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053567.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053567.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..63ae3954aae93ad1e144a67bd6a9101a3ee8afcf
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053567.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053568.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053568.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c137c4ed6b27ee0b3f15108720216476ac0f7f76
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053568.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053569.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053569.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..38948cf72d35ed23c8c6be46669f6bd40d3d6eea
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053569.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053570.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053570.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..39bc60a8d0e2bd7b1d123caa100c5e2e02f9d190
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053570.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053571.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053571.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..72a16d4846777f5d377dfb3ff860353faa42e192
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053571.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053572.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053572.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..e5a938977d74465decb56b0d17a8309e3933f1b6
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053572.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053573.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053573.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..68bd6fc800535eb18b22d5a2e1cc0e948d3c24a9
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053573.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053574.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053574.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..be53bd25cd8fed5af236992ff33230a0802ad3aa
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053574.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053575.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053575.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..495d87d3bbd96ee9fa0db421db07027f5f57e651
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053575.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053576.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053576.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c0399c5c9644830d720186bc300300c510251277
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053576.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053577.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053577.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..4c6bd22e763bf7d71a033631046f97fc5d84936e
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053577.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053578.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053578.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c23f24cc9e453d74947f20e504452d4e22058135
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053578.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053579.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053579.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..549892d90c3d8c68b87c8edbfe72033ff9649922
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053579.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053580.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053580.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..e3409feabd467c8205b87e128358292bbaf3d8eb
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053580.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053581.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053581.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..9db8bb150b63b2129bcabd6508b517f38326081c
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053581.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053582.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053582.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..ffcfc4950c7dbd724f0f046e554a5c1acd7ecc44
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053582.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053583.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053583.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..dd62645b21bffcfa012c582ed106175ce3f3b2ae
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053583.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053584.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053584.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..ca3b652bedb92e9a73a0eba594584eadbf4763dc
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053584.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053585.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053585.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..6b33d28f33240a074e32494458304bebaba45efb
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053585.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053586.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053586.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..ac5ff64617a711b1b0cd56ebe70eb99f19ea9fc6
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053586.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053587.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053587.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..234525577615420b0f6825a1c532bc7c9908662f
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053587.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053588.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053588.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..dd541332ce01341dea79d3e972d160fabc4a45f1
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053588.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053589.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053589.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..077c18e6530418c46f625972e47b834fc2de4047
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053589.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053590.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053590.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..ad7cdfda2e900dc0c2c5ab767ba9e8b2c532ae26
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053590.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053591.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053591.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b9a0079fb666bd84fb95f1dd4b3ccbdc13107aa5
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053591.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053592.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053592.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..3289b02f5e4f6e7a2a59df79ac776078b509738b
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053592.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053593.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053593.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..284411c33c9ded31d1428130cd6194e389d80ec0
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053593.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053594.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053594.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..6f763580ddd70ac79222963e9460e7bb54dadb55
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053594.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053595.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053595.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..44e6016e0ba2b1be6faaa633cfcecd514ea6afeb
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053595.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053596.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053596.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..06fe97fd9c71aa3457ac01d4889eb48a62efa04b
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053596.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053597.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053597.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..a16216dd1bb4246ac6e7a81e476b9f0e1b6f4108
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053597.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053598.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053598.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..441f6fa7acdfea286c1063af1aaa714a58faf03f
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053598.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053599.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053599.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..1a24c6246ff78692a765267c901844d97ea02d7a
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053599.jpg differ
diff --git a/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053600.jpg b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053600.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..0afdab340300b164d639b9c6a295dfe8adf8ccc2
Binary files /dev/null and b/data/examples/mosquito_escapes/cam3/20200216_075905-selection/cam3_20200216_0759053600.jpg differ
diff --git a/data/examples/mosquito_escape/cam3_20200303_030120/cam3_20200303_030120-2d_points.csv b/data/examples/mosquito_escapes/cam3/20200303_030120/cam3_20200303_030120-2d_points.csv
similarity index 100%
rename from data/examples/mosquito_escape/cam3_20200303_030120/cam3_20200303_030120-2d_points.csv
rename to data/examples/mosquito_escapes/cam3/20200303_030120/cam3_20200303_030120-2d_points.csv
diff --git a/data/examples/mosquito_escape/cam3_20200303_030120/cam3_20200303_030120-obj1-2d_points.csv b/data/examples/mosquito_escapes/cam3/20200303_030120/cam3_20200303_030120-obj1-2d_points.csv
similarity index 100%
rename from data/examples/mosquito_escape/cam3_20200303_030120/cam3_20200303_030120-obj1-2d_points.csv
rename to data/examples/mosquito_escapes/cam3/20200303_030120/cam3_20200303_030120-obj1-2d_points.csv
diff --git a/data/examples/mosquito_escape/cam3_20200303_030120/cam3_20200303_030120-obj1-3d_track.csv b/data/examples/mosquito_escapes/cam3/20200303_030120/cam3_20200303_030120-obj1-3d_track.csv
similarity index 100%
rename from data/examples/mosquito_escape/cam3_20200303_030120/cam3_20200303_030120-obj1-3d_track.csv
rename to data/examples/mosquito_escapes/cam3/20200303_030120/cam3_20200303_030120-obj1-3d_track.csv
diff --git a/images/__init__.py b/images/__init__.py
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b74215e0fbbb6512474bdc2ed9cd9b75eff4ad43 100644
--- a/images/__init__.py
+++ b/images/__init__.py
@@ -0,0 +1,3 @@
+from images.image import Image, TypeImage
+from images.imagesequence import ImageSequence
+from images.multiimagesequence import MultiImageSequence
\ No newline at end of file
diff --git a/images/image.py b/images/image.py
new file mode 100644
index 0000000000000000000000000000000000000000..00d38089f4805a16987a2947f63b94d9fb97abc4
--- /dev/null
+++ b/images/image.py
@@ -0,0 +1,622 @@
+"""Images."""
+
+from abc import ABCMeta, abstractmethod
+from enum import Enum
+
+import os
+from typing import Any, Optional, Tuple, Union
+
+import numpy as np
+import PIL.Image
+
+__author__ = "C.J. Voesenek"
+__maintainer__ = "C.J. Voesenek"
+__email__ = "cees.voesenek@wur.nl"
+
+
+class TypeImage(Enum):
+    """Image types.
+
+    Options:
+        BW: Black and white (i.e. binary).
+        GREY: Greyscale images.
+        RGB: RGB images.
+    """
+    BW = "bw"
+    GREY = "grey"
+    RGB = "rgb"
+
+
+class TypeFileImage(Enum):
+    """Image file types.
+
+    Options:
+        TIFF_BW: Black and white TIFFs.
+        TIFF8: 8-bit TIFFs.
+        TIFF16: 16-bit TIFFs.
+    """
+    TIFF_BW = "tiff_bw"
+    TIFF8 = "tiff8"
+    TIFF16 = "tiff16"
+
+
+class OperatorLogical(Enum):
+    """Logical operator types.
+
+    Options:
+        NOT
+        AND
+        OR
+    """
+    NOT = "not"
+    AND = "and"
+    OR = "or"
+
+
+class OperatorComparison(Enum):
+    """Comparison operator types.
+
+    Options:
+        EQUALS
+        LESS
+        GREATER
+        LESS_EQUALS
+        GREATER_EQUALS
+    """
+    EQUALS = "="
+    LESS = "<"
+    GREATER = ">"
+    LESS_EQUALS = "<="
+    GREATER_EQUALS = ">="
+
+
+class _ImageData(metaclass=ABCMeta):
+    """Abstract image data."""
+    @property
+    @abstractmethod
+    def nb_xi(self) -> int: pass
+
+    @property
+    @abstractmethod
+    def nb_eta(self) -> int: pass
+
+    @property
+    @abstractmethod
+    def shape(self) -> int: pass
+
+    @property
+    @abstractmethod
+    def type(self) -> TypeImage: pass
+
+    @property
+    @abstractmethod
+    def get(self) -> np.ndarray: pass
+
+    @property
+    @abstractmethod
+    def clear_cache(self) -> None: pass
+
+    @property
+    @abstractmethod
+    def use_caching(self) -> bool: pass
+
+    @use_caching.setter
+    @abstractmethod
+    def use_caching(self, use_caching: bool) -> None: pass
+
+
+class _ImageDataArray(_ImageData, metaclass=ABCMeta):
+    """Images data as an array."""
+
+    def __init__(self, data: Optional[np.ndarray] = None,
+                 shape: Optional[Tuple[int, int]] = None,
+                 type_data: TypeImage = TypeImage.GREY) -> None:
+        """Initialises image data from scratch."""
+        self._type = type_data  # type: TypeImage
+
+        self._data = None  # type: np.ndarray
+        if data is not None and shape is None:
+            self._data = data
+        elif shape is not None and data is None:
+            self._initialise_array(shape)
+        else:
+            raise ValueError("Specify either data or shape.")
+
+    @property
+    def nb_xi(self) -> int:
+        return self._data.shape[0]
+
+    @property
+    def nb_eta(self) -> int:
+        return self._data.shape[1]
+
+    @property
+    def shape(self) -> Tuple[int, int]:
+        return self._data.shape
+
+    @property
+    def type(self) -> TypeImage:
+        return self._type
+
+    @property
+    def use_caching(self) -> bool:
+        raise RuntimeError("Array-based images do not have a cache.")
+
+    @use_caching.setter
+    def use_caching(self, use_caching: bool) -> None:
+        raise RuntimeError("Array-based images do not have a cache.")
+
+    def clear_cache(self) -> None:
+        raise RuntimeError("Array-based images do not have a cache, "
+                           "so it cannot be cleared.")
+
+    def get(self) -> np.ndarray:
+        return self._data
+
+    @abstractmethod
+    def _initialise_array(self, shape: Tuple[int, int]): pass
+
+    @staticmethod
+    def build(data: np.ndarray = None, shape: Tuple[int, int] = None,
+              type_data: TypeImage = TypeImage.GREY) -> "_ImageDataArray":
+        if type_data == TypeImage.GREY:
+            return _ImageDataGrey(data, shape)
+        elif type_data == TypeImage.BW:
+            return _ImageDataBW(data, shape)
+        elif type_data == TypeImage.RGB:
+            raise NotImplementedError("RGB images have not yet been "
+                                      "implemented.")
+
+
+class _ImageDataGrey(_ImageDataArray):
+    """Greyscale image data."""
+    def __init__(self, data: Optional[np.ndarray] = None,
+                 shape: Optional[Tuple[int, int]] = None) -> None:
+        """Initialises greyscale image data."""
+        super(_ImageDataGrey, self).__init__(data, shape,
+                                             type_data=TypeImage.GREY)
+
+    def _initialise_array(self, shape: Tuple[int, int]) -> None:
+        self._data = np.zeros(shape)
+
+
+class _ImageDataBW(_ImageDataArray):
+    """Black and white image data."""
+    def __init__(self, data: Optional[np.ndarray] = None,
+                 shape: Optional[Tuple[int, int]] = None) -> None:
+        """Initialises greyscale image data."""
+        super(_ImageDataBW, self).__init__(data, shape,
+                                           type_data=TypeImage.BW)
+
+    def _initialise_array(self, shape: Tuple[int, int]) -> None:
+        self._data = np.zeros(shape, dtype=np.bool_)
+
+
+class _ImageDataFile(_ImageData):
+    """Image data from a file."""
+
+    def __init__(self, file: str, type_data: TypeImage,
+                 use_caching: bool = True) -> None:
+        """Initialises image data from a file."""
+        self._file = file  # type:
+        self._use_caching = use_caching  # type: bool
+        self._cache = None  # type: np.ndarray
+        self._type = type_data  # type: TypeImage
+        self._shape = None  # type: Tuple[int, int]
+
+        self._check_existence()
+
+    @property
+    def nb_xi(self) -> int:
+        if self._shape is None:
+            self._load_shape()
+        return self._shape[0]
+
+    @property
+    def nb_eta(self) -> int:
+        if self._shape is None:
+            self._load_shape()
+        return self._shape[1]
+
+    @property
+    def shape(self) -> Tuple[int, int]:
+        if self._shape is None:
+            self._load_shape()
+        return self._shape
+
+    @property
+    def type(self) -> TypeImage:
+        return self._type
+
+    @property
+    def use_caching(self) -> bool:
+        return self._use_caching
+
+    @use_caching.setter
+    def use_caching(self, use_caching: bool) -> None:
+        self._use_caching = use_caching
+
+    def get(self, cache: bool = True) -> np.ndarray:
+        """Gets the image data.
+
+        Args:
+            cache: Whether to save the image data in the cache.
+
+        Returns:
+            The image data.
+        """
+        if self._cache is None:
+            data = self._load_image()
+            return data
+        else:
+            return self._cache
+
+    def clear_cache(self) -> None:
+        """Clears the cached image."""
+        self._cache = None
+
+    def _load_image(self) -> np.ndarray:
+        """Loads the image from the specified file.
+
+        Depending on whether caching is used, this also caches the file.
+
+        Returns:
+            The image data.
+        """
+        if self._type == TypeImage.GREY:
+            image = np.array(PIL.Image.open(self._file))
+            if image.dtype == np.uint8:
+                data = image / (2 ** 8 - 1.0)
+            elif image.dtype == np.uint16:
+                data = image / (2 ** 16 - 1.0)
+            else:
+                raise ValueError("Unknown datatype in file \"{}\" for "
+                                 "greyscale images, can only convert 8-bit "
+                                 "and 16-bit images.".format(self._file))
+            self._shape = data.shape
+        elif self._type == TypeImage.BW:
+            data = np.array(PIL.Image.open(self._file))
+            if data.dtype != "bool":
+                raise ValueError("The specified image \"{}\" is not black and "
+                                 "white.".format(self._file))
+        else:
+            raise NotImplementedError("Other data types than grayscale for "
+                                      "loading images are not yet implemented.")
+
+        if self._use_caching:
+            self._cache = data
+        return data
+
+    def _load_shape(self) -> None:
+        """Reads the dimensions from the image."""
+        image = np.array(PIL.Image.open(self._file))
+        self._shape = image.shape
+
+    def _check_existence(self) -> None:
+        """Checks whether the specified file exists.
+
+        Raises:
+            IOError: File {} does not exist.
+        """
+        if not os.path.isfile(self._file):
+            raise IOError("File {} does not exist.".format(self._file))
+
+
+class Image:
+    """An image.
+
+    Attributes:
+        nb_xi: The number of points along the first image dimension.
+        nb_eta: The number of points along the second image dimension.
+        shape: The number of points along respectively the xi- and eta-
+            dimensions.
+    """
+
+    def __init__(self, **kwargs) -> None:
+        """Initialises an image.
+
+        Images can be loaded from a file, in which case they are optionally
+        cached, allowing you to free memory when the image is not in use
+        anymore. Images can also be created from scratch, in which case they
+        will remain in memory.
+
+        Args:
+            file: The path to an image file to load. Cannot be used in
+                combination with "shape".
+            image_type: The type of image to load.
+            use_caching: Whether to use caching or load the image
+                immediately. Only useable when "file" has been specified.
+            shape: The shape of the image to create. Cannot be used in
+                combination with "file".
+            data: The array containing the image.
+
+        Raises:
+            ValueError: Invalid keyword argument, for file images, use only
+                "type" and "use_cache".
+            ValueError: Invalid keyword argument, for array images, use only
+                "type".
+        """
+        self._data = None  # type: _ImageData
+
+        if "file" in kwargs:
+            # Check for invalid keyword arguments
+            for arg in kwargs:
+                if arg not in ("file", "type", "use_caching"):
+                    raise ValueError("Invalid keyword argument, for file "
+                                     "images, use only \"type\" and "
+                                     "\"use_cache\".")
+            file = kwargs["file"]
+            if "type" not in kwargs:
+                type_image = TypeImage.GREY
+            else:
+                type_image = TypeImage(kwargs["type"])
+            if "use_caching" not in kwargs:
+                use_caching = True
+            else:
+                use_caching = kwargs["use_caching"]
+
+            self._data = _ImageDataFile(file, type_image, use_caching)
+        elif "shape" in kwargs:
+            for arg in kwargs:
+                if arg not in ("shape", "type"):
+                    raise ValueError("Invalid keyword argument, for array "
+                                     "images, use only \"shape\" and \"type\".")
+            shape = kwargs["shape"]
+            if "type" not in kwargs:
+                type_image = TypeImage.GREY
+            else:
+                type_image = TypeImage(kwargs["type"])
+            self._data = _ImageDataArray.build(shape=shape,
+                                               type_data=type_image)
+        elif "data" in kwargs:
+            for arg in kwargs:
+                if arg not in ("data", "type"):
+                    raise ValueError("Invalid keyword argument, "
+                                     "for directly specified array "
+                                     "images, use only \"data\" and \"type\".")
+                data = kwargs["data"]
+                if "type" not in kwargs:
+                    type_image = TypeImage.GREY
+                else:
+                    type_image = TypeImage(kwargs["type"])
+                self._data = _ImageDataArray.build(data=data,
+                                                   type_data=type_image)
+        else:
+            raise ValueError("Specify either \"file\" or \"shape\" as "
+                             "keywords, for images from file or from scratch "
+                             "respectively.")
+
+    @property
+    def nb_xi(self) -> int:
+        return self._data.nb_xi
+
+    @property
+    def nb_eta(self) -> int:
+        return self._data.nb_eta
+
+    @property
+    def shape(self) -> Tuple[int, int]:
+        return self._data.shape
+
+    @property
+    def type(self) -> TypeImage:
+        return self._data.type
+
+    @property
+    def use_caching(self) -> bool:
+        return self._data.use_caching
+
+    @use_caching.setter
+    def use_caching(self, use_caching) -> None:
+        self._data.use_caching = use_caching
+
+    def clear_cache(self) -> None:
+        """Clears the cached data.
+
+        This function will raise an error if caching is not available.
+        """
+        self._data.clear_cache()
+
+    def copy(self) -> "Image":
+        """Copies the image.
+
+        Note that this will turn file-based images into array-based images;
+        the data will be loaded.
+
+        Returns:
+            A physical copy of the image - the array is duplicated in memory.
+        """
+        return Image(data=np.copy(self.get()), type=self.type)
+
+    def get(self) -> np.ndarray:
+        """Gets the image data as an array.
+
+        Returns:
+            An array with the image data.
+        """
+        return self._data.get()
+
+    def gradient(self, return_components: bool = False) \
+            -> Union["Image", Tuple["Image", "Image"]]:
+        """Calculates the image's spatial gradient.
+
+        Args:
+            return_components: Whether to return the components (if True) or a
+                magnitude (if False).
+        """
+        if self.type != TypeImage.GREY:
+            raise ValueError("Can only calculate spatial gradients for "
+                             "greyscale images.")
+
+        grad = np.gradient(self.get(), edge_order=2)
+        if return_components:
+            return Image(data=grad[0], type="grey"), \
+                   Image(data=grad[1], type="grey")
+        else:
+            return Image(data=np.sqrt(grad[0]**2 + grad[1]**2),
+                         type="grey")
+
+    def logical(self, operation: str,
+                other: Optional["Image"] = None) -> "Image":
+        """Performs a logical operation on the image.
+
+        Note that this can only be used on black and white images.
+
+        Returns:
+            The image with the operation performed on it.
+        """
+        if self.type != TypeImage.BW or \
+                (other is not None and other.type != TypeImage.BW):
+            raise ValueError("Logical operations can only be performed on "
+                             "black and white images.")
+
+        operation = OperatorLogical(operation)
+
+        if operation == OperatorLogical.NOT:
+            return Image(data=np.logical_not(self.get()), type=TypeImage.BW)
+        elif operation == OperatorLogical.AND:
+            if other is None:
+                raise ValueError("Specify another operand to perform the "
+                                 "logical AND with.")
+            return Image(data=np.logical_and(self.get(), other.get()),
+                         type=TypeImage.BW)
+        elif operation == OperatorLogical.OR:
+            if other is None:
+                raise ValueError("Specify another operand to perform the "
+                                 "logical OR with.")
+            return Image(data=np.logical_or(self.get(), other.get()),
+                         type=TypeImage.BW)
+
+    def normalise(self, minimum: Optional[float] = None,
+                  maximum: Optional[float] = None):
+        """Normalises the image to the interval [0.0, 1.0].
+
+        Note: values below the minimum and above the maximum will be clipped
+        to 0.0 and 1.0 respectively.
+
+        Args:
+            minimum: The minimum value, i.e. the value that will represent 0.0.
+                If None is specified, it will use the minimum value in the
+                image.
+            maximum: The maximum value, i.e. the value that will represent 1.0.
+                If None is specified, it will use the maximum value in the
+                image.
+        """
+        if self.type != TypeImage.GREY:
+            raise ValueError("Can only normalise greyscale images.")
+
+        if minimum is None:
+            minimum = np.min(self.get())
+        if maximum is None:
+            maximum = np.max(self.get())
+        data = (self.get() - minimum) / (maximum - minimum)
+
+        # Clip values outside [0.0, 1.0].
+        data[data < 0.0] = 0.0
+        data[data > 1.0] = 1.0
+
+        return Image(data=data, type=self.type)
+
+    def threshold(self, threshold: float, operator_comparison: str = ">") \
+            -> "Image":
+        """Thresholds a greyscale image."""
+        if self.type != TypeImage.GREY:
+            raise ValueError("Can only threshold greyscale images.")
+
+        operator_comparison = OperatorComparison(operator_comparison)
+        if operator_comparison == OperatorComparison.EQUALS:
+            return Image(data=self.get() == threshold, type="bw")
+        elif operator_comparison == OperatorComparison.LESS:
+            return Image(data=self.get() < threshold, type="bw")
+        elif operator_comparison == OperatorComparison.GREATER:
+            return Image(data=self.get() > threshold, type="bw")
+        elif operator_comparison == OperatorComparison.LESS_EQUALS:
+            return Image(data=self.get() <= threshold, type="bw")
+        elif operator_comparison == OperatorComparison.GREATER_EQUALS:
+            return Image(data=self.get() >= threshold, type="bw")
+
+    def write(self, file: str, type_file: str = "tiff16") -> None:
+        """Writes the image to a file.
+
+        Args:
+            file: The file to write to.
+            type_file: The file type to write.
+        """
+        type_file = TypeFileImage(type_file)
+        if self.type == TypeImage.GREY:
+            if np.min(self.get()) < 0.0 or np.max(self.get()) > 1.0:
+                raise ValueError("The current image has values outside the "
+                                 "interval [0.0, 1.0], and cannot be written.")
+
+        if self.type == TypeImage.GREY:
+            if type_file == TypeFileImage.TIFF8:
+                arr = (self.get() * (2**8 - 1)).astype(dtype=np.uint8)
+            elif type_file == TypeFileImage.TIFF16:
+                arr = (self.get() * (2**16 - 1)).astype(dtype=np.uint16)
+            else:
+                raise ValueError("Can only write greyscale images to \"tiff8\" "
+                                 "and \"tiff16\" files.")
+            image = PIL.Image.fromarray(arr)
+            image.save(file, format="TIFF")
+        elif self.type == TypeImage.BW:
+            if type_file != TypeFileImage.TIFF_BW:
+                raise ValueError("Can only write B/W images to \"tiff_bw\" ""files.")
+
+            # FIXME: Directly converting from a boolean does not work. We use an
+            # inefficient workaround here. This should be fixed at some point.
+            arr = np.zeros(self.shape, dtype=np.uint8)
+            arr[self.get()] = 255
+            image = PIL.Image.fromarray(arr).convert("1")
+            image.save(file, format="TIFF")
+
+    def __add__(self, other: Union[float, "Image"]) -> "Image":
+        self._check_valid_for_operation("addition", other)
+
+        if type(other) is float or type(other) is int:
+            data = self.get() + other
+        else:
+            data = self.get() + other.get()
+        return Image(data=data, type=TypeImage.GREY)
+
+    def __sub__(self, other: Union[float, int, "Image"]) -> "Image":
+        self._check_valid_for_operation("subtraction", other)
+
+        if type(other) is float or type(other) is int:
+            data = self.get() - other
+        else:
+            data = self.get() - other.get()
+        return Image(data=data, type=TypeImage.GREY)
+
+    def __mul__(self, other: Union[float, int]) -> "Image":
+        self._check_valid_for_operation("multiplication", other)
+
+        data = self.get() * other
+        return Image(data=data, type=TypeImage.GREY)
+
+    def __truediv__(self, other: Union[float, int]) -> "Image":
+        self._check_valid_for_operation("division", other)
+
+        data = self.get() / other
+        return Image(data=data, type=TypeImage.GREY)
+
+    def __eq__(self, other: "Image") -> bool:
+        return np.array_equal(self.get(), other.get())
+
+    def _check_valid_for_operation(self, operation: str, other: Any) -> None:
+        if operation in ("addition", "subtraction"):
+            if not(type(other) is Image or type(other) is float or
+                   type(other) is int):
+                raise ValueError("Can only perform {} with other images or "
+                                 "floating point numbers.".format(operation))
+            if type(other) is Image:
+                if self.type != TypeImage.GREY or other.type != TypeImage.GREY:
+                    raise ValueError("Can only perform {} on grayscale "
+                                     "images.".format(operation))
+                if self.shape != other.shape:
+                    raise ValueError("Can only perform {} on images with the "
+                                     "same shape.".format(operation))
+        elif operation in ("multiplication", "division"):
+            if not (type(other) is float or type(other) is int):
+                raise ValueError("Can only perform {} with a "
+                                 "float or int (which will be treated "
+                                 "as a float).".format(operation))
diff --git a/images/imagesequence.py b/images/imagesequence.py
new file mode 100644
index 0000000000000000000000000000000000000000..f11a6c3305ec98fced1e7a7484fd3638eb766313
--- /dev/null
+++ b/images/imagesequence.py
@@ -0,0 +1,278 @@
+import os
+import re
+from typing import List, Tuple
+
+import numpy as np
+
+from images import Image, TypeImage
+from images.utils import find_common_substrings_with_one_separator, find_uncommon_substrings, find_file_numbers
+
+
+__author__ = "C.J. Voesenek and A. Cribellier"
+__maintainer__ = "A. Cribellier"
+__email__ = "antoine.cribellier@wur.nl"
+
+
+class ImageSequence:
+    """Sequence of images on disk.
+
+    This object can be indexed like an array (i.e. sequence[0]), retrieving
+    the nth image from the sequence.
+    """
+
+    def __init__(self, images: List[Image], names: List[str] = None, path: str = None, check: bool = False) -> None:
+        """Initialises an ImageSequence object.
+
+        Args:
+            images: The images that this sequence comprises.
+            names: Names of the images in the sequence
+            path: The path of the directory with the images
+            check: Check the images for consistency within the sequence.
+        """
+
+        if names is None:
+            names = ['img{}'.format(x) for x in range(1, len(images)+1)]
+
+        self._images = images  # type: List[Image]
+        self._names = names  # type: List[str]
+        self._path = path  # type: str
+
+        # Will try to get image numbers from their names
+        common_substrings_dict = find_common_substrings_with_one_separator(self._names)
+        most_common_substring = next(iter(common_substrings_dict))  # get first key
+        num_occurrence = common_substrings_dict[most_common_substring]
+        if num_occurrence == len(self._names):
+            # uncommon_substrings = find_uncommon_substrings(self._names, most_common_substring)
+            # self._numbers = [int(s) for s in uncommon_substrings]  # type: List[int]
+            self._numbers = find_file_numbers(self._names, most_common_substring)  # type: List[int]
+            assert len(self._numbers) == len(self._names)
+
+        else:
+            self._numbers = list(range(1, len(self._names)+1))  # type: List[int]
+
+        if check:
+            self._check_images()
+
+    @property
+    def nb_images(self) -> int:
+        return len(self._images)
+
+    @property
+    def nb_xi(self) -> int:
+        return self[0].nb_xi
+
+    @property
+    def nb_eta(self) -> int:
+        return self[0].nb_eta
+
+    @property
+    def shape(self) -> Tuple[int, int]:
+        return self[0].shape
+
+    @property
+    def type(self) -> TypeImage:
+        return self[0].type
+
+    def get(self, index: int) -> Image:
+        """Retrieves an image from the sequence.
+
+        Args:
+            index: The index of the image to load.
+
+        Returns:
+            The image at the specified index.
+        """
+        return self._images[index]
+
+    def get_name(self, index: int) -> str:
+        """Retrieves the name of an image from the sequence.
+
+        Args:
+            index: The index of the image.
+
+        Returns:
+            The name of the image at the specified index.
+        """
+        return self._names[index]
+
+    def get_names(self) -> List[str]:
+        """Retrieves the names of all the image of the sequence.
+
+        Returns:
+            The names of the images
+        """
+        return self._names
+
+    def get_numbers(self) -> List[int]:
+        """Retrieves the numbers of all the image of the sequence.
+
+        Returns:
+            The numbers of the images (e.g. frame number)
+        """
+        return self._numbers
+
+    def get_path(self) -> List[str]:
+        """Retrieves the path of the directory where the images of the sequence are.
+
+        Returns:
+            The path of the directory with the images.
+        """
+        return self._path
+
+    def minimum(self, use_caching: bool = None) -> Image:
+        """Calculates the minimum of each pixel in the image sequence.
+
+        Args:
+            use_caching: Whether to use caching while loading all images to
+                calculate the minimum. This will use a lot of memory,
+                but will save loading times if multiple operations are
+                performed.
+
+        Returns:
+            The minimum image of this sequence.
+        """
+        change_caching = use_caching is not None
+
+        image_min = self[0].get()
+        for image in self:
+            if change_caching:
+                use_caching_old = image.use_caching
+                image.use_caching = use_caching
+            image_min = np.minimum(image.get(), image_min)
+            if change_caching:
+                image.use_caching = use_caching_old
+        return Image(data=image_min, type=self.type)
+
+    def maximum(self, use_caching: bool = None) -> Image:
+        """Calculates the maximum of each pixel in the image sequence.
+
+        Args:
+            use_caching: Whether to use caching while loading all images to
+                calculate the maximum. This will use a lot of memory,
+                but will save loading times if multiple operations are
+                performed.
+
+        Returns:
+            The maximum image of this sequence.
+        """
+        change_caching = use_caching is not None
+
+        image_max = self[0].get()
+        for image in self:
+            if change_caching:
+                use_caching_old = image.use_caching
+                image.use_caching = use_caching
+            image_max = np.maximum(image.get(), image_max)
+            if change_caching:
+                image.use_caching = use_caching_old
+        return Image(data=image_max, type=self.type)
+
+    def median(self) -> Image:
+        """Calculates the median of each pixel in the image sequence.
+
+        Returns:
+            The median image of this sequence.
+        """
+        images = np.zeros((self.nb_images, ) + self.shape)
+        for i in range(self.nb_images):
+            # Do not use caching here, as it would use twice the memory for
+            # an already memory-intensive operation.
+            use_caching_old = self[i].use_caching
+            self[i].use_caching = False
+
+            images[i, :, :] = self[i].get()
+
+            self[i].use_caching = use_caching_old
+
+        return Image(data=np.median(images, axis=0), type=self.type)
+
+    def __getitem__(self, index: int) -> Image:
+        return self.get(index)
+
+    def delete(self, index) -> None:
+        """Deletes an image from the sequence.
+        """
+
+        self._images.pop(index)
+        self._names.pop(index)
+
+    def _check_images(self) -> None:
+        shape_ref = self[0].shape
+        type_ref = self[0].type
+        for image in self:
+            if image.shape != shape_ref or image.type != type_ref:
+                raise ValueError("All images in the sequence should have the "
+                                 "same size and type.")
+
+    @staticmethod
+    def from_directory(directory: str,
+                       expr: str = r"\d+(?=\.(tif|png|jpg|bmp))",
+                       identifiers: List[int] = None,
+                       use_caching: bool = True,
+                       check: bool = False,
+                       type_image: str = "grey") -> "ImageSequence":
+        """Gathers image files from directory.
+
+        This function sets the files attribute of the instance to a list of
+        files from the specified directory, matching the regular expression
+        and optionally the image identifiers. If no image identifiers are
+        specified, the list is in alphabetical order. If image identifiers
+        are specified, the output is sorted in the same order
+
+        Args:
+            directory: The path to a directory containing images.
+            expr: Regular expression pattern matching image files.
+            identifiers: The identifiers of the images that should be loaded
+                from this sequence.
+            use_caching: Whether or not to use caching for the images.
+            check: Whether or not to check all images for correctness.
+            type_image: The type of the images ('rgb', 'grey' or 'bw').
+
+        Raises:
+            IOError: Could not match images to all specified identifiers.
+            IOError: No image files matching {expr} found in directory
+                {directory}.
+        """
+        contents = os.listdir(directory)
+        pattern = re.compile(expr)
+
+        files = []
+        file_names = []
+        id_list = []
+        for file_name in contents:
+            match = re.search(pattern, file_name)
+            if match:
+                # Check whether this file is in the (optionally specified) list
+                # of identifiers
+                if identifiers:
+                    id_cur = int(match.group(0))
+                    do_append = id_cur in identifiers
+                    if do_append:
+                        id_list.append(id_cur)
+                else:
+                    do_append = True
+
+                if do_append:
+                    files.append(os.path.join(directory, file_name))
+                    file_names.append(os.path.basename(os.path.normpath(file_name)))
+
+        # If a list of identifiers was specified, check whether we found
+        # them all, then sort the list by identifier
+        if identifiers is not None:
+            if len(identifiers) != len(files):
+                raise IOError("Could not match images to all specified "
+                              "identifiers.")
+            files.sort(key=dict(zip(files, id_list)).get)
+        else:
+            files.sort()
+
+        if len(files) == 0:
+            raise IOError("No image files matching \"" + expr +
+                          "\" found in directory \"" + directory + "\".")
+
+        images = []
+        for file in files:
+            images.append(Image(file=file, type=type_image,
+                                use_caching=use_caching))
+
+        return ImageSequence(images, file_names, directory, check=check)
diff --git a/images/multiimagesequence.py b/images/multiimagesequence.py
new file mode 100644
index 0000000000000000000000000000000000000000..c44c9d620fa2a93a30c5dae3e31b3f0ffe10617c
--- /dev/null
+++ b/images/multiimagesequence.py
@@ -0,0 +1,278 @@
+import os
+from math import gcd
+from typing import Dict, List, Optional, Tuple
+
+import numpy as np
+
+from images import Image, ImageSequence
+
+__author__ = "C.J. Voesenek and A. Cribellier"
+__maintainer__ = "A. Cribellier"
+__email__ = "antoine.cribellier@wur.nl"
+
+
+class MultiImageSequence:
+    """A combination of multiple synchronised image sequences (a.k.a a recording).
+
+    Attributes:
+        names: List of names of the sequences.
+        sequences: A dictionary of image sequences, with the names as keys.
+        nb_sequences: The number of image sequences in this object.
+
+        index: A dictionary of lists with the indices into an image sequence
+            of each overall time step. When images are not defined at a time
+            instant, it contains None.
+
+        frame_rate_total: The overall frame rate of this multi-camera sequence
+        frame_rates: A dictionary of the frame rates of every sequence,
+            with the identifiers as keys.
+
+        time: A list of times at every step.
+        nb_time: The number of time steps in this sequence.
+    """
+
+    def __init__(self, sequences: Dict[str, ImageSequence],
+                 frame_rates: Dict[str, float] or int,
+                 shifts: Dict[str, int] = None,
+                 paths: List[str] = None) -> None:
+        """Initialises a MultiImageSequence.
+
+        Args:
+            sequences: A dictionary of image sequences, with the identifiers
+                as keys.
+            frame_rates: A dictionary of the frame rates of every sequence,
+                with the identifiers as keys or an int if the frame_rate is the same for all.
+            shifts: A dictionary of shifts in number of frames of the start of
+                each sequence with respect to a common time point, with the
+                identifiers as keys. This is used when cameras are running at a
+                different frame rate, but the video sequences do not start at
+                the exact same time instance (as could happen when e.g.,
+                and end trigger is used). For example, a camera running at
+                half the frame rate of another camera may have a shift of one
+                frame w.r.t. the other.
+            paths: List of paths to directories containing each with a recording sequence
+
+        Raises:
+            ValueError: Specify all image sequences as an ImageSequence object.
+        """
+
+        if type(frame_rates) is int:
+            frame_rates = {identifier: frame_rates for identifier in self.names}
+
+        # Check whether the dictionaries have matching identifiers.
+        valid_keys = True
+        for name in sequences.keys():
+            valid_keys = valid_keys and name in frame_rates
+            if shifts is not None:
+                valid_keys = valid_keys and name in shifts
+
+        if not valid_keys:
+            raise ValueError("The dictionaries of image sequences, "
+                             "frame rates, and (optionally) shifts should "
+                             "all have the same keys")
+
+        # Check whether all sequences are ImageSequence objects
+        for key in sequences.keys():
+            if not isinstance(sequences[key], ImageSequence):
+                raise ValueError("Specify all image sequences as an "
+                                 "ImageSequence object.")
+
+        self.sequences = sequences  # type: Dict[str, ImageSequence]
+
+        self.frame_rate_total = None  # type: float
+        self.frame_rates = frame_rates  # type: Dict[str, float]
+        self.index = None  # type: Dict[str, List[int]]
+        self.time = None  # type: np.ndarray
+
+        if not shifts:
+            shifts = dict()
+            for name in self.names:
+                shifts[name] = 0
+        self.shifts = shifts  # type: Dict[str, int]
+
+        if paths is not None:
+            assert len(paths) == len(self.names)
+        self._paths = paths  # type: List[str]
+
+        self._calculate_time()
+
+    @property
+    def names(self) -> List[str]:
+        return list(self.sequences.keys())
+
+    @property
+    def nb_sequences(self) -> int:
+        return len(self.sequences)
+
+    @property
+    def nb_time(self) -> int:
+        return self.time.size
+
+    def _calculate_time(self) -> None:
+        """Calculates a time vector and the sequence indices in it."""
+
+        # Find the greatest common divisor of all frame rate combinations. We
+        # progress through the list, updating the overall frame rate every
+        # time using the greatest common divisor (gcd()).
+        self.frame_rate_total = self.frame_rates[self.names[0]]
+        for name in self.names:
+            div = gcd(self.frame_rate_total, self.frame_rates[name])
+            self.frame_rate_total = div * (self.frame_rate_total //
+                                           div * self.frame_rates[name] // div)
+
+        # Calculate step size with respect to the overall frame rate. Also
+        # adjust the shift to be in the overall time vector.
+        step = dict(zip(self.names, [0] * self.nb_sequences))
+        nb_t = dict(zip(self.names, [0] * self.nb_sequences))
+        for name in self.names:
+            step[name] = self.frame_rate_total // self.frame_rates[name]
+            nb_t[name] = self.sequences[name].nb_images * step[name]
+
+            self.shifts[name] = self.shifts[name] * step[name]
+
+        # Calculate the overall time vector - find the sequence with the
+        # longest total time
+        dt = 1 / self.frame_rate_total
+        for name in self.names:
+            nb_t[name] = self.sequences[name].nb_images * step[name] + self.shifts[name]
+        nb_t = max(nb_t.values())
+        it = range(nb_t)
+        self.time = np.asarray([i * dt for i in it])
+
+        # Calculate indices into every sequence of all time steps.
+        self.index = dict.fromkeys(self.names)
+        for name in self.names:
+            index_cur = [None] * nb_t
+            for frame in range(self.sequences[name].nb_images):
+                ind = step[name] * frame + self.shifts[name]
+                index_cur[ind] = frame
+
+            self.index[name] = index_cur
+
+    def get(self, name: str) -> ImageSequence:
+        """Retrieves an image sequence from its name.
+
+        Args:
+            name: The name (key) of the image sequence to load.
+
+        Returns:
+            The image sequence of the specified name (key).
+        """
+        return self.sequences[name]
+
+    # def get_name(self, index: int) -> str:
+    #     """Retrieves the name (key) of the image sequences of the specified index.
+    #
+    #     Args:
+    #         index: The index of the image sequence to get the name of.
+    #
+    #     Returns:
+    #         The name (key) of the image sequence of the specified index.
+    #     """
+    #     return self.names[index]
+    #
+    # def get_names(self) -> List[str]:
+    #     """Retrieves the names (keys) of all image sequences.
+    #
+    #     Returns:
+    #         The names (keys) of all the image sequences
+    #     """
+    #     return self.names
+
+    def get_path(self, name) -> str:
+        """Retrieves the path of the directory where the image sequence from the specified index are.
+
+        Args:
+            name: The name (key) of the image sequence to load.
+
+        Returns:
+            The path of the directory with image sequence for the specified index
+        """
+
+        return self._paths[self.names.index(name)]
+
+    def get_paths(self) -> List[str]:
+        """Retrieves the paths of the directories where the images of the sequences are.
+
+        Returns:
+            The paths of the directories with the image sequences
+        """
+        return self._paths
+
+    def __getitem__(self, item: Tuple[str, int]) -> Optional[Image]:
+        """Returns the image of sequence name item[0], frame item[1].
+
+        This returns None if no frame is defined at this time step (item[1])
+        for this image sequence name (item[0]).
+
+        Returns:
+            The image of sequence with the name item[0] at the frame
+            item[1].
+        """
+        name, frame = item
+
+        ind = self.index[name][frame]
+        if ind is not None:
+            return self.sequences[name][frame]
+
+        else:
+            return None
+
+    @staticmethod
+    def from_directory(paths: List[str],
+                       frame_rates: List[float] or int,
+                       names: List[any] = None,
+                       shifts: List[int] = None,
+                       expr: str = r"\d+(?=\.(tif|png|jpg|bmp))",
+                       use_caching: bool = False,
+                       check: bool = False,
+                       type_image: str = "grey") -> "MultiImageSequence":
+        """Gathers recording files from directory.
+
+        This function sets the files attribute of the instance to a list of
+        subdirectories from the specified directory.
+        In each of these subdirectories, it matches the regular expression
+        and optionally the recording names. If no recording names are
+        specified, the list is in alphabetical order. If recording names
+        are specified, the output is sorted in the same order
+
+        Args:
+            paths: List of paths to directories containing each with a recording sequence.
+            frame_rates: A dictionary of the frame rates of every sequence,
+                with the identifiers as keys or an int if the frame_rate is the same for all.
+            names: A List of names for the sequences. If None, the names of the folder will be used
+            shifts: A dictionary of shifts in number of frames of the start of
+                each sequence with respect to a common time point, with the
+                identifiers as keys. This is used when cameras are running at a
+                different frame rate, but the video sequences do not start at
+                the exact same time instance (as could happen when e.g.,
+                and end trigger is used). For example, a camera running at
+                half the frame rate of another camera may have a shift of one
+                frame w.r.t. the other.
+            expr: Regular expression pattern matching image files.
+            use_caching: Whether or not to use caching for the recordings.
+            check: Whether or not to check all recordings for correctness.
+            type_image: The type of the images ('rgb', 'grey' or 'bw').
+        """
+
+        if names is None:
+            names = [os.path.basename(os.path.normpath(directory)) for directory in paths]
+
+        if type(frame_rates) is int:
+            frame_rates = {name: frame_rates for name in names}
+
+        else:
+            assert len(frame_rates) == len(paths)
+            frame_rates = {file_name: frame_rates[i] for i, file_name in enumerate(names)}
+
+        if shifts is not None:
+            assert len(shifts) == len(paths)
+            shifts = {file_name: shifts[i] for i, file_name in enumerate(names)}
+
+        sequences = {}
+        for i, directory in enumerate(paths):
+            sequences[names[i]] = ImageSequence.from_directory(directory, expr, use_caching=use_caching,
+                                                                    check=check, type_image=type_image)
+
+        return MultiImageSequence(sequences, frame_rates, shifts, paths)
+
diff --git a/images/process.py b/images/process.py
deleted file mode 100644
index a643b95e17ccd59e14d9c21805b19a98262cb60a..0000000000000000000000000000000000000000
--- a/images/process.py
+++ /dev/null
@@ -1,3136 +0,0 @@
-import time, shutil, yaml, glob, pickle
-
-from scipy import signal
-import scipy.optimize as optimize
-
-from sympy import solve, symbols
-
-from multiprocessing import Pool
-from functools import partial
-
-import cv2
-from PIL import ImageDraw
-
-# import deeplabcut
-# import tensorflow as tf
-#
-# config = tf.compat.v1.ConfigProto()
-# config.gpu_options.allow_growth = True
-# config.log_device_placement = True
-# sess = tf.compat.v1.Session(config=config)
-
-import matplotlib.pyplot as plt
-
-from cam import calib
-import images.utils as img_utils
-
-# TODO remove specifics dependencies to insect body and wings modules => make more flexible
-from skeleton_fitter.skeleton_modules.insect_body_slim import init_body_params, init_body_bounds, init_body_skeleton3d, \
-    build_body_skeleton3d, rotate_body_skeleton3d, translate_body_skeleton3d, estimate_body_parameters_from_skeleton3d
-from skeleton_fitter.skeleton_modules.insect_hybrid_wings_body_slim import init_hybrid_params, init_hybrid_skeleton3d, \
-    build_hybrid_skeleton3d
-from skeleton_fitter.skeleton_modules.insect_wings import init_wings_params, init_wing_bounds, init_wings_skeleton_geometry3d_from_csv, \
-    build_wing_skeleton3d, rotate_and_translate_wing_skeleton3d, estimate_wing_parameters_from_skeleton3d
-
-from skeleton_fitter.optimiser import optim_fit_body_params, optim_fit_limbs_params
-from skeleton_fitter.utils import reproject_skeleton3d_to2d, angle_from_vectors, length_from_vector
-
-from point_tracker.tracker2d import Tracker2D
-from point_tracker.reconstructor3d import Reconstructor3D
-from skeleton_fitter.cost_functions.fly import *
-from images.read import *
-
-
-def find_char(s, ch):
-    return [i for i, ltr in enumerate(s) if ltr == ch]
-
-
-def minimize_perp_distance(x, y, z):
-    def model(params, xyz):
-        a, b, c, d = params
-        x, y, z = xyz
-        length_squared = a ** 2 + b ** 2 + c ** 2
-        return ((a * x + b * y + c * z + d) ** 2 / length_squared).sum()
-
-    def unit_length(params):
-        a, b, c, d = params
-        return a ** 2 + b ** 2 + c ** 2 - 1
-
-    # Initial guess of params using first three points
-    p1 = np.squeeze(np.transpose([x[0], y[0], z[0]]))
-    p2 = np.squeeze(np.transpose([x[1], y[1], z[1]]))
-    p3 = np.squeeze(np.transpose([x[2], y[2], z[2]]))
-
-    v1, v2 = p3 - p1, p2 - p1  # These two vectors are in the plane
-    cp = np.cross(v1, v2)  # the cross product is a vector normal to the plane
-    d = np.dot(cp, p3)  # This evaluates a * x3 + b * y3 + c * z3 which equals d
-    a, b, c = cp
-
-    # constrain the vector perpendicular to the plane be of unit length
-    cons = ({'type': 'eq', 'fun': unit_length})
-    sol = optimize.minimize(model, (a, b, c, d), args=[x, y, z], constraints=cons)
-
-    return tuple(sol.x)
-
-
-def filter_interp(y, framerate, cutoff_frequency):
-    y_filtered = y.copy()
-    is_nan, x = np.isnan(y), lambda z: z.nonzero()[0]
-
-    if sum(~is_nan) == 0: return y_filtered # only nans
-    if sum(is_nan) > 0:  # interpolation needed for filter_high_freq
-        y_filtered[is_nan] = np.interp(x(is_nan), x(~is_nan), y_filtered[~is_nan])
-
-    w = cutoff_frequency / (framerate / 2)  # Normalize the frequency
-    b, a = signal.butter(2, w, 'low')
-
-    y_filtered = signal.filtfilt(b, a, y_filtered)
-    y_filtered[is_nan] = np.nan
-
-    return y_filtered
-
-
-def delete_recordings(rec_dates, cam_paths):
-    for camn in cam_paths.keys():
-
-        if not len(rec_dates[camn]) == 0:
-            if not os.path.exists(os.path.join(cam_paths[camn], 'to_delete')):
-                os.makedirs(os.path.join(cam_paths[camn], 'to_delete'))
-
-            rec_name = 'cam{0}_'.format(camn) + time.strftime('%Y%m%d_%H%M%S', rec_dates[camn])
-
-            os.rename(os.path.join(cam_paths[camn], rec_name),
-                      os.path.join(cam_paths[camn], 'to_delete', rec_name))
-            # shutil.rmtree(os.path.join(self.cam_paths[camn], rec_name), ignore_errors=True)
-
-            # print('>> recording from {0} has been deleted'.format(time.strftime('%Y%m%d_%H%M%S', rec_dates[camn])))
-
-
-class ImagesProcessing:
-    def __init__(self, nb_cam, dlt_path):
-
-        self.cam_paths = {1: '', 2: '', 3: ''}
-        self.cam_save_paths = {1: '', 2: '', 3: ''}
-        self.nb_cam = nb_cam
-
-        self.load_dlt_coefs(dlt_path)
-
-        self.show_plot = False
-
-        self.multiprocessing = True
-        self.nb_process = 5
-
-        self.main_camn = 1
-        self.top_camn = 2
-        self.image_format = 'tif'
-
-        self.nb_leading_zero = 4
-        self.update_leading_zero = False  # the number of leading zeros should be 4
-
-        self.max_diff_date_s = 25
-
-        self.threshold_likelihood = 0.9
-        self.threshold_nb_pts_body = 4
-        self.threshold_nb_pts_wing = 4
-
-        self.cutoff_frequency = 100  # Hz (for Butterworth filter on body params)
-        self.framerate = 12500
-
-        self.body_labels = ['proboscis_tip', 'proboscis_head_joint', 'head_torso_joint', 'right_wing_hinge',
-                       'left_wing_hinge',
-                       'torso_abdomen_joint', 'abdomen_middle', 'abdomen_tip']
-        self.wing_labels = ['hinge', 'leading_edge_q1', 'leading_edge_q2', 'leading_edge_q3', 'tip', 'trailing_edge_q3',
-                       'trailing_edge_q2', 'trailing_edge_q1']
-        # self.leg_labels = ['femur_tibia_joint', 'tibia_tarsus_joint', 'tip']
-        # self.leg_names = ['front', 'middle', 'back']
-
-        self.body_labels_to_keep_cst = ['proboscis_l', 'torso_l', 'torso_r', 'abdomen_l',
-                                        'proboscis_torso_a', 'torso_abdomen_a', 'ratio_com_torso', 'ratio_proboscis_head']
-        self.wing_labels_to_keep_cst = ['span', 'chord', 'aspect_ratio']
-        self.keep_init_aspect_ratio = True
-
-        self.all_folders_cam, self.all_folders_rec_cam, self.all_files_rec_cam, self.all_dates_rec_cam = {}, {}, {}, {}
-        self.skeleton2d, self.skeleton3d = {}, {}
-
-    def load_dlt_coefs(self, dlt_path):
-        self.dlt_path = dlt_path
-        self.dlt_coefs = np.zeros((self.nb_cam, 12))
-
-        dlt_csv = np.genfromtxt(dlt_path, delimiter=',', skip_header=0, names=True)
-        dlt_csv.dtype.names = ['cam{0}'.format(camn) for camn in range(1, self.nb_cam + 1)]
-        for i, camn in enumerate(range(1, self.nb_cam + 1)):
-            self.dlt_coefs[i] = np.append(np.array(dlt_csv['cam{0}'.format(camn)]), 1)
-
-    def init_paths_names(self):
-
-        self.nb_cam = int(sorted(self.cam_paths.keys())[-1])
-
-        number_rec = [None] * self.nb_cam
-        all_files_rec, all_images_rec = {}, {}
-        for camn in range(1, self.nb_cam + 1):
-            self.all_folders_cam[camn] = sorted(os.listdir(self.cam_paths[camn]), key=lambda x: x[5:20])
-            self.all_folders_rec_cam[camn] = [s for s in self.all_folders_cam[camn] if 'cam{0}_'.format(camn) in s]
-            number_rec[camn - 1] = len(self.all_folders_rec_cam[camn])
-
-            self.all_dates_rec_cam[camn] = {}
-            for i, rec_name in enumerate(self.all_folders_rec_cam[camn]):
-                self.all_dates_rec_cam[camn][i] = time.strptime(rec_name, 'cam{0}_%Y%m%d_%H%M%S'.format(camn))
-
-            if self.update_leading_zero:  # Change number of leading zeros (from 6 to 4) if needed
-                for i, rec_name in enumerate(self.all_folders_rec_cam[camn]):
-                    rec_path = os.path.join(self.cam_paths[camn], rec_name)
-
-                    all_files_rec[camn] = os.listdir(os.path.join(rec_path))
-                    all_images_rec[camn] = [s for s in all_files_rec[camn] if '.{0}'.format(self.image_format) in s]
-
-                    if all_images_rec[camn] == []: continue
-                    old_nb_leading_zeros = all_images_rec[camn][0].find('.{0}'.format(self.image_format)) - len(rec_name)
-                    all_images_rec[camn] = sorted(all_images_rec[camn], key=lambda x: x[20:20 + old_nb_leading_zeros])
-
-                    for image_name in all_images_rec[camn]:
-                        old_nb_leading_zeros = image_name.find('.') - len(rec_name)
-                        if old_nb_leading_zeros == self.nb_leading_zero: continue
-
-                        frame = int(image_name[len(rec_name):image_name.find('.{0}'.format(self.image_format))])
-                        os.rename(os.path.join(rec_path, rec_name + '{0:0{1}d}.{2}'.format(frame, old_nb_leading_zeros,
-                                                                                           self.image_format)),
-                                  os.path.join(rec_path, rec_name + '{0:0{1}d}.{2}'.format(frame, self.nb_leading_zero,
-                                                                                           self.image_format)))
-
-        if not all(np.array(number_rec) / number_rec[0]):
-            print('ERROR: Different number of recordings have been found for each cameras!')
-
-    def gen_rec_names_list(self, main_rec_names):
-        rec_names_list = {}
-        for camn in range(1, self.nb_cam + 1):
-            rec_names_list[camn] = []
-
-        for main_rec_name in main_rec_names:
-            main_camn = int(main_rec_name[3:4])
-            main_rec_date = time.strptime(main_rec_name, 'cam{0}_%Y%m%d_%H%M%S'.format(main_camn))
-
-            for camn in range(1, self.nb_cam + 1):
-                rec_date = self.nearest_date(self.all_dates_rec_cam[camn], main_rec_date)
-
-                if len(rec_date) == 0:
-                    print('ERROR: Dates were too different from each other! (will delete recording)')
-                    print(time.strftime('cam #{0} vs. cam{1}_%Y%m%d_%H%M%S'.format(camn, main_camn), main_rec_date))
-                    continue
-
-                rec_names_list[camn].append('cam{0}_'.format(camn) + time.strftime('%Y%m%d_%H%M%S', rec_date))
-
-        return rec_names_list
-
-    def do_batch(self, fn_name, **kwargs):
-        start = time.time()
-
-        step_frame = 1 if 'step_frame' not in kwargs.keys() else kwargs['step_frame']
-        camn_to_process = list(range(1, self.nb_cam +1) if 'camns' not in kwargs.keys() else kwargs['camns'])
-        from_fn_paths = self.get_path_process(kwargs['from_fn_name']) if 'from_fn_name' in kwargs.keys() else self.get_path_process('')
-
-        if fn_name in ['autocontrast', 'equalize']: kwargs['factor'] = None;
-
-        if fn_name in 'analyse_dlc':
-            if 'trainingsetindex' not in kwargs.keys(): kwargs['trainingsetindex'] = 0
-            if 'batchsize' not in kwargs.keys(): kwargs['batchsize'] = 2
-
-        if fn_name in ['fit_skeleton', 'plot_skeleton']:
-            self.body_params_init = init_body_params()
-            self.hybrid_params_init = init_hybrid_params()
-            self.wings_params_init = init_wings_params(self.body_params_init)
-
-            self.body_skeleton3d_init = init_body_skeleton3d(self.body_params_init)
-            self.hybrid_skeleton3d_init = init_hybrid_skeleton3d(self.hybrid_params_init)
-            self.wings_skeleton3d_init, self.wings_geometry_init = \
-                init_wings_skeleton_geometry3d_from_csv(self.wings_params_init, kwargs['csv_path'])
-
-            self.body_bounds_init = init_body_bounds()
-            self.hybrid_bounds_init = init_body_bounds()
-            self.wing_bounds_init = init_wing_bounds(self.body_bounds_init)
-
-        self.print_process(fn_name, kwargs)
-        process_paths = self.get_path_process(fn_name)
-        if process_paths == {1: {}}: process_paths = from_fn_paths
-
-        if 'rec_names' in kwargs.keys():
-            self.all_folders_rec_cam = kwargs['rec_names'].copy()
-            del (kwargs['rec_names'])
-
-        if 'yaml_path' in kwargs.keys():
-            processes_dict = yaml.load(open(os.path.join(kwargs['yaml_path'])))
-
-        main_rec_names = self.all_folders_rec_cam[self.main_camn]
-        for main_rec_name in main_rec_names:
-            main_rec_date = time.strptime(main_rec_name, 'cam{0}_%Y%m%d_%H%M%S'.format(self.main_camn))
-
-            #if main_rec_name[5:13] == '20200229' and int(main_rec_name[-6:]) <= 13915: continue
-
-            start_rec = time.time()
-            will_delete_rec = False
-            rec_names, rec_dates, rec_paths = {}, {}, {}
-            images_names, image_paths, image_save_paths, frames = {}, {}, {}, {}
-            for camn in range(1, self.nb_cam + 1):
-                rec_dates[camn] = self.nearest_date(self.all_dates_rec_cam[camn], main_rec_date)
-
-                if len(rec_dates[camn]) == 0:
-                    print('ERROR: Dates were too different from each other! (will delete recording)')
-                    print(time.strftime('cam #{0} vs. cam{1}_%Y%m%d_%H%M%S'.format(camn, self.main_camn), main_rec_date))
-                    will_delete_rec = True
-                    continue
-
-                rec_names[camn] = 'cam{0}_'.format(camn) + time.strftime('%Y%m%d_%H%M%S', rec_dates[camn])
-                rec_paths[camn] = os.path.join(self.cam_paths[camn], rec_names[camn])
-
-                image_paths[camn] = os.path.join(from_fn_paths[camn], rec_names[camn])
-                image_save_paths[camn] = os.path.join(process_paths[camn], rec_names[camn])
-
-                files_names = sorted(os.listdir(image_paths[camn]), key=lambda x: x[20:20+self.nb_leading_zero])
-                images_names[camn] = [s for s in files_names if '.{0}'.format(self.image_format) in s]
-                frames[camn] = sorted([int(s[20:20+self.nb_leading_zero]) for s in images_names[camn]])
-                if frames[camn]:
-                    frames_new = set(frames[camn]) & set(range(1, frames[camn][-1]+1, step_frame))
-                    images_names[camn] = [image_name for image_name in images_names[camn] if int(image_name[20:20+self.nb_leading_zero]) in frames_new]
-                else:
-                    images_names[camn] = []
-                    if image_paths[camn] == os.path.join(self.cam_paths[camn], rec_names[camn]): will_delete_rec = True
-
-            process_dict = {1: {}}
-            for camn in range(1, self.nb_cam + 1):
-                if will_delete_rec or camn not in camn_to_process: continue
-
-                if 'delete_previous' in kwargs.keys() and kwargs['delete_previous']:
-                    if os.path.exists(image_save_paths[camn]) and rec_paths[camn] != image_save_paths[camn]:
-                        shutil.rmtree(image_save_paths[camn], ignore_errors=True)
-
-                if not os.path.exists(image_save_paths[camn]): os.makedirs(image_save_paths[camn])
-
-                # Read history of processes in a .yaml file
-                process_history_yaml_path = os.path.join(image_paths[camn], rec_names[camn] + '-process_history.yaml')
-                process_dict[camn], cpt = {}, 1
-                if os.path.exists(process_history_yaml_path):
-                    process_dict[camn] = yaml.load(open(process_history_yaml_path))
-
-                if 0 not in process_dict[camn].keys():
-                    last_frame = int(np.max([frames[i][-1] for i in range(1, self.nb_cam + 1)]))
-                    missing_frames = {i: list(set(range(1, last_frame +1)) - set(frames[i])) for i in range(1, self.nb_cam + 1)}
-                    img_size = Image.open(os.path.join(image_paths[camn], images_names[camn][0])).size
-                    process_dict[camn][0] = {'fn': 'raw', 'camn': camn, 'nb_cam': self.nb_cam, 'rec_names': rec_names,
-                                             'paths': self.cam_paths, 'process_paths': self.cam_save_paths, 'img_size': img_size,
-                                             'last_frame': last_frame, 'missing_frames': missing_frames}
-
-            for camn in range(1, self.nb_cam + 1):
-                if will_delete_rec or camn not in camn_to_process: continue
-                else: self.img_size = process_dict[camn][0]['img_size']
-
-                cpt = 1
-                while cpt in process_dict[camn].keys(): cpt += 1
-                process_dict[camn][cpt] = {'fn': fn_name, 'kwargs': kwargs}
-
-                if fn_name in ['analyse_dlc', 'load_dlc', 'recon3d', 'stitch', 'multi_processes']: continue
-
-                if fn_name == 'crop':
-                    coord_objs, obj_names = self.interp_2d_obj(rec_paths[camn], image_paths[camn], image_save_paths[camn])
-
-                    files_names = sorted(os.listdir(rec_paths[camn]), key=lambda x: x[20:20+self.nb_leading_zero])
-                    images_names[camn] = [s for s in files_names if '.{0}'.format(self.image_format) in s]
-                    image_paths[camn] = rec_paths[camn]
-
-                if fn_name == 'track2d':
-                    self.track_2d_objects(image_paths[camn], rec_names[camn], image_paths[camn])
-
-                elif fn_name == 'save_strobe':
-                    radius = 50 if 'radius' not in kwargs.keys() else kwargs['radius']
-                    shape = 'rectangle' if 'shape' not in kwargs.keys() else kwargs['shape']
-
-                    coord_objs, obj_names = self.interp_2d_obj(rec_paths[camn], image_paths[camn], image_save_paths[camn])
-                    self.save_stroboscopic_images(coord_objs, image_paths[camn], rec_names[camn], image_save_paths[camn],
-                                                  radius, shape, step_frame)
-
-                elif fn_name == 'save_avi':
-                    if os.path.exists(image_paths[camn]):
-                        framerate = 24 if 'framerate' not in kwargs.keys() else kwargs['framerate']
-                        lossless = False if 'lossless' not in kwargs.keys() else kwargs['lossless']
-
-                        self.save_avi(framerate, step_frame, lossless, image_paths[camn], image_save_paths[camn])
-
-                elif fn_name == 'delete':
-                    if rec_names[camn] in kwargs['to_del_names']:
-                        will_delete_rec = True
-
-                else:
-                    for i, image_name in enumerate(images_names[camn]):
-                        img = read_image(os.path.join(image_paths[camn], image_name))
-                        image_save_name = image_name
-
-                        if fn_name == 'crop':
-                            for obj_name in obj_names:
-                                x_pxs, y_pxs = np.array([]), np.array([])
-                                for x_name in [s for s in coord_objs[obj_name].keys() if 'x_' in s]:
-                                    x_pxs = np.append(x_pxs, coord_objs[obj_name][x_name][i])
-                                    y_pxs = np.append(y_pxs, coord_objs[obj_name][x_name.replace('x_', 'y_')][i])
-
-                                if not x_pxs.size or not x_pxs.size:
-                                    print('ERROR: x_px and/or y_px is empty!!')
-                                    continue
-
-                                img = img_utils.crop(img, x_pxs, y_pxs, kwargs['height_crop'], kwargs['width_crop'])
-                                image_save_name = image_name[:-len(self.image_format) - 1] \
-                                                  + '-' + obj_name + '.{0}'.format(self.image_format)
-
-                        elif fn_name == 'rotate':
-                            img = img.rotate(kwargs['degrees'])
-
-                        elif fn_name == 'resize':
-                            img = img_utils.resize(img, kwargs['resize_ratio'])
-
-                        elif fn_name in ['enhance_contrast', 'enhance_brightness', 'enhance_sharpness', 'enhance_color',
-                                         'autocontrast', 'equalize']:
-                            img = img_utils.enhance(img, fn_name, kwargs['factor'])
-
-                        elif fn_name == 'save_tiff':
-                            img_utils.save_tiff(image_name, image_paths[camn], image_save_paths[camn])
-
-                        if fn_name in ['crop', 'rotate', 'resize', 'sample', 'enhance_contrast', 'enhance_brightness',
-                                       'enhance_sharpness', 'enhance_color', 'autocontrast', 'equalize']:
-                            img.save(os.path.join(image_save_paths[camn], image_save_name))
-                            img.close()
-
-            if will_delete_rec:
-                delete_recordings(rec_dates, self.cam_paths)
-
-            elif fn_name == 'stitch':
-                self.stitch_images(images_names, image_paths, image_save_paths)
-
-            elif fn_name == 'recon3d':
-                pts_csv = {}
-                for camn in range(1, self.nb_cam + 1):
-                    pts_csv[camn] = np.genfromtxt(os.path.join(image_paths[camn], rec_names[camn] + '-2d_points.csv'),
-                                                  delimiter=',', skip_header=0, names=True)
-
-                self.reconstruct_3d_tracks(pts_csv, rec_names, image_save_paths)
-
-            elif fn_name == 'analyse_dlc':
-                video_paths = glob.glob(os.path.join(image_paths[self.main_camn], '*.avi'))
-
-                if not len(video_paths) == 0:
-                    deeplabcut.analyze_videos(kwargs['cfg_path'], video_paths, destfolder=image_save_paths[self.main_camn],
-                                              shuffle=kwargs['shuffle'], trainingsetindex=kwargs['trainingsetindex'],
-                                              batchsize=kwargs['batchsize'], save_as_csv=True)
-
-                    if 'save_avi' in kwargs.keys() and kwargs['save_avi']:
-                        deeplabcut.create_video_with_all_detections(image_save_paths[self.main_camn], video_paths,
-                                                                    kwargs['model_name'], destfolder=image_save_paths[self.main_camn])
-
-            elif fn_name == 'load_dlc':
-                # self.load_dlc_from_csv(kwargs['model_name'], image_paths[self.main_camn], kwargs['tracker_method'])
-                # self.load_dlc_from_hdf5(kwargs['model_name'], image_paths[self.main_camn], kwargs['tracker_method'])
-                self.load_dlc_from_full_pickle(kwargs['model_name'], image_paths[self.main_camn])
-
-                #self.test_unwrap()
-                self.unscramble_views2d_dlc(process_dict)
-                self.reverse_processes_2d_points(process_dict)
-                self.save2d_dlc(image_save_paths)
-
-                #self.unscramble_sides2d_and_recon3d_dlc()
-                self.recon3d_dlc()
-                self.save3d_dlc(image_save_paths)
-
-            elif fn_name == 'fit_skeleton':
-                csv_paths = glob.glob(os.path.join(image_paths[self.main_camn], '*-3d_points.csv'))
-                if not len(csv_paths) == 0:
-                    self.load_skeleton3d_from_csv(kwargs['model_name'], image_paths)
-                    self.load_skeleton2d_from_csv(kwargs['model_name'], image_paths)
-
-                    if 'angles_to_correct' not in kwargs.keys(): kwargs['angles_to_correct'] = []
-                    self.optim_fit_skeleton(kwargs['animal_name'], kwargs['body_param_names'], kwargs['wing_param_names'], kwargs['res_method'],
-                                            kwargs['opt_method'], rec_paths, image_save_paths, kwargs['angles_to_correct'])
-
-            elif fn_name == 'plot_skeleton':
-                csv_paths = glob.glob(os.path.join(image_paths[self.main_camn], '*-2d_points.csv'))
-                if not len(csv_paths) == 0:
-                    self.load_skeleton2d_from_csv(kwargs['model_name'], image_paths)
-                    body_params, wings_params = self.load_params_from_csv(image_paths[self.main_camn])
-
-                    self.plot_skeleton2d(body_params, wings_params, rec_paths, step_frame,
-                                         save_images=True, save_paths=image_save_paths)
-
-                    if 'save_avi' in kwargs.keys() and kwargs['save_avi']:
-                        framerate = 24 if 'framerate' not in kwargs.keys() else kwargs['framerate']
-                        lossless = False if 'lossless' not in kwargs.keys() else kwargs['lossless']
-                        self.save_avi(framerate, step_frame, lossless, image_save_paths[self.main_camn], image_save_paths[self.main_camn])
-
-            elif fn_name == 'multi_processes':
-                print('> Processing {0}...'.format(main_rec_name))
-                multi_processes_dict = {}
-                for camn in range(1, self.nb_cam + 1):
-                    multi_processes_dict[camn] = processes_dict.copy()
-                    multi_processes_dict[camn][0] = process_dict[camn][0]
-                self.multi_processes(multi_processes_dict, images_names, frames, rec_names)
-
-                print('> {0} as been processed (time elapsed: {1:.4f} s)'.format(main_rec_name, time.time() - start_rec))
-                continue
-
-            # Save history
-            for camn in range(1, self.nb_cam + 1):
-                if will_delete_rec or camn not in camn_to_process: continue
-
-                with open(os.path.join(process_paths[camn], rec_names[camn],
-                                       rec_names[camn] + '-process_history.yaml'), 'w') as yaml_file:
-                    yaml.safe_dump(process_dict[camn], yaml_file, encoding='utf-8', allow_unicode=True)
-
-            print('> {0} as been processed (time elapsed: {1:.4f} s)'.format(main_rec_name, time.time() - start_rec))
-
-        self.init_paths_names()
-        print('> Full batch as been processed (total time elapsed: {0:.4f} s)'.format(time.time() - start))
-
-    def print_process(self, fn_name, kwargs):
-        print('____________________________')
-        if fn_name == 'multi_processes':
-            print('> Starting batch images: Running multiple processes')
-        elif fn_name == 'sample':
-            print('> Starting batch images: Copying samples')
-        elif fn_name == 'resize':
-            print('> Starting batch images: Up/Down-sizing images (ratio: {0})'.format(kwargs['resize_ratio']))
-        elif fn_name == 'enhance_contrast':
-            print('> Starting batch images: Enhancing contrast of images (factor: {0})'.format(kwargs['factor']))
-        elif fn_name == 'enhance_brightness':
-            print('> Starting batch images: Enhancing brightness of images (factor: {0})'.format(kwargs['factor']))
-        elif fn_name == 'enhance_sharpness':
-            print('> Starting batch images: Enhancing sharpness of images (factor: {0})'.format(kwargs['factor']))
-        elif fn_name == 'enhance_color':
-            print('> Starting batch images: Enhancing color of images (factor: {0})'.format(kwargs['factor']))
-        elif fn_name == 'autocontrast':
-            print('> Starting batch images: Auto-Enhancing contrast of images')
-        elif fn_name == 'equalize':
-            print('> Starting batch images: Equalizing of images')
-        elif fn_name == 'crop':
-            print('> Starting batch images: Cropping images (height: {0}, width: {1})'.format(kwargs['height_crop'], kwargs['width_crop']))
-        elif fn_name == 'rotate':
-            print('> Starting batch images: Rotating cameras views {0} at {1} degrees '.format(kwargs['camns'], kwargs['degrees']))
-        elif fn_name == 'stitch':
-            print('> Starting batch images: Stitching all cameras views together')
-        elif fn_name == 'track2d':
-            print('> Starting batch images: Tracking 2d coordinates of blobs')
-        elif fn_name == 'save_strobe':
-            print('> Starting batch images: Generating stroboscopic images from 2d coordinates')
-        elif fn_name == 'recon3d':
-            print('> Starting batch images: Reconstructing 3d tracks from 2d coordinates')
-        elif fn_name == 'save_tiff':
-            print('> Starting batch images: Saving recordings as 8 bits .tiff')
-        elif fn_name == 'save_avi':
-            print('> Starting batch images: Saving recordings as .avi')
-        elif fn_name == 'analyse_dlc':
-            print('> Starting batch images: Analysing videos with DeepLabCut (model: {0})'.format(kwargs['model_name']))
-        elif fn_name == 'load_dlc':
-            print('> Starting batch images: Load points from DeepLabCut (unscramble, reconstruct3d ... - model: {0})'.format(kwargs['model_name']))
-        elif fn_name == 'fit_skeleton':
-            print('> Starting batch images: Optimizing fit btw skeletons to get transformation/rotation parameters')
-        elif fn_name == 'plot_skeleton':
-            print('> Starting batch images: Plot (or save) skeleton on raw images')
-        elif fn_name == 'delete':
-            print('> Starting batch images: Deleting following recordings: {0}'.format(kwargs['to_del_names']))
-
-    def get_path_process(self, fn_name):
-        process_paths = {1: {}}
-        for camn in range(1, self.nb_cam + 1):
-            if fn_name == '':
-                process_paths[camn] = self.cam_paths[camn]
-            elif fn_name == 'crop':
-                process_paths[camn] = os.path.join(self.cam_save_paths[camn], '_Cropped')
-            elif fn_name == 'sample':
-                process_paths[camn] = os.path.join(self.cam_save_paths[camn], '_Sample')
-            elif fn_name == 'resize':
-                process_paths[camn] = os.path.join(self.cam_save_paths[camn], '_Resized')
-            elif fn_name in ['enhance_contrast', 'enhance_brightness', 'enhance_sharpness', 'enhance_color',
-                             'autocontrast', 'equalize']:
-                process_paths[camn] = os.path.join(self.cam_save_paths[camn], '_Enhanced')
-            elif fn_name == 'save_tiff':
-                process_paths[camn] = os.path.join(self.cam_save_paths[camn], '_Tiff')
-            elif fn_name == 'save_avi':
-                process_paths[camn] = os.path.join(self.cam_save_paths[camn], '_Avi')
-            elif fn_name == 'save_strobe':
-                process_paths[camn] = os.path.join(self.cam_save_paths[camn], '_Strobe')
-            elif fn_name == 'stitch':
-                process_paths[camn] = os.path.join(self.cam_save_paths[camn], '_Stitched')
-            elif fn_name in ['analyse_dlc', 'load_dlc']:
-                process_paths[camn] = os.path.join(self.cam_save_paths[camn], '_DLC')
-            elif fn_name == 'plot_skeleton':
-                process_paths[camn] = os.path.join(self.cam_save_paths[camn], '_Plots')
-            elif fn_name in ['rotate', 'delete', 'track2d', 'recon3d', 'multi_processes', 'fit_skeleton']:
-                break
-            else:
-                print('WARN: incorrect function name (fn_name = {0})'.format(fn_name))
-                exit()
-
-        return process_paths
-
-    def multi_processes(self, multi_processes_dict, all_images_names, all_frames, rec_names):
-        multi_processes_nums = list(multi_processes_dict[1].keys())
-        rec_paths = self.get_path_process('')
-
-        process_dict = {}
-        for camn in range(1, self.nb_cam + 1): process_dict[camn] = {0: multi_processes_dict[camn][0]}
-
-        self.img_size = multi_processes_dict[1][0]['img_size']
-
-        img, obj_names = {}, []
-        for multi_processes_num in multi_processes_nums[1:]:
-            start = time.time()
-            fn_name = multi_processes_dict[1][multi_processes_num]['fn']
-            kwargs = multi_processes_dict[1][multi_processes_num]['kwargs']
-            step_frame = 1 if 'step_frame' not in kwargs.keys() else kwargs['step_frame']
-            camn_to_process = list(range(1, self.nb_cam + 1) if 'camns' not in kwargs.keys() else kwargs['camns'])
-
-            from_fn_paths = self.get_path_process(kwargs['from_fn_name']) if 'from_fn_name' in kwargs.keys() else self.get_path_process('')
-            process_paths = self.get_path_process(fn_name)
-            if process_paths == {1: {}}: process_paths = from_fn_paths
-
-            images_names, image_rec_paths, image_paths, image_save_paths = {}, {}, {}, {}
-            for camn in range(1, self.nb_cam + 1):
-                if all_frames[camn]:
-                    frames_new = set(all_frames[camn]) & set(range(1, all_frames[camn][-1], step_frame))
-                    images_names[camn] = [s for s in all_images_names[camn] if int(s[20:20+self.nb_leading_zero]) in frames_new]
-                else:
-                    images_names[camn] = []
-
-                image_rec_paths[camn] = os.path.join(rec_paths[camn], rec_names[camn])
-                image_paths[camn] = os.path.join(from_fn_paths[camn], rec_names[camn])
-                image_save_paths[camn] = os.path.join(process_paths[camn], rec_names[camn])
-
-                if 'delete_previous' in kwargs.keys() and kwargs['delete_previous']:
-                    if os.path.exists(image_save_paths[camn]) and rec_paths[camn] != image_save_paths[camn]:
-                        shutil.rmtree(image_save_paths[camn], ignore_errors=True)
-
-            for camn in range(1, self.nb_cam +1):
-                process_history_yaml_path = os.path.join(image_paths[camn], rec_names[camn] + '-process_history.yaml')
-                if os.path.exists(process_history_yaml_path):
-                    process_dict[camn] = yaml.load(open(process_history_yaml_path))
-
-                if not obj_names or 'obj0' in obj_names:
-                    file_names = sorted(os.listdir(image_paths[camn]), key=lambda x: x[20:20 + self.nb_leading_zero])
-                    csv_names = [s for s in file_names if '-2d_points.csv' in s and '-obj' in s]
-
-                    if csv_names:
-                        obj_names = np.unique([s[find_char(s, '-')[0] + 1:find_char(s, '-')[1]] for s in csv_names])
-                        if 'obj0' in img[camn].keys(): img[camn] = {}
-                    else:
-                        obj_names = ['obj0']
-
-                if not os.path.exists(image_save_paths[camn]): os.makedirs(image_save_paths[camn])
-                if not camn in camn_to_process: continue
-                if fn_name in ['recon3d', 'stitch']: continue
-
-                if fn_name == 'crop':
-                    coord_objs, obj_names = self.interp_2d_obj(image_rec_paths[camn], image_paths[camn], image_save_paths[camn])
-
-                elif fn_name == 'track2d':
-                    self.track_2d_objects(image_paths[camn], rec_names[camn], image_save_paths[camn])
-                    continue
-
-                elif fn_name == 'save_strobe':
-                    radius = 50 if 'radius' not in kwargs.keys() else kwargs['radius']
-                    shape = 'rectangle' if 'shape' not in kwargs.keys() else kwargs['shape']
-
-                    coord_objs, _ = self.interp_2d_obj(image_rec_paths[camn], image_paths[camn], image_save_paths[camn])
-                    self.save_stroboscopic_images(coord_objs, image_paths[camn], rec_names[camn], image_save_paths[camn],
-                                                  radius, shape, step_frame)
-                    continue
-
-                elif fn_name == 'save_avi':
-                    framerate = 24 if 'framerate' not in kwargs.keys() else kwargs['framerate']
-                    lossless = False if 'lossless' not in kwargs.keys() else kwargs['lossless']
-
-                if camn not in img.keys(): img[camn] = {}
-
-                for obj_name in obj_names:
-                    if obj_name not in img[camn].keys():
-                        img[camn][obj_name] = {}
-
-                    #if fn_name not in ['track2d', 'recon3d'] \
-                    #        and obj_name == 'obj0' and 'from_fn_name' in kwargs.keys(): break
-
-                    if fn_name == 'save_avi':
-                        if 'obj0' == obj_name: save_name = rec_names[camn] + '.avi'
-                        else: save_name = rec_names[camn] + '-' + obj_name + '.avi'
-
-                        if len(np.shape(np.asarray(img[camn][obj_name][0]))) != 2: break
-                        height_img, width_img = np.shape(np.asarray(img[camn][obj_name][0]))
-
-                        codec = cv2.VideoWriter_fourcc(*'HFYU') if lossless else cv2.VideoWriter_fourcc(*'MP42')
-                        writer = cv2.VideoWriter(os.path.join(image_save_paths[camn], save_name), codec, framerate, (width_img, height_img))
-                        continue
-
-                    for i, image_name in enumerate(images_names[camn]):
-                        if i not in img[camn][obj_name].keys():
-                            img[camn][obj_name][i] = read_image(os.path.join(image_rec_paths[camn], image_name))
-
-                        if not img[camn][obj_name][i]: continue
-
-                        if fn_name == 'crop':
-                            x_pxs, y_pxs = np.array([]), np.array([])
-                            for x_name in [s for s in coord_objs[obj_name].keys() if 'x_' in s]:
-                                x_pxs = np.append(x_pxs, coord_objs[obj_name][x_name][i])
-                                y_pxs = np.append(y_pxs, coord_objs[obj_name][x_name.replace('x_', 'y_')][i])
-
-                            if not x_pxs.size or not x_pxs.size:
-                                print('ERROR: x_px and/or y_px is empty!!')
-                                continue
-
-                            img[camn][obj_name][i] = \
-                                img_utils.crop(img[camn][obj_name][i], x_pxs, y_pxs, kwargs['height_crop'], kwargs['width_crop'])
-
-                        elif fn_name == 'rotate':
-                            img[camn][obj_name][i] = img[camn][obj_name][i].rotate(kwargs['degrees'])
-
-                        elif fn_name == 'resize':
-                            img[camn][obj_name][i] = img_utils.resize(img[camn][obj_name][i], kwargs['resize_ratio'])
-
-                        elif fn_name in ['enhance_contrast', 'enhance_brightness', 'enhance_sharpness', 'enhance_color',
-                                         'autocontrast', 'equalize']:
-                            img[camn][obj_name][i] = img_utils.enhance(img[camn][obj_name][i], fn_name, kwargs['factor'])
-
-                        elif fn_name == 'save_avi':
-                            writer.write(cv2.cvtColor(np.asarray(img[camn][obj_name][i]), cv2.COLOR_GRAY2BGR))
-
-                    if fn_name == 'save_avi': writer.release()
-
-                    if 'step_frame' in kwargs.keys() or multi_processes_num == multi_processes_nums[-1]:  # Will save images
-                        if fn_name in ['crop', 'rotate', 'resize', 'sample', 'enhance_contrast', 'enhance_brightness',
-                                       'enhance_sharpness', 'enhance_color', 'autocontrast', 'equalize']:
-                            for i, image_name in enumerate(images_names[camn]):
-                                if 'obj0' == obj_name: image_save_name = image_name
-                                else: image_save_name = image_name[:-len(self.image_format) - 1] + '-' + obj_name \
-                                                        + '.{0}'.format(self.image_format)
-
-                                img[camn][obj_name][i].save(os.path.join(image_save_paths[camn], image_save_name))
-                                #img[camn][obj_name][i] = []
-
-            if fn_name == 'recon3d':
-                pts_csv = {}
-                for camn in range(1, self.nb_cam + 1):
-                    pts_csv[camn] = np.genfromtxt(os.path.join(image_paths[camn], rec_names[camn] + '-2d_points.csv'),
-                                                  delimiter=',', skip_header=0, names=True)
-
-                self.reconstruct_3d_tracks(pts_csv, rec_names, image_save_paths)
-
-            elif fn_name == 'stitch':
-                ref_images, refs_images = set(), {}
-                for camn in range(1, self.nb_cam + 1):
-                    refs_images[camn] = [image_name[20:-(len(self.image_format) + 1)] for image_name in images_names[camn]]
-                    ref_images = ref_images & set(refs_images[camn]) if len(ref_images) > 0 else set(refs_images[camn])
-
-                ref_images = np.sort(list(ref_images))
-
-                index_i = {}
-                for camn in range(1, self.nb_cam + 1):
-                    index_i[camn] = [refs_images[camn].index(ref_image) for ref_image in ref_images]
-
-                for obj_name in obj_names:
-                    #if obj_name == 'obj0' and 'from_fn_name' in kwargs.keys(): break
-
-                    for camn in range(1, self.nb_cam + 1):
-                        if obj_name not in img[camn].keys():
-                            img[camn][obj_name] = {}
-
-                    for i, _ in enumerate(index_i[self.main_camn]):
-                        imgs = {}
-                        for camn in range(1, self.nb_cam + 1):
-                            if i not in img[camn][obj_name].keys():
-                                img[camn][obj_name][i] = read_image(os.path.join(image_rec_paths[camn], images_names[camn][i]))
-
-                            imgs[camn] = img[camn][obj_name][i]
-                            img[camn][obj_name][i] = []
-
-                        img[self.main_camn][obj_name][i] = img_utils.stitch_images(imgs, self.nb_cam)
-
-                if multi_processes_num == multi_processes_nums[-1]:
-                    for i, image_name in enumerate(images_names[camn]):
-                        if 'obj0' == obj_name: image_save_name = image_name
-                        else: image_save_name = image_name[:-len(self.image_format) - 1] + '-' + obj_name + '.{0}'.format(self.image_format)
-                        img[self.main_camn][obj_name][i].save(os.path.join(image_save_paths[self.main_camn], image_save_name))
-                        img[self.main_camn][obj_name][i].close()
-
-            for camn in range(1, self.nb_cam + 1):
-                with open(os.path.join(process_paths[camn], rec_names[camn], rec_names[camn] + '-process_history.yaml'), 'w') as yaml_file:
-                    process_dict[camn][multi_processes_num] = multi_processes_dict[camn][multi_processes_num]
-
-                    yaml.safe_dump(process_dict[camn], yaml_file, encoding='utf-8', allow_unicode=True)
-
-            print('>> fn {0} as been performed (time elapsed: {1:.4f} s)'.format(fn_name, time.time() - start))
-
-    def save_avi(self, framerate, step_frame, lossless, image_path, save_path):
-        all_files_names = sorted(os.listdir(image_path), key=lambda x: x[20:20+self.nb_leading_zero])
-        all_image_names = [s for s in all_files_names if '.{0}'.format(self.image_format) in s]
-        obj_names = np.unique([s[s.find('-')+1:-(len(self.image_format)+1)] for s in all_image_names])
-
-        for obj_name in obj_names:
-            image_names = sorted([i for i in all_image_names if obj_name in i], key=lambda x: x[20:20+self.nb_leading_zero])
-            save_name = image_names[0][:20] + image_names[0][20+self.nb_leading_zero:-(len(self.image_format)+1)] + '.avi'
-
-            img = cv2.imread(os.path.join(image_path, image_names[0]), cv2.IMREAD_UNCHANGED)
-            is_rgb = len(img.shape) == 3
-            if is_rgb:  height_img, width_img, _ = img.shape
-            else: height_img, width_img = img.shape
-
-            # commun: 'MP42', 'XVID' or lossless: 'HFYU'
-            codec = cv2.VideoWriter_fourcc(*'HFYU') if lossless else cv2.VideoWriter_fourcc(*'MP42')
-            writer = cv2.VideoWriter(os.path.join(save_path, save_name), codec, framerate, (width_img, height_img,))
-
-            for i in np.arange(0, len(image_names), step_frame):
-                img_16bit = cv2.imread(os.path.join(image_path, image_names[i]), cv2.IMREAD_UNCHANGED)
-                if is_rgb: writer.write(img_16bit)
-                else: writer.write(cv2.cvtColor(img_16bit, cv2.COLOR_GRAY2BGR))
-                #cv2.imshow(obj_name, img_16bit)
-                #cv2.waitKey(1)
-
-            writer.release()
-            
-    def track_2d_objects(self, image_path, save_name, save_path):
-
-        tracker = Tracker2D()
-        tracker.initialize()
-        tracker.load_images(image_path, self.image_format)
-
-        tracker.do_tracking()
-
-        # tracker.show_plot = self.show_plot
-        if self.show_plot:
-            all_files = sorted(os.listdir(image_path), key=lambda x: x[20:20+self.nb_leading_zero])
-            all_images = [s for s in all_files if '.{0}'.format(self.image_format) in s]
-
-            fig = plt.figure()
-            ax = fig.add_subplot(111)
-            plt.imshow(Image.open(os.path.join(image_path, all_images[0])))
-            ax.scatter(tracker.points['x'], tracker.points['y'])
-            ax.set_xlabel('x (m)')
-            ax.set_ylabel('y (m)')
-            plt.show()
-
-        tracker.save_csv(save_name, save_path)
-
-    def save_stroboscopic_images(self, coord_objs, image_path, save_name, save_path, radius, shape, step_frame):
-
-        if len(coord_objs.keys()) == 0:
-            print('WARM: coord_objs is empty!')
-            return
-
-        tracker = Tracker2D()
-
-        tracker.dist2Threshold = 7
-        tracker.initialize()
-        tracker.load_images(image_path, self.image_format)
-
-        for obj_name in coord_objs.keys():
-            tracker.points = {'x': np.array(coord_objs[obj_name]['x_px']), 'y': np.array(coord_objs[obj_name]['y_px']),
-                              'frame': np.array(coord_objs[obj_name]['frame']),
-                              'area': np.ones(np.size(coord_objs[obj_name]['x_px'])) * np.nan}
-
-            tracker.gen_stroboscopic_image(radius, shape, step_frame, flip_time=True)
-            tracker.save_stroboscopic_image(save_name + '{0:0{1}d}'.format(1, self.nb_leading_zero) + '-' + obj_name, save_path)
-
-    def reconstruct_3d_points(self, pts_csv, save_names, save_paths, save=True):
-        reconstructor = Reconstructor3D(self.nb_cam, self.img_size)
-        reconstructor.dlt_coefs = self.dlt_coefs
-
-        xs_coord = np.array([pts_csv[camn]['x_px'] for camn in range(1, self.nb_cam + 1)])
-        ys_coord = np.array([pts_csv[camn]['y_px'] for camn in range(1, self.nb_cam + 1)])
-        frames_coord = np.array([pts_csv[camn]['frame'] for camn in range(1, self.nb_cam + 1)])
-
-        xs_pose, ys_pose, zs_pose, frames_pose, indexes, rmses, = \
-            reconstructor.recon_objs(xs_coord, ys_coord, frames_coord, with_missing=False)
-
-        save_names_list = [save_names[camn] for camn in range(1, self.nb_cam + 1)]
-        save_paths_list = [save_paths[camn] for camn in range(1, self.nb_cam + 1)]
-        if save: reconstructor.save_points_csv(save_names_list, save_paths_list, save_type='all_frames')
-
-        return xs_pose, ys_pose, zs_pose, frames_pose, indexes, rmses
-
-    def reconstruct_3d_tracks(self, pts_csv, save_names, save_paths, save=True):
-        reconstructor = Reconstructor3D(self.nb_cam, self.img_size)
-        reconstructor.dlt_coefs = self.dlt_coefs
-
-        xs_coord = np.array([pts_csv[camn]['x_px'] for camn in range(1, self.nb_cam + 1)])
-        ys_coord = np.array([pts_csv[camn]['y_px'] for camn in range(1, self.nb_cam + 1)])
-        frames_coord = np.array([pts_csv[camn]['frame'] for camn in range(1, self.nb_cam + 1)])
-
-        xs_pose, ys_pose, zs_pose, frames_pose, indexes_pts, _, = \
-            reconstructor.recon_objs(xs_coord, ys_coord, frames_coord, with_missing=False)
-
-        tracks_dict = reconstructor.recon_tracks(xs_pose, ys_pose, zs_pose, frames_pose, indexes_pts)
-
-        if not xs_pose or not tracks_dict['obj']: return
-
-        save_names_list = [save_names[camn] for camn in range(1, self.nb_cam + 1)]
-        save_paths_list = [save_paths[camn] for camn in range(1, self.nb_cam + 1)]
-        if save: reconstructor.save_tracks_csv(save_names_list, save_paths_list)
-
-        if self.show_plot:
-            xs_pose, ys_pose, zs_pose = np.concatenate(xs_pose), np.concatenate(ys_pose), np.concatenate(zs_pose)
-
-            fig = plt.figure()
-            ax = fig.add_subplot(111, projection='3d')
-            ax.scatter(xs_pose, ys_pose, zs_pose, marker='+')
-            for num_obj in tracks_dict['obj'].keys():
-                ax.scatter(tracks_dict['obj'][num_obj]['x'], tracks_dict['obj'][num_obj]['y'],
-                           tracks_dict['obj'][num_obj]['z'], marker='o')
-            ax.set_xlabel('x (m)')
-            ax.set_ylabel('y (m)')
-            ax.set_zlabel('z (m)')
-            plt.show()
-
-        return tracks_dict
-
-    def load_dlc_from_csv(self, model_name, rec_path, tracker_method):
-        process_dict = yaml.load(open(glob.glob(os.path.join(rec_path, '*-process_history.yaml'))[0]))
-        frames = list(range(1, process_dict[0]['last_frame']))
-        missing_frames = list(np.unique(np.concatenate([process_dict[0]['missing_frames'][camn] for camn in range(1, self.nb_cam + 1)])))
-        [missing_frames.remove(missing_frame) for missing_frame in list(set(missing_frames) - set(frames))]
-        [frames.remove(missing_frame) for missing_frame in missing_frames]
-
-        if tracker_method == 'skeleton': tracker_method_str = '_sk'
-        elif tracker_method == 'box': tracker_method_str = '_bk'
-
-        dlc_csv_paths = glob.glob(os.path.join(rec_path, '*' + model_name + tracker_method_str + '.csv'))
-
-        obj_names = []
-        for dlc_csv_path in dlc_csv_paths: # go throught the various obj
-            obj_names.append(dlc_csv_path[len(rec_path) + 1:dlc_csv_path.index(model_name)])
-
-        self.skeleton2d = {'obj_names': obj_names, 'model_name': model_name}
-        for i, obj_name in enumerate(obj_names):
-            df = pd.read_csv(dlc_csv_paths[i], header=[1, 2])
-            label_names = np.unique([s[0][:-2] for s in df.keys()[1:]])
-            new_label_names = [label.replace(' ', '_') for label in label_names]
-
-            self.skeleton2d[obj_name] = {'frames': frames, 'label_names': new_label_names}
-            for j, label in enumerate(label_names):
-                new_label = new_label_names[j]
-
-                self.skeleton2d[obj_name][new_label] = {1: {}}
-                for camn in range(1, self.nb_cam + 1):
-                    label_key = label + ' {0}'.format(camn)
-
-                    self.skeleton2d[obj_name][new_label][camn] = {'x': np.array(df[(label_key, 'x')]),
-                                                                  'y': np.array(df[(label_key, 'y')]),
-                                                                  'likelihood': np.array(df[(label_key, 'likelihood')])}
-
-    def load_dlc_from_full_pickle(self, model_name, rec_path):
-        process_dict = yaml.load(open(glob.glob(os.path.join(rec_path, '*-process_history.yaml'))[0]))
-        frames = list(range(1, process_dict[0]['last_frame']))
-        missing_frames = list(np.unique(np.concatenate([process_dict[0]['missing_frames'][camn] for camn in range(1, self.nb_cam + 1)])))
-        [missing_frames.remove(missing_frame) for missing_frame in list(set(missing_frames) - set(frames))]
-        [frames.remove(missing_frame) for missing_frame in missing_frames]
-
-        dlc_pickle_paths = glob.glob(os.path.join(rec_path, '*' + model_name + '_full.pickle'))
-
-        obj_names = []
-        for dlc_pickle_path in dlc_pickle_paths:  # go throught the various obj
-            obj_names.append(dlc_pickle_path[len(rec_path) + 22:dlc_pickle_path.index(model_name)])
-
-        self.skeleton2d = {'obj_names': obj_names, 'model_name': model_name}
-        for obj_name in obj_names:
-            with open(dlc_pickle_paths[0], "rb") as file:
-                full_dict = pickle.load(file)
-
-            header = full_dict.pop("metadata")
-            label_names = np.unique([s[:-2] for s in header["all_joints_names"]])
-            new_label_names = [label.replace(' ', '_') for label in label_names]
-
-            self.skeleton2d[obj_name] = {'frames': frames, 'label_names': new_label_names}
-            for i, label in enumerate(label_names):
-                new_label = new_label_names[i]
-
-                self.skeleton2d[obj_name][new_label] = {1: {}}
-                for camn in range(1, self.nb_cam + 1):
-                    self.skeleton2d[obj_name][new_label][camn] = {'x': np.nan * np.ones(len(frames)),
-                                                                  'y': np.nan * np.ones(len(frames)),
-                                                                  'likelihood': np.nan * np.ones(len(frames))}
-
-            for frame_name in full_dict.keys():
-                try:
-                    frame_in = frames.index(int(frame_name[-4:]) + 1)
-                except ValueError:
-                    continue
-
-                for i, label in enumerate(label_names):
-                    new_label = new_label_names[i]
-
-                    for camn in range(1, self.nb_cam + 1):
-                        try: label_in = header["all_joints_names"].index(label + ' {0}'.format(camn))
-                        except ValueError: continue
-
-                        if len(full_dict[frame_name]['confidence'][label_in]) == 0: continue
-                        best_in = np.argmax(full_dict[frame_name]['confidence'][label_in])
-
-                        self.skeleton2d[obj_name][new_label][camn]['x'][frame_in] = \
-                            full_dict[frame_name]['coordinates'][0][label_in][best_in][0]
-                        self.skeleton2d[obj_name][new_label][camn]['y'][frame_in] = \
-                            full_dict[frame_name]['coordinates'][0][label_in][best_in][1]
-                        self.skeleton2d[obj_name][new_label][camn]['likelihood'][frame_in] = \
-                            full_dict[frame_name]['confidence'][label_in][best_in]
-
-    def load_dlc_from_hdf5(self, model_name, rec_path, tracker_method):
-        process_dict = yaml.load(open(glob.glob(os.path.join(rec_path, '*-process_history.yaml'))[0]))
-        frames = list(range(1, process_dict[0]['last_frame']))
-        missing_frames = np.unique(
-            [np.squeeze(process_dict[0]['missing_frames'][camn]) for camn in range(1, self.nb_cam + 1)])
-        [frames.remove(missing_frame) for missing_frame in missing_frames]
-
-        if tracker_method == 'skeleton': tracker_method_str = '_sk'
-        elif tracker_method == 'box': tracker_method_str = '_bk'
-
-        dlc_hdf5_paths = glob.glob(os.path.join(rec_path, '*' + model_name + tracker_method_str + '.h5'))
-
-        obj_names = []
-        for dlc_hdf5_path in dlc_hdf5_paths:  # go throught the various obj
-            obj_names.append(dlc_hdf5_path[len(rec_path) + 22:dlc_hdf5_path.index(model_name)])
-
-        self.skeleton2d = {'obj_names': obj_names, 'model_name': model_name}
-        for i, obj_name in enumerate(obj_names):
-            hdf5_dict = pd.read_hdf(dlc_hdf5_paths[i], 'df_with_missing')
-            # hdf5_dict.to_csv(os.path.join(save_path, hdf5_name.split(".h5")[0] + '.csv'))
-
-            # network_name = hdf5_dict.columns[0][0]
-            hdf5_dict.columns = hdf5_dict.columns.droplevel([0, 1])  # remove first two lines of header (network_name, individual)
-
-            label_names = np.unique([s[:-2] for s in hdf5_dict.columns.droplevel(1)])
-            new_label_names = [label.replace(' ', '_') for label in label_names]
-            # frames = hdf5_dict.index
-
-            self.skeleton2d[obj_name] = {'frames': frames, 'label_names': new_label_names}
-            for i, label in enumerate(label_names):
-                new_label = new_label_names[i]
-
-                self.skeleton2d[obj_name][new_label] = {1: {}}
-                for camn in range(1, self.nb_cam + 1):
-                    label_key = label + ' {0}'.format(camn)
-
-                    self.skeleton2d[obj_name][new_label][camn] = {'x': np.array(hdf5_dict[(label_key, 'x')]),
-                                                                  'y': np.array(hdf5_dict[(label_key, 'y')]),
-                                                                  'likelihood': np.array(hdf5_dict[(label_key, 'likelihood')])}
-
-                    # self.skeleton2d[obj_name][new_label][camn] = [[1]*3 for i in range(len(hdf5_dict[(label_key, 'x')]))]
-                    # # self.skeleton2d[obj_name][new_label][camn] = np.ones((len(hdf5_dict[(label_key, 'x')]), 3))
-                    # self.skeleton2d[obj_name][new_label][camn][:, 0] = np.array(hdf5_dict[(label_key, 'x')])
-                    # self.skeleton2d[obj_name][new_label][camn][:, 1] = np.array(hdf5_dict[(label_key, 'y')])
-                    # self.skeleton2d[obj_name][new_label][camn][:, 2] = np.array(hdf5_dict[(label_key, 'likelihood')])
-
-    def load_skeleton2d_from_csv(self, model_name, paths):
-        csv_paths = glob.glob(os.path.join(paths[self.main_camn], '*-2d_points.csv'))
-        obj_names = np.unique([csv_path[len(paths[self.main_camn]) + 1:csv_path.index('-')] for csv_path in csv_paths])
-
-        self.skeleton2d = {'obj_names': obj_names, 'model_name': model_name}
-        for obj_name in obj_names:
-            label_names = np.unique([csv_path[csv_path.index('-') + 1:csv_path.index('-2d_points.csv')] for csv_path in csv_paths])
-
-            self.skeleton2d[obj_name] = {'frames': [], 'label_names': label_names}
-            for label in label_names:
-                self.skeleton2d[obj_name][label] = {1: {}}
-                for camn in range(1, self.nb_cam + 1):
-                    pts_csv = np.genfromtxt(os.path.join(paths[camn], obj_name + '-' + label + '-2d_points.csv'),
-                                            delimiter=',', skip_header=0, names=True)
-
-                    self.skeleton2d[obj_name]['frames'] = \
-                        np.unique(np.concatenate((self.skeleton2d[obj_name]['frames'], pts_csv['frame'])))
-                    self.skeleton2d[obj_name][label][camn] = \
-                        {'x': pts_csv['x_px'], 'y': pts_csv['y_px'], 'likelihood': pts_csv['likelihood']}
-
-    def load_skeleton3d_from_csv(self, model_name, paths):
-        csv_paths = glob.glob(os.path.join(paths[self.main_camn], '*-3d_points.csv'))
-        obj_names = np.unique([csv_path[len(paths[self.main_camn]) + 1:csv_path.index('-')] for csv_path in csv_paths])
-
-        if len(csv_paths) == 0:
-            raise ValueError("Couldn't find any *-3d_points.csv file (path: {0})".format(os.path.join(paths[self.main_camn])))
-
-        self.skeleton3d = {'obj_names': obj_names, 'model_name': model_name}
-        for obj_name in obj_names:
-            obj_csv_paths = [csv_path for csv_path in csv_paths if obj_name in csv_path]
-            label_names = np.unique([csv_path[csv_path.index(obj_name + '-') + len(obj_name) +1:
-                                              csv_path.index('-3d_points.csv')] for csv_path in obj_csv_paths])
-
-            self.skeleton3d[obj_name] = {'frames': [], 'label_names': label_names}
-            for label in label_names:
-                self.skeleton3d[obj_name][label] = {1: {}}
-                for camn in range(1, self.nb_cam + 1):
-                    pts_csv = np.genfromtxt(os.path.join(paths[camn], obj_name + '-' + label + '-3d_points.csv'),
-                                            delimiter=',', skip_header=0, names=True)
-
-                    self.skeleton3d[obj_name]['frames'] = \
-                        np.unique(np.concatenate((self.skeleton3d[obj_name]['frames'], pts_csv['frame'])))
-                    self.skeleton3d[obj_name][label] = {'x': pts_csv['x'], 'y': pts_csv['y'], 'z': pts_csv['z']}
-
-    def load_params_from_csv(self, path):
-        obj_names = self.skeleton2d['obj_names']
-
-        body_params, wings_params = {}, {}
-        for obj_name in obj_names:
-            body_params[obj_name], wings_params[obj_name] = {}, {}
-            params_csv = np.genfromtxt(os.path.join(path, 'skeleton_parameters-' + obj_name + '.csv'),
-                                       delimiter=',', skip_header=0, names=True)
-
-            for body_param_name in self.body_params_init.keys():
-                body_params[obj_name][body_param_name] = params_csv[body_param_name]
-
-            for side in ['right', 'left']:
-                wings_params[obj_name][side] = {}
-                for wing_param_name in self.wings_params_init[side].keys():
-                    if wing_param_name in self.body_params_init.keys():
-                        wings_params[obj_name][side][wing_param_name] = params_csv[wing_param_name]
-                    else:
-                        wings_params[obj_name][side][wing_param_name] = params_csv[wing_param_name + '_' + side]
-
-        return body_params, wings_params
-
-    def unscramble_views2d_dlc(self, process_dict):
-        # Run on unconverted 2d coords from dlc
-        # shared_axis: 0 if the combination of cam share the x axis (top-left to top-right)
-        # or 1 if they share the y axis (top-left to bottom left)
-
-        process_names = [process_dict[1][process_num]['fn'] for process_num in process_dict[1].keys()]
-        crop_num = process_names.index('crop')
-        crop_num = crop_num[-1] if isinstance(crop_num, list) else crop_num
-        width_crop = process_dict[1][crop_num]['kwargs']['width_crop']
-        height_crop = process_dict[1][crop_num]['kwargs']['height_crop']
-
-        for obj_name in self.skeleton2d['obj_names']:
-
-            # Detect body parts in wrong view
-            for label in self.skeleton2d[obj_name]['label_names']:
-                is_nan, new_camns = {}, {}
-                sum_in_out = np.zeros(np.size(self.skeleton2d[obj_name]['frames']))
-                for camn in range(1, self.nb_cam + 1):
-                    is_nan[camn] = np.isnan(self.skeleton2d[obj_name][label][camn]['x'])
-
-                    # in_out will be 0 if the camera view is correct, otherwise an int (-1, 1, 2, etc)
-                    in_out_x = np.ceil((self.skeleton2d[obj_name][label][camn]['x'] - (camn-1)*width_crop)/width_crop) -1
-                    in_out_y = np.ceil((self.skeleton2d[obj_name][label][camn]['y'])/height_crop) -1
-
-                    new_camns[camn] = np.ones(np.size(in_out_x)) *camn
-                    for i, frame in enumerate(self.skeleton2d[obj_name]['frames']): # assumes that stitching was done along x axis
-                        if (in_out_x[i] + camn) < 1 or (in_out_x[i] + camn) > self.nb_cam: new_camns[camn][i] = np.nan
-                        elif in_out_y[i] < 0 or in_out_y[i] > 0: new_camns[camn][i] = np.nan
-                        else: new_camns[camn][i] = in_out_x[i] + camn
-
-                    sum_in_out += new_camns[camn] - camn
-
-                skeleton2d_cur_bodypart = copy.deepcopy(self.skeleton2d[obj_name][label])
-                for camn in range(1, self.nb_cam + 1):
-                    skeleton2d_cur_bodypart[camn]['x'] *= np.nan
-                    skeleton2d_cur_bodypart[camn]['y'] *= np.nan
-
-                for i, frame in enumerate(self.skeleton2d[obj_name]['frames']):
-                    for camn in range(1, self.nb_cam + 1):
-                        new_camn = new_camns[camn][i]
-                        if np.isnan(new_camn) or is_nan[camn][i]: continue
-
-                        if sum_in_out[i] == 0 or (is_nan[new_camn][i] and sum_in_out[i] != 0):
-                            skeleton2d_cur_bodypart[new_camn]['x'][i] = self.skeleton2d[obj_name][label][camn]['x'][i]
-                            skeleton2d_cur_bodypart[new_camn]['y'][i] = self.skeleton2d[obj_name][label][camn]['y'][i]
-
-                        elif not np.isnan(new_camns[new_camn][i]):
-                            skeleton2d_cur_bodypart[new_camn]['x'][i] = self.skeleton2d[obj_name][label][new_camn]['x'][i]
-                            skeleton2d_cur_bodypart[new_camn]['y'][i] = self.skeleton2d[obj_name][label][new_camn]['y'][i]
-
-                # if self.show_plot:
-                #     fig, axs = plt.subplots(1, self.nb_cam)
-                #     for i, camn in enumerate(range(1, self.nb_cam + 1)):
-                #         axs[i].clear()
-                #         axs[i].plot(self.skeleton2d[obj_name][label][camn]['x'], self.skeleton2d[obj_name][label][camn]['y'])
-                #         axs[i].plot(skeleton2d_cur_bodypart[camn]['x'], skeleton2d_cur_bodypart[camn]['y'])
-                #         axs[i].set_ylabel('y pos (m) - ' + label)
-                #         axs[i].set_xlabel('x pos (m)')
-                #     plt.show()
-
-                self.skeleton2d[obj_name][label] = skeleton2d_cur_bodypart
-
-    def unscramble_sides2d_and_recon3d_dlc(self):
-        # do not work as planed
-
-        reconstructor = Reconstructor3D(self.nb_cam, self.img_size)
-        reconstructor.dlt_coefs = self.dlt_coefs
-
-        # Unscamble case where side is wrong on only a minority of cam views
-        self.skeleton3d = {'obj_names': self.skeleton2d['obj_names'], 'model_name': self.skeleton2d['model_name']}
-        for obj_name in self.skeleton2d['obj_names']:
-            label_names_right = [s for s in self.skeleton2d[obj_name]['label_names'] if 'right' in s]
-            other_label_names = [s for s in self.skeleton2d[obj_name]['label_names'] if
-                                 'right' not in s or 'left' not in s]
-
-            self.skeleton3d[obj_name] = {'frames': self.skeleton2d[obj_name]['frames'],
-                                         'label_names': self.skeleton2d[obj_name]['label_names']}
-            for label in self.skeleton3d[obj_name]['label_names']:
-                self.skeleton3d[obj_name][label] = {'x': np.nan * np.ones(np.size(self.skeleton2d[obj_name]['frames'])),
-                                                    'y': np.nan * np.ones(np.size(self.skeleton2d[obj_name]['frames'])),
-                                                    'z': np.nan * np.ones(np.size(self.skeleton2d[obj_name]['frames']))}
-
-            # Compute angles btw the right and left wing/bodyparts
-            angles, angles_out, mean_angles = {}, {}, {}
-            for i, camn in enumerate(range(1, self.nb_cam + 1)):
-                angles[camn], angles_out[camn] = {}, {}
-                mean_angles[camn] = np.zeros(np.size(self.skeleton2d[obj_name][label_names_right[0]][camn]['x']))
-                for label_right in label_names_right:
-                    label_left = label_right.replace('right', 'left')
-                    label = label_right.replace('right', '')
-
-                    x_right = self.skeleton2d[obj_name][label_right][camn]['x']
-                    y_right = self.skeleton2d[obj_name][label_right][camn]['y']
-                    x_left = self.skeleton2d[obj_name][label_left][camn]['x']
-                    y_left = self.skeleton2d[obj_name][label_left][camn]['y']
-
-                    angles[camn][label] = np.arctan2(y_right - y_left, x_right - x_left)
-                    #angles[camn][label] = np.arctan((y_right - y_left) / (x_right - x_left))
-
-                    angles[camn][label] = abs(angles[camn][label]) # doesn't not work when pi jump btw pi/2 and -pi/2
-
-                    # angles[camn][label] = np.fmod(angles[camn][label], 2 * np.pi)
-                    # angles[camn][label][~np.isnan(angles[camn][label])] = \
-                    #     np.unwrap(angles[camn][label][~np.isnan(angles[camn][label])], discont=2 * np.pi)
-                    # # angles[camn][label][~np.isnan(angles[camn][label])] = \
-                    # #    np.unwrap(angles[camn][label][~np.isnan(angles[camn][label])] * 2, discont=2 * np.pi * 0.8) / 2
-
-                    is_nan, x = np.isnan(angles[camn][label]), lambda z: z.nonzero()[0]
-                    if sum(is_nan) > 0:  # interpolation needed for filter_high_freq
-                        angles[camn][label][is_nan] = np.interp(x(is_nan), x(~is_nan), angles[camn][label][~is_nan])
-
-                    w = self.cutoff_frequency / (self.framerate / 2)  # Normalize the frequency
-                    b, a = signal.butter(2, w, 'low')
-
-                    mean_angles[camn] += signal.filtfilt(b, a, angles[camn][label])
-
-                mean_angles[camn] = mean_angles[camn] / len(label_names_right)
-
-                min_angle = 0
-                for label_right in label_names_right:
-                    label = label_right.replace('right', '')
-                    in_out = np.where(abs(angles[camn][label] - mean_angles[camn]) > np.pi / 2)
-                    angles_out[camn][label] = [s if i in in_out[0] else np.nan for i, s in enumerate(angles[camn][label])]
-                    min_angle = np.min([np.min(angles[camn][label]), min_angle])
-
-            if self.show_plot:
-                fig1, axs1 = plt.subplots(self.nb_cam, 1)
-                fig1.suptitle('Angle of the line btw right and left wing part - Before unscrambling')
-                for i, camn in enumerate(range(1, self.nb_cam + 1)):
-                    axs1[i].clear()
-                    for label_right in label_names_right:
-                        label = label_right.replace('right', '')
-                        frames = list(range(0, len(angles_out[camn][label])))
-
-                        axs1[i].scatter(frames, angles[camn][label], label=label, marker='.')
-                        axs1[i].scatter(frames, angles_out[camn][label], c='r', label='out', marker='*')
-
-                    axs1[i].plot(mean_angles[camn], 'k', label='mean')
-                    axs1[i].set_ylabel('angle (rad)')
-                    axs1[i].set_xlabel('frames - ' + label_right)
-
-            for i, frame in enumerate(self.skeleton2d[obj_name]['frames']):
-                for label_right in label_names_right:
-                    label_left = label_right.replace('right', 'left')
-                    label = label_right.replace('right', '')
-
-                    #if all([np.isnan(angles_out[camn][label][i]) for camn in range(1, self.nb_cam + 1)]): continue
-
-                    xs_coord, ys_coord, frames_coord, likelihood_coords = [], [], [], []
-                    for camn in range(1, self.nb_cam + 1):
-                        if not np.isnan(angles_out[camn][label][i]):  # Switch side
-                            self.skeleton2d[obj_name][label_right][camn]['x'][i], self.skeleton2d[obj_name][label_left][camn]['x'][i] = \
-                                self.skeleton2d[obj_name][label_left][camn]['x'][i], self.skeleton2d[obj_name][label_right][camn]['x'][i]
-                            self.skeleton2d[obj_name][label_right][camn]['y'][i], self.skeleton2d[obj_name][label_left][camn]['y'][i] = \
-                                self.skeleton2d[obj_name][label_left][camn]['y'][i], self.skeleton2d[obj_name][label_right][camn]['y'][i]
-                            self.skeleton2d[obj_name][label_right][camn]['likelihood'][i], self.skeleton2d[obj_name][label_left][camn]['likelihood'][i] = \
-                                self.skeleton2d[obj_name][label_left][camn]['likelihood'][i], self.skeleton2d[obj_name][label_right][camn]['likelihood'][i]
-
-                        x_right = self.skeleton2d[obj_name][label_right][camn]['x'][i].copy()
-                        y_right = self.skeleton2d[obj_name][label_right][camn]['y'][i].copy()
-                        x_left = self.skeleton2d[obj_name][label_left][camn]['x'][i].copy()
-                        y_left = self.skeleton2d[obj_name][label_left][camn]['y'][i].copy()
-
-                        likelihood_right = self.skeleton2d[obj_name][label_right][camn]['likelihood'][i]
-                        likelihood_left = self.skeleton2d[obj_name][label_left][camn]['likelihood'][i]
-
-                        likelihood_coords.append([likelihood_right, likelihood_left])
-                        xs_coord.append([x_right, x_left])
-                        ys_coord.append([y_right, y_left])
-                        frames_coord.append([frame, frame])
-
-                    xs_pose, ys_pose, zs_pose, frames_pose, indexes, rmses, = \
-                        reconstructor.recon_objs(xs_coord, ys_coord, frames_coord, with_missing=False, sort_by_rmse=False)
-
-                    # if len(xs_pose) == 0:
-                    #     xs_pose, ys_pose, zs_pose = [[np.nan, np.nan]], [[np.nan, np.nan]], [[np.nan, np.nan]]
-                    #     indexes = [[np.array([0, 0, 0]), np.array([1, 1, 1])]]
-                    #
-                    # else:
-                    #     rmses
-                    #     # order rmse
-                    #
-                    # cpt = 0
-                    # while len(xs_pose[0]) > cpt:
-                    #     # Remove duplicate usage of points and give priority to first rows (with lowest rmse)
-                    #     # should have max two rows in the end (one per side)
-                    #
-                    #     comparison_indexes = np.array([False] * self.nb_cam)  # False if different from previous rows
-                    #     for j in range(1, cpt + 1):
-                    #         comparison_indexes += np.array(
-                    #             [indexes[0][cpt][k] == indexes[0][cpt - j][k] for k in range(0, self.nb_cam)])
-                    #
-                    #     # Remove coordinates because it use at least one 2d points that was used already
-                    #     if comparison_indexes.any():
-                    #         xs_pose[0] = [s for k, s in enumerate(xs_pose[0]) if k != cpt]
-                    #         ys_pose[0] = [s for k, s in enumerate(ys_pose[0]) if k != cpt]
-                    #         indexes[0] = [s for k, s in enumerate(indexes[0]) if k != cpt]
-                    #     else:
-                    #         cpt += 1
-                    #
-                    # if len(xs_pose[0]) == 1:
-                    #     xs_pose[0].append(np.nan), ys_pose[0].append(np.nan), zs_pose[0].append(np.nan)
-                    #     indexes[0].append([abs(index - 1) for index in indexes[0]][0])
-                    #
-                    # # if np.nansum(indexes[0][0]) > np.nansum(indexes[0][1]) \
-                    # #         and indexes[0][1][1] == 0:  # use top cam (#2) as reference for identifying side
-                    # if indexes[0][1][1] == 0:  # use top cam (#2) as reference for identifying side
-                    #     indexes[0][0], indexes[0][1] = indexes[0][1], indexes[0][0]
-                    #     xs_pose[0].reverse(), ys_pose[0].reverse(), zs_pose[0].reverse()
-
-                    if len(xs_pose) == 0:
-                        xs_pose, ys_pose, zs_pose = [[np.nan, np.nan]], [[np.nan, np.nan]], [[np.nan, np.nan]]
-                        indexes = [[np.array([0, 0, 0]), np.array([1, 1, 1])]]
-
-                    elif len(xs_pose[0]) == 1:
-                        xs_pose[0].append(np.nan), ys_pose[0].append(np.nan), zs_pose[0].append(np.nan)
-                        indexes[0].append([abs(index - 1) for index in indexes[0]][0])
-                    #
-                    # elif len(xs_pose[0]) > 2:
-                    #     indexes_top_right = np.array([s for s in indexes[0] if s[self.top_camn - 1] == 0.0]).astype(int)
-                    #     indexes_top_left = np.array([s for s in indexes[0] if s[self.top_camn - 1] == 1.0]).astype(int)
-                    #     rmses_top_right = np.array([rmses[0][i] for i, s in enumerate(indexes[0]) if s[self.top_camn - 1] == 0.0])
-                    #     rmses_top_left = np.array([rmses[0][i] for i, s in enumerate(indexes[0]) if s[self.top_camn - 1] == 1.0])
-                    #
-                    #     indexes = [[np.array([0, 0, 0]), np.array([1, 1, 1])]]
-                    #     xs_pose_new, ys_pose_new, zs_pose_new = [[np.nan, np.nan]], [[np.nan, np.nan]], [[np.nan, np.nan]]
-                    #     if len(rmses_top_right) > 1 and len(rmses_top_left) > 1:
-                    #         combi = list(itertools.product(list(range(0, len(rmses_top_right))), list(range(0, len(rmses_top_left)))))
-                    #
-                    #         new_combi = []
-                    #         for j in range(0, len(combi)):  # Keep only the combinations that are compatible
-                    #             if (indexes_top_right[combi[j][0]] + indexes_top_left[combi[j][1]] == [1, 1, 1]).all():
-                    #                 new_combi.append(combi[j])
-                    #
-                    #         if not len(new_combi) == 0:
-                    #             rmse_combi = [np.sqrt(rmses_top_right[s[0]] ** 2 + rmses_top_left[s[1]] ** 2) for s in new_combi]
-                    #             best_combi = new_combi[np.argsort(rmse_combi)[0]]
-                    #
-                    #             indexes = [[indexes_top_right[best_combi[0]], indexes_top_left[best_combi[1]]]]
-                    #
-                    #         else:
-                    #             in_sort_right = np.argsort(rmses_top_right)
-                    #             in_sort_left = np.argsort(rmses_top_left)
-                    #
-                    #             if rmses_top_right[in_sort_right[0]] < rmses_top_left[in_sort_left[0]]:
-                    #                 indexes = [[indexes_top_right[in_sort_right[0]], abs(indexes_top_right[in_sort_right[0]] -1)]]
-                    #             else:
-                    #                 indexes = [[abs(indexes_top_left[in_sort_left[0]] -1), indexes_top_left[in_sort_left[0]]]]
-                    #
-                    #         xs_pose_new[0][0], ys_pose_new[0][0], zs_pose_new[0][0] = \
-                    #             xs_pose[0][indexes[0][0][0]], ys_pose[0][indexes[0][0][1]], zs_pose[0][indexes[0][0][2]]
-                    #         xs_pose_new[0][1], ys_pose_new[0][1], zs_pose_new[0][1] = \
-                    #             xs_pose[0][indexes[0][1][0]], ys_pose[0][indexes[0][1][1]], zs_pose[0][indexes[0][1][2]]
-                    #
-                    #     elif not len(rmses_top_right) == 0 and len(rmses_top_left) == 0:
-                    #         in_sort_right = np.argsort(rmses_top_right)
-                    #         in_sort_left = np.argsort(rmses_top_left)
-                    #
-                    #         if len(rmses_top_right) == 0:
-                    #             indexes[0][0] = abs(indexes_top_left[in_sort_left[0]] - 1)
-                    #         else:
-                    #             indexes[0][0] = indexes_top_right[in_sort_right[0]]
-                    #             xs_pose_new[0][0], ys_pose_new[0][0], zs_pose_new[0][0] = \
-                    #                 xs_pose[0][indexes[0][0][0]], ys_pose[0][indexes[0][0][1]], zs_pose[0][indexes[0][0][2]]
-                    #
-                    #         if len(rmses_top_left) == 0:
-                    #             indexes[0][1] = abs(indexes_top_right[in_sort_right[0]] - 1)
-                    #         else:
-                    #             indexes[0][1] = indexes_top_left[in_sort_left[0]]
-                    #             xs_pose_new[0][1], ys_pose_new[0][1], zs_pose_new[0][1] = \
-                    #                 xs_pose[0][indexes[0][1][0]], ys_pose[0][indexes[0][1][1]], zs_pose[0][indexes[0][1][2]]
-                    #
-                    #     xs_pose, ys_pose, zs_pose = xs_pose_new, ys_pose_new, zs_pose_new
-                    #
-                    # if indexes[0][1][self.top_camn-1] == 0:  # use top cam as reference for identifying side
-                    #     indexes[0][0], indexes[0][1] = indexes[0][1], indexes[0][0]
-                    #     xs_pose[0].reverse(), ys_pose[0].reverse(), zs_pose[0].reverse()
-                    #
-                    # if (indexes[0] != np.array([0, 0, 0])).any():
-                    #     for j, camn in enumerate(range(1, self.nb_cam + 1)):
-                    #         if not np.isnan(indexes[0][0][j]) and not np.isnan(indexes[0][1][j]):
-                    #             in_right = int(indexes[0][0][j])
-                    #             self.skeleton2d[obj_name][label_right][camn]['x'][i] = xs_coord[j][in_right]
-                    #             self.skeleton2d[obj_name][label_right][camn]['y'][i] = ys_coord[j][in_right]
-                    #             self.skeleton2d[obj_name][label_right][camn]['likelihood'][i] = likelihood_coords[j][in_right]
-                    #
-                    #             in_left = int(indexes[0][1][j])
-                    #             self.skeleton2d[obj_name][label_left][camn]['x'][i] = xs_coord[j][in_left]
-                    #             self.skeleton2d[obj_name][label_left][camn]['y'][i] = ys_coord[j][in_left]
-                    #             self.skeleton2d[obj_name][label_left][camn]['likelihood'][i] = likelihood_coords[j][in_left]
-
-                    self.skeleton3d[obj_name][label_right]['x'][i], self.skeleton3d[obj_name][label_right]['y'][i], \
-                    self.skeleton3d[obj_name][label_right]['z'][i] = xs_pose[0][0], ys_pose[0][0], zs_pose[0][0]
-
-                    self.skeleton3d[obj_name][label_left]['x'][i], self.skeleton3d[obj_name][label_left]['y'][i], \
-                    self.skeleton3d[obj_name][label_left]['z'][i] = xs_pose[0][1], ys_pose[0][1], zs_pose[0][1]
-
-                for label in other_label_names:
-                    xs_coord, ys_coord, frames_coord = [], [], []
-                    for camn in range(1, self.nb_cam + 1):
-                        xs_coord.append([self.skeleton2d[obj_name][label][camn]['x'][i].copy()])
-                        ys_coord.append([self.skeleton2d[obj_name][label][camn]['y'][i].copy()])
-                        frames_coord.append([frame])
-
-                    xs_pose, ys_pose, zs_pose, frames_pose, indexes, rmses, = \
-                        reconstructor.recon_objs(xs_coord, ys_coord, frames_coord, with_missing=False)
-
-                    if len(xs_pose) == 0:
-                        self.skeleton3d[obj_name][label]['x'][i], self.skeleton3d[obj_name][label]['y'][i], \
-                        self.skeleton3d[obj_name][label]['z'][i] = np.nan, np.nan, np.nan
-                    else:
-                        self.skeleton3d[obj_name][label]['x'][i], self.skeleton3d[obj_name][label]['y'][i], \
-                        self.skeleton3d[obj_name][label]['z'][i] = xs_pose[0][0], ys_pose[0][0], zs_pose[0][0]
-
-            if self.show_plot:
-                fig2, axs2 = plt.subplots(self.nb_cam, 1)
-                fig2.suptitle('Angle of the line btw right and left wing part - After unscrambling')
-                for i, camn in enumerate(range(1, self.nb_cam + 1)):
-                    axs2[i].clear()
-                    for label_right in label_names_right:
-                        label_left = label_right.replace('right', 'left')
-                        x_right = self.skeleton2d[obj_name][label_right][camn]['x']
-                        y_right = self.skeleton2d[obj_name][label_right][camn]['y']
-                        x_left = self.skeleton2d[obj_name][label_left][camn]['x']
-                        y_left = self.skeleton2d[obj_name][label_left][camn]['y']
-
-                        angles = np.arctan2((y_right - y_left), (x_right - x_left))
-                        #angles = np.arctan((y_right - y_left) / (x_right - x_left))
-                        #angles[~np.isnan(angles)] = np.unwrap(angles[~np.isnan(angles)] * 2, discont=2 * np.pi * 0.8) / 2
-                        angles = np.mod(angles, 2*np.pi)
-
-                        axs2[i].plot(angles, label=label_right.replace('right', ''))
-                    axs2[i].set_ylabel('angle (rad)')
-                    axs2[i].set_xlabel('frames - ' + label_right)
-                plt.legend
-                plt.show()
-
-    def unscramble_sides3d_dlc(self, body_params):
-        # Unscramble case where side is wrong on all cam views
-        for obj_name in self.skeleton3d['obj_names']:
-            label_names_right = [s for s in self.skeleton3d[obj_name]['label_names'] if 'right' in s]
-
-            if (self.skeleton2d[obj_name]['frames'] != self.skeleton3d[obj_name]['frames']).any():
-                raise ValueError('frames should be the same for skeleton2d and skeleton3d')
-
-            if self.show_plot:
-                fig1, axs1 = plt.subplots(self.nb_cam, 1)
-                for i, camn in enumerate(range(1, self.nb_cam + 1)):
-                    axs1[i].clear()
-                    for label_right in label_names_right:
-                        label_left = label_right.replace('right', 'left')
-                        x_right = self.skeleton2d[obj_name][label_right][camn]['x']
-                        y_right = self.skeleton2d[obj_name][label_right][camn]['y']
-                        x_left = self.skeleton2d[obj_name][label_left][camn]['x']
-                        y_left = self.skeleton2d[obj_name][label_left][camn]['y']
-
-                        axs1[i].plot(np.arctan2(y_right - y_left, x_right - x_left))  # angle of the line btw the two sides
-                    axs1[i].set_ylabel('angle btw right and left (rad)')
-                    axs1[i].set_xlabel('frames - ' + label_right)
-
-            for i, frame in enumerate(self.skeleton3d[obj_name]['frames']):
-                body_params_frame = {}
-                for label in body_params[obj_name].keys():
-                    body_params_frame[label] = body_params[obj_name][label][i].copy()
-
-                body_skeleton3d = copy.deepcopy(self.body_skeleton3d_init)
-                body_skeleton3d = build_body_skeleton3d(body_skeleton3d, body_params_frame)
-                body_skeleton3d = rotate_body_skeleton3d(body_skeleton3d, body_params_frame)
-                body_skeleton3d = translate_body_skeleton3d(body_skeleton3d, body_params_frame)
-                # body_skeleton2d = reproject_skeleton3d_to2d(body_skeleton3d, self.dlt_coefs)
-
-                for label_right in label_names_right:
-                    label_left = label_right.replace('right', 'left')
-
-                    x_right = self.skeleton3d[obj_name][label_right]['x'][i].copy()
-                    y_right = self.skeleton3d[obj_name][label_right]['y'][i].copy()
-                    z_right = self.skeleton3d[obj_name][label_right]['z'][i].copy()
-
-                    x_left = self.skeleton3d[obj_name][label_left]['x'][i].copy()
-                    y_left = self.skeleton3d[obj_name][label_left]['y'][i].copy()
-                    z_left = self.skeleton3d[obj_name][label_left]['z'][i].copy()
-
-                    xyz_right = [x_right, y_right, z_right]
-                    xyz_left = [x_left, y_left, z_left]
-
-                    dist_right2right_hinge = np.linalg.norm(body_skeleton3d['right_wing_hinge'] - xyz_right)
-                    dist_left2right_hinge = np.linalg.norm(body_skeleton3d['right_wing_hinge'] - xyz_left)
-                    dist_right2left_hinge = np.linalg.norm(body_skeleton3d['left_wing_hinge'] - xyz_right)
-                    dist_left2left_hinge = np.linalg.norm(body_skeleton3d['left_wing_hinge'] - xyz_left)
-
-                    if dist_right2right_hinge > dist_right2left_hinge and dist_left2left_hinge > dist_left2right_hinge:
-                        x_right, y_right, z_right, x_left, y_left, z_left = x_left, y_left, z_left, x_right, y_right, z_right
-
-                        self.skeleton3d[obj_name][label_right]['x'][i], self.skeleton3d[obj_name][label_right]['y'][i], \
-                        self.skeleton3d[obj_name][label_right]['z'][i] = x_right, y_right, z_right
-
-                        self.skeleton3d[obj_name][label_left]['x'][i], self.skeleton3d[obj_name][label_left]['y'][i], \
-                        self.skeleton3d[obj_name][label_left]['z'][i] = x_left, y_left, z_left
-
-                        for camn in range(1, self.nb_cam + 1):
-                            self.skeleton2d[obj_name][label_right][camn]['x'][i], \
-                            self.skeleton2d[obj_name][label_right][camn]['y'][i], \
-                            self.skeleton2d[obj_name][label_left][camn]['x'][i], \
-                            self.skeleton2d[obj_name][label_left][camn]['y'][i] = \
-                                self.skeleton2d[obj_name][label_left][camn]['x'][i], \
-                                self.skeleton2d[obj_name][label_left][camn]['y'][i], \
-                                self.skeleton2d[obj_name][label_right][camn]['x'][i], \
-                                self.skeleton2d[obj_name][label_right][camn]['y'][i]
-
-            if self.show_plot:
-                fig2, axs2 = plt.subplots(self.nb_cam, 1)
-                for i, camn in enumerate(range(1, self.nb_cam + 1)):
-                    axs2[i].clear()
-                    for label_right in label_names_right:
-                        label_left = label_right.replace('right', 'left')
-                        x_right = self.skeleton2d[obj_name][label_right][camn]['x']
-                        y_right = self.skeleton2d[obj_name][label_right][camn]['y']
-                        x_left = self.skeleton2d[obj_name][label_left][camn]['x']
-                        y_left = self.skeleton2d[obj_name][label_left][camn]['y']
-
-                        axs2[i].plot(
-                            np.arctan2(y_right - y_left, x_right - x_left))  # angle of the line btw the two sides
-                    axs2[i].set_ylabel('angle btw right and left (rad)')
-                    axs2[i].set_xlabel('frames - ' + label_right)
-
-                plt.show()
-
-    def reverse_processes_2d_points(self, process_dict):
-        [width_img, height_img] = self.img_size
-        for camn in range(1, self.nb_cam + 1):
-            process_names = [process_dict[camn][process_num]['fn'] for process_num in process_dict[camn].keys()]
-
-            for obj_name in self.skeleton2d['obj_names']:
-                if 'stitch' in process_names or 'rotate' in process_names:
-                    if 'crop' in process_names:
-                        crop_num = process_names.index('crop')
-                        crop_num = crop_num[-1] if isinstance(crop_num, list) else crop_num
-                        width_crop = process_dict[camn][crop_num]['kwargs']['width_crop']
-                        height_crop = process_dict[camn][crop_num]['kwargs']['height_crop']
-
-                    else:
-                        width_crop, height_crop = width_img, height_img
-
-                if 'crop' in process_names:
-                    crop_path = self.get_path_process('crop')[process_dict[camn][0]['camn']]
-                    csv_path = glob.glob(os.path.join(crop_path, process_dict[camn][0]['rec_names'][camn],
-                                                      '*' + obj_name + '-2d_points.csv'))
-
-                    pts_csv = np.genfromtxt(csv_path[0], delimiter=',', skip_header=0, names=True)
-
-                    temp_set = set(self.skeleton2d[obj_name]['frames'])
-                    in_frame = [i for i, val in enumerate(pts_csv['frame']) if val in temp_set]
-
-                    width_crop = process_dict[camn][crop_num]['kwargs']['width_crop']
-                    height_crop = process_dict[camn][crop_num]['kwargs']['height_crop']
-
-                    x_crop = pts_csv['x_px'][in_frame] - width_crop / 2
-                    y_crop = pts_csv['y_px'][in_frame] - height_crop / 2
-
-                    for i in range(0, len(in_frame)):
-                        x_crop[i] = np.max([np.min([x_crop[i], width_img - width_crop]), 0])
-                        y_crop[i] = np.max([np.min([y_crop[i], height_img - height_crop]), 0])
-
-                for label in self.skeleton2d[obj_name]['label_names']:
-                    if 'stitch' in process_names:
-                        self.skeleton2d[obj_name][label][camn]['x'] += - width_crop * (camn - 1)
-
-                    if 'rotate' in process_names and \
-                            camn in process_dict[camn][process_names.index('rotate')]['kwargs']['camns']:
-                        x = self.skeleton2d[obj_name][label][camn]['x'].copy() - width_crop / 2
-                        y = self.skeleton2d[obj_name][label][camn]['y'].copy() - height_crop / 2
-                        teta = np.deg2rad(process_dict[camn][process_names.index('rotate')]['kwargs']['degrees'])
-
-                        self.skeleton2d[obj_name][label][camn]['x'] = x * np.cos(teta) - y * np.sin(teta) + width_crop / 2
-                        self.skeleton2d[obj_name][label][camn]['y'] = x * np.sin(teta) + y * np.cos(teta) + height_crop / 2
-
-                    if 'crop' in process_names:
-                        self.skeleton2d[obj_name][label][camn]['x'] += x_crop
-                        self.skeleton2d[obj_name][label][camn]['y'] += y_crop
-
-                if self.show_plot:
-                    raw_rec_path = os.path.join(process_dict[camn][0]['paths'][process_dict[camn][0]['camn']],
-                                                process_dict[camn][0]['rec_names'][process_dict[camn][0]['camn']])
-                    all_files = sorted(os.listdir(raw_rec_path), key=lambda x: x[20:20 + self.nb_leading_zero])
-                    all_images = [s for s in all_files if '.{0}'.format(self.image_format) in s]
-
-                    fig = plt.figure()
-                    for i, image in enumerate(all_images):
-                        if i % 500: continue  # plot every 100 frames
-                        ax = fig.add_subplot(111)
-                        plt.clf()
-                        plt.imshow(Image.open(os.path.join(raw_rec_path, image)))
-                        # ax.scatter(x_crop + width_crop/2, y_crop + height_crop/2, marker='o')
-                        for label in self.skeleton2d[obj_name]['label_names']:
-                            # if 'wing' not in label: continue
-                            if self.skeleton2d[obj_name][label][camn]['likelihood'][i] > self.threshold_likelihood:
-                                ax.scatter(self.skeleton2d[obj_name][label][camn]['x'][i],
-                                           self.skeleton2d[obj_name][label][camn]['y'][i], 1)
-                        ax.set_xlabel('x (m)')
-                        ax.set_ylabel('y (m)')
-                        plt.pause(0.1)
-                        # plt.show()
-
-    def test_unwrap(self):
-        reconstructor = Reconstructor3D(self.nb_cam, self.img_size)
-        reconstructor.dlt_coefs = self.dlt_coefs
-
-        # Unscamble case where side is wrong on only a minority of cam views
-        self.skeleton3d = {'obj_names': self.skeleton2d['obj_names'], 'model_name': self.skeleton2d['model_name']}
-        for obj_name in self.skeleton3d['obj_names']:
-            label_names_right = [s for s in self.skeleton2d[obj_name]['label_names'] if 'right' in s]
-
-            self.skeleton3d[obj_name] = {'frames': self.skeleton2d[obj_name]['frames'],
-                                         'label_names': self.skeleton2d[obj_name]['label_names']}
-            for label in self.skeleton3d[obj_name]['label_names']:
-                self.skeleton3d[obj_name][label] = {'x': np.nan * np.ones(np.size(self.skeleton3d[obj_name]['frames'])),
-                                                    'y': np.nan * np.ones(np.size(self.skeleton3d[obj_name]['frames'])),
-                                                    'z': np.nan * np.ones(np.size(self.skeleton3d[obj_name]['frames']))}
-
-            for i, frame in enumerate(self.skeleton3d[obj_name]['frames']):
-                for label in self.skeleton3d[obj_name]['label_names']:
-                    xs_coord, ys_coord, frames_coord = [], [], []
-                    for camn in range(1, self.nb_cam + 1):
-                        xs_coord.append([self.skeleton2d[obj_name][label][camn]['x'][i].copy()])
-                        ys_coord.append([self.skeleton2d[obj_name][label][camn]['y'][i].copy()])
-                        frames_coord.append([frame])
-
-                    xs_pose, ys_pose, zs_pose, frames_pose, indexes, rmses, = \
-                        reconstructor.recon_objs(xs_coord, ys_coord, frames_coord, with_missing=False)
-
-                    if len(xs_pose) == 0:
-                        self.skeleton3d[obj_name][label]['x'][i], self.skeleton3d[obj_name][label]['y'][i], \
-                        self.skeleton3d[obj_name][label]['z'][i] = np.nan, np.nan, np.nan
-                    else:
-                        self.skeleton3d[obj_name][label]['x'][i], self.skeleton3d[obj_name][label]['y'][i], \
-                        self.skeleton3d[obj_name][label]['z'][i] = xs_pose[0][0], ys_pose[0][0], zs_pose[0][0]
-
-            fig1, axs1 = plt.subplots(self.nb_cam, 1)
-            for i, camn in enumerate(range(1, self.nb_cam + 1)):
-                axs1[i].clear()
-                for label_right in label_names_right:
-                    label_left = label_right.replace('right', 'left')
-                    x_right = self.skeleton2d[obj_name][label_right][camn]['x']
-                    y_right = self.skeleton2d[obj_name][label_right][camn]['y']
-                    x_left = self.skeleton2d[obj_name][label_left][camn]['x']
-                    y_left = self.skeleton2d[obj_name][label_left][camn]['y']
-
-                    angle = np.arctan((y_right - y_left) / (x_right - x_left))
-                    angle[~np.isnan(angle)] = np.unwrap(angle[~np.isnan(angle)] * 2, discont=2 * np.pi * 0.8) / 2
-
-                    angle2 = np.arctan2((y_right - y_left), (x_right - x_left))
-                    median_diff_angle = np.nanmedian(angle2 - angle)
-                    if abs(abs(median_diff_angle) - np.pi) < 0.2:
-                        if median_diff_angle > 0:
-                            angle2 -= np.pi
-                        elif median_diff_angle < 0:
-                            angle2 += np.pi
-
-                    in_diff = np.array([False] * len(angle))
-                    is_not_nan = ~np.isnan(angle) & ~np.isnan(angle2)
-                    in_diff[is_not_nan] = abs(angle2[is_not_nan] - angle[is_not_nan]) > np.pi / 2
-
-                    axs1[i].plot(self.skeleton2d[obj_name]['frames'], angle)  # angle of the line btw the two sides
-                    axs1[i].plot(np.array(self.skeleton2d[obj_name]['frames'])[in_diff], angle2[in_diff], marker='*',
-                                 linestyle='None')
-
-                axs1[i].set_ylabel('angle btw right and left (rad)')
-                axs1[i].set_xlabel('frames - ' + label_right)
-
-            plt.show()
-
-    def recon3d_dlc(self):
-        reconstructor = Reconstructor3D(self.nb_cam, self.img_size)
-        reconstructor.dlt_coefs = self.dlt_coefs
-
-        # Unscamble case where side is wrong on only a minority of cam views
-        self.skeleton3d = {'obj_names': self.skeleton2d['obj_names'], 'model_name': self.skeleton2d['model_name']}
-        for obj_name in self.skeleton3d['obj_names']:
-            # label_names_right = [s for s in self.skeleton2d[obj_name]['label_names'] if 'right' in s]
-            # other_label_names = [s for s in self.skeleton2d[obj_name]['label_names'] if
-            #                      'right' not in s or 'left' not in s]
-
-            self.skeleton3d[obj_name] = {'frames': self.skeleton2d[obj_name]['frames'],
-                                         'label_names': self.skeleton2d[obj_name]['label_names']}
-            for label in self.skeleton3d[obj_name]['label_names']:
-                self.skeleton3d[obj_name][label] = {'x': np.nan * np.ones(np.size(self.skeleton2d[obj_name]['frames'])),
-                                                    'y': np.nan * np.ones(np.size(self.skeleton2d[obj_name]['frames'])),
-                                                    'z': np.nan * np.ones(np.size(self.skeleton2d[obj_name]['frames']))}
-
-            for i, frame in enumerate(self.skeleton2d[obj_name]['frames']):
-                for label in self.skeleton3d[obj_name]['label_names']:
-                    xs_coord, ys_coord, frames_coord = [], [], []
-                    for camn in range(1, self.nb_cam + 1):
-                        xs_coord.append([self.skeleton2d[obj_name][label][camn]['x'][i].copy()])
-                        ys_coord.append([self.skeleton2d[obj_name][label][camn]['y'][i].copy()])
-                        frames_coord.append([frame])
-
-                    xs_pose, ys_pose, zs_pose, frames_pose, indexes, rmses, = \
-                        reconstructor.recon_objs(xs_coord, ys_coord, frames_coord, with_missing=False)
-
-                    if len(xs_pose) == 0:
-                        self.skeleton3d[obj_name][label]['x'][i], self.skeleton3d[obj_name][label]['y'][i], \
-                        self.skeleton3d[obj_name][label]['z'][i] = np.nan, np.nan, np.nan
-                    else:
-                        self.skeleton3d[obj_name][label]['x'][i], self.skeleton3d[obj_name][label]['y'][i], \
-                        self.skeleton3d[obj_name][label]['z'][i] = xs_pose[0][0], ys_pose[0][0], zs_pose[0][0]
-
-    def save2d_dlc(self, save_paths):
-        for obj_name in self.skeleton2d['obj_names']:
-            for label in self.skeleton2d[obj_name]['label_names']:
-                for camn in range(1, self.nb_cam + 1):
-                    if not os.path.exists(save_paths[camn]): os.makedirs(save_paths[camn])
-                    np.savetxt(os.path.join(save_paths[camn], obj_name + '-' + label + '-2d_points.csv'),
-                               np.c_[self.skeleton2d[obj_name]['frames'], self.skeleton2d[obj_name][label][camn]['x'],
-                                     self.skeleton2d[obj_name][label][camn]['y'],
-                                     self.skeleton2d[obj_name][label][camn]['likelihood']],
-                               delimiter=',', header='frame,x_px,y_px,likelihood')
-
-                # # To save all in same .csv file
-                # for camn in range(1, self.nb_cam + 1):
-                #     if not os.path.exists(save_paths[camn]): os.makedirs(save_paths[camn])
-                #     header = header + ', x{0}_px, y{0}_px, likelihood{0}'.format(camn)
-                #     array_csv = np.concatenate((array_csv, np.c_[self.skeleton2d[obj_name][label][camn]['x'],
-                #                                                  self.skeleton2d[obj_name][label][camn]['y'],
-                #                                                  self.skeleton2d[obj_name][label][camn]['likelihood']), axis=1)
-                #
-                # np.savetxt(os.path.join(save_paths[self.main_camn], obj_name + '-' + label + '-2d_points.csv'),
-                #            array_csv, delimiter=',', header=header)
-
-    def save3d_dlc(self, save_paths):
-        for obj_name in self.skeleton3d['obj_names']:
-            for label in self.skeleton3d[obj_name]['label_names']:
-                for camn in range(1, self.nb_cam + 1):
-                    if not os.path.exists(save_paths[camn]): os.makedirs(save_paths[camn])
-                    np.savetxt(os.path.join(save_paths[camn], obj_name + '-' + label + '-3d_points.csv'),
-                               np.c_[self.skeleton3d[obj_name]['frames'], self.skeleton3d[obj_name][label]['x'],
-                                     self.skeleton3d[obj_name][label]['y'], self.skeleton3d[obj_name][label]['z']],
-                               delimiter=',', header='frame,x,y,z')
-
-    def estimate_body_wings_params(self, interpolate_nans=False, filter_high_freq=False):
-        body_params_labels = self.body_params_init.keys()
-        wing_params_labels = self.wings_params_init['right'].keys()
-
-        if len(self.skeleton3d['obj_names']) == 0:
-            raise ValueError('obj_names is empty')
-
-        self.body_params, self.wings_params, self.mean_body_params, self.mean_wings_params = {}, {}, {}, {}
-        for obj_name in self.skeleton3d['obj_names']:
-            frames = self.skeleton3d[obj_name]['frames']
-
-            self.body_params[obj_name], self.wings_params[obj_name] = {}, {}
-            self.mean_body_params[obj_name], self.mean_wings_params[obj_name] = {}, {}
-            for label in body_params_labels:
-                self.body_params[obj_name][label] = np.nan * np.ones(frames.shape)
-
-            for side in ['right', 'left']:
-                self.wings_params[obj_name][side] = {}
-                for label in wing_params_labels:
-                    self.wings_params[obj_name][side][label] = np.nan * np.ones(frames.shape)
-
-            for i, frame in enumerate(frames):
-                #for label in self.skeleton3d[obj_name]['label_names']:
-
-                body_skeleton3d, wings_skeleton3d = {}, {'right': {}, 'left': {}}
-                for body_label in self.body_labels:
-                    body_skeleton3d[body_label] = np.array([self.skeleton3d[obj_name][body_label]['x'][i],
-                                                            self.skeleton3d[obj_name][body_label]['y'][i],
-                                                            self.skeleton3d[obj_name][body_label]['z'][i]])
-
-                if self.keep_init_aspect_ratio: init_aspect_ratio = {}
-
-                for side in ['right', 'left']:
-                    for wing_label in self.wing_labels:
-                        label = side + '_wing_' + wing_label
-                        wings_skeleton3d[side][wing_label] = \
-                            np.array([self.skeleton3d[obj_name][label]['x'][i],
-                                      self.skeleton3d[obj_name][label]['y'][i],
-                                      self.skeleton3d[obj_name][label]['z'][i]])
-
-                    if self.keep_init_aspect_ratio:
-                        span_v = self.wings_skeleton3d_init[side]['tip'] - self.wings_skeleton3d_init[side]['hinge']
-                        chord_v = self.wings_skeleton3d_init[side]['leading_edge_q2'] - self.wings_skeleton3d_init[side]['trailing_edge_q2']
-                        init_aspect_ratio[side] = np.round(length_from_vector(span_v), 9) / np.round(length_from_vector(chord_v), 9)
-                        init_aspect_ratio[side] = init_aspect_ratio[side] * np.ones(np.shape(self.wings_params[obj_name][side]['span']))
-
-                    # for leg_name in leg_names:
-                    #     for leg_label in leg_labels:
-                    #         label = side + '_leg_' + leg_name + '_' + leg_label
-                    #         body_skeleton3d[label] = np.array([self.skeleton3d[obj_name][label]['x'][i],
-                    #                                            self.skeleton3d[obj_name][label]['y'][i],
-                    #                                            self.skeleton3d[obj_name][label]['z'][i]])
-
-                body_params = estimate_body_parameters_from_skeleton3d(body_skeleton3d, self.body_params_init)
-                for label in body_params_labels:
-                    self.body_params[obj_name][label][i] = body_params[label]
-
-                for side in ['right', 'left']:
-                    wing_param = estimate_wing_parameters_from_skeleton3d(wings_skeleton3d[side], body_params,
-                                                                          self.wings_params_init[side], side)
-
-                    for label in wing_params_labels:
-                        self.wings_params[obj_name][side][label][i] = wing_param[label]
-
-            if self.keep_init_aspect_ratio:
-                for side in ['right', 'left']:
-                    self.wings_params[obj_name][side]['aspect_ratio'] = init_aspect_ratio[side]
-                    self.wings_params[obj_name][side]['chord'] = self.wings_params[obj_name][side]['span']/init_aspect_ratio[side]
-
-            # if self.show_plot:
-            #     # Plot body and wings params that will be kept cst
-            #     fig1, axs1 = plt.subplots(len(self.body_labels_to_keep_cst), 1)
-            #     for i, label in enumerate(self.body_labels_to_keep_cst):
-            #         y = self.body_params[obj_name][label]
-            #         axs1[i].plot(frames, y, '.', label='estim')
-            #         axs1[i].plot(frames, np.nanmean(y) * np.ones(frames.shape), '-', label='mean')
-            #         axs1[i].plot(frames, np.nanmedian(y) * np.ones(frames.shape), '-', label='median')
-            #         axs1[i].plot(frames, stats.mode(y)[0] * np.ones(frames.shape), '-', label='mode')
-            #
-            #         axs1[i].set_ylabel(label)
-            #     axs1[i].set_xlabel('frames')
-            #     fig1.suptitle('Body parameters in function of time')
-            #     fig1.legend()
-            #
-            #     fig2, axs2 = plt.subplots(len(self.wing_labels_to_keep_cst), 1)
-            #     for i, label in enumerate(self.wing_labels_to_keep_cst):
-            #         for side in ['right', 'left']:
-            #             y = self.wings_params[obj_name][side][label]
-            #             axs2[i].plot(frames, y, '.', label='estim_' + side)
-            #             axs2[i].plot(frames, np.nanmean(y) * np.ones(frames.shape), '-', label='mean_' + side)
-            #             axs2[i].plot(frames, np.nanmedian(y) * np.ones(frames.shape), '-', label='median_' + side)
-            #             axs2[i].plot(frames, stats.mode(y)[0] * np.ones(frames.shape), '-', label='mode_' + side)
-            #         axs2[i].set_ylabel(label)
-            #     axs2[i].set_xlabel('frames')
-            #     fig2.suptitle('Wings parameters in function of time')
-            #     fig2.legend()
-            #
-            #     plt.show()
-
-            # Compute median value of body and wings length (should be constant for one animal)
-            for label in self.body_labels_to_keep_cst:
-                self.body_params[obj_name][label] = \
-                    np.nanmedian(self.body_params[obj_name][label]) * np.ones(frames.shape)
-                 # print('>>> mean {0}: {1}'.format(label, self.body_params[obj_name][label][0]))
-
-            self.body_params[obj_name] = self.filter_interp_params(self.body_params[obj_name], body_params_labels,
-                                                                   interpolate_nans, filter_high_freq)
-
-            for label in self.wing_labels_to_keep_cst:
-                mean_over_sides = np.nanmedian([np.nanmedian(self.wings_params[obj_name][side][label]) for side in ['right', 'left']])
-                for side in ['right', 'left']:
-                    self.wings_params[obj_name][side][label] = mean_over_sides * np.ones(frames.shape)
-                # print('>>> mean {0}: {1} (right and left)'.format(label, self.wings_params[obj_name]['right'][label][0]))
-
-            for side in ['right', 'left']:
-                self.wings_params[obj_name][side] = \
-                    self.filter_interp_params(self.wings_params[obj_name][side], ['x_hinge', 'y_hinge', 'z_hinge'],
-                                              False, filter_high_freq)
-                self.wings_params[obj_name][side] = \
-                    self.filter_interp_params(self.wings_params[obj_name][side], wing_params_labels,
-                                              interpolate_nans, False)
-
-            # Copy all body_params in wings_params
-            for side in ['right', 'left']:
-                for label in body_params_labels:
-                    self.wings_params[obj_name][side][label] = self.body_params[obj_name][label].copy()
-
-        return self.body_params, self.wings_params
-
-    def estimate_wing_hinges(self, interpolate_nans=False, filter_high_freq=False):
-        wing_params_labels = self.wings_params_init['right'].keys()
-
-        dpt = 3
-        x, y, z = symbols('x y z')
-
-        if len(self.skeleton3d['obj_names']) == 0:
-            raise ValueError('obj_names is empty')
-
-        for obj_name in self.skeleton3d['obj_names']:
-            frames = self.skeleton3d[obj_name]['frames']
-
-            if self.show_plot:
-                init_hinges = {}
-                for side in ['right', 'left']:
-                    init_hinges[side] = {'x': self.wings_params[obj_name][side]['x_hinge'].copy(),
-                                         'y': self.wings_params[obj_name][side]['y_hinge'].copy(),
-                                         'z': self.wings_params[obj_name][side]['z_hinge'].copy()}
-
-            for side in ['right', 'left']:
-                for label in wing_params_labels:
-                    self.wings_params[obj_name][side][label] = np.nan * np.ones(frames.shape)
-
-            for side in ['right', 'left']:
-                a = np.ones(np.shape(frames)) * np.nan
-                b, c, d = a.copy(), a.copy(), a.copy()
-                for i, frame in enumerate(frames):
-                    x_coords = np.ones((len(self.wing_labels), 1)) * np.nan
-                    y_coords, z_coords = x_coords.copy(), x_coords.copy()
-                    for j, wing_label in enumerate(self.wing_labels):
-                        label = side + '_wing_' + wing_label
-
-                        x_coords[j] = self.skeleton3d[obj_name][label]['x'][i]
-                        y_coords[j] = self.skeleton3d[obj_name][label]['y'][i]
-                        z_coords[j] = self.skeleton3d[obj_name][label]['z'][i]
-
-                    if len(x_coords) < 3: continue
-                    params = minimize_perp_distance(x_coords, y_coords, z_coords)
-                    a[i], b[i], c[i], d[i] = params
-
-                    if i >= 2*dpt:
-                        eq = [a[i - 2*dpt] * x + b[i - 2*dpt] * y + c[i - 2*dpt] * z + d[i - 2*dpt],
-                              a[i - 1*dpt] * x + b[i - 1*dpt] * y + c[i - 1*dpt] * z + d[i - 1*dpt],
-                              a[i] * x + b[i] * y + c[i] * z + d[i]]
-                        sol = solve(eq, dict=True)
-
-                        if len(sol) > 0 and x in sol[0].keys() and y in sol[0].keys() and z in sol[0].keys():
-                            self.wings_params[obj_name][side]['x_hinge'][i] = sol[0][x]
-                            self.wings_params[obj_name][side]['y_hinge'][i] = sol[0][y]
-                            self.wings_params[obj_name][side]['z_hinge'][i] = sol[0][z]
-
-            for side in ['right', 'left']:
-                self.wings_params[obj_name][side] = \
-                    self.filter_interp_params(self.wings_params[obj_name][side], ['x_hinge', 'y_hinge', 'z_hinge'],
-                                              interpolate_nans, filter_high_freq)
-
-            if self.show_plot:
-                fig, axs = plt.subplots(1, 3)
-                for i, coord in enumerate(['x', 'y', 'z']):
-                    axs[i].clear()
-                    for side in ['right', 'left']:
-                        axs[i].scatter(frames, init_hinges[side][coord], marker='o', label='init_' + side)
-                        axs[i].scatter(frames, self.wings_params[obj_name][side][coord + '_hinge'], marker='+', label='est_' + side)
-                    axs[i].set_ylabel(coord + ' pos (m)')
-                    axs[i].set_xlabel('frames')
-                plt.legend()
-                plt.show()
-
-        return self.wings_params
-
-    def optim_fit_skeleton(self, animal_name, body_param_names, wing_param_names, res_method, opt_method, rec_paths, save_paths, angles_to_correct=[]):
-        body_params_init, wings_params_init = self.estimate_body_wings_params(interpolate_nans=True, filter_high_freq=True)
-        #wings_params_init = self.estimate_wing_hinges(interpolate_nans=True, filter_high_freq=True)
-        self.save_skeleton_params_in_csv(body_params_init, wings_params_init, save_paths[self.main_camn])
-
-        start = time.time()
-        body_params, nb_visible_pts_body, rmse_body, nb_iterations_body = \
-            self.optim_fit_all_hybrid_params(animal_name, body_params_init, wings_params_init, body_param_names, res_method,
-                                             opt_method, rec_paths, interpolate_nans=False, filter_high_freq=True)
-        print('>> optim hybrid done! (time elapsed: {0:.4f} s)'.format(time.time() - start))
-
-        # body_params, nb_visible_pts_body, rmse_body, nb_iterations_body = \
-        #     self.optim_fit_all_body_params(animal_name, body_params_init, body_param_names, res_method, opt_method, rec_paths,
-        #                                    interpolate_nans=False, filter_high_freq=True)
-        # #body_params = body_params_init
-        # print('>> optim body done! (time elapsed: {0:.4f} s)'.format(time.time() - start))
-
-        # self.unscramble_sides3d_dlc(body_params)
-        # self.save2d_dlc(save_paths)
-        # self.save3d_dlc(save_paths)
-
-        start = time.time()
-        wings_params, nb_visible_pts_wings, rmse_wings, nb_iterations_wings = \
-            self.optim_fit_all_wings_params(animal_name, body_params, wings_params_init, wing_param_names, res_method, opt_method,
-                                            rec_paths, interpolate_nans=False, filter_high_freq=True)
-        print('>> optim wings done! (time elapsed: {0:.4f} s)'.format(time.time() - start))
-
-        #body_params, wings_params = self.correct_body_wings_params(body_params, wings_params, angles_to_correct, rec_paths)
-
-        if self.show_plot:
-            for obj_name in body_params.keys():
-                frames = list(range(0, len(body_params[obj_name][body_param_names[0]])))
-
-                # Plot body params
-                fig1, axs1 = plt.subplots(len(body_param_names), 1)
-                for i, label in enumerate(body_param_names):
-                    axs1[i].plot(frames, body_params_init[obj_name][label], '-', label='init')
-                    axs1[i].plot(frames, body_params[obj_name][label], '-', label='optim')
-                    axs1[i].set_ylabel(label)
-                axs1[i].set_xlabel('frames')
-                fig1.suptitle('Body parameters in function of time')
-                fig1.legend()
-
-                # Plot wings params
-                fig2, axs2 = plt.subplots(len(wing_param_names), 1)
-                for i, label in enumerate(wing_param_names):
-                    for side in wings_params[obj_name].keys():
-                        axs2[i].plot(frames, wings_params_init[obj_name][side][label], '-', label='init_' + side)
-                        axs2[i].plot(frames, wings_params[obj_name][side][label], '-', label='optim_' + side)
-                    axs2[i].set_ylabel(label)
-                axs2[i].set_xlabel('frames')
-                fig2.suptitle('Wings parameters in function of time')
-                fig2.legend()
-
-            plt.show()
-
-        self.save_skeleton_params_in_csv(body_params, wings_params, save_paths[self.main_camn])
-
-        self.save_obj_metric_in_csv(nb_visible_pts_body, 'nb_visible_pts-body', save_paths[self.main_camn])
-        self.save_obj_metric_in_csv(nb_visible_pts_wings, 'nb_visible_pts-wings', save_paths[self.main_camn])
-        self.save_obj_metric_in_csv(nb_iterations_body, 'nb_iterations-body', save_paths[self.main_camn])
-        self.save_obj_metric_in_csv(nb_iterations_wings, 'nb_iterations-wings', save_paths[self.main_camn])
-        self.save_obj_metric_in_csv(rmse_body, 'rmse-body', save_paths[self.main_camn])
-        self.save_obj_metric_in_csv(rmse_wings, 'rmse-wings', save_paths[self.main_camn])
-
-    def optim_fit_all_hybrid_params(self, animal_name, body_params_init, wings_params_init, param_names, res_method, opt_method, rec_paths,
-                                  interpolate_nans=False, filter_high_freq=False, first_optim_wings=False):
-
-        hybrid_params_init = copy.deepcopy(body_params_init)
-        if '2d' in res_method: self.hybrid_skeleton2d = copy.deepcopy(self.skeleton2d)
-        else: self.hybrid_skeleton3d = copy.deepcopy(self.skeleton3d)
-
-        if first_optim_wings:
-            start = time.time()
-            # First optim on wing to estimate wing tips and hinges positions + deviation_a
-            wing_param_names = ['stroke_a', 'deviation_a', 'rotation_a', 'x_hinge', 'y_hinge', 'z_hinge']
-            wings_params, _, _ = \
-                self.optim_fit_all_wings_params(body_params_init, wings_params_init, wing_param_names, res_method,
-                                                opt_method, rec_paths, interpolate_nans=False, filter_high_freq=True)
-
-            obj_names = self.skeleton2d['obj_names'] if '2d' in res_method else self.skeleton3d['obj_names']
-            for obj_name in obj_names:
-
-                frames = self.skeleton2d[obj_name]['frames'] if '2d' in res_method else self.skeleton3d[obj_name]['frames']
-                for i, frame in enumerate(frames):
-
-                    wings_params_frame = {'right': {}, 'left': {}}
-                    for side in self.wings_skeleton3d_init.keys():
-                        for label in wings_params[obj_name][side].keys():
-                            wings_params_frame[side][label] = wings_params[obj_name][side][label][i]
-
-                    wings_skeleton3d_new = copy.deepcopy(self.wings_skeleton3d_init)
-                    wings_skeleton2d_new = {'right': {}, 'left': {}}
-                    for side in self.wings_skeleton3d_init.keys():
-                        wings_skeleton3d_new[side] = build_wing_skeleton3d(wings_skeleton3d_new[side], wings_params_frame[side])
-                        wings_skeleton3d_new[side] = \
-                            rotate_and_translate_wing_skeleton3d(wings_skeleton3d_new[side], wings_params_frame[side], side)
-
-                        if '2d' in res_method:
-                            wings_skeleton2d_new[side] = reproject_skeleton3d_to2d(wings_skeleton3d_new[side], self.dlt_coefs)
-
-                        for label in ['tip', 'hinge']:
-                            wing_label = side + '_wing_' + label
-
-                            if '2d' in res_method:
-                                for camn in range(1, self.nb_cam + 1):
-                                    self.hybrid_skeleton2d[obj_name][wing_label][camn]['x'][i] = wings_skeleton2d_new[side][label][camn][0]
-                                    self.hybrid_skeleton2d[obj_name][wing_label][camn]['y'][i] = wings_skeleton2d_new[side][label][camn][1]
-                                    self.hybrid_skeleton2d[obj_name][wing_label][camn]['likelihood'][i] = 1.0
-
-                            else:
-                                self.hybrid_skeleton3d[obj_name][wing_label]['x'][i] = wings_skeleton3d_new[side][label][0]
-                                self.hybrid_skeleton3d[obj_name][wing_label]['y'][i] = wings_skeleton3d_new[side][label][1]
-                                self.hybrid_skeleton3d[obj_name][wing_label]['z'][i] = wings_skeleton3d_new[side][label][2]
-
-            print('>> first optim wing done! (time elapsed: {0:.4f} s)'.format(time.time() - start))
-
-        res_method = 'hybrid_' + res_method
-
-        hybrid_param_ests, nb_visible_pts, rmse, nb_iterations = {}, {}, {}, {}
-        obj_names = self.hybrid_skeleton2d['obj_names'] if '2d' in res_method else self.hybrid_skeleton3d['obj_names']
-        for obj_name in obj_names:
-            frames = self.hybrid_skeleton2d[obj_name]['frames'] if '2d' in res_method else self.hybrid_skeleton3d[obj_name]['frames']
-            # frames = [frame for i, frame in enumerate(frames) if i % 100 == 0.0]
-
-            # Low pass filter the position of wing tips and hinges + deviation_a and span
-            for param_name in ['deviation_a', 'span']:
-                hybrid_params_init[obj_name]['wbaverage_' + param_name] = \
-                    np.mean([np.transpose(filter_interp(wings_params_init[obj_name]['right'][param_name],
-                                                        self.framerate, self.cutoff_frequency)),
-                             np.transpose(filter_interp(wings_params_init[obj_name]['left'][param_name],
-                                                        self.framerate, self.cutoff_frequency))], axis=0)
-
-            for side in self.wings_params_init.keys():
-                for label in ['tip', 'hinge']:
-                    wing_label = side +'_wing_' + label
-                    if '2d' in res_method:
-                        for camn in range(1, self.nb_cam + 1):
-                            self.hybrid_skeleton2d[obj_name][wing_label][camn]['x'] = \
-                                filter_interp(self.hybrid_skeleton2d[obj_name][wing_label][camn]['x'],
-                                                   self.framerate, self.cutoff_frequency)
-                            self.hybrid_skeleton2d[obj_name][wing_label][camn]['y'] = \
-                                filter_interp(self.hybrid_skeleton2d[obj_name][wing_label][camn]['y'],
-                                                   self.framerate, self.cutoff_frequency)
-                            self.hybrid_skeleton2d[obj_name][wing_label][camn]['likelihood'] = \
-                                np.ones(np.size(self.hybrid_skeleton2d[obj_name][wing_label][camn]['x'],
-                                                self.framerate, self.cutoff_frequency))
-
-                    else:
-                        self.hybrid_skeleton3d[obj_name][wing_label]['x'] = \
-                            filter_interp(self.hybrid_skeleton3d[obj_name][wing_label]['x'],
-                                               self.framerate, self.cutoff_frequency)
-                        self.hybrid_skeleton3d[obj_name][wing_label]['y'] = \
-                            filter_interp(self.hybrid_skeleton3d[obj_name][wing_label]['y'],
-                                               self.framerate, self.cutoff_frequency)
-                        self.hybrid_skeleton3d[obj_name][wing_label]['z'] = \
-                            filter_interp(self.hybrid_skeleton3d[obj_name][wing_label]['z'],
-                                               self.framerate, self.cutoff_frequency)
-
-            shape_frame = np.array(frames).shape
-            nb_iterations[obj_name] = np.zeros(shape_frame)
-            nb_visible_pts[obj_name], rmse[obj_name] = np.ones(shape_frame) * np.nan, np.ones(shape_frame) * np.nan
-            if obj_name in hybrid_params_init.keys():
-                hybrid_param_ests[obj_name] = copy.deepcopy(hybrid_params_init[obj_name])
-            else:
-                hybrid_param_ests[obj_name] = {}
-                for label in self.hybrid_params_init.keys():
-                    hybrid_param_ests[obj_name][label] = np.ones(shape_frame) * self.hybrid_params_init[label]
-
-            if self.show_plot and not self.multiprocessing:
-                fig, axs = plt.subplots(1, self.nb_cam)
-                all_images = {}
-                for camn in range (1, self.nb_cam +1):
-                    all_files = sorted(os.listdir(rec_paths[camn]), key=lambda x: x[20:20 + self.nb_leading_zero])
-                    all_images[camn] = [s for s in all_files if '.{0}'.format(self.image_format) in s]
-
-            if self.multiprocessing: args = [(frame,) for frame in frames]
-
-            #for i, frame in enumerate(tqdm.tqdm(frames)):  # to print progress bar
-            for i, frame in enumerate(frames):
-                if obj_name in hybrid_params_init.keys():
-                    hybrid_params_init_frame = {}
-                    for label in self.hybrid_params_init.keys():
-                        hybrid_params_init_frame[label] = hybrid_params_init[obj_name][label][i]
-                else:
-                    hybrid_params_init_frame = self.hybrid_params_init
-
-                nb_points = len(self.hybrid_skeleton3d_init.keys())
-                nb_visible_pts[obj_name][i] = nb_points
-                if '3d' in res_method:
-                    hybrid_skeleton3d = {}
-                    for hybrid_label in self.hybrid_skeleton3d_init.keys():
-                        hybrid_skeleton3d[hybrid_label] = \
-                            np.array([self.hybrid_skeleton3d[obj_name][hybrid_label]['x'][i],
-                                      self.hybrid_skeleton3d[obj_name][hybrid_label]['y'][i],
-                                      self.hybrid_skeleton3d[obj_name][hybrid_label]['z'][i]])
-
-                        if np.isnan(hybrid_skeleton3d[hybrid_label]).any(): nb_visible_pts[obj_name][i] -= 1
-
-                elif '2d' in res_method:
-                    hybrid_skeleton2d = {}
-                    for hybrid_label in self.hybrid_skeleton3d_init.keys():
-                        hybrid_skeleton2d[hybrid_label] = {1: np.array([])}
-                        for camn in range(1, self.nb_cam + 1):
-
-                            if self.hybrid_skeleton2d[obj_name][hybrid_label][camn]['likelihood'][i] > self.threshold_likelihood:
-                                hybrid_skeleton2d[hybrid_label][camn] = \
-                                    np.array([self.hybrid_skeleton2d[obj_name][hybrid_label][camn]['x'][i],
-                                              self.hybrid_skeleton2d[obj_name][hybrid_label][camn]['y'][i],
-                                              self.hybrid_skeleton2d[obj_name][hybrid_label][camn]['likelihood'][i]])
-                            else:
-                                hybrid_skeleton2d[hybrid_label][camn] = \
-                                    np.array([np.nan, np.nan, self.hybrid_skeleton2d[obj_name][hybrid_label][camn]['likelihood'][i]])
-
-                                nb_visible_pts[obj_name][i] -= 1 / self.nb_cam
-
-                return_nan = nb_visible_pts[obj_name][i] <= self.threshold_nb_pts_body - 2 # to compensate for the extra wing tips
-
-                if not self.multiprocessing:
-                    if '3d' in res_method:
-                        hybrid_param_ests_frame, rmse[obj_name][i], nb_iterations[obj_name][i] = \
-                            optim_fit_body_params(animal_name, self.hybrid_skeleton3d_init, hybrid_skeleton3d, hybrid_params_init_frame,
-                                                  param_names, res_method, opt_method, self.hybrid_bounds_init, return_nan)
-
-                    elif '2d' in res_method:
-                        hybrid_param_ests_frame, rmse[obj_name][i], nb_iterations[obj_name][i] = \
-                            optim_fit_body_params(animal_name, self.hybrid_skeleton3d_init, hybrid_skeleton2d, hybrid_params_init_frame,
-                                                  param_names, res_method, opt_method, self.hybrid_bounds_init, return_nan, dlt_coefs=self.dlt_coefs)
-
-                    for label in hybrid_param_ests_frame.keys():
-                        hybrid_param_ests[obj_name][label][i] = hybrid_param_ests_frame[label]
-
-                    if self.show_plot and (frame % 20 == 0.0 or frame == 1):
-                        hybrid_skeleton3d_init = copy.deepcopy(self.hybrid_skeleton3d_init)
-                        hybrid_skeleton3d_init = build_hybrid_skeleton3d(hybrid_skeleton3d_init, hybrid_params_init_frame)
-                        hybrid_skeleton3d_init = rotate_body_skeleton3d(hybrid_skeleton3d_init, hybrid_params_init_frame)
-                        hybrid_skeleton3d_init = translate_body_skeleton3d(hybrid_skeleton3d_init, hybrid_params_init_frame)
-                        hybrid_skeleton2d_init = reproject_skeleton3d_to2d(hybrid_skeleton3d_init, self.dlt_coefs)
-
-                        hybrid_skeleton3d_new = copy.deepcopy(self.hybrid_skeleton3d_init)
-                        hybrid_skeleton3d_new = build_hybrid_skeleton3d(hybrid_skeleton3d_new, hybrid_param_ests_frame)
-                        hybrid_skeleton3d_new = rotate_body_skeleton3d(hybrid_skeleton3d_new, hybrid_param_ests_frame)
-                        hybrid_skeleton3d_new = translate_body_skeleton3d(hybrid_skeleton3d_new, hybrid_param_ests_frame)
-                        hybrid_skeleton2d_new = reproject_skeleton3d_to2d(hybrid_skeleton3d_new, self.dlt_coefs)
-
-                        # Compute reprojection of 3d reconstruction from dlc 2d points
-                        hybrid_skeleton3d_repro2d = {}
-                        for label in self.hybrid_skeleton3d_init.keys():
-                            hybrid_skeleton3d_repro2d[label] = {1: []}
-                            for j, camn in enumerate(range(1, self.nb_cam + 1)):
-                                uv = calib.find2d(1, self.dlt_coefs[j], np.array([[self.hybrid_skeleton3d[obj_name][label]['x'][i],
-                                                                                 self.hybrid_skeleton3d[obj_name][label]['y'][i], self.hybrid_skeleton3d[obj_name][label]['z'][i]]]))
-                                hybrid_skeleton3d_repro2d[label][camn] = np.array([uv[0][0], uv[0][1], 1.0])
-
-                        if '3d' in res_method:
-                            hybrid_skeleton2d = reproject_skeleton3d_to2d(hybrid_skeleton3d, self.dlt_coefs)
-
-                        # Plot 2d repojection of hybrid skeleton
-                        for j, camn in enumerate(range(1, self.nb_cam +1)):
-                            center_of_mass_xy = \
-                                (hybrid_skeleton2d_new['torso_abdomen_joint'][camn] * hybrid_param_ests_frame['ratio_com_torso'] \
-                                 + hybrid_skeleton2d_new['head_torso_joint'][camn] * (1 - hybrid_param_ests_frame['ratio_com_torso']))
-
-                            if np.isnan(center_of_mass_xy).any(): continue
-
-                            axs[j].clear()
-                            axs[j].imshow(Image.open(os.path.join(rec_paths[camn], all_images[camn][i])))
-                            for label in hybrid_skeleton2d_new.keys():
-                                if hybrid_skeleton2d[label][camn][2] > self.threshold_likelihood:
-                                    axs[j].scatter(hybrid_skeleton2d[label][camn][0],
-                                                   hybrid_skeleton2d[label][camn][1], marker='.', color='k')
-                                    axs[j].scatter(hybrid_skeleton3d_repro2d[label][camn][0],
-                                                   hybrid_skeleton3d_repro2d[label][camn][1], marker='o', facecolors='none', edgecolors='k')
-                                axs[j].scatter(hybrid_skeleton2d_init[label][camn][0],
-                                               hybrid_skeleton2d_init[label][camn][1], marker='o', facecolors='none', edgecolors='b')
-                                axs[j].scatter(hybrid_skeleton2d_new[label][camn][0],
-                                               hybrid_skeleton2d_new[label][camn][1], marker='+', color='r')
-
-                            axs[j].set_xlim((center_of_mass_xy[0] - 100, center_of_mass_xy[0] + 100))
-                            axs[j].set_ylim((center_of_mass_xy[1] + 100, center_of_mass_xy[1] - 100))
-
-                        plt.pause(0.05)
-                        # plt.show()
-
-                        # param_to_print = ['pitch_a', 'roll_a', 'yaw_a', 'ratio_hybrid']
-                        # for label in param_to_print:
-                        #     print('{0}: init: {1} and est: {2}'.format(label, hybrid_params_init_frame[label], hybrid_param_ests_frame[label]))
-
-                else:
-                    if '3d' in res_method:
-                        args[i] = (self.hybrid_skeleton3d_init, hybrid_skeleton3d, hybrid_params_init_frame, param_names,
-                                    res_method, opt_method, self.hybrid_bounds_init, return_nan,)
-
-                    elif '2d' in res_method:
-                        args[i] = (self.hybrid_skeleton3d_init, hybrid_skeleton2d, hybrid_params_init_frame, param_names,
-                                    res_method, opt_method, self.hybrid_bounds_init, return_nan,)
-
-            if self.multiprocessing:
-                pool_hybrid = Pool(self.nb_process)
-                if '3d' in res_method:
-                    results = pool_hybrid.starmap(optim_fit_body_params, args)
-
-                elif '2d' in res_method:
-                    results = pool_hybrid.starmap(partial(optim_fit_body_params, dlt_coefs=self.dlt_coefs), args)
-
-                pool_hybrid.close()
-                for i, frame in enumerate(frames):
-                    hybrid_param_ests_frame, rmse[obj_name][i], nb_iterations[obj_name][i] = results[i]
-                    for label in hybrid_param_ests_frame.keys():
-                        hybrid_param_ests[obj_name][label][i] = hybrid_param_ests_frame[label]
-
-            hybrid_param_ests[obj_name] = self.filter_interp_params(hybrid_param_ests[obj_name], param_names,
-                                                                    interpolate_nans, filter_high_freq)
-
-        return hybrid_param_ests, nb_visible_pts, rmse, nb_iterations
-
-    def optim_fit_all_body_params(self, animal_name, body_params_init, param_names, res_method, opt_method, rec_paths,
-                                  interpolate_nans=False, filter_high_freq=False):
-
-        res_method = 'body_' + res_method
-
-        body_param_ests, nb_visible_pts, rmse, nb_iterations = {}, {}, {}, {}
-        obj_names = self.skeleton2d['obj_names'] if '2d' in res_method else self.skeleton3d['obj_names']
-        for obj_name in obj_names:
-            frames = self.skeleton2d[obj_name]['frames'] if '2d' in res_method else self.skeleton3d[obj_name]['frames']
-            # frames = [frame for i, frame in enumerate(frames) if i % 100 == 0.0]
-
-            shape_frame = np.array(frames).shape
-            nb_iterations[obj_name] = np.zeros(shape_frame)
-            rmse[obj_name], nb_visible_pts[obj_name] = np.ones(shape_frame) * np.nan, np.ones(shape_frame) * np.nan
-            if obj_name in body_params_init.keys():
-                body_param_ests[obj_name] = copy.deepcopy(body_params_init[obj_name])
-            else:
-                body_param_ests[obj_name] = {}
-                for label in self.body_params_init.keys():
-                    body_param_ests[obj_name][label] = np.ones(shape_frame) * self.body_params_init[label]
-
-            if self.show_plot and not self.multiprocessing:
-                fig, axs = plt.subplots(1, self.nb_cam)
-                all_images = {}
-                for camn in range (1, self.nb_cam +1):
-                    all_files = sorted(os.listdir(rec_paths[camn]), key=lambda x: x[20:20 + self.nb_leading_zero])
-                    all_images[camn] = [s for s in all_files if '.{0}'.format(self.image_format) in s]
-
-            if self.multiprocessing: args = [(frame,) for frame in frames]
-
-            #for i, frame in enumerate(tqdm.tqdm(frames)):  # to print progress bar
-            for i, frame in enumerate(frames):
-                if obj_name in body_params_init.keys():
-                    body_params_init_frame = {}
-                    for label in self.body_params_init.keys():
-                        body_params_init_frame[label] = body_params_init[obj_name][label][i]
-                else:
-                    body_params_init_frame = self.body_params_init
-
-                nb_points = len(self.body_skeleton3d_init.keys())
-                nb_visible_pts[obj_name][i] = nb_points
-                if '3d' in res_method:
-                    body_skeleton3d = {}
-                    for body_label in self.body_skeleton3d_init.keys():
-                        body_skeleton3d[body_label] = \
-                            np.array([self.skeleton3d[obj_name][body_label]['x'][i],
-                                      self.skeleton3d[obj_name][body_label]['y'][i],
-                                      self.skeleton3d[obj_name][body_label]['z'][i]])
-
-                        if np.isnan(body_skeleton3d[body_label]).any(): nb_visible_pts[obj_name][i] -= 1
-
-                elif '2d' in res_method:
-                    body_skeleton2d = {}
-                    for body_label in self.body_skeleton3d_init.keys():
-                        body_skeleton2d[body_label] = {1: np.array([])}
-                        for camn in range(1, self.nb_cam + 1):
-
-                            if self.skeleton2d[obj_name][body_label][camn]['likelihood'][i] > self.threshold_likelihood:
-                                body_skeleton2d[body_label][camn] = \
-                                    np.array([self.skeleton2d[obj_name][body_label][camn]['x'][i],
-                                              self.skeleton2d[obj_name][body_label][camn]['y'][i],
-                                              self.skeleton2d[obj_name][body_label][camn]['likelihood'][i]])
-                            else:
-                                body_skeleton2d[body_label][camn] = \
-                                    np.array([np.nan, np.nan, self.skeleton2d[obj_name][body_label][camn]['likelihood'][i]])
-
-                                nb_visible_pts[obj_name][i] -= 1 / self.nb_cam
-
-                return_nan = nb_visible_pts[obj_name][i] <= self.threshold_nb_pts_body
-
-                if not self.multiprocessing:
-                    if '3d' in res_method:
-                        body_param_ests_frame, rmse[obj_name][i], nb_iterations[obj_name][i] = \
-                            optim_fit_body_params(animal_name, self.body_skeleton3d_init, body_skeleton3d, body_params_init_frame,
-                                                  param_names, res_method, opt_method, self.body_bounds_init, return_nan)
-
-                    elif '2d' in res_method:
-                        body_param_ests_frame, rmse[obj_name][i], nb_iterations[obj_name][i] = \
-                            optim_fit_body_params(animal_name, self.body_skeleton3d_init, body_skeleton2d, body_params_init_frame,
-                                                  param_names, res_method, opt_method, self.body_bounds_init, return_nan, dlt_coefs=self.dlt_coefs)
-
-                    for label in body_param_ests_frame.keys():
-                        body_param_ests[obj_name][label][i] = body_param_ests_frame[label]
-
-                    if self.show_plot and (frame % 20 == 0.0 or frame == 1):
-                        body_skeleton3d_init = copy.deepcopy(self.body_skeleton3d_init)
-                        body_skeleton3d_init = build_body_skeleton3d(body_skeleton3d_init, body_params_init_frame)
-                        body_skeleton3d_init = rotate_body_skeleton3d(body_skeleton3d_init, body_params_init_frame)
-                        body_skeleton3d_init = translate_body_skeleton3d(body_skeleton3d_init, body_params_init_frame)
-                        body_skeleton2d_init = reproject_skeleton3d_to2d(body_skeleton3d_init, self.dlt_coefs)
-
-                        body_skeleton3d_new = copy.deepcopy(self.body_skeleton3d_init)
-                        body_skeleton3d_new = build_body_skeleton3d(body_skeleton3d_new, body_param_ests_frame)
-                        body_skeleton3d_new = rotate_body_skeleton3d(body_skeleton3d_new, body_param_ests_frame)
-                        body_skeleton3d_new = translate_body_skeleton3d(body_skeleton3d_new, body_param_ests_frame)
-                        body_skeleton2d_new = reproject_skeleton3d_to2d(body_skeleton3d_new, self.dlt_coefs)
-
-                        # Compute reprojection of 3d reconstruction from dlc 2d points
-                        body_skeleton3d_repro2d = {}
-                        for label in self.body_skeleton3d_init.keys():
-                            body_skeleton3d_repro2d[label] = {1: []}
-                            for j, camn in enumerate(range(1, self.nb_cam + 1)):
-                                uv = calib.find2d(1, self.dlt_coefs[j], np.array([[self.skeleton3d[obj_name][label]['x'][i],
-                                                                                 self.skeleton3d[obj_name][label]['y'][i], self.skeleton3d[obj_name][label]['z'][i]]]))
-                                body_skeleton3d_repro2d[label][camn] = np.array([uv[0][0], uv[0][1], 1.0])
-
-                        if '3d' in res_method:
-                            body_skeleton2d = reproject_skeleton3d_to2d(body_skeleton3d, self.dlt_coefs)
-
-                        # Plot 2d repojection of body skeleton
-                        for j, camn in enumerate(range(1, self.nb_cam +1)):
-                            center_of_mass_xy = \
-                                (body_skeleton2d_new['torso_abdomen_joint'][camn] * body_param_ests_frame['ratio_com_torso'] \
-                                 + body_skeleton2d_new['head_torso_joint'][camn] * (1 - body_param_ests_frame['ratio_com_torso']))
-
-                            if np.isnan(center_of_mass_xy).any(): continue
-
-                            axs[j].clear()
-                            axs[j].imshow(Image.open(os.path.join(rec_paths[camn], all_images[camn][i])))
-                            for label in body_skeleton2d_new.keys():
-                                if body_skeleton2d[label][camn][2] > self.threshold_likelihood:
-                                    axs[j].scatter(body_skeleton2d[label][camn][0],
-                                                   body_skeleton2d[label][camn][1], marker='.', color='k')
-                                    axs[j].scatter(body_skeleton3d_repro2d[label][camn][0],
-                                                   body_skeleton3d_repro2d[label][camn][1], marker='o', facecolors='none', edgecolors='k')
-                                axs[j].scatter(body_skeleton2d_init[label][camn][0],
-                                               body_skeleton2d_init[label][camn][1], marker='o', facecolors='none', edgecolors='b')
-                                axs[j].scatter(body_skeleton2d_new[label][camn][0],
-                                               body_skeleton2d_new[label][camn][1], marker='+', color='r')
-
-                            axs[j].set_xlim((center_of_mass_xy[0] - 100, center_of_mass_xy[0] + 100))
-                            axs[j].set_ylim((center_of_mass_xy[1] + 100, center_of_mass_xy[1] - 100))
-
-                        plt.pause(0.05)
-                        # plt.show()
-
-                        # param_to_print = ['pitch_a', 'roll_a', 'yaw_a', 'ratio_body']
-                        # for label in param_to_print:
-                        #     print('{0}: init: {1} and est: {2}'.format(label, body_params_init_frame[label], body_param_ests_frame[label]))
-
-                else:
-                    if '3d' in res_method:
-                        args[i] = (self.body_skeleton3d_init, body_skeleton3d, body_params_init_frame, param_names,
-                                    res_method, opt_method, self.body_bounds_init, return_nan,)
-
-                    elif '2d' in res_method:
-                        args[i] = (self.body_skeleton3d_init, body_skeleton2d, body_params_init_frame, param_names,
-                                    res_method, opt_method, self.body_bounds_init, return_nan,)
-
-            if self.multiprocessing:
-                pool_body = Pool(self.nb_process)
-                if '3d' in res_method:
-                    results = pool_body.starmap(optim_fit_body_params, args)
-
-                elif '2d' in res_method:
-                    results = pool_body.starmap(partial(optim_fit_body_params, dlt_coefs=self.dlt_coefs), args)
-
-                pool_body.close()
-                for i, frame in enumerate(frames):
-                    body_param_ests_frame, rmse[obj_name][i], nb_iterations[obj_name][i] = results[i]
-                    for label in body_param_ests_frame.keys():
-                        body_param_ests[obj_name][label][i] = body_param_ests_frame[label]
-
-            body_param_ests[obj_name] = self.filter_interp_params(body_param_ests[obj_name], param_names,
-                                                                  interpolate_nans, filter_high_freq)
-
-        return body_param_ests, nb_visible_pts, rmse, nb_iterations
-
-    def optim_fit_all_wings_params(self, animal_name, body_params_init, wings_params_init, param_names, res_method, opt_method,
-                                   rec_paths, interpolate_nans=False, filter_high_freq=False):
-
-        res_method = 'wing_' + res_method
-
-        obj_names = self.skeleton2d['obj_names'] if '2d' in res_method else self.skeleton3d['obj_names']
-        for obj_name in obj_names:
-            if obj_name in wings_params_init.keys():
-                for side in self.wings_params_init.keys():
-                    for label in body_params_init[obj_name].keys():
-                        wings_params_init[obj_name][side][label] = body_params_init[obj_name][label]
-
-        wings_params_ests, nb_visible_pts, rmse, nb_iterations = copy.deepcopy(wings_params_init), {}, {}, {}
-        for obj_name in obj_names:
-            frames = self.skeleton2d[obj_name]['frames'] if '2d' in res_method else self.skeleton3d[obj_name]['frames']
-            # frames = [frame for i, frame in enumerate(frames) if i % 100 == 0.0]
-
-            if self.show_plot and not self.multiprocessing:
-                fig, axs = plt.subplots(1, self.nb_cam)
-                all_images = {}
-                for camn in range(1, self.nb_cam + 1):
-                    all_files = sorted(os.listdir(rec_paths[camn]), key=lambda x: x[20:20 + self.nb_leading_zero])
-                    all_images[camn] = [s for s in all_files if '.{0}'.format(self.image_format) in s]
-
-            if self.multiprocessing: args = [(frame,) for frame in frames]
-
-            shape_frame = np.array(frames).shape
-            rmse[obj_name] = {'right': np.ones(shape_frame) * np.nan, 'left': np.ones(shape_frame) * np.nan}
-            nb_iterations[obj_name] = {'right': np.zeros(shape_frame), 'left': np.zeros(shape_frame)}
-            nb_visible_pts[obj_name] = {'right': np.ones(shape_frame) * np.nan, 'left': np.ones(shape_frame) * np.nan}
-            #for i, frame in enumerate(tqdm.tqdm(frames)):   # to print progress bar
-            for i, frame in enumerate(frames):
-                if obj_name in wings_params_init.keys():
-                    wings_params_init_frame = {}
-                    for side in self.wings_params_init.keys():
-                        wings_params_init_frame[side] = {}
-                        for label in self.wings_params_init[side].keys():
-                            wings_params_init_frame[side][label] = wings_params_init[obj_name][side][label][i]
-                else:
-                    wings_params_init_frame = self.wings_params_init
-
-                nb_points = len(self.body_skeleton3d_init.keys())
-                for side in self.wings_skeleton3d_init.keys():
-                    nb_visible_pts[obj_name][side][i] = nb_points
-
-                if '3d' in res_method:
-                    wings_skeleton3d = {'right': {}, 'left': {}}
-                    for side in self.wings_skeleton3d_init.keys():
-                        for wing_label in self.wings_skeleton3d_init[side].keys():
-                            label = side + '_wing_' + wing_label
-                            wings_skeleton3d[side][wing_label] = \
-                                np.array([self.skeleton3d[obj_name][label]['x'][i],
-                                          self.skeleton3d[obj_name][label]['y'][i],
-                                          self.skeleton3d[obj_name][label]['z'][i]])
-
-                            if np.isnan(wings_skeleton3d[side][wing_label]).any():
-                                nb_visible_pts[obj_name][side][i] -= 1
-
-                elif '2d' in res_method:
-                    wings_skeleton2d = {'right': {}, 'left': {}}
-                    for side in self.wings_skeleton3d_init.keys():
-                        for wing_label in self.wings_skeleton3d_init[side].keys():
-
-                            wings_skeleton2d[side][wing_label] = {1: []}
-                            for camn in range(1, self.nb_cam + 1):
-                                label = side + '_wing_' + wing_label
-
-                                if self.skeleton2d[obj_name][label][camn]['likelihood'][i] > self.threshold_likelihood:
-                                    wings_skeleton2d[side][wing_label][camn] = \
-                                        np.array([self.skeleton2d[obj_name][label][camn]['x'][i],
-                                                  self.skeleton2d[obj_name][label][camn]['y'][i],
-                                                  self.skeleton2d[obj_name][label][camn]['likelihood'][i]])
-                                else:
-                                    wings_skeleton2d[side][wing_label][camn] = \
-                                        np.array([np.nan, np.nan, self.skeleton2d[obj_name][label][camn]['likelihood'][i]])
-
-                                    nb_visible_pts[obj_name][side][i] -= 1 / self.nb_cam
-
-                body_params_frame = {}
-                for label in body_params_init[obj_name].keys():
-                    body_params_frame[label] = body_params_init[obj_name][label][i]
-
-                body_skeleton3d = copy.deepcopy(self.body_skeleton3d_init)
-                body_skeleton3d = build_body_skeleton3d(body_skeleton3d, body_params_frame)
-                body_skeleton3d = rotate_body_skeleton3d(body_skeleton3d, body_params_frame)
-                body_skeleton3d = translate_body_skeleton3d(body_skeleton3d, body_params_frame)
-
-                return_nan = [False, False]
-                for j, side in enumerate(self.wings_skeleton3d_init.keys()):
-                    [wings_params_init_frame[side]['x_hinge'], wings_params_init_frame[side]['y_hinge'],
-                     wings_params_init_frame[side]['z_hinge']] = body_skeleton3d['{0}_wing_hinge'.format(side)]
-
-                    return_nan[j] = nb_visible_pts[obj_name][side][i] <= self.threshold_nb_pts_wing
-                    if return_nan[j]:
-                        for label in param_names:
-                            wings_params_ests[obj_name][side][label][i] = np.nan
-                        continue
-
-                    for label in ['yaw_a', 'pitch_a', 'roll_a', 'x_com', 'y_com', 'z_com']:
-                        wings_params_init_frame[side][label] = body_params_frame[label]
-
-                if not self.multiprocessing:
-                    if 'geo' in res_method:
-                        if '3d' in res_method:
-                            wings_params_est_frame, rmse_frame, nb_iterations_frame = \
-                                optim_fit_limbs_params(animal_name, self.wings_geometry3d_init, wings_skeleton3d, wings_params_init_frame,
-                                                       param_names, res_method, opt_method, self.wing_bounds_init, return_nan)
-
-                        elif '2d' in res_method:
-                            wings_params_est_frame, rmse_frame, nb_iterations_frame = \
-                                optim_fit_limbs_params(animal_name, self.wings_geometry3d_init, wings_skeleton2d, wings_params_init_frame,
-                                                       param_names, res_method, opt_method, self.wing_bounds_init, return_nan,
-                                                       dlt_coefs=self.dlt_coefs)
-                    else:
-                        if '3d' in res_method:
-                            wings_params_est_frame, rmse_frame, nb_iterations_frame = \
-                                optim_fit_limbs_params(animal_name, self.wings_skeleton3d_init, wings_skeleton3d, wings_params_init_frame,
-                                                       param_names, res_method, opt_method, self.wing_bounds_init, return_nan)
-
-                        elif '2d' in res_method:
-                            wings_params_est_frame, rmse_frame, nb_iterations_frame = \
-                                optim_fit_limbs_params(animal_name, self.wings_skeleton3d_init, wings_skeleton2d, wings_params_init_frame,
-                                                       param_names, res_method, opt_method, self.wing_bounds_init, return_nan,
-                                                       dlt_coefs=self.dlt_coefs)
-
-                    for side in self.wings_skeleton3d_init.keys():
-                        rmse[obj_name][side][i] = rmse_frame[side]
-                        nb_iterations[obj_name][side][i] = nb_iterations_frame[side]
-                        for label in wings_params_est_frame[side].keys():
-                            wings_params_ests[obj_name][side][label][i] = wings_params_est_frame[side][label]
-
-                    if self.show_plot and frame % 20 == 0.0:
-                        body_skeleton2d_new = reproject_skeleton3d_to2d(body_skeleton3d, self.dlt_coefs)
-
-                        wings_skeleton3d_new = copy.deepcopy(self.wings_skeleton3d_init)
-                        wings_skeleton3d_init = copy.deepcopy(self.wings_skeleton3d_init)
-                        wings_skeleton2d_new, wings_skeleton2d_init = {'right': {}, 'left': {}}, {'right': {}, 'left': {}}
-                        wings_skeleton3d_repro2d = {'right': {}, 'left': {}}
-                        if '3d' in res_method: wings_skeleton2d = copy.deepcopy(wings_skeleton2d_new)
-                        for side in self.wings_skeleton3d_init.keys():
-                            wings_skeleton3d_init[side] = build_wing_skeleton3d(wings_skeleton3d_init[side], wings_params_init_frame[side])
-                            wings_skeleton3d_init[side] = rotate_and_translate_wing_skeleton3d(wings_skeleton3d_init[side], wings_params_init_frame[side], side)
-                            wings_skeleton2d_init[side] = reproject_skeleton3d_to2d(wings_skeleton3d_init[side], self.dlt_coefs)
-
-                            wings_skeleton3d_new[side] = build_wing_skeleton3d(wings_skeleton3d_new[side], wings_params_est_frame[side])
-                            wings_skeleton3d_new[side] = rotate_and_translate_wing_skeleton3d(wings_skeleton3d_new[side], wings_params_est_frame[side], side)
-                            wings_skeleton2d_new[side] = reproject_skeleton3d_to2d(wings_skeleton3d_new[side], self.dlt_coefs)
-
-                            # Compute reprojection of 3d reconstruction from dlc 2d points
-                            for label in self.wings_skeleton3d_init[side].keys():
-                                wings_skeleton3d_repro2d[side][label] = {1: []}
-                                for j, camn in enumerate(range(1, self.nb_cam + 1)):
-                                    label2 = side + '_wing_' + label
-                                    uv = calib.find2d(1, self.dlt_coefs[j], np.array([[self.skeleton3d[obj_name][label2]['x'][i],
-                                                                                     self.skeleton3d[obj_name][label2]['y'][i], self.skeleton3d[obj_name][label2]['z'][i]]]))
-                                    wings_skeleton3d_repro2d[side][label][camn] = np.array([uv[0][0], uv[0][1], 1.0])
-
-                            if '3d' in res_method:
-                                wings_skeleton2d[side] = reproject_skeleton3d_to2d(wings_skeleton3d[side], self.dlt_coefs)
-
-                        for j, camn in enumerate(range(1, self.nb_cam + 1)):
-                            center_of_mass_xy = \
-                                (body_skeleton2d_new['torso_abdomen_joint'][camn] * body_params_frame['ratio_com_torso'] \
-                                 + body_skeleton2d_new['head_torso_joint'][camn] * (1 - body_params_frame['ratio_com_torso']))
-
-                            if np.isnan(center_of_mass_xy).any(): continue
-
-                            axs[j].clear()
-                            axs[j].imshow(Image.open(os.path.join(rec_paths[camn], all_images[camn][i])))
-                            for side in self.wings_skeleton3d_init.keys():
-                                for label in wings_skeleton2d_new[side].keys():
-                                    if wings_skeleton2d[side][label][camn][2] > self.threshold_likelihood:
-                                        axs[j].scatter(wings_skeleton2d[side][label][camn][0],
-                                                       wings_skeleton2d[side][label][camn][1], marker='.', color='k')
-                                        axs[j].scatter(wings_skeleton3d_repro2d[side][label][camn][0],
-                                                       wings_skeleton3d_repro2d[side][label][camn][1], marker='o', facecolors='none', edgecolors='k')
-                                    axs[j].scatter(wings_skeleton2d_init[side][label][camn][0],
-                                                   wings_skeleton2d_init[side][label][camn][1], marker='o', facecolors='none', edgecolors='b')
-                                    axs[j].scatter(wings_skeleton2d_new[side][label][camn][0],
-                                                   wings_skeleton2d_new[side][label][camn][1], marker='+', color='r')
-
-                            axs[j].set_xlim((center_of_mass_xy[0] - 100, center_of_mass_xy[0] + 100))
-                            axs[j].set_ylim((center_of_mass_xy[1] + 100, center_of_mass_xy[1] - 100))
-
-                        plt.pause(0.05)
-                        #plt.show()
-
-                        # param_to_print = ['stroke_a', 'deviation_a', 'rotation_a', 'ratio_wing']
-                        # for label in param_to_print:
-                        #     print('{0}: init: {1} and est: {2}'.format(label, wings_params_init_frame[side][label],
-                        #                                                wings_params_est_frame[side][label]))
-
-                else:
-                    if 'geo' in res_method:
-                        if '3d' in res_method:
-                            args[i] = (self.wings_geometry3d_init, wings_skeleton3d, wings_params_init_frame, param_names,
-                                       res_method, opt_method, self.wing_bounds_init, return_nan)
-
-                        elif '2d' in res_method:
-                            args[i] = (self.wings_geometry3d_init, wings_skeleton2d, wings_params_init_frame, param_names,
-                                       res_method, opt_method, self.wing_bounds_init, return_nan)
-                    else:
-                        if '3d' in res_method:
-                            args[i] = (self.wings_skeleton3d_init, wings_skeleton3d, wings_params_init_frame, param_names,
-                                       res_method, opt_method, self.wing_bounds_init, return_nan)
-
-                        elif '2d' in res_method:
-                            args[i] = (self.wings_skeleton3d_init, wings_skeleton2d, wings_params_init_frame, param_names,
-                                       res_method, opt_method, self.wing_bounds_init, return_nan)
-
-            if self.multiprocessing:
-                pool_wings = Pool(self.nb_process)
-                if '3d' in res_method:
-                    results = pool_wings.starmap(optim_fit_limbs_params, args)
-
-                elif '2d' in res_method:
-                    results = pool_wings.starmap(partial(optim_fit_limbs_params, dlt_coefs=self.dlt_coefs), args)
-
-                pool_wings.close()
-                for i, frame in enumerate(frames):
-                    wings_params_est_frame, rmse_frame, nb_iterations_frame = results[i]
-                    for side in self.wings_skeleton3d_init.keys():
-                        rmse[obj_name][side][i] = rmse_frame[side]
-                        nb_iterations[obj_name][side][i] = nb_iterations_frame[side]
-                        for label in wings_params_est_frame[side].keys():
-                            wings_params_ests[obj_name][side][label][i] = wings_params_est_frame[side][label]
-
-            for side in self.wings_skeleton3d_init.keys():
-                wings_params_ests[obj_name][side] = \
-                    self.filter_interp_params(wings_params_ests[obj_name][side], ['x_hinge', 'y_hinge', 'z_hinge'],
-                                              False, filter_high_freq)
-                wings_params_ests[obj_name][side] = \
-                    self.filter_interp_params(wings_params_ests[obj_name][side], param_names, interpolate_nans, False)
-
-        return wings_params_ests, nb_visible_pts, rmse, nb_iterations
-
-    def correct_body_wings_params(self, body_params, wings_params, angle_names_to_correct, rec_paths):
-        # Will correct asymetrical wing angles (mean value should be the same on both side)
-        body_params_corrected = copy.deepcopy(body_params)
-        wings_params_corrected = copy.deepcopy(wings_params)
-
-        obj_names = self.skeleton2d['obj_names']
-        for obj_name in obj_names:
-
-            wings_params_mean = {} # Find angles error (btw stroke_a and deviation_a of both sides)
-            for side in self.wings_skeleton3d_init.keys():
-                wings_params_mean[side] = \
-                    self.filter_interp_params(wings_params[obj_name][side], angle_names_to_correct, False, True)
-
-            if 'stroke_a' in angle_names_to_correct:
-                diff_stroke_a = wings_params_mean['right']['stroke_a'] - wings_params_mean['left']['stroke_a']
-                # body_params_corrected[obj_name]['yaw_a'] -= diff_stroke_a / 2
-                # wings_params_corrected[obj_name]['right']['stroke_a'] -= diff_stroke_a / 2
-                # wings_params_corrected[obj_name]['left']['stroke_a'] += diff_stroke_a / 2
-                # wings_params_corrected[obj_name]['right']['yaw_a'] -= diff_stroke_a / 2
-                # wings_params_corrected[obj_name]['left']['yaw_a'] -= diff_stroke_a / 2
-
-            if 'deviation_a' in angle_names_to_correct:
-                diff_deviation_a = wings_params_mean['right']['deviation_a'] - wings_params_mean['left']['deviation_a']
-                # body_params_corrected[obj_name]['roll_a'] -= diff_deviation_a / 2
-                # wings_params_corrected[obj_name]['right']['deviation_a'] -= diff_deviation_a / 2
-                # wings_params_corrected[obj_name]['left']['deviation_a'] += diff_deviation_a / 2
-                # wings_params_corrected[obj_name]['right']['roll_a'] -= diff_deviation_a / 2
-                # wings_params_corrected[obj_name]['left']['roll_a'] -= diff_deviation_a / 2
-
-            if 'rotation_a' in angle_names_to_correct:
-                raise ValueError('There is not reason for rotation_a to be corrected!'
-                                 'Any asymmetry btw sides cannot results from estimation error of the pitch angle')
-
-            if self.show_plot:
-                # Plot wing tips pos
-                frames = self.skeleton2d[obj_name]['frames']
-                wing_tips, wing_hinges = {'right': {}, 'left': {}}, {'right': {}, 'left': {}}
-                for side in self.wings_skeleton3d_init.keys():
-                    for coord in ['x', 'y', 'z']:
-                        wing_tips[side][coord], wing_hinges[side][coord] = np.ones(np.size(frames)) * np.nan, np.ones(
-                            np.size(frames)) * np.nan
-
-                tip_hinge_a = np.ones(np.size(frames)) * np.nan
-                tip_torso_a = np.ones(np.size(frames)) * np.nan
-                for i, frame in enumerate(frames):
-                    body_params_frame, wings_params_frame = {}, {'right': {}, 'left': {}}
-                    for label in body_params[obj_name].keys():
-                        body_params_frame[label] = body_params[obj_name][label][i]
-
-                    body_skeleton3d_est = copy.deepcopy(self.body_skeleton3d_init)
-                    body_skeleton3d_est = build_body_skeleton3d(body_skeleton3d_est, body_params_frame)
-                    body_skeleton3d_est = rotate_body_skeleton3d(body_skeleton3d_est, body_params_frame)
-                    body_skeleton3d_est = translate_body_skeleton3d(body_skeleton3d_est, body_params_frame)
-
-                    wings_skeleton3d_mean = copy.deepcopy(self.wings_skeleton3d_init)
-                    for side in self.wings_skeleton3d_init.keys():
-                        for label in wings_params[obj_name][side].keys():
-                            wings_params_frame[side][label] = wings_params_mean[side][label][i]
-
-                        wings_skeleton3d_mean[side] = build_wing_skeleton3d(wings_skeleton3d_mean[side],
-                                                                            wings_params_frame[side])
-                        wings_skeleton3d_mean[side] = rotate_and_translate_wing_skeleton3d(wings_skeleton3d_mean[side],
-                                                                                           wings_params_frame[side],
-                                                                                           side)
-
-                        wing_tips[side]['x'][i], wing_tips[side]['y'][i], wing_tips[side]['z'][i] = \
-                            wings_skeleton3d_mean[side]['tip'][0], wings_skeleton3d_mean[side]['tip'][1], \
-                            wings_skeleton3d_mean[side]['tip'][2]
-                        wing_hinges[side]['x'][i], wing_hinges[side]['y'][i], wing_hinges[side]['z'][i] = \
-                            wings_skeleton3d_mean[side]['hinge'][0], wings_skeleton3d_mean[side]['hinge'][1], \
-                            wings_skeleton3d_mean[side]['hinge'][2]
-
-                        tip_vect = np.array(
-                            [wing_tips['right']['x'][i], wing_tips['right']['y'][i], wing_tips['right']['z'][i]]) \
-                                   - np.array(
-                            [wing_tips['left']['x'][i], wing_tips['left']['y'][i], wing_tips['left']['z'][i]])
-                        hinge_vect = np.array(
-                            [wing_hinges['right']['x'][i], wing_hinges['right']['y'][i], wing_hinges['right']['z'][i]]) \
-                                     - np.array(
-                            [wing_hinges['left']['x'][i], wing_hinges['left']['y'][i], wing_hinges['left']['z'][i]])
-
-                        torso_vect = body_skeleton3d_est['head_torso_joint'] - body_skeleton3d_est[
-                            'torso_abdomen_joint']
-
-                        tip_hinge_a[i] = angle_from_vectors(tip_vect, hinge_vect)
-                        tip_torso_a[i] = angle_from_vectors(tip_vect, torso_vect)
-
-                fig_test, axs_test = plt.subplots(1, 3)
-                fig_test.suptitle('Average position of wing tip over time')
-                for side in self.wings_skeleton3d_init.keys():
-                    axs_test[0].plot(wing_tips[side]['x'] - body_params[obj_name]['x_com'], label=side + '_tip')
-                    axs_test[1].plot(wing_tips[side]['y'] - body_params[obj_name]['y_com'], label=side + '_tip')
-                    axs_test[2].plot(wing_tips[side]['z'] - body_params[obj_name]['z_com'], label=side + '_tip')
-
-                    axs_test[0].plot(wing_hinges[side]['x'] - body_params[obj_name]['x_com'], label=side + '_hinge')
-                    axs_test[1].plot(wing_hinges[side]['y'] - body_params[obj_name]['y_com'], label=side + '_hinge')
-                    axs_test[2].plot(wing_hinges[side]['z'] - body_params[obj_name]['z_com'], label=side + '_hinge')
-
-                    axs_test[0].set_ylabel('x (m)'), axs_test[1].set_ylabel('y (m)'), axs_test[2].set_ylabel('z (m)')
-
-                axs_test[2].set_xlabel('frames')
-                fig_test.legend()
-
-                fig_test2, axs_test2 = plt.subplots(1, 2)
-                fig_test2.suptitle('Average angle btw  wing tip vector and torso vector over time')
-                axs_test2[0].plot(tip_hinge_a, label='tip_hinge_a')
-                axs_test2[0].plot(diff_stroke_a, label='diff_stroke_a')
-                axs_test2[0].plot(diff_deviation_a, label='diff_deviation_a')
-                # axs_test2[0].plot(diff_rotation_a, label='diff_rotation_a')
-                axs_test2[1].plot(tip_torso_a, label='tip_torso_a')
-                axs_test2[0].set_ylabel('angle (deg)')
-
-                fig_test2.legend()
-
-                self.plot_skeleton2d(body_params_corrected, wings_params_corrected, rec_paths)
-
-        return body_params_corrected, wings_params_corrected
-
-    def plot_skeleton2d(self, body_params, wings_params, rec_paths, modulo=1,
-                        show_body=True, show_wings=True, save_images=False, save_paths={}):
-
-        # TODO remove the folowing data in code
-        camns_to_rotate = [2]
-        degrees = 270
-
-        obj_names = self.skeleton2d['obj_names']
-        for obj_name in obj_names:
-            if not show_body and not show_wings: break
-
-            if self.show_plot:
-                fig, axs = plt.subplots(1, self.nb_cam, figsize=(15, 20))
-                plt.axis('off')
-
-            all_images = {}
-            for camn in range(1, self.nb_cam + 1):
-                all_files = sorted(os.listdir(rec_paths[camn]), key=lambda x: x[20:20 + self.nb_leading_zero])
-                all_images[camn] = [s for s in all_files if '.{0}'.format(self.image_format) in s]
-
-            frames = self.skeleton2d[obj_name]['frames']
-            for i, frame in enumerate(frames):
-                if not frame % modulo == 0.0 and not frame == 1: continue
-
-                body_skeleton2d_dlc = {}
-                for body_label in self.skeleton2d[obj_name]['label_names']:
-                    body_skeleton2d_dlc[body_label] = {1: np.array([])}
-                    for camn in range(1, self.nb_cam + 1):
-                        if self.skeleton2d[obj_name][body_label][camn]['likelihood'][i] > self.threshold_likelihood:
-                            body_skeleton2d_dlc[body_label][camn] = \
-                                np.array([self.skeleton2d[obj_name][body_label][camn]['x'][i],
-                                          self.skeleton2d[obj_name][body_label][camn]['y'][i],
-                                          self.skeleton2d[obj_name][body_label][camn]['likelihood'][i]])
-                        else:
-                            body_skeleton2d_dlc[body_label][camn] = \
-                                np.array([np.nan, np.nan, self.skeleton2d[obj_name][body_label][camn]['likelihood'][i]])
-
-                if show_body:
-                    body_params_frame = {}
-                    for label in body_params[obj_name].keys():
-                        body_params_frame[label] = body_params[obj_name][label][i]
-
-                    body_skeleton3d = copy.deepcopy(self.body_skeleton3d_init)
-                    body_skeleton3d = build_body_skeleton3d(body_skeleton3d, body_params_frame)
-                    body_skeleton3d = rotate_body_skeleton3d(body_skeleton3d, body_params_frame)
-                    body_skeleton3d = translate_body_skeleton3d(body_skeleton3d, body_params_frame)
-                    body_skeleton2d = reproject_skeleton3d_to2d(body_skeleton3d, self.dlt_coefs)
-
-                if show_wings:
-                    wings_skeleton3d = copy.deepcopy(self.wings_skeleton3d_init)
-
-                    wings_params_frame = {'right': {}, 'left': {}}
-                    wings_skeleton2d, wings_skeleton2d_dlc = {'right': {}, 'left': {}}, {'right': {}, 'left': {}}
-                    for side in self.wings_skeleton3d_init.keys():
-                        for label in wings_params[obj_name][side].keys():
-                            wings_params_frame[side][label] = wings_params[obj_name][side][label][i]
-
-                        wings_skeleton3d[side] = build_wing_skeleton3d(wings_skeleton3d[side], wings_params_frame[side])
-                        wings_skeleton3d[side] = rotate_and_translate_wing_skeleton3d(wings_skeleton3d[side], wings_params_frame[side], side)
-                        wings_skeleton2d[side] = reproject_skeleton3d_to2d(wings_skeleton3d[side], self.dlt_coefs)
-
-                        for wing_label in self.wings_skeleton3d_init[side].keys():
-                            wings_skeleton2d_dlc[side][wing_label] = {1: []}
-                            for camn in range(1, self.nb_cam + 1):
-                                label = side + '_wing_' + wing_label
-                                if self.skeleton2d[obj_name][label][camn]['likelihood'][i] > self.threshold_likelihood:
-                                    wings_skeleton2d_dlc[side][wing_label][camn] = \
-                                        np.array([self.skeleton2d[obj_name][label][camn]['x'][i],
-                                                  self.skeleton2d[obj_name][label][camn]['y'][i],
-                                                  self.skeleton2d[obj_name][label][camn]['likelihood'][i]])
-                                else:
-                                    wings_skeleton2d_dlc[side][wing_label][camn] = \
-                                        np.array([np.nan, np.nan, self.skeleton2d[obj_name][label][camn]['likelihood'][i]])
-
-                prev_img = None
-                for j, camn in enumerate(range(1, self.nb_cam + 1)):
-                    center_of_mass_xy = \
-                        (body_skeleton2d['torso_abdomen_joint'][camn] * body_params_frame['ratio_com_torso'] \
-                         + body_skeleton2d['head_torso_joint'][camn] * (1 - body_params_frame['ratio_com_torso']))
-
-                    if np.isnan(center_of_mass_xy).any(): continue
-
-                    if self.show_plot or save_images:
-                        img = read_image(os.path.join(rec_paths[camn], all_images[camn][i]))
-
-                    if self.show_plot:
-                        axs[j].clear()
-                        axs[j].imshow(img)
-
-                        if show_body:
-                            for label in body_skeleton2d.keys():
-                                if body_skeleton2d_dlc[label][camn][2] > self.threshold_likelihood:
-                                    axs[j].scatter(body_skeleton2d_dlc[label][camn][0],
-                                                   body_skeleton2d_dlc[label][camn][1], marker='.', color='k')
-                                axs[j].scatter(body_skeleton2d[label][camn][0],
-                                               body_skeleton2d[label][camn][1], marker='+', color='r')
-
-                        if show_wings:
-                            for side in self.wings_skeleton3d_init.keys():
-                                for label in wings_skeleton2d[side].keys():
-                                    if wings_skeleton2d_dlc[side][label][camn][2] > self.threshold_likelihood:
-                                        axs[j].scatter(wings_skeleton2d_dlc[side][label][camn][0],
-                                                       wings_skeleton2d_dlc[side][label][camn][1], marker='.', color='k')
-                                    axs[j].scatter(wings_skeleton2d[side][label][camn][0],
-                                                   wings_skeleton2d[side][label][camn][1], marker='+', color='r')
-
-                        axs[j].set_xlim((center_of_mass_xy[0] - 100, center_of_mass_xy[0] + 100))
-                        axs[j].set_ylim((center_of_mass_xy[1] + 100, center_of_mass_xy[1] - 100))
-
-                        if camn == self.nb_cam:
-                            plt.pause(0.05)
-                            # fig.savefig(os.path.join(save_paths[self.main_camn], all_images[camn][i]),
-                            #             bbox_inches='tight', transparent=True, pad_inches=0)
-
-                    if save_images:
-                        img = img.convert('RGB')
-                        draw = ImageDraw.Draw(img)
-                        if show_body:
-                            body_line = []
-                            for label in body_skeleton2d.keys():
-                                if body_skeleton2d_dlc[label][camn][2] > self.threshold_likelihood:
-                                    draw.point((body_skeleton2d_dlc[label][camn][0], body_skeleton2d_dlc[label][camn][1]), fill='blue')
-                                draw.point((body_skeleton2d[label][camn][0], body_skeleton2d[label][camn][1]), fill='red')
-
-                                if not 'hinge' in label:
-                                    body_line.append((body_skeleton2d[label][camn][0], body_skeleton2d[label][camn][1]))
-                            draw.line(body_line, fill='red', width=2)
-
-                        if show_wings:
-                            for side in self.wings_skeleton3d_init.keys():
-                                wing_line = []
-                                for label in wings_skeleton2d[side].keys():
-                                    if wings_skeleton2d_dlc[side][label][camn][2] > self.threshold_likelihood:
-                                        draw.point((wings_skeleton2d_dlc[side][label][camn][0], wings_skeleton2d_dlc[side][label][camn][1]), fill='blue')
-
-                                    draw.point((wings_skeleton2d[side][label][camn][0], wings_skeleton2d[side][label][camn][1]), fill='red')
-                                    wing_line.append((wings_skeleton2d[side][label][camn][0], wings_skeleton2d[side][label][camn][1]))
-
-                                frst_label = list(wings_skeleton2d[side].keys())[0]
-                                wing_line.append((wings_skeleton2d[side][frst_label][camn][0], wings_skeleton2d[side][frst_label][camn][1]))
-                                draw.line(wing_line, fill='red', width=2)
-
-                        left, right = int(center_of_mass_xy[0] - 100), int(center_of_mass_xy[0] + 100)
-                        top, bottom = int(center_of_mass_xy[1] - 100), int(center_of_mass_xy[1] + 100)
-                        img = img.crop((left, top, right, bottom))
-
-                        if camn in camns_to_rotate:
-                            img = img.rotate(degrees)
-                        # img.show()
-                        # img.save(os.path.join(save_paths[camn], all_images[camn][i]), compression='lzw')
-                        # img.close()
-
-                        # Stitch various cam views
-                        if prev_img is None:
-                            prev_img = img.copy()
-
-                        else:
-                            dst = Image.new('RGB', (prev_img.width + img.width, prev_img.height))
-                            dst.paste(prev_img, (0, 0))
-                            dst.paste(img, (prev_img.width, 0))
-                            prev_img = dst.copy()
-
-                if not prev_img is None:
-                    save_name = all_images[self.main_camn][i][:-len(self.image_format) - 1] \
-                                + '-' + obj_name + '.{0}'.format(self.image_format)
-                    prev_img.save(os.path.join(save_paths[self.main_camn], save_name), compression='lzw')
-                    prev_img.close()
-                    img.close()
-
-    def filter_interp_params(self, params, param_names, interpolate_nans, filter_high_freq):
-        if not interpolate_nans and not filter_high_freq: return params
-        new_params = copy.deepcopy(params)
-
-        if self.show_plot: fig, axs = plt.subplots(len(param_names), 1)
-
-        for i, label in enumerate(param_names):
-            is_nan, x = np.isnan(new_params[label]), lambda z: z.nonzero()[0]
-            if sum(~is_nan) == 0: continue  # only nans
-
-            if self.show_plot: axs[i].plot(new_params[label], label='raw')
-
-            if '_a' in label:
-                # fig2 = plt.figure()
-                # ax2 = fig2.add_subplot(111)
-                # ax2.plot(new_params[label], label='raw')
-
-                new_params[label][~is_nan] = np.unwrap(np.deg2rad(new_params[label][~is_nan]))
-                # ax2.plot(np.rad2deg(new_params[label]), label='unwrapped')
-
-            if sum(is_nan) > 0:  # interpolation needed for filter_high_freq
-                new_params[label][is_nan] = np.interp(x(is_nan), x(~is_nan), new_params[label][~is_nan])
-
-            if filter_high_freq and np.nanstd(new_params[label]) > 10e-6:
-                w = self.cutoff_frequency / (self.framerate / 2)  # Normalize the frequency
-                b, a = signal.butter(2, w, 'low')
-
-                new_params[label] = signal.filtfilt(b, a, new_params[label])
-
-                if not interpolate_nans and sum(is_nan) > 0:  # to fill back with nan if didnt want to interpolate
-                    new_params[label][is_nan] = np.nan
-
-            if '_a' in label:  # Wrap angles on [-pi, pi)
-                new_params[label] = np.rad2deg((new_params[label] + np.pi) % (2 * np.pi) - np.pi)
-                # ax2.plot(new_params[label], label='wrapped')
-                # ax2.set_ylabel(label)
-                # ax2.set_xlabel('frames')
-
-            if self.show_plot:
-                axs[i].plot(new_params[label], label='filt')
-                axs[i].set_ylabel(label)
-                axs[i].set_xlabel('frames')
-
-        if self.show_plot:
-            plt.legend()
-            # plt.show()
-
-        return new_params
-
-    def save_skeleton_params_in_csv(self, body_params, wings_params, save_path):
-        for obj_name in self.skeleton3d['obj_names']:
-            body_params_names = body_params[obj_name].keys()
-
-            params_list, params_names = [self.skeleton3d[obj_name]['frames']], 'frame'
-            for param_name in body_params_names:
-                params_list.append(body_params[obj_name][param_name])
-                params_names += ',' + param_name
-
-            for side in self.wings_params_init.keys():
-                for param_name in wings_params[obj_name][side].keys():
-                    if param_name in body_params_names: continue
-                    params_list.append(wings_params[obj_name][side][param_name])
-                    params_names += ',' + param_name + '_' + side
-
-            np.savetxt(os.path.join(save_path, 'skeleton_parameters-' + obj_name + '.csv'),
-                       np.c_[np.transpose(params_list)], delimiter=',', header=params_names)
-
-    def save_obj_metric_in_csv(self, list_dict, list_name, save_path):
-        for obj_name in self.skeleton3d['obj_names']:
-            params_list, params_names = [self.skeleton3d[obj_name]['frames']], 'frame'
-            if isinstance(list_dict[obj_name], dict):
-                for side in self.wings_params_init.keys():
-                    params_list.append(list_dict[obj_name][side])
-                    params_names += ',' + list_name + '_' + side
-            else:
-                params_list.append(list_dict[obj_name])
-                params_names += ',' + list_name
-
-            np.savetxt(os.path.join(save_path, list_name + '-' + obj_name + '.csv'),
-                       np.c_[np.transpose(params_list)], delimiter=',', header=params_names)
-
-    def stitch_images(self, images_names, image_paths, image_save_paths):
-        ref_images, refs_images = set(), {}
-        for camn in range(1, self.nb_cam + 1):
-            refs_images[camn] = [image_name[20:-(len(self.image_format) + 1)] for image_name in images_names[camn]]
-            ref_images = ref_images & set(refs_images[camn]) if len(ref_images) > 0 else set(refs_images[camn])
-
-        ref_images = np.sort(list(ref_images))
-
-        index_i = {}
-        for camn in range(1, self.nb_cam + 1):
-            index_i[camn] = [refs_images[camn].index(ref_image) for ref_image in ref_images]
-
-        for i, _ in enumerate(index_i[self.main_camn]):
-            imgs = {}
-            for camn in range(1, self.nb_cam + 1):
-                imgs[camn] = Image.open(os.path.join(image_paths[camn], images_names[camn][index_i[camn][i]]))
-
-            img = img_utils.stitch_images(imgs, self.nb_cam)
-            img.save(os.path.join(image_save_paths[self.main_camn], images_names[self.main_camn][index_i[self.main_camn][i]]), compression='lzw')
-
-    def interp_2d_obj(self, rec_path, csv_path, save_path):
-        rec_file_names = sorted(os.listdir(rec_path), key=lambda x: x[20:20+self.nb_leading_zero])
-        rec_image_names = [s for s in rec_file_names if '.{0}'.format(self.image_format) in s]
-        frames = [int(s[20:20+self.nb_leading_zero]) for s in rec_image_names]
-
-        filenames_csv_path = os.listdir(csv_path)
-        image_names_csv_path = [s for s in filenames_csv_path if '.{0}'.format(self.image_format) in s]
-        image_names_csv_path = sorted(image_names_csv_path, key=lambda x: x[20:20+self.nb_leading_zero])
-
-        csv_names = glob.glob(os.path.join(csv_path, '*-*-2d_points.csv'))
-        csv_names = [csv_name[len(csv_path) + 1:] for csv_name in csv_names]
-
-        obj_names = [csv_name[csv_name.find('-') + 1:csv_name.find('-2d_points.csv')] for csv_name in csv_names]
-
-        coord_objs = {}
-        if len(obj_names) == 0:
-            for i, csv_name in enumerate(csv_names): coord_objs[obj_names[i]] = {}
-            return coord_objs, obj_names
-
-        img = Image.open(os.path.join(rec_path, rec_image_names[0]))
-        if len(image_names_csv_path) == 0:
-            resize_ratio = 1
-        else:
-            img_down = Image.open(os.path.join(csv_path, image_names_csv_path[0]))
-            resize_ratio = img_down.size[0] / img.size[0]
-
-        for i, csv_name in enumerate(csv_names):
-            coord_objs[obj_names[i]] = \
-                self.interpolate_coord(img, resize_ratio, frames, csv_name, csv_path, save_path)
-
-        return coord_objs, obj_names
-
-    def interpolate_coord(self, img, resize_ratio, frames, csv_name, csv_path, save_path):
-        width_img, height_img = img.size
-
-        old_obj_dict = np.genfromtxt(os.path.join(csv_path, csv_name), delimiter=',', skip_header=0, names=True)
-
-        # TODO use resize_ratio in interpolation
-        x_px = np.interp(frames, old_obj_dict['frame'], old_obj_dict['x_px'])
-        y_px = np.interp(frames, old_obj_dict['frame'], old_obj_dict['y_px'])
-
-        for i, _ in enumerate(frames):
-            if x_px[i] > width_img: x_px[i] = width_img
-            if y_px[i] > height_img: y_px[i] = height_img
-            if x_px[i] < 0: x_px[i] = 0
-            if y_px[i] < 0: y_px[i] = 0
-
-        np.savetxt(os.path.join(save_path, csv_name), np.c_[frames, x_px, y_px], delimiter=',', header='frame,x_px,y_px')
-        obj_dict = {'frame': frames, 'x_px': x_px, 'y_px': y_px}
-
-        if self.show_plot:
-            fig = plt.figure()
-            ax = fig.add_subplot(111)
-            plt.imshow(img)
-            ax.scatter(obj_dict['x_px'], obj_dict['y_px'], marker='*')
-            ax.scatter(old_obj_dict['x_px'], old_obj_dict['y_px'], marker='x')
-            ax.set_xlabel('x (m)')
-            ax.set_ylabel('y (m)')
-            plt.xlim(0, width_img)
-            plt.ylim(height_img, 0)
-            plt.show()
-
-        return obj_dict
-
-    def nearest_date(self, dates_dict, date):
-        diff_s = [abs(time.mktime(dates_dict[i]) - time.mktime(date)) for i in dates_dict]
-        near_diff_s = [x for x in diff_s if x < self.max_diff_date_s]
-
-        if len(near_diff_s) is 0:
-            return []
-        else:
-            return dates_dict[diff_s.index(min(near_diff_s))]
-
-
-if __name__ == '__main__':
-
-    img_process = ImagesProcessing()
-
-    #path = 'G:'
-    #img_process.cam_paths = {1: os.path.join(path, 'test'), 2: os.path.join(path, 'test'), 3: os.path.join(path, 'test')}
-    #img_process.cam_save_paths = {1: os.path.join(path, 'test', '_Process'), 2: os.path.join(path, 'test', '_Process'), 3: os.path.join(path, 'test', '_Process')}
-    #img_process.cam_paths = {1: 'F:\\Photron1', 2: 'Y:\\', 3: 'Z:\\'}
-    #img_process.cam_save_paths = {1: 'F:\\Photron1\\_Process', 2: 'Y:\\_Process', 3: 'Z:\\_Process'}
-    img_process.cam_paths = {1: '/media/user/MosquitoEscape_Photron1/Photron1',
-                             2: '/media/user/MosquitoEscape_Photron2/Photron2',
-                             3: '/media/user/MosquitoEscape_Photron3/Photron3'}
-    img_process.cam_save_paths = {1: '/media/user/MosquitoEscape_Photron1/Photron1/_Process',
-                                  2: '/media/user/MosquitoEscape_Photron2/Photron2/_Process',
-                                  3: '/media/user/MosquitoEscape_Photron3/Photron3/_Process'}
-
-    #img_process.cam_paths = {1: 'F:\\Photron1'}
-    #img_process.cam_save_paths = {1: 'F:\\Photron1\\_Process'}
-
-    img_process.init_paths_names()
-    rec_names_to_process = {}
-    for camn in range(1, img_process.nb_cam + 1):
-        rec_names_to_process[camn] = [s for s in img_process.all_folders_rec_cam[camn] if '20200303' in s]
-
-    img_process.do_batch('resize', rec_names=rec_names_to_process, step_frame=100, resize_ratio=1)
-    # img_process.do_batch('crop', rec_names=rec_names_to_process, height_crop=200, width_crop=200, old_step_frame=100)
-    # img_process.do_batch('save_tiff', rec_names=rec_names_to_process)
-
-    # img_process.do_batch('sample', rec_names=rec_names_to_process, step_frame=1)
-
-
-
-
diff --git a/images/utils.py b/images/utils.py
index 07534670ba7c71d3f855e7d0d9af4cb35d2477be..376d701c0e2dd68537572495caf0c13c443a340f 100644
--- a/images/utils.py
+++ b/images/utils.py
@@ -1,4 +1,6 @@
-import os
+import fnmatch
+from difflib import SequenceMatcher
+from typing import List, Dict
 
 import numpy as np
 from PIL import Image, ImageEnhance, ImageOps
@@ -66,8 +68,107 @@ def stitch_images(imgs, nb_cam):
     return img
 
 
-def save_tiff(image_name, rec_path, save_path):
-    img = Image.open(os.path.join(rec_path, image_name))
+def save_tiff(img, save_path):
     array = np.uint8(np.array(img) / 256)
-    Image.fromarray(array).save(os.path.join(save_path, image_name), compression='lzw')
+    Image.fromarray(array).save(save_path, compression='lzw')
+
+
+def find_common_substrings(str_list: List[str], keep_shifted_matched: bool = True, keep_only_counts_higher_than: int = None,
+                           return_nun_matches_instead_occurrences: bool = False, ) -> Dict[str, int]:
+
+    substring_counts = {}
+    for i in range(0, len(str_list)):
+
+        for j in range(i+1, len(str_list)):
+
+            string1 = str_list[i]
+            string2 = str_list[j]
+
+            match = SequenceMatcher(None, string1, string2).find_longest_match(0, len(string1), 0, len(string2))
+
+            if not keep_shifted_matched and match.a != match.b: continue
+
+            matching_substring = string1[match.a:match.a+match.size]
+
+            if matching_substring not in substring_counts:
+                substring_counts[matching_substring] = 1
+            else:
+                substring_counts[matching_substring] += 1
+
+    if keep_only_counts_higher_than is not None:
+        substring_counts = {key: substring_counts[key] for key in substring_counts.keys()
+                            if substring_counts[key] >= keep_only_counts_higher_than}
+
+    if not return_nun_matches_instead_occurrences:
+        for key in substring_counts.keys():
+            substring_counts[key] = sum(key in s for s in str_list)
+
+    # Sort in descending order
+    substring_counts = dict(sorted(substring_counts.items(), key=lambda item: item[1], reverse=True))
+
+    return substring_counts
+
+
+def find_common_substrings_with_one_separator(str_list: List[str], return_nun_matches_instead_occurrences: bool = False,
+                                              keep_only_counts_higher_than: int = None) -> Dict[str, int]:
+    """
+        Fill find the common substring with separator (*), so is searching for blocks of two longest
+        substring (non shifted) matches between all strings in the given list
+
+    Args:
+        str_list: List of strings, usually file names
+        (e.g. ['cam1_202205040001-obj1.tif', 'cam1_202205040002-obj1.tif', 'cam1_202205040003-obj1.tif', ...]
+                -> then most common substrings with separator is 'cam1_20220504000*-obj1.tif')
+        return_nun_matches_instead_occurence:
+        keep_only_counts_higher_than:
+    """
+
+    substring_counts = {}
+    for i in range(0, len(str_list)):
+
+        for j in range(i+1, len(str_list)):
+
+            string1 = str_list[i]
+            string2 = str_list[j]
+
+            matches = SequenceMatcher(None, string1, string2).get_matching_blocks()
+
+            if len(matches)-1 != 2: continue  # If there is more or less than two common substrings
+            if matches[0].a != matches[0].b and matches[1].a != matches[1].b: continue  # If the matches are shifted
+
+            matching_substring = string1[matches[0].a:matches[0].a+matches[0].size] + '*' + string1[matches[1].a:matches[1].a+matches[1].size]
+
+            if matching_substring not in substring_counts:
+                substring_counts[matching_substring] = 1
+            else:
+                substring_counts[matching_substring] += 1
+
+    if keep_only_counts_higher_than is not None:
+        substring_counts = {key: substring_counts[key] for key in substring_counts.keys()
+                            if substring_counts[key] >= keep_only_counts_higher_than}
+
+    if not return_nun_matches_instead_occurrences:
+        for key in substring_counts.keys():
+            substring_counts[key] = len(fnmatch.filter(str_list, key))
+
+    # Sort in descending order
+    substring_counts = dict(sorted(substring_counts.items(), key=lambda item: item[1], reverse=True))
+
+    return substring_counts
+
+
+def find_uncommon_substrings(str_list: List[str], common_substring) -> List[str]:
+
+    ind_star = common_substring.find('*')
+    uncommon_substrings = [s[ind_star:ind_star-len(common_substring)+1] for s in str_list]
+
+    return uncommon_substrings
+
+
+def find_file_numbers(str_list: List[str], common_substring) -> List[int]:
+
+    ind_star = common_substring.find('*')
+    file_numbers = [int(s[ind_star:s.rfind('.')]) for s in str_list]
+
+    return file_numbers
 
diff --git a/main_process_mating.py b/main_process_mating.py
index 93f5e0b8c8a5c49e7c2b554d2de85c3e46b5b1d6..e719a1ff0e87ac25c839808de9df205533abb8ca 100644
--- a/main_process_mating.py
+++ b/main_process_mating.py
@@ -1,14 +1,16 @@
 import os, time
-from images.process import ImagesProcessing
+from process.batch_processing import BatchProcessing
 
 #import deeplabcut
 
+# TODO Update from examples in test_process_batch
+
 start = time.time()
 
-xyz_path = os.path.join(os.getcwd(), 'data/calib/xyz_calibration_device_small.csv')
+xyz_path = os.path.join(os.getcwd(), 'data/calib/examples/mosquito_escapes/to_delete/xyz_calibration_device_small.csv')
 #xy_path = os.path.join(os.getcwd(), 'data/calib/20200615_xypts-rot.csv')
-xy_path = os.path.join(os.getcwd(), 'data/calib/20200615_xypts.csv')
-dlt_path = os.path.join(os.getcwd(), 'data/calib/20200615_DLTcoefs-py.csv')
+xy_path = os.path.join(os.getcwd(), 'data/calib/examples/mosquito_escapes/to_delete/20200615_xypts.csv')
+dlt_path = os.path.join(os.getcwd(), 'data/calib/examples/mosquito_escapes/to_delete/20200615_DLTcoefs-py.csv')
 dlc_cfg_path = '/home/user/Desktop/Antoine/_DLC/config.yaml'
 
 img_size = (896, 896)
@@ -16,7 +18,7 @@ img_size = (896, 896)
 # Generate dlt coefficients
 #gen_dlt(3, img_size, xyz_path, xy_path, dlt_path)
 
-img_process = ImagesProcessing(3, dlt_path)
+img_process = BatchProcessing()
 img_process.cam_paths = {1: '/media/user/MosquitoLanding_Photron3_Backup/Photron1/_MatingKinematics',
                          2: '/media/user/MosquitoLanding_Photron3_Backup/Photron2/_MatingKinematics',
                          3: '/media/user/MosquitoLanding_Photron3_Backup/Photron3/_MatingKinematics'}
@@ -59,26 +61,26 @@ for camn in range(1, img_process.nb_cam + 1):
 
 # > do everything automatically use 'multi_processes'
 # Do cropping, rotating, stitching, etc at once and save as .avi (save much time and space)
-img_process.do_batch('multi_processes', rec_names=rec_names_to_process, yaml_path='processes.yaml', delete_previous=True)
+img_process._do_batch('multi_processes', rec_names_to_process=rec_names_to_process, yaml_path='processes.yaml', delete_previous=True)
 
 # > OR do each step separately
-# Convert images to 8 bits and reduce framerate (save in cam_save_paths/_Sample)
-# img_process.do_batch('sample', rec_names=rec_names_to_process, step_frame=50, delete_previous=True)
+# Convert images to 8 bits and reduce frame_rate (save in cam_save_paths/_Sample)
+# img_process.do_batch('sample', rec_names_to_process=rec_names_to_process, step_frame=50, delete_previous=True)
 #
 # # Do 2d tracking (blob detection) on images in cam_save_paths/_Sample
-# img_process.do_batch('track2d', rec_names=rec_names_to_process, from_fn_name='sample')
+# img_process.do_batch('track2d', rec_names_to_process=rec_names_to_process, from_fn_name='sample')
 #
 # # Do 3d reconstruction of tracks
-# img_process.do_batch('recon3d', rec_names=rec_names_to_process, from_fn_name='sample')
+# img_process.do_batch('recon3d', rec_names_to_process=rec_names_to_process, from_fn_name='sample', dlt_path=dlt_path)
 #
 # # Crop all frames and save in cam_save_paths/_Cropped
-# img_process.do_batch('crop', rec_names=rec_names_to_process, from_fn_name='sample', height_crop=200, width_crop=200, delete_previous=True)
+# img_process.do_batch('crop', rec_names_to_process=rec_names_to_process, from_fn_name='sample', height_crop=200, width_crop=200, delete_previous=True)
 #
 # # Rotate view 2 (to always have piston coming from right side)
-# img_process.do_batch('rotate', rec_names=rec_names_to_process, from_fn_name='crop', camns=[2], degrees=270)
+# img_process.do_batch('rotate', rec_names_to_process=rec_names_to_process, from_fn_name='crop', camn_to_process=[2], degrees=270)
 #
 # # Stitch all views together and save in cam_save_paths/_Stitched
-# img_process.do_batch('stitch', rec_names=rec_names_to_process, from_fn_name='crop', delete_previous=True)
+# img_process.do_batch('stitch', rec_names_to_process=rec_names_to_process, from_fn_name='crop', delete_previous=True)
 
 
 # -----------------------------------------------------------------------------------------------------------------------
@@ -97,18 +99,18 @@ wing_param_names = ['stroke_a', 'deviation_a', 'rotation_a']
 
 
 # # Track features using DeepLabCut
-# img_process.do_batch('analyse_dlc', rec_names=rec_names_to_process, from_fn_name='save_avi', cfg_path=dlc_cfg_path,
-#                     shuffle=shuffle, trainingsetindex=0, batchsize=5, save_avi=False, model_name=model_name, delete_previous=True)
+# img_process.do_batch('analyse_dlc', rec_names_to_process=rec_names_to_process, from_fn_name='save_avi', cfg_path=dlc_cfg_path,
+#                     shuffle=shuffle, trainingset_index=0, batch_size=5, save_avi=False, model_name=model_name, delete_previous=True)
 
 # # Load (+ filtering and unscrambling) 2d coords from DLC + Reverse processes (unstitch, rotate back, uncrop) + Reconstruct 3d coord
-# img_process.do_batch('load_dlc', rec_names=rec_names_to_process, from_fn_name='analyse_dlc', model_name=model_name,
-#                       tracker_method='skeleton')
+# img_process.do_batch('load_dlc', rec_names_to_process=rec_names_to_process, from_fn_name='analyse_dlc', model_name=model_name,
+#                       tracker_method='skeleton', dlt_path=dlt_path)
 
 # # Optimize fit of skeleton to find body and wings angles
 # img_process.multiprocessing = True
 # img_process.threshold_likelihood = 0.85
-# img_process.do_batch('fit_skeleton', rec_names=rec_names_to_process, from_fn_name='load_dlc', model_name=model_name,
+# img_process.do_batch('fit_skeleton', rec_names_to_process=rec_names_to_process, from_fn_name='load_dlc', model_name=model_name,
 #                      csv_path=csv_wings_geo_path, body_param_names=body_param_names, wing_param_names=wing_param_names,
-#                      animal_name="fly", res_method=res_method, opt_method=opt_method)
+#                      animal_name="fly", res_method=res_method, opt_method=opt_method, dlt_path=dlt_path)
 
 print('> All processes as been processed (total time elapsed: {0:.4f} s)'.format(time.time() - start))
\ No newline at end of file
diff --git a/point_tracker/__init__.py b/point_tracker/__init__.py
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1d8a7a8d303df66d9cf01d8eec0855d2e822aed0 100644
--- a/point_tracker/__init__.py
+++ b/point_tracker/__init__.py
@@ -0,0 +1,2 @@
+from point_tracker.tracker2d import Tracker2D
+from point_tracker.reconstructor3d import Reconstructor3D
diff --git a/point_tracker/reconstructor3d.py b/point_tracker/reconstructor3d.py
index 2887d0014ce3b16f6c633a02801ffb1c3a1c9d87..54c8dbe7062ec4e318727dce9cec770faa62aacf 100644
--- a/point_tracker/reconstructor3d.py
+++ b/point_tracker/reconstructor3d.py
@@ -1,69 +1,14 @@
-from __future__ import print_function
 import os
+
 import numpy as np
 import matplotlib.pyplot as plt
+from mpl_toolkits.mplot3d import Axes3D
 
 from dataclasses import dataclass
 
-from cam import calib
-
-
-def gen_dlt(nb_cam, img_size, xyz_path, xy_path, save_path, points_to_remove=[], plot_error_analysis=False):
-    coord_calib_csv = np.genfromtxt(xyz_path, delimiter=',', skip_header=0, names=True)
-    pts_calib_csv = np.genfromtxt(xy_path, delimiter=',', skip_header=0, names=True)
-
-    coord_calib = {'x': np.array([s for i, s in enumerate(coord_calib_csv['x']) if i+1 not in points_to_remove]),
-                   'y': np.array([s for i, s in enumerate(coord_calib_csv['y']) if i+1 not in points_to_remove]),
-                   'z': np.array([s for i, s in enumerate(coord_calib_csv['z']) if i+1 not in points_to_remove])}
-
-    reconstructor = Reconstructor3D(nb_cam, img_size)
-
-    if plot_error_analysis: errors = {}
-
-    pts_calib = {}
-    for camn in range(1, nb_cam + 1):
-        pts_calib['cam{0}'.format(camn)] = \
-            {'x': np.array([s for i, s in enumerate(pts_calib_csv['cam{0}_X'.format(camn)]) if i+1 not in points_to_remove]),
-             'y': np.array([s for i, s in enumerate(pts_calib_csv['cam{0}_Y'.format(camn)]) if i+1 not in points_to_remove])}
-
-        # Fill img_pts_calib with pixel coordinates from Matlab tracking (have to transform coordinate system)
-        img_pts_calib = np.array([pts_calib['cam{0}'.format(camn)]['x'],
-                                  img_size[1] - pts_calib['cam{0}'.format(camn)]['y']], np.float32).T
-
-        # Remove nan
-        index = ~np.isnan(img_pts_calib).any(axis=1)
-        img_pts_calib = img_pts_calib[index]
-
-        obj_coord_calib = np.array([coord_calib['x'][index], coord_calib['y'][index], coord_calib['z'][index]]).T
-
-        _, rmse = reconstructor.gen_dltcoef(camn, obj_coord_calib, img_pts_calib)
-
-        print('>> The reprojection error of cam{0} is {1} pixels'.format(camn, rmse))
+from camera import calib
+from camera.calib import read_dlt_coefs, gen_dlt_coefs_from_paths, save_dlt_coefs
 
-        if plot_error_analysis:
-            errors[camn] = np.zeros(len(img_pts_calib.T[0]))
-            for i in range(len(img_pts_calib.T[0])):
-                img_pts_calib_wo_pt = np.array([[s for j, s in enumerate(img_pts_calib.T[0]) if j is not i],
-                                                [s for j, s in enumerate(img_pts_calib.T[0]) if j is not i]]).T
-                obj_coord_calib_wo_pt = np.array([[s for j, s in enumerate(obj_coord_calib.T[0]) if j is not i],
-                                                  [s for j, s in enumerate(obj_coord_calib.T[1]) if j is not i],
-                                                  [s for j, s in enumerate(obj_coord_calib.T[2]) if j is not i]]).T
-
-                _, errors[camn][i] = reconstructor.gen_dltcoef(camn, obj_coord_calib_wo_pt, img_pts_calib_wo_pt)
-
-    reconstructor.save_dltcoef(save_path)
-    print('> DLT coefficient have been saved in {0}'.format(save_path))
-
-    if plot_error_analysis:
-        # Plot all rmse without one point
-
-        for camn in range(1, nb_cam + 1):
-            fig = plt.figure('Error analysis (cam{0})'.format(camn))
-            ax = fig.add_subplot(111)
-            ax.scatter(list(range(1, len(errors[camn]) +1)), errors[camn])
-            ax.set_xlabel('x (m)')
-            ax.set_ylabel('y (m)')
-            plt.show()
 
 @dataclass
 class Reconstructor3DSettings:
@@ -72,28 +17,39 @@ class Reconstructor3DSettings:
     threshold_delay_tracks: int = 10  # max delay (in frames) between two successive 3d points to set as a track
     threshold_length_tracks: int = 5
 
+    image_ratio: float = 1.0  # ratio between size of the images used for calibration over the analyzed images (if changed).
+
+    @staticmethod
+    def from_dict(dictionary):
+        reconstructor_sets = Reconstructor3DSettings()
+        for key, value in dictionary.items():
+            setattr(reconstructor_sets, key, value)
+
+        return reconstructor_sets
+
 
 class Reconstructor3D:
-    def __init__(self, nb_cam, img_size):
+    def __init__(self, dlt_coefs):
         """
         Class used to reconstruct 3d coordinates of object from 2d coordinates on images
 
         Args:
-            nb_cam: number of camera (minimum 2)
-            img_size: size of the images in pixel (width, height)
+            dlt_coefs: DLT coefficients (11*nb_cam)
         """
 
-        self.nb_cam = nb_cam
-        self.img_size = img_size
+        assert len(dlt_coefs[0]) == 11
+
+        self.dlt_coefs = dlt_coefs
+        self.nb_cam = len(self.dlt_coefs)
 
         # TODO this use of dataclass Reconstructor3DSettings doesn't seems really useful
-        self.recon3d_settings = Reconstructor3DSettings()
+        self.settings = Reconstructor3DSettings()
 
-        self.dlt_coefs = np.zeros((self.nb_cam, 12))
         self.pts_dict = {}
         self.objs_dict = {}
 
-    def read_dltcoef(self, path):
+    @staticmethod
+    def read_dlt_coefs(path) -> "Reconstructor3D":
         """
         Read DLT coefficients from .csv file
 
@@ -101,49 +57,9 @@ class Reconstructor3D:
             path: path of the .csv file with DLT (11) coefficients (along rows) for all camera (along column)
         """
 
-        dlt_csv = np.genfromtxt(path, delimiter=',', skip_header=0, names=True)
-        dlt_csv.dtype.names = ['cam{0}'.format(camn) for camn in range(1, self.nb_cam + 1)]
-
-        for i, camn in enumerate(range(1, self.nb_cam + 1)):
-            self.dlt_coefs[i] = np.append(np.array(dlt_csv[camn]), 1)
-
-    def save_dltcoef(self, path):
-        """
-        Save DLT coefficients in .csv file
-
-        Args:
-            path: path of the .csv file with DLT (11) coefficients (along rows) for all camera (along column)
-        """
-
-        dlt_coefs = np.zeros((self.nb_cam, 11))
-        for i, camn in enumerate(range(1, self.nb_cam + 1)):
-            dlt_coefs[i] = self.dlt_coefs[:-1]
-
-        header = ['cam{0}'.format(camn) for camn in range(1, self.nb_cam + 1)]
-        header = ','.join(header)
-
-        np.savetxt(path, np.c_[dlt_coefs].T, delimiter=',', header=header)
-
-    def gen_dltcoef(self, camn, obj_coord, img_pts):
-        """
-        Generate DLT coefficients for one camera
-
-        args are vectors of vectors (e.g. np.array([[451, 123], [78, 45], [78, 79]])) of size nb_obj*nb_coord
-
-        Args:
-            camn: num of camera
-            obj_coord: known 3d coordinates of the objects
-            img_pts: 2d coordinates for all objects for one camera
-
-        Returns:
-            dlt_coefs_cam: DLT coefficient (12: 11 + 1(= 1.0)) for one camera
-            mean_repro_err: mean reprojcetion error in pixel (or Root-mean-square deviation)
-        """
-
-        dlt_coefs_cam, repro_err = calib.calib(3, obj_coord, img_pts)
-        self.dlt_coefs[camn-1] = dlt_coefs_cam
+        dlt_coefs = read_dlt_coefs(path)
 
-        return dlt_coefs_cam, repro_err
+        return Reconstructor3D(dlt_coefs)
 
     def recon_objs(self, xs_pts, ys_pts, frames_coord, with_missing=True, sort_by_rmse=True):
         """
@@ -271,8 +187,8 @@ class Reconstructor3D:
 
         #xs_px_obj, ys_px_obj = [], []
         xs_obj, ys_obj, zs_obj, rmses_obj, index_match_pts = [], [], [], [], np.array([])
-        for num_combi in range(0, len(combi_indexes_frames)):
-            index_match = list(combi_indexes_frames[num_combi])
+        for nb_combi in range(0, len(combi_indexes_frames)):
+            index_match = list(combi_indexes_frames[nb_combi])
 
             index_match2 = index_match.copy()
             for i in range(0, self.nb_cam):
@@ -293,9 +209,11 @@ class Reconstructor3D:
                     pts_cam = np.array([xs_pts[i][index_match[i]], ys_pts[i][index_match[i]]]).T
                     pts_cams = np.vstack([pts_cams, pts_cam]) if pts_cams.size > 0 else pts_cam
 
-            obj_coord = calib.recon3D(3, self.nb_cam - count_missing, cur_Ls, pts_cams)
+            obj_coord = calib.recon3d(3, self.nb_cam - count_missing, cur_Ls, pts_cams*self.settings.image_ratio)
 
             repro_pts_cams = calib.find2d(self.nb_cam, self.dlt_coefs, np.array([obj_coord]))
+            repro_pts_cams = repro_pts_cams/self.settings.image_ratio
+
             repro_pts_cams_wo_missing = \
                 np.array([repro_pts_cams[j] for j, _ in enumerate(range(1, self.nb_cam + 1)) if index_match2[j] != None])
 
@@ -303,7 +221,7 @@ class Reconstructor3D:
             correction_factor = count_missing * np.sqrt(2) if count_missing > 0 else 1
             mean_repro_err = np.sqrt(np.mean(np.sum((repro_pts_cams_wo_missing - pts_cams) ** 2, 1))) * correction_factor
 
-            if mean_repro_err < self.recon3d_settings.threshold_mean_repro_err:
+            if mean_repro_err < self.settings.threshold_mean_repro_err:
                 # Fill tracking results
                 xs_obj.append(obj_coord[0]), ys_obj.append(obj_coord[1]), zs_obj.append(obj_coord[2])
                 rmses_obj.append(mean_repro_err)
@@ -327,7 +245,7 @@ class Reconstructor3D:
             indexes_pts: index of the matched 2d pts used to reconstruct 3d coordinates for all frames
 
         Returns:
-            tracks_dict: dictionary with tracks of all objects (e.g. tracks_dict[num_obj] = {'x': [], ...})
+            tracks_dict: dictionary with tracks of all objects (e.g. tracks_dict[nb_obj] = {'x': [], ...})
         """
 
         self.tracks_dict = {'obj': {}, 'obj_names': []}
@@ -341,15 +259,15 @@ class Reconstructor3D:
             self.tracks_dict = {'obj': {}, 'obj_names': []}
             return self.tracks_dict
 
-        num_objs_live = []
+        nb_objs_live = []
 
         min_frame_diff = np.min(np.array(frames_objs[1:]) - np.array(frames_objs[:-1]))
 
         for i, frame in enumerate(frames_objs):
-            if len(num_objs_live) == 0:  # to initialize first track
+            if len(nb_objs_live) == 0:  # to initialize first track
                 for j in range(0, len(xs_objs[i])):
-                    num_objs_live.append(j + 1)
-                    self.tracks_dict['obj'][num_objs_live[-1]] = {'x': [xs_objs[i][j]], 'y': [ys_objs[i][j]],
+                    nb_objs_live.append(j + 1)
+                    self.tracks_dict['obj'][nb_objs_live[-1]] = {'x': [xs_objs[i][j]], 'y': [ys_objs[i][j]],
                             'z': [zs_objs[i][j]], 'frame': [frame], 'index_pts': [indexes_pts[i][j]]}
 
                 continue
@@ -360,66 +278,66 @@ class Reconstructor3D:
             #  then find best match for next obj with all points except the previously matched point
             for j in range(0, len(xs_objs[i])):
                 distances = []
-                for num_obj_live in num_objs_live:
-                    if self.tracks_dict['obj'][num_obj_live]['frame'][-1] == frame:  # to avoid using same frame twice
+                for nb_obj_live in nb_objs_live:
+                    if self.tracks_dict['obj'][nb_obj_live]['frame'][-1] == frame:  # to avoid using same frame twice
                         distances.append(np.nan)
                     else:
-                        distances.append(np.sqrt((self.tracks_dict['obj'][num_obj_live]['x'][-1] - xs_objs[i][j]) ** 2
-                                               + (self.tracks_dict['obj'][num_obj_live]['y'][-1] - ys_objs[i][j]) ** 2
-                                               + (self.tracks_dict['obj'][num_obj_live]['z'][-1] - zs_objs[i][j]) ** 2))
+                        distances.append(np.sqrt((self.tracks_dict['obj'][nb_obj_live]['x'][-1] - xs_objs[i][j]) ** 2
+                                               + (self.tracks_dict['obj'][nb_obj_live]['y'][-1] - ys_objs[i][j]) ** 2
+                                               + (self.tracks_dict['obj'][nb_obj_live]['z'][-1] - zs_objs[i][j]) ** 2))
 
                 # if distance between two successive object is small, then they are stitched together in one track
-                if not np.isnan(distances).all() and np.nanmin(distances) < self.recon3d_settings.threshold_distance_tracks:
-                    num_obj_distance_min = num_objs_live[np.nanargmin(distances)]
+                if not np.isnan(distances).all() and np.nanmin(distances) < self.settings.threshold_distance_tracks:
+                    nb_obj_distance_min = nb_objs_live[np.nanargmin(distances)]
 
-                    self.tracks_dict['obj'][num_obj_distance_min]['x'].append(xs_objs[i][j])
-                    self.tracks_dict['obj'][num_obj_distance_min]['y'].append(ys_objs[i][j])
-                    self.tracks_dict['obj'][num_obj_distance_min]['z'].append(zs_objs[i][j])
-                    self.tracks_dict['obj'][num_obj_distance_min]['frame'].append(frame)
-                    self.tracks_dict['obj'][num_obj_distance_min]['index_pts'] = \
-                        np.vstack([self.tracks_dict['obj'][num_obj_distance_min]['index_pts'], indexes_pts[i][j]])
+                    self.tracks_dict['obj'][nb_obj_distance_min]['x'].append(xs_objs[i][j])
+                    self.tracks_dict['obj'][nb_obj_distance_min]['y'].append(ys_objs[i][j])
+                    self.tracks_dict['obj'][nb_obj_distance_min]['z'].append(zs_objs[i][j])
+                    self.tracks_dict['obj'][nb_obj_distance_min]['frame'].append(frame)
+                    self.tracks_dict['obj'][nb_obj_distance_min]['index_pts'] = \
+                        np.vstack([self.tracks_dict['obj'][nb_obj_distance_min]['index_pts'], indexes_pts[i][j]])
 
                 else:  # start a new tracks
-                    num_objs_live.append(num_objs_live[-1] + 1)
-                    self.tracks_dict['obj'][num_objs_live[-1]] = {'x': [xs_objs[i][j]], 'y': [ys_objs[i][j]],
+                    nb_objs_live.append(nb_objs_live[-1] + 1)
+                    self.tracks_dict['obj'][nb_objs_live[-1]] = {'x': [xs_objs[i][j]], 'y': [ys_objs[i][j]],
                             'z': [zs_objs[i][j]], 'frame': [frame], 'index_pts': [indexes_pts[i][j]]}
 
-                # remove obj from num_objs_live if too old (more than a given number of frames)
-                for k, num_obj_live in enumerate(num_objs_live):
-                    frame_diff = frame - self.tracks_dict['obj'][num_obj_live]['frame'][-1]
-                    if frame_diff > (self.recon3d_settings.threshold_delay_tracks * min_frame_diff):
-                        del num_objs_live[k]
+                # remove obj from nb_objs_live if too old (more than a given number of frames)
+                for k, nb_obj_live in enumerate(nb_objs_live):
+                    frame_diff = frame - self.tracks_dict['obj'][nb_obj_live]['frame'][-1]
+                    if frame_diff > (self.settings.threshold_delay_tracks * min_frame_diff):
+                        del nb_objs_live[k]
 
         # Get rid of tracks that are too short and add nan to other
-        for num_obj in list(self.tracks_dict['obj'].keys()):
-            if len(self.tracks_dict['obj'][num_obj]['x']) <= self.recon3d_settings.threshold_length_tracks:
-                del self.tracks_dict['obj'][num_obj]
+        for nb_obj in list(self.tracks_dict['obj'].keys()):
+            if len(self.tracks_dict['obj'][nb_obj]['x']) <= self.settings.threshold_length_tracks:
+                del self.tracks_dict['obj'][nb_obj]
             else:
-                self.tracks_dict['obj_names'].append('obj{0}'.format(num_obj))
+                self.tracks_dict['obj_names'].append('obj{0}'.format(nb_obj))
 
-                frames_obj = self.tracks_dict['obj'][num_obj]['frame']
+                frames_obj = self.tracks_dict['obj'][nb_obj]['frame']
                 frames_obj = np.arange(np.min(frames_obj), np.max(frames_obj), min_frame_diff)
                 xs_objs, ys_objs, zs_objs, index_pts = [], [], [], []
                 for i, frame in enumerate(frames_obj):
-                    in_frame = np.squeeze(np.where(frame == self.tracks_dict['obj'][num_obj]['frame']))
+                    in_frame = np.squeeze(np.where(frame == self.tracks_dict['obj'][nb_obj]['frame']))
 
                     if in_frame.shape == (0,):
                         xs_objs.append(np.nan), ys_objs.append(np.nan), zs_objs.append(np.nan)
                         index_pts.append([None, None, None])
                     else:
-                        xs_objs.append(self.tracks_dict['obj'][num_obj]['x'][in_frame])
-                        ys_objs.append(self.tracks_dict['obj'][num_obj]['y'][in_frame])
-                        zs_objs.append(self.tracks_dict['obj'][num_obj]['z'][in_frame])
-                        index_pts.append(self.tracks_dict['obj'][num_obj]['index_pts'][in_frame])
+                        xs_objs.append(self.tracks_dict['obj'][nb_obj]['x'][in_frame])
+                        ys_objs.append(self.tracks_dict['obj'][nb_obj]['y'][in_frame])
+                        zs_objs.append(self.tracks_dict['obj'][nb_obj]['z'][in_frame])
+                        index_pts.append(self.tracks_dict['obj'][nb_obj]['index_pts'][in_frame])
 
-                self.tracks_dict['obj'][num_obj]['x'] = xs_objs
-                self.tracks_dict['obj'][num_obj]['y'] = ys_objs
-                self.tracks_dict['obj'][num_obj]['z'] = zs_objs
-                self.tracks_dict['obj'][num_obj]['frame'] = frames_obj
-                self.tracks_dict['obj'][num_obj]['index_pts'] = index_pts
+                self.tracks_dict['obj'][nb_obj]['x'] = xs_objs
+                self.tracks_dict['obj'][nb_obj]['y'] = ys_objs
+                self.tracks_dict['obj'][nb_obj]['z'] = zs_objs
+                self.tracks_dict['obj'][nb_obj]['frame'] = frames_obj
+                self.tracks_dict['obj'][nb_obj]['index_pts'] = index_pts
 
         print(">>> {0} track(s) have been kept (out of {1} reconstructed tracks)"
-              .format(len(self.tracks_dict['obj_names']), num_objs_live[-1]))
+              .format(len(self.tracks_dict['obj_names']), nb_objs_live[-1]))
 
         return self.tracks_dict
 
@@ -445,10 +363,13 @@ class Reconstructor3D:
 
             for j, camn in enumerate(range(1, self.nb_cam + 1)):
                 repro_pts_cam = calib.find2d(1, self.dlt_coefs[j], obj_coord).T
+                repro_pts_cams = repro_pts_cams/self.settings.image_ratio
 
                 xs_pts[j].append(repro_pts_cam[0][0])
                 ys_pts[j].append(repro_pts_cam[1][0])
 
+        xs_pts, ys_pts = xs_pts/self.settings.image_ratio, ys_pts/self.settings.image_ratio
+
         return np.array(xs_pts), np.array(ys_pts)
 
     def save_points_csv(self, names, paths, save_type='all_points'):
@@ -497,75 +418,47 @@ class Reconstructor3D:
             paths: paths where to save .csv files
         """
 
-        for num_obj in list(self.tracks_dict['obj'].keys()):
+        for nb_obj in list(self.tracks_dict['obj'].keys()):
 
             obj_coords = np.array([])
-            for i in range(0, len(self.tracks_dict['obj'][num_obj]['x'])):
-                obj_coord = np.array([self.tracks_dict['obj'][num_obj]['x'][i],
-                                      self.tracks_dict['obj'][num_obj]['y'][i],
-                                      self.tracks_dict['obj'][num_obj]['z'][i]])
+            for i in range(0, len(self.tracks_dict['obj'][nb_obj]['x'])):
+                obj_coord = np.array([self.tracks_dict['obj'][nb_obj]['x'][i],
+                                      self.tracks_dict['obj'][nb_obj]['y'][i],
+                                      self.tracks_dict['obj'][nb_obj]['z'][i]])
                 obj_coords = np.vstack([obj_coords, obj_coord]) if obj_coords.size else obj_coord
 
             for i, camn in enumerate(range(1, self.nb_cam + 1)):
                 repro_pts_cams = calib.find2d(1, self.dlt_coefs[i], np.array(obj_coords)).T
+                repro_pts_cams = repro_pts_cams/self.settings.image_ratio
 
-                if not np.array_equal(self.tracks_dict['obj'][num_obj]['frame'],
-                                      np.sort(np.unique(self.tracks_dict['obj'][num_obj]['frame']))):
+                if not np.array_equal(self.tracks_dict['obj'][nb_obj]['frame'],
+                                      np.sort(np.unique(self.tracks_dict['obj'][nb_obj]['frame']))):
                     raise ValueError('frames_objs must be sorted and unique')
 
-                np.savetxt(os.path.join(paths[i], names[i] + '-obj{0}-2d_points.csv'.format(num_obj)),
-                           np.c_[self.tracks_dict['obj'][num_obj]['frame'], repro_pts_cams[0], repro_pts_cams[1]],
+                np.savetxt(os.path.join(paths[i], names[i] + '-obj{0}-2d_points.csv'.format(nb_obj)),
+                           np.c_[self.tracks_dict['obj'][nb_obj]['frame'], repro_pts_cams[0], repro_pts_cams[1]],
                            delimiter=',', header='frame,x_px,y_px')
-                np.savetxt(os.path.join(paths[i], names[i] + '-obj{0}-3d_track.csv'.format(num_obj)),
-                           np.c_[self.tracks_dict['obj'][num_obj]['frame'], self.tracks_dict['obj'][num_obj]['x'],
-                                 self.tracks_dict['obj'][num_obj]['y'], self.tracks_dict['obj'][num_obj]['z']],
+                np.savetxt(os.path.join(paths[i], names[i] + '-obj{0}-3d_track.csv'.format(nb_obj)),
+                           np.c_[self.tracks_dict['obj'][nb_obj]['frame'], self.tracks_dict['obj'][nb_obj]['x'],
+                                 self.tracks_dict['obj'][nb_obj]['y'], self.tracks_dict['obj'][nb_obj]['z']],
                            delimiter=',', header='frame,x,y,z')
 
 
 if __name__ == '__main__':
 
-    image_format = 'tif'
-    # rec_names = {1: 'cam1_20200303_030117'}
-    # image_paths = {1: '/media/user/MosquitoEscape_Photron1/Photron1/_Process/_DownSized/'}
+    path = '../data/examples/mosquito_escapes/'
+    image_paths = {1: os.path.join(path, 'cam1'), 2: os.path.join(path, 'cam2'), 3: os.path.join(path, 'cam3')}
     rec_names = {1: 'cam1_20200303_030117', 2: 'cam2_20200303_030120', 3: 'cam3_20200303_030120'}
-    image_paths = {1: '/media/user/MosquitoEscape_Photron1/Photron1/_Process/_Sample/',
-                   2: '/media/user/MosquitoEscape_Photron2/Photron2/_Process/_Sample/',
-                   3: '/media/user/MosquitoEscape_Photron3/Photron3/_Process/_Sample/'}
-
-    nb_cam = 3
-    img_size = (1024, 1024)
-
-    # coord_calib_csv = np.genfromtxt('data/calib/xyz_calibration_device_small.csv', delimiter=',', skip_header=0, names=True)
-    # pts_calib_csv = np.genfromtxt('data/calib/20200401_xypts.csv', delimiter=',', skip_header=0, names=True)
-    coord_calib_csv = np.genfromtxt('data/calib/xyz_calibration_device_big.csv', delimiter=',', skip_header=0, names=True)
-    pts_calib_csv = np.genfromtxt('data/calib/20200305_xypts.csv', delimiter=',', skip_header=0, names=True)
-
-    coord_calib = {'x': np.array(coord_calib_csv['x']), 'y': np.array(coord_calib_csv['y']),
-                   'z': np.array(coord_calib_csv['z'])}
-
-    reconstructor = Reconstructor3D(nb_cam, img_size)
-
-    # generate calibration (DLT coefficients)
-    pts_calib = {}
-    for camn in range(1, nb_cam + 1):
-        pts_calib[camn] = {'x': np.array(pts_calib_csv['cam{0}_X'.format(camn)]),
-                           'y': np.array(pts_calib_csv['cam{0}_Y'.format(camn)])}
-
-        # Fill img_pts_calib with pixel coordinates from Matlab tracking (have to transform coordinate system)
-        img_pts_calib = np.array([pts_calib[camn]['x'], img_size[1] - pts_calib[camn]['y']], np.float32).T
-
-        # Remove nan
-        index = ~np.isnan(img_pts_calib).any(axis=1)
-        img_pts_calib = img_pts_calib[index]
 
-        obj_coord_calib = np.array([coord_calib['x'][index], coord_calib['y'][index], coord_calib['z'][index]]).T
+    coord_calib_csv_path = '../data/calib/examples/mosquito_escapes/xyz_calibration_device.csv'
+    pts_calib_csv_path = '../data/calib/examples/mosquito_escapes/20200306_xypts.csv'
 
-        _, rmse = reconstructor.gen_dltcoef(camn, obj_coord_calib, img_pts_calib)
+    dlt_coefs, _ = gen_dlt_coefs_from_paths(coord_calib_csv_path, pts_calib_csv_path, from_matlab=True, img_height=1024)
+    save_dlt_coefs(dlt_coefs, '../data/calib/examples/mosquito_escapes/20200306_DLTcoefs-py.csv')
 
-        print('>>> The mean reprojection error of cam{0} is {1:.2f} pixels'.format(camn, rmse))
+    nb_cam = len(rec_names.keys())
 
-    #reconstructor.read_dltcoef('data/calib/20200305_DLTcoefs.csv')
-    reconstructor.save_dltcoef('data/calib/20200305_DLTcoefs-py.csv')
+    reconstructor = Reconstructor3D(dlt_coefs)
 
     # From .csv file, fill xs_pose, ys_pose and frames_pose (2d coordinates of blobs)
     pts_csv = {}
@@ -582,13 +475,14 @@ if __name__ == '__main__':
     tracks_dict = reconstructor.recon_tracks(xs_pose, ys_pose, zs_pose, frames_pose, indexes_pts)
 
     save_names = [rec_names[camn] for camn in range(1, nb_cam + 1)]
-    save_paths = [image_paths[camn] for camn in range(1, nb_cam + 1)]
+    save_paths = [os.path.join(image_paths[camn], rec_names[camn]) for camn in range(1, nb_cam + 1)]
     reconstructor.save_tracks_csv(save_names, save_paths)
 
     fig = plt.figure()
-    ax = fig.add_subplot(111, projection='3d')
-    for num_obj in range(1, tracks_dict['nb_tracks'] + 1):
-        ax.scatter(tracks_dict['obj'][num_obj]['x'], tracks_dict['obj'][num_obj]['y'], tracks_dict['obj'][num_obj]['z'])
+    #ax = fig.add_subplot(111, projection='3d')
+    ax = Axes3D(fig)
+    for obj_num in tracks_dict['obj'].keys():
+        ax.scatter(tracks_dict['obj'][obj_num]['x'], tracks_dict['obj'][obj_num]['y'], tracks_dict['obj'][obj_num]['z'])
     # ax.scatter(xs_pose, ys_pose, zs_pose)
     ax.set_xlabel('x (m)')
     ax.set_ylabel('y (m)')
diff --git a/point_tracker/tracker2d.py b/point_tracker/tracker2d.py
index ce7e0e3e54f85e7916210e9ccc7b9de70b2f876a..6fe781b40725a227b44f557de0dcf3ec9ab536e7 100644
--- a/point_tracker/tracker2d.py
+++ b/point_tracker/tracker2d.py
@@ -1,20 +1,29 @@
-from __future__ import print_function
 import os
-import cv2, glob
+import cv2
 import numpy as np
 from PIL import Image
 from collections import defaultdict
 
+from typing import List
+
 from dataclasses import dataclass
 
 
 @dataclass
-class BackgroundSubstractorSettings:
+class BackgroundSubtractorSettings:
     type: str = 'KNN'  # 'KNN' or 'MOG2'
     varThreshold: int = 12
     dist2Threshold: int = 50
     shadow_threshold: int = 1
 
+    @staticmethod
+    def from_dict(dictionary):
+        background_sets = BackgroundSubtractorSettings()
+        for key, value in dictionary.items():
+            setattr(background_sets, key, value)
+
+        return background_sets
+
 
 @dataclass
 class BlobDetectorSettings:
@@ -30,6 +39,14 @@ class BlobDetectorSettings:
     aspect_ratio_min: float = 0.8
     eucli_dist_max: float = 5.0
 
+    @staticmethod
+    def from_dict(dictionary):
+        blob_detector_sets = BlobDetectorSettings()
+        for key, value in dictionary.items():
+            setattr(blob_detector_sets, key, value)
+
+        return blob_detector_sets
+
 
 class Tracker2D:
     def __init__(self):
@@ -43,61 +60,48 @@ class Tracker2D:
 
         self.points = defaultdict(list)
 
-        self.rec_name = 'test'
-        self.save_path = os.getcwd()
+        self.get_blob_detector(BlobDetectorSettings())
+        self.get_back_subtractor(BackgroundSubtractorSettings())
 
-        self.back_substractor_settings = BackgroundSubstractorSettings()
-        self.blob_detector_settings = BlobDetectorSettings()
-
-        self.initialize()
-
-    def initialize(self):
-        """
-
-        """
-
-        self.get_blob_detector()
-        self.get_back_substractor()
-
-    def load_images(self, path, image_format):
+    def load_images(self, image_names: List[str], path: str, frames: List[int] = None) -> None:
         """
 
         Args:
-            path:
-            image_format:
+            image_names: a List of image names
+            path: The path where the images are
+            frames: a list of int with the frame number corresponding to the images
         """
-        image_paths = glob.glob(os.path.join(path, '*.{0}'.format(image_format)))
-        image_names = [os.path.basename(image_path) for image_path in image_paths]
 
-        self.rec_name = image_names[0][:20]
-        self.save_path = path
+        self.images = [cv2.imread(os.path.join(path, image_name), cv2.IMREAD_GRAYSCALE) for image_name in image_names]
 
-        self.images = [cv2.imread(image_path, cv2.IMREAD_GRAYSCALE) for image_path in image_paths]
-        self.frames = [int(image_name[20:-len('.{0}'.format(image_format))]) for image_name in image_names]
+        if frames is None:
+            self.frames = list(range(1, len(self.images) +1))
+        else:
+            self.frames = frames
 
         in_sort = np.argsort(self.frames)
         self.images = [self.images[in_s] for in_s in in_sort]
         self.frames = [self.frames[in_s] for in_s in in_sort]
 
-    def do_tracking(self):
+    def do_tracking(self, ind_img_for_background: List[int] = None):
         """
 
         """
 
-        if len(self.images) is 0:
-            print("WARN: you need to load images first")
-            return
+        assert len(self.images) != 0, "Images need to be loaded first"
+
+        if ind_img_for_background is None: ind_img_for_background = range(0, len(self.images))
 
-        [self.back_substractor.apply(image, learningRate=-1) for image in self.images[:-6]]
+        [self.back_subtractor.apply(self.images[i], learningRate=-1) for i in ind_img_for_background]
 
-        self.background = self.back_substractor.getBackgroundImage()
+        self.background = self.back_subtractor.getBackgroundImage()
         self.background = cv2.medianBlur(self.background, 5)  # Add median filter to images
 
         self.points = defaultdict(list)
 
         self.foreground_masks = self.images.copy()
         for i, image in enumerate(self.images):
-            self.foreground_masks[i] = self.back_substractor.apply(image, learningRate=-1)
+            self.foreground_masks[i] = self.back_subtractor.apply(image, learningRate=-1)
             self.foreground_masks[i] = cv2.medianBlur(self.foreground_masks[i], 3)  # Add median filter to images
 
             blobs = self.blob_detector.detect((255 - self.foreground_masks[i]))  # Detect blobs
@@ -131,14 +135,14 @@ class Tracker2D:
             print("WARN: you need to track or load 2d points first")
             return
 
-        [self.back_substractor.apply(image, learningRate=-1) for image in self.images[:-6]]
+        [self.back_subtractor.apply(image, learningRate=-1) for image in self.images[:-6]]
 
         if flip_time:  # Will start analysing last frame
             images, frames = np.flip(self.images, axis=0), np.flip(self.frames)
         else:
             images, frames = self.images, self.frames
 
-        self.background = self.back_substractor.getBackgroundImage()
+        self.background = self.back_subtractor.getBackgroundImage()
         self.background = cv2.medianBlur(self.background, 5)  # Add median filter to images
 
         if 'strip' in shape: height, width = images[0].shape
@@ -152,7 +156,7 @@ class Tracker2D:
         for i, image in enumerate(images):
             if not i % step == 0: continue
 
-            self.foreground_masks[i] = self.back_substractor.apply(image, learningRate=0)
+            self.foreground_masks[i] = self.back_subtractor.apply(image, learningRate=0)
             self.foreground_masks[i] = cv2.medianBlur(self.foreground_masks[i], 3)  # Add median filter to images
 
             in_frame = np.where(frames[i] == self.points['frame'])[0]
@@ -183,28 +187,28 @@ class Tracker2D:
 
         self.strobe_img = self.strobe_img.astype(np.uint8)
 
-    def get_back_substractor(self):
-        if self.back_substractor_settings.type is 'MOG2':
-            self.back_substractor = cv2.createBackgroundSubtractorMOG2(history=-1, varThreshold=self.back_substractor_settings.varThreshold, detectShadows=True)
+    def get_back_subtractor(self, back_subtractor_settings: BackgroundSubtractorSettings()):
+        if back_subtractor_settings.type is 'MOG2':
+            self.back_subtractor = cv2.createBackgroundSubtractorMOG2(history=-1, varThreshold=back_subtractor_settings.varThreshold, detectShadows=True)
 
-        elif self.back_substractor_settings.type is 'KNN':
-            self.back_substractor = cv2.createBackgroundSubtractorKNN(history=-1, dist2Threshold=self.back_substractor_settings.dist2Threshold, detectShadows=True)
-            self.back_substractor.setShadowThreshold(self.back_substractor_settings.shadow_threshold)  # 0.5 by default
+        elif back_subtractor_settings.type is 'KNN':
+            self.back_subtractor = cv2.createBackgroundSubtractorKNN(history=-1, dist2Threshold=back_subtractor_settings.dist2Threshold, detectShadows=True)
+            self.back_subtractor.setShadowThreshold(back_subtractor_settings.shadow_threshold)  # 0.5 by default
 
-    def get_blob_detector(self):
+    def get_blob_detector(self, blob_detector_settings: BlobDetectorSettings()):
         # Setup SimpleBlobDetector parameters.
         params = cv2.SimpleBlobDetector_Params()
 
-        params.minThreshold = self.blob_detector_settings.threshold_min
-        params.maxThreshold = self.blob_detector_settings.threshold_max
+        params.minThreshold = blob_detector_settings.threshold_min
+        params.maxThreshold = blob_detector_settings.threshold_max
 
         params.filterByArea = True
-        params.minArea = self.blob_detector_settings.area_min
-        params.maxArea = self.blob_detector_settings.area_max
+        params.minArea = blob_detector_settings.area_min
+        params.maxArea = blob_detector_settings.area_max
 
         params.filterByInertia = True
-        params.minInertiaRatio = self.blob_detector_settings.inertia_ratio_min
-        params.maxInertiaRatio = self.blob_detector_settings.inertia_ratio_max
+        params.minInertiaRatio = blob_detector_settings.inertia_ratio_min
+        params.maxInertiaRatio = blob_detector_settings.inertia_ratio_max
 
         params.filterByCircularity = False
         params.filterByConvexity = False
@@ -231,6 +235,7 @@ class Tracker2D:
 
 
 if __name__ == '__main__':
+    import glob
 
     image_format = 'tif'
     # rec_name = 'cam1_20200303_030117'
@@ -242,11 +247,15 @@ if __name__ == '__main__':
                    3: '/media/user/MosquitoEscape_Photron3/Photron3/_Process/_DownSized/'}
 
     tracker = Tracker2D()
-    tracker.initialize()
 
     for camn in range(1, nb_cam + 1):
         print(os.path.join(image_paths[camn], rec_names[camn]))
-        tracker.load_images(os.path.join(image_paths[camn], rec_names[camn]), image_format)
+
+        all_image_paths = glob.glob(os.path.join(image_paths[camn], '*.{0}'.format(image_format)))
+        image_names = [os.path.basename(image_path) for image_path in all_image_paths]
+        frames = [int(image_name[20:-len('.{0}'.format(image_format))]) for image_name in image_names]
+
+        tracker.load_images(image_names, image_paths[camn], frames)
         tracker.do_tracking()
 
         tracker.save_csv(rec_names[camn], os.path.join(image_paths[camn], rec_names[camn]))
diff --git a/analysis/__init__.py b/process/__init__.py
similarity index 100%
rename from analysis/__init__.py
rename to process/__init__.py
diff --git a/process/batch_processing.py b/process/batch_processing.py
new file mode 100644
index 0000000000000000000000000000000000000000..b8288d9e501c59f2cbbae788daec2bc1ca59d8bc
--- /dev/null
+++ b/process/batch_processing.py
@@ -0,0 +1,1090 @@
+import time, shutil, yaml, glob
+
+import cv2
+
+from typing import Dict, List
+
+import camera.calib
+import images.utils as img_utils
+import process.utils as process_utils
+
+from process.skeleton_fitting import SkeletonFitting
+from images.multiimagesequence import MultiImageSequence
+from point_tracker.tracker2d import BlobDetectorSettings, BackgroundSubtractorSettings
+from point_tracker.reconstructor3d import Reconstructor3DSettings
+
+
+# import deeplabcut
+# import tensorflow as tf
+#
+# config = tf.compat.v1.ConfigProto()
+# config.gpu_options.allow_growth = True
+# config.log_device_placement = True
+# sess = tf.compat.v1.Session(config=config)
+
+
+# TODO remove specifics dependencies to insect body and wings modules => make more flexible
+from process.dlc_postprocessing import load_dlc_from_full_pickle, save_dlc2d_points_in_csv, save_dlc3d_points_in_csv, \
+    unscramble_views2d_dlc, save_stroboscopic_images, reverse_processes_2d_points, recon3d_dlc
+from process.images_preprocessing import crop_all_images, crop_image, stitch_all_images, stitch_images_multiprocessing, save_avi
+from process.point_tracking import reconstruct_3d_tracks, track_2d_objects, interp_2d_coords_of_objs
+from process.skeleton_fitting import load_skeleton2d_from_csv, load_skeleton3d_from_csv
+
+from skeleton_fitter.animals.fly import *
+from images.read import *
+
+
+class BatchProcessing:
+    """
+        Class used to process batches of recordings (each consisting of sequences of images).
+
+        Can be initialised using from_directory_by_dates or from_directory_by_names. Images can be pre-processed
+        using functions like sample_images, track2d, recon3d, crop_images and stitch_images. The recordings can be
+        saved to .avi using save_avi, and analysed using analyse_dlc (deeplabcut). Then the tracking results from
+        deeplabcut can be loaded using load_dlc and a 3d skeleton can be fitted to these results using fit_skeleton.
+
+    """
+    def __init__(self, recordings: List[MultiImageSequence], save_path: str, nb_cam: int = 2) -> None:
+        """
+
+        Args:
+            recordings: A sequence of MultiImageSequence consisting of sequences of images for each camera
+            save_path: Path to the directory where the results of the processing will be saved.
+            nb_cam: The number of camera (need to be superior or equal to 2)
+        """
+
+        self.recordings = recordings  # type: List[MultiImageSequence]
+        self.nb_recordings = len(self.recordings)  # type: int
+        self.save_path = save_path  # type: str
+
+        if nb_cam < 2:
+            raise ValueError('The number of camera (nb_cam) should be superior or equal to 2')
+        assert all([nb_cam == r.nb_sequences for r in self.recordings])
+        self.nb_cam = nb_cam  # type: int
+
+        self.recording_names = [{camn: os.path.basename(os.path.normpath(r.get_path(r.names[camn-1])))
+                                 for camn in range(1, self.nb_cam + 1)} for r in self.recordings]  # type: List[Dict[str: str]]
+
+        self.recording_paths = [{camn: os.path.dirname(r.get_path(r.names[camn-1]))
+                                 for camn in range(1, self.nb_cam + 1)} for r in self.recordings]  # type: List[Dict[str: str]]
+
+        self.main_camn = 1  # type: int
+
+    @staticmethod
+    def from_directory_by_dates(recording_paths: List[str], frame_rate: int, save_path: str = None,
+                                type_image: str = "grey", expr: str = r"\d{8}_\d{6}",
+                                format_date_str: str ='%Y%m%d_%H%M%S', threshold_s: int = 25,
+                                move_to_delete: bool = False) -> "BatchProcessing":
+        """
+            Initiate BatchProcessing by gathering recording files from directory.
+            It will match the recording folders for all cameras by checking the recording dates in their name.
+
+        Args:
+            recording_paths: List of paths to directories containing each with a recording sequence.
+            frame_rate: The frame_rate of all recordings (we assume it is the same for all).
+            save_path: Path to the directory where the results of the processing will be saved.
+                If None, this path will be in the first recording path.
+            expr: Regular expression pattern matching recording folders with date.
+            format_date_str: format of the date and time in the folder names.
+            threshold_s: Will check if the date in the folder names differ from less than threshold in second
+            type_image: The type of the images ('rgb', 'grey' or 'bw').
+            move_to_delete: Whether or not to move the unmatched recording folders to a to_delete folder
+                (will be in the same parent directory as the recording folders)
+        """
+
+        if save_path is None:
+            save_path = recording_paths[0]
+
+        nb_cam = len(recording_paths)
+        cam_names = [camn for camn in range(1, nb_cam +1)]
+
+        matched_folder_names = process_utils.match_recordings_by_date(recording_paths, expr=expr,
+                                                                      format_date_str=format_date_str,
+                                                                      threshold_s=threshold_s)
+
+        if move_to_delete:
+            un_matched_folder_names = process_utils.get_unmatched_recordings(recording_paths, matched_folder_names)
+
+            if un_matched_folder_names:
+                for ind_cam in range(0, len(recording_paths)):
+                    destination_path = os.path.join(recording_paths[ind_cam], 'to_delete')
+
+                    if not os.path.exists(destination_path):
+                        os.makedirs(destination_path)
+
+                    for i in range(0, len(un_matched_folder_names)):
+                        if un_matched_folder_names[i]:
+                            os.rename(os.path.join(recording_paths[ind_cam], un_matched_folder_names[i][ind_cam]),
+                                      os.path.join(destination_path, un_matched_folder_names[i][ind_cam]))
+
+        recordings = []
+        for i in range(0, len(matched_folder_names)):
+            matched_directories = [os.path.join(recording_paths[ind_cam], f) for ind_cam, f in enumerate(matched_folder_names[i])]
+            recordings.append(MultiImageSequence.from_directory(matched_directories, frame_rate, cam_names, expr,
+                                                                type_image=type_image))
+
+        return BatchProcessing(recordings, save_path, nb_cam)
+
+    @staticmethod
+    def from_directory_by_names(recording_paths: List[str], frame_rate: int, save_path: str = None,
+                                type_image: str = "grey", threshold_ratio_diff: float = None,
+                                threshold_nb_diff_char: int = None, move_to_delete: bool = True) -> "BatchProcessing":
+        """
+            Initiate BatchProcessing by gathering recording files from directory.
+            It will match the recording folders for all cameras by checking their names.
+
+        Args:
+            recording_paths: List of paths to directories containing each with a recording sequence.
+            frame_rate: The frame_rate of all recordings (we assume it is the same for all).
+            save_path: Path to the directory where the results of the processing will be saved.
+                If None, this path will be in the first recording path.
+            threshold_ratio_diff: Will check the similarity percentage (between 0 and 1)
+            threshold_nb_diff_char: Will check how many characters are different between the file names.
+            type_image: The type of the images ('rgb', 'grey' or 'bw').
+            move_to_delete: Whether or not to move the unmatched recording folders to a to_delete folder
+                (will be in the same parent directory as the recording folders)
+        """
+
+        if save_path is None:
+            save_path = recording_paths[0]
+
+        nb_cam = len(recording_paths)
+        cam_names = [camn for camn in range(1, nb_cam +1)]
+
+        matched_folder_names = process_utils.match_recordings_by_name(recording_paths, threshold_ratio_diff=threshold_ratio_diff,
+                                                                      threshold_nb_diff_char=threshold_nb_diff_char)
+        if move_to_delete:
+            un_matched_folder_names = process_utils.get_unmatched_recordings(recording_paths, matched_folder_names)
+
+            if un_matched_folder_names:
+                for ind_cam in range(0, len(recording_paths)):
+                    destination_path = os.path.join(recording_paths[ind_cam], 'to_delete')
+
+                    if not os.path.exists(destination_path):
+                        os.makedirs(destination_path)
+
+                    for i in range(0, len(un_matched_folder_names)):
+                        if un_matched_folder_names[i]:
+                            os.rename(os.path.join(recording_paths[ind_cam], un_matched_folder_names[i][ind_cam]),
+                                      os.path.join(destination_path, un_matched_folder_names[i][ind_cam]))
+
+        recordings = []
+        for i in range(0, len(matched_folder_names)):
+            matched_directories = [os.path.join(recording_paths[ind_cam], f) for ind_cam, f in enumerate(matched_folder_names[i])]
+            recordings.append(MultiImageSequence.from_directory(matched_directories, frame_rate, cam_names, type_image=type_image))
+
+        return BatchProcessing(recordings, save_path, nb_cam)
+
+    def multi_processes(self, yaml_path: str, **kwargs: Dict[str, any]) -> None:
+        """
+            Running multiple processes in parallel (much faster tha one by one) from yaml file
+
+        Args:
+            yaml_path: path of the .yaml file containing the parameters of the multiples processes to run in parallel
+
+            Optional arguments:
+                rec_names_to_process: List[str]
+                camn_to_process: List[int]
+                from_fn_name: str (to continue from previous process)
+                delete_previous: bool = False
+        """
+
+        kwargs['yaml_path'] = yaml_path
+        self._do_batch('multi_processes', **kwargs)
+
+    def sample_images(self, step_frame: int, **kwargs: Dict[str, any]) -> None:
+        """
+            Copy samples of the images sequences for all recordings
+
+        Args:
+            step_frame: The number of images between samples to take
+
+            Optional arguments:
+                rec_names_to_process: List[str]
+                camn_to_process: List[int]
+                from_fn_name: str (to continue from previous process)
+                delete_previous: bool = False
+        """
+
+        kwargs['step_frame'] = step_frame
+        self._do_batch('sample', **kwargs)
+
+    def resize_images(self, resize_ratio: float, **kwargs: Dict[str, any]) -> None:
+        """
+            Up/Down-sizing of the images from a resize_ratio
+
+        Args:
+            resize_ratio: Ratio for up- or down-sizing of the images
+
+            Optional arguments:
+                rec_names_to_process: List[str]
+                camn_to_process: List[int]
+                from_fn_name: str (to continue from previous process)
+                delete_previous: bool = False
+        """
+
+        kwargs['resize_ratio'] = resize_ratio
+        self._do_batch('resize', **kwargs)
+   
+    def enhance_images(self, fn_name: str, factor: float, **kwargs: Dict[str, any]) -> None:
+        """
+
+        Args:
+            fn_name: 'contrast', 'brightness', 'sharpness' or 'color'
+            factor: enhancing factor: 1.0 always returns a copy of the original image,
+                    lower factors mean less color (brightness, contrast, etc), and higher values more
+
+            Optional arguments:
+                rec_names_to_process: List[str]
+                camn_to_process: List[int]
+                from_fn_name: str (to continue from previous process)
+                delete_previous: bool = False
+        """
+
+        kwargs['factor'] = factor
+        if fn_name == 'contrast': # Enhancing contrast of images
+            self._do_batch('enhance_contrast', **kwargs)
+
+        elif fn_name == 'enhance_brightness': # Enhancing brightness of images
+            self._do_batch('enhance_color', **kwargs)
+
+        elif fn_name == 'sharpness': # Enhancing sharpness of images
+            self._do_batch('enhance_color', **kwargs)
+
+        elif fn_name == 'color': # Enhancing color of images
+            self._do_batch('enhance_color', **kwargs)
+
+        else:
+            raise ValueError("fn_name unknown. It has to be either 'contrast', 'brightness', 'sharpness' or 'color'")
+
+    def auto_contrast_images(self, **kwargs: Dict[str, any]) -> None:
+        """
+            Will run an auto-contrasting algorythm on each image
+
+        Args:
+            resize_ratio: Ratio for up- or down-sizing of the images
+
+            Optional arguments:
+                rec_names_to_process: List[str]
+                camn_to_process: List[int]
+                from_fn_name: str (to continue from previous process)
+                delete_previous: bool = False
+        """
+
+        self._do_batch('autocontrast', **kwargs)
+
+    def equalize_images(self, **kwargs: Dict[str, any]) -> None:
+        """
+            Will equalize all images of each sequence together
+
+        Args:
+            Optional arguments:
+                rec_names_to_process: List[str]
+                camn_to_process: List[int]
+                from_fn_name: str (to continue from previous process)
+                delete_previous: bool = False
+        """
+
+        self._do_batch('equalize', **kwargs)
+
+    def crop_images(self, from_fn_name: str, height: int, width: int, **kwargs: Dict[str, any]) -> None:
+        """
+            Crop images at the given height and width around previously tracked 2d coordinates in pixels
+
+        Args:
+            from_fn_name: the name of the previous process, which result-folder (/_Process/_???)
+                          contains the *-*-2d_points.csv file will the tracking results that will be used for this cropping
+            height: height to which the images will each be cropped
+            width: width to which the images will each be cropped
+
+            Optional arguments:
+                rec_names_to_process: List[str]
+                camn_to_process: List[int]
+                from_fn_name: str (to continue from previous process)
+                delete_previous: bool = False
+        """
+
+        kwargs['from_fn_name'] = from_fn_name
+        kwargs['height_crop'], kwargs['width_crop'] = height, width
+        self._do_batch('crop', **kwargs)
+
+    def rotate_images(self, degrees: int, **kwargs: Dict[str, any]) -> None:
+        """
+            Rotating cameras views of a given number of degrees
+
+        Args:
+            degrees: Angle at which the images will be rotated counter clockwise around its centre
+
+            Optional arguments:
+                rec_names_to_process: List[str]
+                camn_to_process: List[int]
+                from_fn_name: str (to continue from previous process)
+                delete_previous: bool = False
+        """
+
+        kwargs['degrees'] = degrees
+        self._do_batch('rotate', **kwargs)
+
+    def stitch_images(self, **kwargs: Dict[str, any]) -> None:
+        """
+            Stitch all cameras views together to make one image per frame
+
+        Args:
+            Optional arguments:
+                rec_names_to_process: List[str]
+                camn_to_process: List[int]
+                from_fn_name: str (to continue from previous process)
+                delete_previous: bool = False
+        """
+
+        self._do_batch('stitch', **kwargs)
+
+    def track2d(self, back_subtractor_settings: BackgroundSubtractorSettings = None,
+                blob_detector_settings: BlobDetectorSettings = None, **kwargs: Dict[str, any]) -> None:
+        """
+            Track 2d coordinates of blobs detected on the images using background subtraction
+
+        Args:
+            back_subtractor_settings: BackgroundSubtractorSettings class
+            blob_detector_settings: BlobDetectorSettings class
+
+            Optional arguments:
+                rec_names_to_process: List[str]
+                camn_to_process: List[int]
+                from_fn_name: str (to continue from previous process)
+                delete_previous: bool = False
+        """
+
+        kwargs['back_subtractor_settings'] = back_subtractor_settings if back_subtractor_settings is not None else BackgroundSubtractorSettings()
+        kwargs['blob_detector_settings'] = blob_detector_settings if blob_detector_settings is not None else BlobDetectorSettings()
+        self._do_batch('track2d', **kwargs)
+
+    def recon3d(self, dlt_path: str, reconstructor_settings: Reconstructor3DSettings = None, **kwargs: Dict[str, any]) -> None:
+        """
+            Reconstructing 3d tracks from 2d coordinates (previously computed using track2d)
+
+        Args:
+            dlt_path: path of the .csv file containing th 11*nb_cam DLT coefficients (3d calibration)
+            reconstructor_settings: Reconstructor3DSettings class
+
+            Optional arguments:
+                rec_names_to_process: List[str]
+                camn_to_process: List[int]
+                from_fn_name: str (to continue from previous process)
+                delete_previous: bool = False
+        """
+
+        kwargs['dlt_path'] = dlt_path
+        kwargs['reconstructor_settings'] = reconstructor_settings if reconstructor_settings is not None else Reconstructor3DSettings()
+        self._do_batch('recon3d', **kwargs)
+
+    def save_tiff(self, **kwargs: Dict[str, any]) -> None:
+        """
+            Save images as 8 bits .tiff
+
+        Args:
+            Optional arguments:
+                rec_names_to_process: List[str]
+                camn_to_process: List[int]
+                from_fn_name: str (to continue from previous process)
+                delete_previous: bool = False
+        """
+
+        self._do_batch('save_tiff', **kwargs)
+
+    def save_stroboscopic_image(self, back_subtractor_settings: BackgroundSubtractorSettings = None,
+                                blob_detector_settings: BlobDetectorSettings = None, **kwargs: Dict[str, any]) -> None:
+        """
+            Generate stroboscopic images (composite image base on sampled images and 2d track of the objects)
+            Then save one stroboscopic image per object, per view and per recording
+
+        Args:
+            back_subtractor_settings: BackgroundSubtractorSettings class
+            blob_detector_settings: BlobDetectorSettings class
+
+            Optional arguments:
+                rec_names_to_process: List[str]
+                camn_to_process: List[int]
+                from_fn_name: str (to continue from previous process)
+                delete_previous: bool = False
+        """
+
+        kwargs['back_subtractor_settings'] = back_subtractor_settings if back_subtractor_settings is not None else BackgroundSubtractorSettings()
+        kwargs['blob_detector_settings'] = blob_detector_settings if blob_detector_settings is not None else BlobDetectorSettings()
+        self._do_batch('save_strobe', **kwargs)
+
+    def save_avi(self, frame_rate: int = 24,  lossless: bool = False, **kwargs: Dict[str, any]) -> None:
+        """
+            Save the recordings as .avi
+
+        Args:
+            frame_rate: the frame rate at which the video will be saved
+            lossless: Whether or not to save lossless video (e.g. not compressed)
+
+            Optional arguments:
+                rec_names_to_process: List[str]
+                camn_to_process: List[int]
+                from_fn_name: str (to continue from previous process)
+                delete_previous: bool = False
+        """
+
+        kwargs['frame_rate'] = frame_rate
+        kwargs['lossless'] = lossless
+        self._do_batch('save_avi', **kwargs)
+
+    def analyse_dlc(self, cfg_path: str, shuffle: int, trainingset_index: int, batch_size: int, model_name: str,
+                    save_avi: bool = False, **kwargs: Dict[str, any]) -> None:
+        """
+            Analysing videos with DeepLabCut
+
+        Args:
+            cfg_path:
+            shuffle:
+            trainingset_index:
+            batch_size:
+            save_avi:
+            model_name:
+
+            Optional arguments:
+                rec_names_to_process: List[str]
+                camn_to_process: List[int]
+                from_fn_name: str (to continue from previous process)
+                delete_previous: bool = False
+        """
+
+        kwargs['fg_path'] = cfg_path
+        kwargs['shuffle'] = shuffle
+        kwargs['trainingset_index'] = trainingset_index
+        kwargs['batch_size'] = batch_size
+        kwargs['save_avi'] = save_avi
+        kwargs['model_name'] = model_name
+        self._do_batch('analyse_dlc', **kwargs)
+
+    def load_dlc(self, model_name: str, tracker_method: str, dlt_path: str, **kwargs: Dict[str, any]) -> None:
+        """
+            Load points from DeepLabCut (unscramble, reconstruct3d ...
+
+        Args:
+            model_name:
+            tracker_method:
+            dlt_path: path of the .csv file containing th 11*nb_cam DLT coefficients (3d calibration)
+
+            Optional arguments:
+                rec_names_to_process: List[str]
+                camn_to_process: List[int]
+                from_fn_name: str (to continue from previous process)
+                delete_previous: bool = False
+        """
+
+        kwargs['model_name'] = model_name
+        kwargs['tracker_method'] = tracker_method
+        kwargs['dlt_path'] = dlt_path
+        self._do_batch('load_dlc', **kwargs)
+
+    def fit_skeleton(self, animal_name: str, model_name: str, res_method: str, opt_method: str, dlt_path: str, **kwargs: Dict[str, any]) -> None:
+        """
+            Optimizing fit btw skeletons to get transformation/rotation parameters
+
+        Args:
+            animal_name:
+            model_name:
+            res_method:
+            opt_method:
+            dlt_path: path of the .csv file containing th 11*nb_cam DLT coefficients (3d calibration)
+
+            Optional arguments:
+                rec_names_to_process: List[str]
+                camn_to_process: List[int]
+                from_fn_name: str (to continue from previous process)
+                delete_previous: bool = False
+        """
+
+        # TODO check parameter needed for fit_skeleton, shouldn't require body or wing specific lits
+        # csv_path=csv_wings_geo_path, body_param_names=body_param_names, wing_param_names=wing_param_names,
+
+        kwargs['animal_name'] = animal_name
+        kwargs['model_name'] = model_name
+        kwargs['res_method'], kwargs['opt_method'] = res_method, opt_method
+        kwargs['dlt_path'] = dlt_path
+        self._do_batch('fit_skeleton', **kwargs)
+
+    def plot_skeleton(self, **kwargs: Dict[str, any]) -> None:
+        """
+            Plot (or save) skeleton on raw images
+
+        Args:
+            Optional arguments:
+                rec_names_to_process: List[str]
+                camn_to_process: List[int]
+                from_fn_name: str (to continue from previous process)
+                delete_previous: bool = False
+        """
+
+        self._do_batch('plot_skeleton', **kwargs)
+
+    def delete(self, to_del_names: Dict[str, List[str]], **kwargs: Dict[str, any]) -> None:
+        """
+            Move list of recording folder in a 'to_delete' folder
+
+        Args:
+            to_del_names: 
+            Optional arguments:
+                rec_names_to_process: List[str]
+                camn_to_process: List[int]
+                from_fn_name: str (to continue from previous process)
+                delete_previous: bool = False
+        """
+
+        kwargs['to_del_names'] = to_del_names
+        self._do_batch('delete', **kwargs)
+
+    def _do_batch(self, fn_name: str, **kwargs: Dict[str, any]) -> None:
+        """
+            Function used to run batch processing on all the recordings
+
+        Args:
+            fn_name: Name of the function to run the batch on (e.g. 'track2d', 'crop', 'analyse_dlc', 'fit_skeleton')
+            **kwargs: A dictionary with the process parameters
+        """
+
+        start = time.time()
+
+        step_frame = 1 if 'step_frame' not in kwargs.keys() else kwargs['step_frame']
+        show_plot = False if 'show_plot' not in kwargs.keys() else kwargs['show_plot']
+        delete_previous = 'delete_previous' in kwargs.keys() and kwargs['delete_previous']
+
+        kwargs['camn_to_process'] = list(range(1, self.nb_cam +1)) if 'camn_to_process' not in kwargs.keys() else kwargs['camn_to_process']
+
+        if fn_name in ['autocontrast', 'equalize']:
+            kwargs['factor'] = None
+
+        if fn_name in 'analyse_dlc':
+            if 'trainingset_index' not in kwargs.keys(): kwargs['trainingset_index'] = 0
+            if 'batch_size' not in kwargs.keys(): kwargs['batch_size'] = 2
+
+        if fn_name in ['fit_skeleton', 'plot_skeleton']:
+            assert 1 == 0  # TODO run init of SkeletonFitting here
+
+        self._print_process(fn_name, kwargs)
+        process_path = self._get_path_process(fn_name)
+        from_fn_path = self._get_path_process(kwargs['from_fn_name']) if 'from_fn_name' in kwargs.keys() else self._get_path_process('')
+        if process_path is None: process_path = from_fn_path
+
+        main_rec_names = [names[self.main_camn] for names in self.recording_names]
+        if 'rec_names_to_process' in kwargs.keys():
+            main_rec_names = [name for name in main_rec_names if name in kwargs['rec_names_to_process']]
+            del kwargs['rec_names_to_process']
+
+        # Main process loop: Will do each process step one at a time for each recording
+        for ind_rec, main_rec_name in enumerate(main_rec_names):
+            start_rec = time.time()
+            will_delete_rec = False
+
+            recording = self.recordings[ind_rec]
+            rec_names = self.recording_names[ind_rec]
+            rec_paths = self.recording_paths[ind_rec]
+
+            frames = {camn: recording.get(camn).get_numbers() for camn in range(1, self.nb_cam + 1)}
+
+            if 'from_fn_name' in kwargs.keys():
+                image_paths = {camn: os.path.join(from_fn_path, rec_names[self.main_camn],
+                                                    'cam{0}'.format(camn)) for camn in range(1, self.nb_cam + 1)}
+                image_names = {camn: process_utils.get_image_names_in_directory(image_paths[camn])
+                               for camn in range(1, self.nb_cam + 1)}
+
+                process_dict = yaml.load(open(os.path.join(from_fn_path, rec_names[self.main_camn],
+                                                           rec_names[self.main_camn] + '-process_history.yaml')))
+
+            else:
+                image_names = {camn: recording.get(camn).get_names() for camn in range(1, self.nb_cam + 1)}
+                image_paths = {camn: recording.get(camn).get_path() for camn in range(1, self.nb_cam + 1)}
+
+                process_dict = self._get_init_process_dict(recording, rec_paths, frames)
+
+            if fn_name == 'multi_processes':
+                print('> Processing {0}...'.format(main_rec_name))
+                multi_processes_dict = yaml.load(open(os.path.join(kwargs['yaml_path'])))
+                multi_processes_dict[0] = process_dict[0]
+
+                self._multi_processes(ind_rec, multi_processes_dict, show_plot)
+
+                print('> {0} as been processed (time elapsed: {1:.4f} s)'.format(main_rec_name, time.time() - start_rec))
+                continue
+
+            save_paths = {camn: os.path.join(process_path, rec_names[self.main_camn],
+                                             'cam{0}'.format(camn)) for camn in range(1, self.nb_cam + 1)}
+
+            cpt = 1
+            while cpt in process_dict.keys(): cpt += 1
+            process_dict[cpt] = {'fn': fn_name, 'kwargs': process_utils.convert_kwargs_to_be_writen_in_yaml(kwargs)}
+
+            if fn_name == 'stitch':
+                stitch_all_images(image_names, image_paths, frames, save_paths, self.main_camn)
+
+            elif fn_name == 'recon3d':
+                pts_csv = {}
+                for camn in range(1, self.nb_cam + 1):
+                    pts_csv[camn] = np.genfromtxt(os.path.join(save_paths[camn], rec_names[camn] + '-2d_points.csv'),
+                                                  delimiter=',', skip_header=0, names=True)
+
+                recon_sets = Reconstructor3DSettings() if 'reconstructor_settings' not in kwargs.keys() \
+                                                       else kwargs['reconstructor_settings']
+                reconstruct_3d_tracks(pts_csv, kwargs['dlt_path'], rec_names, save_paths,
+                                      show_plot=show_plot, reconstructor_settings=recon_sets)
+
+            elif fn_name == 'analyse_dlc':
+                video_paths = glob.glob(os.path.join(image_paths[self.main_camn], '*.avi'))
+
+                if not len(video_paths) == 0:
+                    deeplabcut.analyze_videos(kwargs['cfg_path'], video_paths, destfolder=save_paths[self.main_camn],
+                                              shuffle=kwargs['shuffle'], trainingset_index=kwargs['trainingset_index'],
+                                              batch_size=kwargs['batch_size'], save_as_csv=True)
+
+                    if 'save_avi' in kwargs.keys() and kwargs['save_avi']:
+                        deeplabcut.create_video_with_all_detections(save_paths[self.main_camn], video_paths,
+                                                                    kwargs['model_name'], destfolder=save_paths[self.main_camn])
+
+            elif fn_name == 'load_dlc':
+                # TODO Give options to load from csv, pickle or hdf5?
+                # skeleton2d = self.load_dlc_from_csv(kwargs['model_name'], image_paths[self.main_camn], kwargs['tracker_method'], self.nb_cam)
+                # skeleton2d = self.load_dlc_from_hdf5(kwargs['model_name'], image_paths[self.main_camn], kwargs['tracker_method'], self.nb_cam)
+                skeleton2d = load_dlc_from_full_pickle(kwargs['model_name'], image_paths[self.main_camn])
+
+                #self.test_unwrap(kwargs['dlt_path'])
+                unscramble_views2d_dlc(skeleton2d, process_dict)
+                reverse_processes_2d_points(skeleton2d, process_dict, self.threshold_likelihood, image_sizes[camn], self.save_path)
+                save_dlc2d_points_in_csv(skeleton2d, save_paths)
+
+                #self.unscramble_sides2d_and_recon3d_dlc(kwargs['dlt_path'])
+                recon3d_dlc(skeleton2d, kwargs['dlt_path'])
+                save_dlc3d_points_in_csv(skeleton3d, save_paths)
+
+            elif fn_name == 'fit_skeleton':
+                skeleton_fitting = SkeletonFitting()
+
+                csv_paths = glob.glob(os.path.join(image_paths[self.main_camn], '*-3d_points.csv'))
+                if not len(csv_paths) == 0:
+                    # TODO load in SkeletonFitting? currently the skeletons are not used
+                    skeleton3d = load_skeleton3d_from_csv(kwargs['model_name'], image_paths, self.main_camn)
+                    skeleton2d = load_skeleton2d_from_csv(kwargs['model_name'], image_paths, self.main_camn)
+
+                    if 'angles_to_correct' not in kwargs.keys(): kwargs['angles_to_correct'] = []
+                    skeleton_fitting.optim_fit_skeleton(kwargs['animal_name'], kwargs['body_param_names'],
+                                            kwargs['wing_param_names'], kwargs['res_method'], kwargs['opt_method'],
+                                            rec_paths, save_paths, kwargs['dlt_path'])
+
+            elif fn_name == 'plot_skeleton':
+                skeleton_fitting = SkeletonFitting()
+
+                csv_paths = glob.glob(os.path.join(image_paths[self.main_camn], '*-2d_points.csv'))
+                if not len(csv_paths) == 0:
+                    load_skeleton2d_from_csv(kwargs['model_name'], image_paths, self.main_camn)
+                    body_params, wings_params = skeleton_fitting.load_params_from_csv(image_paths[self.main_camn])
+
+                    dlt_coefs = camera.calib.read_dlt_coefs(kwargs['dlt_path'])
+                    skeleton_fitting.plot_skeleton2d(body_params, wings_params, rec_paths, dlt_coefs, modulo=step_frame,
+                                         save_images=True, save_paths=save_paths)
+
+                    if 'save_avi' in kwargs.keys() and kwargs['save_avi']:
+                        frame_rate = 24 if 'frame_rate' not in kwargs.keys() else kwargs['frame_rate']
+                        lossless = False if 'lossless' not in kwargs.keys() else kwargs['lossless']
+
+                        save_avi(image_names[camn], image_paths[camn], rec_names[camn], save_paths[camn],
+                                 frame_rate, step_frame, lossless)
+
+            elif will_delete_rec:
+                rec_names_to_del = {camn: recording.get(camn).get_names() for camn in range(1, self.nb_cam + 1)}
+                rec_paths_to_del = {camn: recording.get(camn).get_path() for camn in range(1, self.nb_cam + 1)}
+                self._delete_recordings(rec_names_to_del, rec_paths_to_del)
+
+            else:
+                for camn in range(1, self.nb_cam + 1):
+                    if will_delete_rec or camn not in kwargs['camn_to_process']: continue
+
+                    if delete_previous:
+                        if os.path.exists(save_paths[camn]) and rec_paths[camn] != save_paths[camn]:
+                            shutil.rmtree(save_paths[camn], ignore_errors=True)
+
+                    if not os.path.exists(save_paths[camn]): os.makedirs(save_paths[camn])
+
+                    image_sizes = process_dict[0]['image_sizes']
+
+                    if fn_name == 'track2d':
+                        back_sub_sets = BackgroundSubtractorSettings() if 'back_subtractor_settings' not in kwargs.keys() \
+                                                                       else kwargs['back_subtractor_settings']
+                        blob_det_sets = BlobDetectorSettings() if 'blob_detector_settings' not in kwargs.keys() \
+                                                               else kwargs['blob_detector_settings']
+
+                        track_2d_objects(recording.get(camn), rec_names[camn], save_paths[camn], show_plot=show_plot,
+                                         back_subtractor_settings=back_sub_sets, blob_detector_settings=blob_det_sets)
+
+                    elif fn_name == 'crop':
+                        coords_objs = interp_2d_coords_of_objs(frames[camn], image_paths[camn], save_paths[camn],
+                                                               image_sizes[camn], show_plot=show_plot)
+                        assert coords_objs, "Could not find any 3d track"
+
+                        crop_all_images(coords_objs, image_names[camn], image_paths[camn], save_paths[camn],
+                                        kwargs['height_crop'], kwargs['width_crop'])
+
+                    elif fn_name == 'save_strobe':
+                        radius = 50 if 'radius' not in kwargs.keys() else kwargs['radius']
+                        shape = 'rectangle' if 'shape' not in kwargs.keys() else kwargs['shape']
+
+                        coords_objs = interp_2d_coords_of_objs(frames[camn], image_paths[camn], save_paths[camn],
+                                                               image_sizes[camn], show_plot=show_plot)
+                        assert coords_objs, "Could not find any 3d track"
+
+                        back_sub_sets = BackgroundSubtractorSettings() if 'back_subtractor_settings' not in kwargs.keys() \
+                                                                       else kwargs['back_subtractor_settings']
+                        blob_det_sets = BlobDetectorSettings() if 'blob_detector_settings' not in kwargs.keys() \
+                                                               else kwargs['blob_detector_settings']
+
+                        save_stroboscopic_images(recording.get(camn), frames[camn], coords_objs, image_names[camn][0],
+                                                 save_paths[camn], radius, shape, step_frame,
+                                                 back_subtractor_settings=back_sub_sets,
+                                                 blob_detector_settings=blob_det_sets)
+
+                    elif fn_name == 'save_avi':
+                        if os.path.exists(image_paths[camn]):
+                            frame_rate = 24 if 'frame_rate' not in kwargs.keys() else kwargs['frame_rate']
+                            lossless = False if 'lossless' not in kwargs.keys() else kwargs['lossless']
+
+                            save_avi(image_names[camn], image_paths[camn], rec_names[camn], save_paths[camn],
+                                     frame_rate, step_frame, lossless)
+
+                    elif fn_name == 'delete':
+                        if rec_names[camn] in kwargs['to_del_names']:
+                            will_delete_rec = True
+
+                    else:
+                        for image_name in image_names[camn]:
+                            img = read_image(os.path.join(image_paths[camn], image_name))
+                            image_save_name = image_name
+
+                            if fn_name == 'rotate':
+                                img = img.rotate(kwargs['degrees'])
+
+                            elif fn_name == 'resize':
+                                img = img_utils.resize(img, kwargs['resize_ratio'])
+
+                            elif fn_name in ['enhance_contrast', 'enhance_brightness', 'enhance_sharpness', 'enhance_color',
+                                             'autocontrast', 'equalize']:
+                                img = img_utils.enhance(img, fn_name, kwargs['factor'])
+
+                            elif fn_name == 'save_tiff':
+                                img = Image.open(os.path.join(image_paths[camn], image_name))
+                                img_utils.save_tiff(img, os.path.join(save_paths[camn], image_name))
+
+                            if fn_name in ['rotate', 'resize', 'sample', 'enhance_contrast', 'enhance_brightness',
+                                           'enhance_sharpness', 'enhance_color', 'autocontrast', 'equalize']:
+                                img.save(os.path.join(save_paths[camn], image_save_name))
+                                img.close()
+
+                    if os.path.exists(save_paths[camn]) and not os.listdir(save_paths[camn]):  # if save_path empty
+                        shutil.rmtree(save_paths[camn], ignore_errors=True)
+
+            # Save process history in yaml file
+            self._write_process_dict(process_dict, rec_names[self.main_camn], os.path.join(process_path, rec_names[self.main_camn]))
+
+            print('> {0} as been processed (time elapsed: {1:.4f} s)'.format(main_rec_name, time.time() - start_rec))
+
+        print('> Full batch as been processed (total time elapsed: {0:.4f} s)'.format(time.time() - start))
+
+    def _multi_processes(self, ind_rec: int, multi_processes_dict: Dict[int, Dict[str, any]], show_plot=False) -> None:
+        image_sizes = multi_processes_dict[0]['image_sizes']
+
+        img, obj_names = {}, []
+        multi_processes_nums = list(multi_processes_dict.keys())
+        for multi_processes_num in multi_processes_nums[1:]:
+            start = time.time()
+            fn_name = multi_processes_dict[multi_processes_num]['fn']
+            kwargs = multi_processes_dict[multi_processes_num]['kwargs']
+            step_frame = 1 if 'step_frame' not in kwargs.keys() else kwargs['step_frame']
+            kwargs['camn_to_process'] = list(range(1, self.nb_cam + 1) if 'camn_to_process' not in kwargs.keys()
+                                                                       else kwargs['camn_to_process'])
+            recording = self.recordings[ind_rec]
+            rec_names = self.recording_names[ind_rec]
+            rec_paths = self.recording_paths[ind_rec]
+
+            from_fn_path = self._get_path_process(kwargs['from_fn_name']) if 'from_fn_name' in kwargs.keys() \
+                                                                          else self._get_path_process('')
+            process_path = self._get_path_process(fn_name)
+            if process_path is None: process_path = from_fn_path
+
+            image_names = {camn: self.recordings[ind_rec].get(camn).get_names() for camn in range(1, self.nb_cam + 1)}
+            image_paths = {camn: recording.get(camn).get_path() for camn in range(1, self.nb_cam + 1)}
+
+            save_paths = {camn: os.path.join(process_path, rec_names[self.main_camn],
+                                             'cam{0}'.format(camn)) for camn in range(1, self.nb_cam + 1)}
+
+            frames = {camn: recording.get(camn).get_numbers() for camn in range(1, self.nb_cam + 1)}
+
+            for camn in range(1, self.nb_cam + 1):
+                if 'delete_previous' in kwargs.keys() and kwargs['delete_previous']:
+                    if os.path.exists(save_paths[camn]) and rec_paths[camn] != save_paths[camn]:
+                        shutil.rmtree(save_paths[camn], ignore_errors=True)
+                        
+            for camn in range(1, self.nb_cam +1):
+
+                if not obj_names or 'obj0' in obj_names:
+                    file_names = sorted(os.listdir(image_paths[camn]), key=lambda s: s[:s.rfind('.')])
+                    csv_names = [s for s in file_names if '-2d_points.csv' in s and '-obj' in s]
+
+                    if csv_names:
+                        obj_names = np.unique([s[process_utils.find_char(s, '-')[0] + 1:process_utils.find_char(s, '-')[1]] for s in csv_names])
+                        if 'obj0' in img[camn].keys(): img[camn] = {}
+                    else:
+                        obj_names = ['obj0']
+
+                if not os.path.exists(save_paths[camn]): os.makedirs(save_paths[camn])
+                if not camn in kwargs['camn_to_process']: continue
+                if fn_name in ['recon3d', 'stitch']: continue
+
+                if fn_name == 'track2d':
+                    back_sub_sets = BackgroundSubtractorSettings() if 'back_subtractor_settings' not in kwargs.keys() \
+                        else BackgroundSubtractorSettings.from_dict(kwargs['back_subtractor_settings'])
+                    blob_det_sets = BlobDetectorSettings() if 'blob_detector_settings' not in kwargs.keys() \
+                        else BlobDetectorSettings.from_dict(kwargs['blob_detector_settings'])
+
+                    track_2d_objects(recording.get(camn), rec_names[camn], save_paths[camn],
+                                     back_subtractor_settings=back_sub_sets,
+                                     blob_detector_settings=blob_det_sets, show_plot=show_plot)
+                    continue
+
+                elif fn_name == 'crop':
+                    csv_path = os.path.join(from_fn_path, rec_names[self.main_camn], 'cam{0}'.format(camn))
+                    coords_objs = interp_2d_coords_of_objs(frames[camn], csv_path, save_paths[camn],
+                                                           image_sizes[camn], show_plot=show_plot)
+                    assert coords_objs, "Could not find any 3d track"
+                    obj_names = coords_objs.keys()
+
+                elif fn_name == 'save_strobe':
+                    radius = 50 if 'radius' not in kwargs.keys() else kwargs['radius']
+                    shape = 'rectangle' if 'shape' not in kwargs.keys() else kwargs['shape']
+
+                    csv_path = os.path.join(from_fn_path, rec_names[self.main_camn], 'cam{0}'.format(camn))
+                    coords_objs = interp_2d_coords_of_objs(frames[camn], csv_path, save_paths[camn],
+                                                           image_sizes[camn], show_plot=show_plot)
+                    assert coords_objs, "Could not find any 3d track"
+
+                    back_sub_sets = BackgroundSubtractorSettings() if 'back_subtractor_settings' not in kwargs.keys() else kwargs['back_subtractor_settings']
+                    blob_det_sets = BlobDetectorSettings() if 'blob_detector_settings' not in kwargs.keys() else kwargs['blob_detector_settings']
+
+                    save_stroboscopic_images(recording.get(camn), frames[camn], coords_objs, image_names[camn][0],
+                                             save_paths[camn], radius, shape, step_frame,
+                                             back_subtractor_settings=back_sub_sets,
+                                             blob_detector_settings=blob_det_sets)
+                    continue
+
+                elif fn_name == 'save_avi':
+                    frame_rate = 24 if 'frame_rate' not in kwargs.keys() else kwargs['frame_rate']
+                    lossless = False if 'lossless' not in kwargs.keys() else kwargs['lossless']
+
+                if camn not in img.keys(): img[camn] = {}
+
+                for obj_name in obj_names:
+                    if obj_name not in img[camn].keys():
+                        img[camn][obj_name] = {}
+
+                    #if fn_name not in ['track2d', 'recon3d'] \
+                    #        and obj_name == 'obj0' and 'from_fn_name' in kwargs.keys(): break
+
+                    if fn_name == 'save_avi':
+                        if 'obj0' == obj_name: save_name = rec_names[camn] + '.avi'
+                        else: save_name = rec_names[camn] + '-' + obj_name + '.avi'
+
+                        if len(np.shape(np.asarray(img[camn][obj_name][0]))) != 2: break
+                        height_img, width_img = np.shape(np.asarray(img[camn][obj_name][0]))
+
+                        codec = cv2.VideoWriter_fourcc(*'HFYU') if lossless else cv2.VideoWriter_fourcc(*'MP42')
+                        writer = cv2.VideoWriter(os.path.join(save_paths[camn], save_name), codec, frame_rate, (width_img, height_img))
+                        #continue
+
+                    for ind_img, image_name in enumerate(image_names[camn]):
+                        if ind_img not in img[camn][obj_name].keys():
+                            img[camn][obj_name][ind_img] = read_image(os.path.join(image_paths[camn], image_name))
+
+                        if not img[camn][obj_name][ind_img]: continue
+
+                        if fn_name == 'crop':
+                            coords_obj = {key: coords_objs[obj_name][key][ind_img] for key in coords_objs[obj_name].keys()}
+                            
+                            img[camn][obj_name][ind_img] = crop_image(img[camn][obj_name][ind_img], coords_obj,
+                                                                      kwargs['height_crop'], kwargs['width_crop'])
+
+                        elif fn_name == 'rotate':
+                            img[camn][obj_name][ind_img] = img[camn][obj_name][ind_img].rotate(kwargs['degrees'])
+
+                        elif fn_name == 'resize':
+                            img[camn][obj_name][ind_img] = img_utils.resize(img[camn][obj_name][ind_img], kwargs['resize_ratio'])
+
+                        elif fn_name in ['enhance_contrast', 'enhance_brightness', 'enhance_sharpness', 'enhance_color',
+                                         'autocontrast', 'equalize']:
+                            img[camn][obj_name][ind_img] = img_utils.enhance(img[camn][obj_name][ind_img], fn_name, kwargs['factor'])
+
+                        elif fn_name == 'save_avi':
+                            writer.write(cv2.cvtColor(np.asarray(img[camn][obj_name][ind_img]), cv2.COLOR_GRAY2BGR))
+
+                    if fn_name == 'save_avi': writer.release()
+
+                    if 'step_frame' in kwargs.keys() or multi_processes_num == multi_processes_nums[-1]:  # Will save images
+                        if fn_name in ['crop', 'rotate', 'resize', 'sample', 'enhance_contrast', 'enhance_brightness',
+                                       'enhance_sharpness', 'enhance_color', 'autocontrast', 'equalize']:
+                            for ind_img, image_name in enumerate(image_names[camn]):
+                                if 'obj0' == obj_name: image_save_name = image_name
+                                else: image_save_name = image_name[:image_name.rfind('.')] + '-' + obj_name + image_name[image_name.rfind('.'):]
+
+                                img[camn][obj_name][ind_img].save(os.path.join(save_paths[camn], image_save_name))
+                                #img[camn][obj_name][i] = []
+
+                if os.path.exists(save_paths[camn]) and not os.listdir(save_paths[camn]):  # if save_path empty
+                    shutil.rmtree(save_paths[camn], ignore_errors=True)
+
+            if fn_name == 'recon3d':
+                pts_csv = {}
+                for camn in range(1, self.nb_cam + 1):
+                    pts_csv[camn] = np.genfromtxt(os.path.join(save_paths[camn], rec_names[camn] + '-2d_points.csv'),
+                                                  delimiter=',', skip_header=0, names=True)
+
+                recon_sets = Reconstructor3DSettings() if 'reconstructor_settings' not in kwargs.keys() \
+                    else Reconstructor3DSettings.from_dict(kwargs['reconstructor_settings'])
+                reconstruct_3d_tracks(pts_csv, kwargs['dlt_path'], rec_names, save_paths, show_plot=show_plot,
+                                      reconstructor_settings=recon_sets)
+
+            elif fn_name == 'stitch':
+                img = stitch_images_multiprocessing(img, image_names, image_paths, frames, save_paths, obj_names,
+                                                    multi_processes_num, multi_processes_nums, self.main_camn)
+            
+            # Save process history in yaml file
+            self._write_process_dict(multi_processes_dict, rec_names[self.main_camn],
+                                     os.path.join(process_path, rec_names[self.main_camn]))
+
+            print('>> fn {0} as been performed (time elapsed: {1:.4f} s)'.format(fn_name, time.time() - start))
+
+    @staticmethod
+    def _print_process(fn_name: str, kwargs: Dict[str, any]) -> None:
+        print('____________________________')
+        if fn_name == 'multi_processes':
+            print('> Starting batch images: Running multiple processes')
+        elif fn_name == 'sample':
+            print('> Starting batch images: Copying samples')
+        elif fn_name == 'resize':
+            print('> Starting batch images: Up/Down-sizing images (ratio: {0})'.format(kwargs['resize_ratio']))
+        elif fn_name == 'enhance_contrast':
+            print('> Starting batch images: Enhancing contrast of images (factor: {0})'.format(kwargs['factor']))
+        elif fn_name == 'enhance_brightness':
+            print('> Starting batch images: Enhancing brightness of images (factor: {0})'.format(kwargs['factor']))
+        elif fn_name == 'enhance_sharpness':
+            print('> Starting batch images: Enhancing sharpness of images (factor: {0})'.format(kwargs['factor']))
+        elif fn_name == 'enhance_color':
+            print('> Starting batch images: Enhancing color of images (factor: {0})'.format(kwargs['factor']))
+        elif fn_name == 'autocontrast':
+            print('> Starting batch images: Auto-Enhancing contrast of images')
+        elif fn_name == 'equalize':
+            print('> Starting batch images: Equalizing of images')
+        elif fn_name == 'crop':
+            print('> Starting batch images: Cropping images (height: {0}, width: {1})'.format(kwargs['height_crop'], kwargs['width_crop']))
+        elif fn_name == 'rotate':
+            print('> Starting batch images: Rotating cameras views {0} at {1} degrees '.format(kwargs['camn_to_process'], kwargs['degrees']))
+        elif fn_name == 'stitch':
+            print('> Starting batch images: Stitching all cameras views together')
+        elif fn_name == 'track2d':
+            print('> Starting batch images: Tracking 2d coordinates of blobs')
+        elif fn_name == 'recon3d':
+            print('> Starting batch images: Reconstructing 3d tracks from 2d coordinates')
+        elif fn_name == 'save_tiff':
+            print('> Starting batch images: Saving recordings as 8 bits .tiff')
+        elif fn_name == 'save_strobe':
+            print('> Starting batch images: Generating stroboscopic images from 2d coordinates')
+        elif fn_name == 'save_avi':
+            print('> Starting batch images: Saving recordings as .avi')
+        elif fn_name == 'analyse_dlc':
+            print('> Starting batch images: Analysing videos with DeepLabCut (model: {0})'.format(kwargs['model_name']))
+        elif fn_name == 'load_dlc':
+            print('> Starting batch images: Load points from DeepLabCut (unscramble, reconstruct3d ... - model: {0})'.format(kwargs['model_name']))
+        elif fn_name == 'fit_skeleton':
+            print('> Starting batch images: Optimizing fit btw skeletons to get transformation/rotation parameters')
+        elif fn_name == 'plot_skeleton':
+            print('> Starting batch images: Plot (or save) skeleton on raw images')
+        elif fn_name == 'delete':
+            print('> Starting batch images: Deleting following recordings: {0}'.format(kwargs['to_del_names']))
+
+    def _get_path_process(self, fn_name: str = '') -> str:
+
+        if fn_name == 'crop':
+            process_path = os.path.join(self.save_path, '_Cropped')
+        elif fn_name == 'sample':
+            process_path = os.path.join(self.save_path, '_Sample')
+        elif fn_name == 'resize':
+            process_path = os.path.join(self.save_path, '_Resized')
+        elif fn_name in ['enhance_contrast', 'enhance_brightness', 'enhance_sharpness', 'enhance_color', 'autocontrast', 'equalize']:
+            process_path = os.path.join(self.save_path, '_Enhanced')
+        elif fn_name == 'save_tiff':
+            process_path = os.path.join(self.save_path, '_Tiff')
+        elif fn_name == 'save_strobe':
+            process_path = os.path.join(self.save_path, '_Strobe')
+        elif fn_name == 'save_avi':
+            process_path = os.path.join(self.save_path, '_Avi')
+        elif fn_name == 'stitch':
+            process_path = os.path.join(self.save_path, '_Stitched')
+        elif fn_name in ['analyse_dlc', 'load_dlc']:
+            process_path = os.path.join(self.save_path, '_DLC')
+        elif fn_name == 'plot_skeleton':
+            process_path = os.path.join(self.save_path, '_Plots')
+
+        elif fn_name in ['', 'rotate', 'delete', 'track2d', 'recon3d', 'multi_processes', 'fit_skeleton']:
+            process_path = None
+
+        else:
+            raise ValueError('WARN: incorrect function name (fn_name = {0})'.format(fn_name))
+
+        return process_path
+
+    def _get_init_process_dict(self, recording: MultiImageSequence, rec_paths: Dict[str, str],
+                               frames: Dict[str, List[int]]) -> Dict[any, any]:
+
+        process_dict, cpt = {}, 1
+
+        last_frame = int(np.max([frames[camn][-1] for camn in range(1, self.nb_cam + 1)]))
+
+        missing_frames, image_sizes = {1: {}}, {1: {}}
+        for camn in range(1, self.nb_cam + 1):
+            missing_frames[camn] = list(set(range(1, last_frame +1)) - set(frames[camn]))
+            image_sizes[camn] = Image.open(os.path.join(recording.get(camn).get_path(), recording.get(camn).get_names()[0])).size
+
+        process_dict[0] = {'fn': 'initialization', 'nb_cam': self.nb_cam,
+                           'paths': rec_paths, 'process_path': self.save_path,
+                           'image_sizes': image_sizes, 'last_frame': last_frame, 'missing_frames': missing_frames}
+
+        return process_dict
+
+    @staticmethod
+    def _write_process_dict(process_dict: Dict[int, Dict[str, any]], save_name: str, save_path: str) -> None:
+        with open(os.path.join(save_path, save_name + '-process_history.yaml'), 'w') as yaml_file:
+            yaml.safe_dump(process_dict, yaml_file, encoding='utf-8', allow_unicode=True)
+
+    @staticmethod
+    def _delete_recordings(rec_names: Dict[str, List[str]], rec_paths: Dict[str, List[str]]) -> None:
+
+        for camn in rec_paths.keys():
+
+            if not len(rec_names[camn]) == 0:
+                if not os.path.exists(os.path.join(rec_paths[camn], 'to_delete')):
+                    os.makedirs(os.path.join(rec_paths[camn], 'to_delete'))
+
+                for rec_name in rec_names:
+                    os.rename(os.path.join(rec_paths[camn], rec_name),
+                              os.path.join(rec_paths[camn], 'to_delete', rec_name))
+                    # shutil.rmtree(os.path.join(self.cam_paths[camn], rec_name), ignore_errors=True)
+
+                    # print('>> recording from {0} has been deleted'.format(time.strftime('%Y%m%d_%H%M%S', rec_dates[camn])))
+
+
+if __name__ == '__main__':
+
+    # TODO update loading BatchProcessing from path
+    print('TODO')
+
+
+
+
diff --git a/process/dlc_postprocessing.py b/process/dlc_postprocessing.py
new file mode 100644
index 0000000000000000000000000000000000000000..27844d71dc31760b91e5e4492df3d51601c110f9
--- /dev/null
+++ b/process/dlc_postprocessing.py
@@ -0,0 +1,857 @@
+import copy, glob, os, pickle
+
+import pandas as pd
+import numpy as np
+import yaml
+
+import matplotlib.pyplot as plt
+from PIL import Image
+from scipy import signal
+
+from point_tracker import Reconstructor3D
+from point_tracker.reconstructor3d import Reconstructor3DSettings
+
+from point_tracker.tracker2d import Tracker2D, BlobDetectorSettings, BackgroundSubtractorSettings
+
+import skeleton_fitter.skeleton_modules.insect_body_slim as body
+
+
+def load_dlc_from_csv(model_name, tracker_method, path):
+    process_dict = yaml.load(open(glob.glob(os.path.join(path, '*-process_history.yaml'))[0]))
+    frames = list(range(1, process_dict[0]['last_frame']))
+
+    nb_cam = len(process_dict[0]['missing_frames'].keys())
+    missing_frames = list(np.unique(np.concatenate([process_dict[0]['missing_frames'][camn] for camn in range(1, nb_cam + 1)])))
+    [missing_frames.remove(missing_frame) for missing_frame in list(set(missing_frames) - set(frames))]
+    [frames.remove(missing_frame) for missing_frame in missing_frames]
+
+    if tracker_method == 'skeleton': tracker_method_str = '_sk'
+    elif tracker_method == 'box': tracker_method_str = '_bk'
+
+    dlc_csv_paths = glob.glob(os.path.join(path, '*' + model_name + tracker_method_str + '.csv'))
+
+    obj_names = []
+    for dlc_csv_path in dlc_csv_paths: # go throught the various obj
+        obj_names.append(dlc_csv_path[len(path) + 1:dlc_csv_path.index(model_name)])
+
+    skeleton2d = {'obj_names': obj_names, 'model_name': model_name}
+    for i, obj_name in enumerate(obj_names):
+        df = pd.read_csv(dlc_csv_paths[i], header=[1, 2])
+        label_names = np.unique([s[0][:-2] for s in df.keys()[1:]])
+        new_label_names = [label.replace(' ', '_') for label in label_names]
+
+        skeleton2d[obj_name] = {'frames': frames, 'label_names': new_label_names}
+        for j, label in enumerate(label_names):
+            new_label = new_label_names[j]
+
+            skeleton2d[obj_name][new_label] = {1: {}}
+            for camn in range(1, nb_cam + 1):
+                label_key = label + ' {0}'.format(camn)
+
+                skeleton2d[obj_name][new_label][camn] = {'x': np.array(df[(label_key, 'x')]),
+                                                              'y': np.array(df[(label_key, 'y')]),
+                                                              'likelihood': np.array(df[(label_key, 'likelihood')])}
+
+    return skeleton2d
+
+
+def load_dlc_from_full_pickle(model_name, path):
+    process_dict = yaml.load(open(glob.glob(os.path.join(path, '*-process_history.yaml'))[0]))
+    frames = list(range(1, process_dict[0]['last_frame']))
+
+    nb_cam = len(process_dict[0]['missing_frames'].keys())
+    missing_frames = list(np.unique(np.concatenate([process_dict[0]['missing_frames'][camn] for camn in range(1, nb_cam + 1)])))
+    [missing_frames.remove(missing_frame) for missing_frame in list(set(missing_frames) - set(frames))]
+    [frames.remove(missing_frame) for missing_frame in missing_frames]
+
+    dlc_pickle_paths = glob.glob(os.path.join(path, '*' + model_name + '_full.pickle'))
+
+    obj_names = []
+    for dlc_pickle_path in dlc_pickle_paths:  # go throught the various obj
+        obj_names.append(dlc_pickle_path[len(path) + 22:dlc_pickle_path.index(model_name)])
+
+    skeleton2d = {'obj_names': obj_names, 'model_name': model_name}
+    for obj_name in obj_names:
+        with open(dlc_pickle_paths[0], "rb") as file:
+            full_dict = pickle.load(file)
+
+        header = full_dict.pop("metadata")
+        label_names = np.unique([s[:-2] for s in header["all_joints_names"]])
+        new_label_names = [label.replace(' ', '_') for label in label_names]
+
+        skeleton2d[obj_name] = {'frames': frames, 'label_names': new_label_names}
+        for i, label in enumerate(label_names):
+            new_label = new_label_names[i]
+
+            skeleton2d[obj_name][new_label] = {1: {}}
+            for camn in range(1, nb_cam + 1):
+                skeleton2d[obj_name][new_label][camn] = {'x': np.nan * np.ones(len(frames)),
+                                                              'y': np.nan * np.ones(len(frames)),
+                                                              'likelihood': np.nan * np.ones(len(frames))}
+
+        for frame_name in full_dict.keys():
+            try:
+                frame_in = frames.index(int(frame_name[-4:]) + 1)
+            except ValueError:
+                continue
+
+            for i, label in enumerate(label_names):
+                new_label = new_label_names[i]
+
+                for camn in range(1, nb_cam + 1):
+                    try: label_in = header["all_joints_names"].index(label + ' {0}'.format(camn))
+                    except ValueError: continue
+
+                    if len(full_dict[frame_name]['confidence'][label_in]) == 0: continue
+                    best_in = np.argmax(full_dict[frame_name]['confidence'][label_in])
+
+                    skeleton2d[obj_name][new_label][camn]['x'][frame_in] = \
+                        full_dict[frame_name]['coordinates'][0][label_in][best_in][0]
+                    skeleton2d[obj_name][new_label][camn]['y'][frame_in] = \
+                        full_dict[frame_name]['coordinates'][0][label_in][best_in][1]
+                    skeleton2d[obj_name][new_label][camn]['likelihood'][frame_in] = \
+                        full_dict[frame_name]['confidence'][label_in][best_in]
+
+    return skeleton2d
+
+
+def load_dlc_from_hdf5(model_name, tracker_method, path):
+    process_dict = yaml.load(open(glob.glob(os.path.join(path, '*-process_history.yaml'))[0]))
+    frames = list(range(1, process_dict[0]['last_frame']))
+
+    nb_cam = len(process_dict[0]['missing_frames'].keys())
+    missing_frames = np.unique(
+        [np.squeeze(process_dict[0]['missing_frames'][camn]) for camn in range(1, nb_cam + 1)])
+    [frames.remove(missing_frame) for missing_frame in missing_frames]
+
+    if tracker_method == 'skeleton': tracker_method_str = '_sk'
+    elif tracker_method == 'box': tracker_method_str = '_bk'
+
+    dlc_hdf5_paths = glob.glob(os.path.join(path, '*' + model_name + tracker_method_str + '.h5'))
+
+    obj_names = []
+    for dlc_hdf5_path in dlc_hdf5_paths:  # go throught the various obj
+        obj_names.append(dlc_hdf5_path[len(path) + 22:dlc_hdf5_path.index(model_name)])
+
+    skeleton2d = {'obj_names': obj_names, 'model_name': model_name}
+    for i, obj_name in enumerate(obj_names):
+        hdf5_dict = pd.read_hdf(dlc_hdf5_paths[i], 'df_with_missing')
+        # hdf5_dict.to_csv(os.path.join(save_path, hdf5_name.split(".h5")[0] + '.csv'))
+
+        # network_name = hdf5_dict.columns[0][0]
+        hdf5_dict.columns = hdf5_dict.columns.droplevel([0, 1])  # remove first two lines of header (network_name, individual)
+
+        label_names = np.unique([s[:-2] for s in hdf5_dict.columns.droplevel(1)])
+        new_label_names = [label.replace(' ', '_') for label in label_names]
+        # frames = hdf5_dict.index
+
+        skeleton2d[obj_name] = {'frames': frames, 'label_names': new_label_names}
+        for i, label in enumerate(label_names):
+            new_label = new_label_names[i]
+
+            skeleton2d[obj_name][new_label] = {1: {}}
+            for camn in range(1, nb_cam + 1):
+                label_key = label + ' {0}'.format(camn)
+
+                skeleton2d[obj_name][new_label][camn] = {'x': np.array(hdf5_dict[(label_key, 'x')]),
+                                                              'y': np.array(hdf5_dict[(label_key, 'y')]),
+                                                              'likelihood': np.array(hdf5_dict[(label_key, 'likelihood')])}
+
+                # skeleton2d[obj_name][new_label][camn] = [[1]*3 for i in range(len(hdf5_dict[(label_key, 'x')]))]
+                # # skeleton2d[obj_name][new_label][camn] = np.ones((len(hdf5_dict[(label_key, 'x')]), 3))
+                # skeleton2d[obj_name][new_label][camn][:, 0] = np.array(hdf5_dict[(label_key, 'x')])
+                # skeleton2d[obj_name][new_label][camn][:, 1] = np.array(hdf5_dict[(label_key, 'y')])
+                # skeleton2d[obj_name][new_label][camn][:, 2] = np.array(hdf5_dict[(label_key, 'likelihood')])
+
+    return skeleton2d
+
+
+def save_dlc2d_points_in_csv(skeleton2d, save_paths):
+
+    for obj_name in skeleton2d['obj_names']:
+        for label in skeleton2d[obj_name]['label_names']:
+
+            nb_cam = len(skeleton2d[obj_name][label].keys())
+            for camn in range(1, nb_cam + 1):
+                if not os.path.exists(save_paths[camn]): os.makedirs(save_paths[camn])
+                np.savetxt(os.path.join(save_paths[camn], obj_name + '-' + label + '-2d_points.csv'),
+                           np.c_[skeleton2d[obj_name]['frames'], skeleton2d[obj_name][label][camn]['x'],
+                                 skeleton2d[obj_name][label][camn]['y'],
+                                 skeleton2d[obj_name][label][camn]['likelihood']],
+                           delimiter=',', header='frame,x_px,y_px,likelihood')
+
+            # # To save all in same .csv file
+            # for camn in range(1, nb_cam + 1):
+            #     if not os.path.exists(save_paths[camn]): os.makedirs(save_paths[camn])
+            #     header = header + ', x{0}_px, y{0}_px, likelihood{0}'.format(camn)
+            #     array_csv = np.concatenate((array_csv, np.c_[self.skeleton2d[obj_name][label][camn]['x'],
+            #                                                  self.skeleton2d[obj_name][label][camn]['y'],
+            #                                                  self.skeleton2d[obj_name][label][camn]['likelihood']), axis=1)
+            #
+            # np.savetxt(os.path.join(save_paths[self.main_camn], obj_name + '-' + label + '-2d_points.csv'),
+            #            array_csv, delimiter=',', header=header)
+
+
+def save_dlc3d_points_in_csv(skeleton3d, save_paths):
+    for obj_name in skeleton3d['obj_names']:
+        for label in skeleton3d[obj_name]['label_names']:
+
+            nb_cam = len(skeleton3d[obj_name][label].keys())
+            for camn in range(1, nb_cam + 1):
+                if not os.path.exists(save_paths[camn]): os.makedirs(save_paths[camn])
+                np.savetxt(os.path.join(save_paths[camn], obj_name + '-' + label + '-3d_points.csv'),
+                           np.c_[skeleton3d[obj_name]['frames'], skeleton3d[obj_name][label]['x'],
+                                 skeleton3d[obj_name][label]['y'], skeleton3d[obj_name][label]['z']],
+                           delimiter=',', header='frame,x,y,z')
+
+
+def unscramble_views2d_dlc(skeleton2d, process_dict):
+    # Run on unconverted 2d coords from dlc
+    # shared_axis: 0 if the combination of cam share the x axis (top-left to top-right)
+    # or 1 if they share the y axis (top-left to bottom left)
+
+    process_names = [process_dict[1][process_num]['fn'] for process_num in process_dict[1].keys()]
+    crop_num = process_names.index('crop')
+    crop_num = crop_num[-1] if isinstance(crop_num, list) else crop_num
+    width_crop = process_dict[1][crop_num]['kwargs']['width_crop']
+    height_crop = process_dict[1][crop_num]['kwargs']['height_crop']
+
+    for obj_name in skeleton2d['obj_names']:
+
+        # Detect body parts in wrong view
+        for label in skeleton2d[obj_name]['label_names']:
+            is_nan, new_camns = {}, {}
+            sum_in_out = np.zeros(np.size(skeleton2d[obj_name]['frames']))
+
+            nb_cam = len(skeleton2d[obj_name][label].keys())
+            for camn in range(1, nb_cam + 1):
+                is_nan[camn] = np.isnan(skeleton2d[obj_name][label][camn]['x'])
+
+                # in_out will be 0 if the camera view is correct, otherwise an int (-1, 1, 2, etc)
+                in_out_x = np.ceil((skeleton2d[obj_name][label][camn]['x'] - (camn - 1) * width_crop) / width_crop) - 1
+                in_out_y = np.ceil((skeleton2d[obj_name][label][camn]['y']) / height_crop) - 1
+
+                new_camns[camn] = np.ones(np.size(in_out_x)) *camn
+                for i, frame in enumerate(skeleton2d[obj_name]['frames']): # assumes that stitching was done along x axis
+                    if (in_out_x[i] + camn) < 1 or (in_out_x[i] + camn) > nb_cam: new_camns[camn][i] = np.nan
+                    elif in_out_y[i] < 0 or in_out_y[i] > 0: new_camns[camn][i] = np.nan
+                    else: new_camns[camn][i] = in_out_x[i] + camn
+
+                sum_in_out += new_camns[camn] - camn
+
+            skeleton2d_cur_bodypart = copy.deepcopy(skeleton2d[obj_name][label])
+            for camn in range(1, nb_cam + 1):
+                skeleton2d_cur_bodypart[camn]['x'] *= np.nan
+                skeleton2d_cur_bodypart[camn]['y'] *= np.nan
+
+            for i, frame in enumerate(skeleton2d[obj_name]['frames']):
+                for camn in range(1, nb_cam + 1):
+                    new_camn = new_camns[camn][i]
+                    if np.isnan(new_camn) or is_nan[camn][i]: continue
+
+                    if sum_in_out[i] == 0 or (is_nan[new_camn][i] and sum_in_out[i] != 0):
+                        skeleton2d_cur_bodypart[new_camn]['x'][i] = skeleton2d[obj_name][label][camn]['x'][i]
+                        skeleton2d_cur_bodypart[new_camn]['y'][i] = skeleton2d[obj_name][label][camn]['y'][i]
+
+                    elif not np.isnan(new_camns[new_camn][i]):
+                        skeleton2d_cur_bodypart[new_camn]['x'][i] = skeleton2d[obj_name][label][new_camn]['x'][i]
+                        skeleton2d_cur_bodypart[new_camn]['y'][i] = skeleton2d[obj_name][label][new_camn]['y'][i]
+
+            # if self.show_plot:
+            #     fig, axs = plt.subplots(1, nb_cam)
+            #     for i, camn in enumerate(range(1, nb_cam + 1)):
+            #         axs[i].clear()
+            #         axs[i].plot(self.skeleton2d[obj_name][label][camn]['x'], self.skeleton2d[obj_name][label][camn]['y'])
+            #         axs[i].plot(skeleton2d_cur_bodypart[camn]['x'], skeleton2d_cur_bodypart[camn]['y'])
+            #         axs[i].set_ylabel('y pos (m) - ' + label)
+            #         axs[i].set_xlabel('x pos (m)')
+            #     plt.show()
+
+            skeleton2d[obj_name][label] = skeleton2d_cur_bodypart
+
+
+def unscramble_sides3d_dlc(skeleton3d, skeleton2d, body_skeleton3d_init, body_params, show_plot=False):
+    # Unscramble case where side is wrong on all cam views
+    for obj_name in skeleton3d['obj_names']:
+        label_names_right = [s for s in skeleton3d[obj_name]['label_names'] if 'right' in s]
+
+        if (skeleton2d[obj_name]['frames'] != skeleton3d[obj_name]['frames']).any():
+            raise ValueError('frames should be the same for skeleton2d and skeleton3d')
+
+        if show_plot:
+            nb_cam = len(skeleton2d[obj_name][label_names_right[0]].keys())
+
+            fig1, axs1 = plt.subplots(nb_cam, 1)
+            for i, camn in enumerate(range(1, nb_cam + 1)):
+                axs1[i].clear()
+                for label_right in label_names_right:
+                    label_left = label_right.replace('right', 'left')
+                    x_right = skeleton2d[obj_name][label_right][camn]['x']
+                    y_right = skeleton2d[obj_name][label_right][camn]['y']
+                    x_left = skeleton2d[obj_name][label_left][camn]['x']
+                    y_left = skeleton2d[obj_name][label_left][camn]['y']
+
+                    axs1[i].plot(np.arctan2(y_right - y_left, x_right - x_left))  # angle of the line btw the two sides
+                axs1[i].set_ylabel('angle btw right and left (rad)')
+                axs1[i].set_xlabel('frames - ' + label_right)
+
+        for i, frame in enumerate(skeleton3d[obj_name]['frames']):
+            body_params_frame = {}
+            for label in body_params[obj_name].keys():
+                body_params_frame[label] = body_params[obj_name][label][i].copy()
+
+            # TODO get rid of dependency to skeleton_fitter.skeleton_modules.insect_body_slim?
+            body_skeleton3d = copy.deepcopy(body_skeleton3d_init)
+            body_skeleton3d = body.build_skeleton3d(body_skeleton3d, body_params_frame)
+            body_skeleton3d = body.rotate_skeleton3d(body_skeleton3d, body_params_frame)
+            body_skeleton3d = body.translate_skeleton3d(body_skeleton3d, body_params_frame)
+            # body_skeleton2d = body.reproject_skeleton3d_to2d(body_skeleton3d, self.dlt_coefs)
+
+            for label_right in label_names_right:
+                label_left = label_right.replace('right', 'left')
+
+                x_right = skeleton3d[obj_name][label_right]['x'][i].copy()
+                y_right = skeleton3d[obj_name][label_right]['y'][i].copy()
+                z_right = skeleton3d[obj_name][label_right]['z'][i].copy()
+
+                x_left = skeleton3d[obj_name][label_left]['x'][i].copy()
+                y_left = skeleton3d[obj_name][label_left]['y'][i].copy()
+                z_left = skeleton3d[obj_name][label_left]['z'][i].copy()
+
+                xyz_right = [x_right, y_right, z_right]
+                xyz_left = [x_left, y_left, z_left]
+
+                dist_right2right_hinge = np.linalg.norm(body_skeleton3d['right_wing_hinge'] - xyz_right)
+                dist_left2right_hinge = np.linalg.norm(body_skeleton3d['right_wing_hinge'] - xyz_left)
+                dist_right2left_hinge = np.linalg.norm(body_skeleton3d['left_wing_hinge'] - xyz_right)
+                dist_left2left_hinge = np.linalg.norm(body_skeleton3d['left_wing_hinge'] - xyz_left)
+
+                if dist_right2right_hinge > dist_right2left_hinge and dist_left2left_hinge > dist_left2right_hinge:
+                    x_right, y_right, z_right, x_left, y_left, z_left = x_left, y_left, z_left, x_right, y_right, z_right
+
+                    skeleton3d[obj_name][label_right]['x'][i], skeleton3d[obj_name][label_right]['y'][i], \
+                    skeleton3d[obj_name][label_right]['z'][i] = x_right, y_right, z_right
+
+                    skeleton3d[obj_name][label_left]['x'][i], skeleton3d[obj_name][label_left]['y'][i], \
+                    skeleton3d[obj_name][label_left]['z'][i] = x_left, y_left, z_left
+
+                    for camn in range(1, nb_cam + 1):
+                        skeleton2d[obj_name][label_right][camn]['x'][i], \
+                        skeleton2d[obj_name][label_right][camn]['y'][i], \
+                        skeleton2d[obj_name][label_left][camn]['x'][i], \
+                        skeleton2d[obj_name][label_left][camn]['y'][i] = \
+                            skeleton2d[obj_name][label_left][camn]['x'][i], \
+                            skeleton2d[obj_name][label_left][camn]['y'][i], \
+                            skeleton2d[obj_name][label_right][camn]['x'][i], \
+                            skeleton2d[obj_name][label_right][camn]['y'][i]
+
+        if show_plot:
+            fig2, axs2 = plt.subplots(nb_cam, 1)
+            for i, camn in enumerate(range(1, nb_cam + 1)):
+                axs2[i].clear()
+                for label_right in label_names_right:
+                    label_left = label_right.replace('right', 'left')
+                    x_right = skeleton2d[obj_name][label_right][camn]['x']
+                    y_right = skeleton2d[obj_name][label_right][camn]['y']
+                    x_left = skeleton2d[obj_name][label_left][camn]['x']
+                    y_left = skeleton2d[obj_name][label_left][camn]['y']
+
+                    axs2[i].plot(
+                        np.arctan2(y_right - y_left, x_right - x_left))  # angle of the line btw the two sides
+                axs2[i].set_ylabel('angle btw right and left (rad)')
+                axs2[i].set_xlabel('frames - ' + label_right)
+
+            plt.show()
+
+
+def save_stroboscopic_images(image_sequence, frames, coords_objs, save_name, save_path, radius, shape, step_frame,
+                             back_subtractor_settings: BackgroundSubtractorSettings,
+                             blob_detector_settings: BlobDetectorSettings,):
+
+    if len(coords_objs.keys()) == 0:
+        print('WARM: coords_objs is empty!')
+        return
+
+    tracker = Tracker2D()
+    tracker.get_back_subtractor(back_subtractor_settings)
+    tracker.get_blob_detector(blob_detector_settings)
+
+    tracker.dist2Threshold = 7
+    tracker.load_images(image_sequence.get_names(), image_sequence.get_path(), frames)
+
+    for obj_name in coords_objs.keys():
+        tracker.points = {'x': np.array(coords_objs[obj_name]['x_px']), 'y': np.array(coords_objs[obj_name]['y_px']),
+                          'frame': np.array(coords_objs[obj_name]['frame']),
+                          'area': np.ones(np.size(coords_objs[obj_name]['x_px'])) * np.nan}
+
+        tracker.gen_stroboscopic_image(radius, shape, step_frame, flip_time=True)
+        tracker.save_stroboscopic_image(save_name + '-' + obj_name, save_path)
+
+
+def unscramble_sides2d_and_recon3d_dlc(cutoff_frequency, frame_rate, skeleton2d, dlt_path,
+                                       reconstructor_settings: Reconstructor3DSettings, show_plot=False):
+    # TODO! check why do not work as planed or delete
+
+    reconstructor = Reconstructor3D.read_dlt_coefs(dlt_path)
+    reconstructor.settings = reconstructor_settings
+    nb_cam = reconstructor.nb_cam
+
+    # Unscamble case where side is wrong on only a minority of cam views
+    skeleton3d = {'obj_names': skeleton2d['obj_names'], 'model_name': skeleton2d['model_name']}
+    for obj_name in skeleton2d['obj_names']:
+        label_names_right = [s for s in skeleton2d[obj_name]['label_names'] if 'right' in s]
+        other_label_names = [s for s in skeleton2d[obj_name]['label_names'] if
+                             'right' not in s or 'left' not in s]
+
+        skeleton3d[obj_name] = {'frames': skeleton2d[obj_name]['frames'],
+                                     'label_names': skeleton2d[obj_name]['label_names']}
+        for label in skeleton3d[obj_name]['label_names']:
+            skeleton3d[obj_name][label] = {'x': np.nan * np.ones(np.size(skeleton2d[obj_name]['frames'])),
+                                                'y': np.nan * np.ones(np.size(skeleton2d[obj_name]['frames'])),
+                                                'z': np.nan * np.ones(np.size(skeleton2d[obj_name]['frames']))}
+
+        # Compute angles btw the right and left wing/bodyparts
+        angles, angles_out, mean_angles = {}, {}, {}
+        for i, camn in enumerate(range(1, nb_cam + 1)):
+            angles[camn], angles_out[camn] = {}, {}
+            mean_angles[camn] = np.zeros(np.size(skeleton2d[obj_name][label_names_right[0]][camn]['x']))
+            for label_right in label_names_right:
+                label_left = label_right.replace('right', 'left')
+                label = label_right.replace('right', '')
+
+                x_right = skeleton2d[obj_name][label_right][camn]['x']
+                y_right = skeleton2d[obj_name][label_right][camn]['y']
+                x_left = skeleton2d[obj_name][label_left][camn]['x']
+                y_left = skeleton2d[obj_name][label_left][camn]['y']
+
+                angles[camn][label] = np.arctan2(y_right - y_left, x_right - x_left)
+                #angles[camn][label] = np.arctan((y_right - y_left) / (x_right - x_left))
+
+                angles[camn][label] = abs(angles[camn][label]) # doesn't not work when pi jump btw pi/2 and -pi/2
+
+                # angles[camn][label] = np.fmod(angles[camn][label], 2 * np.pi)
+                # angles[camn][label][~np.isnan(angles[camn][label])] = \
+                #     np.unwrap(angles[camn][label][~np.isnan(angles[camn][label])], discont=2 * np.pi)
+                # # angles[camn][label][~np.isnan(angles[camn][label])] = \
+                # #    np.unwrap(angles[camn][label][~np.isnan(angles[camn][label])] * 2, discont=2 * np.pi * 0.8) / 2
+
+                is_nan, x = np.isnan(angles[camn][label]), lambda z: z.nonzero()[0]
+                if sum(is_nan) > 0:  # interpolation needed for filter_high_freq
+                    angles[camn][label][is_nan] = np.interp(x(is_nan), x(~is_nan), angles[camn][label][~is_nan])
+
+                w = cutoff_frequency / (frame_rate / 2)  # Normalize the frequency
+                b, a = signal.butter(2, w, 'low')
+
+                mean_angles[camn] += signal.filtfilt(b, a, angles[camn][label])
+
+            mean_angles[camn] = mean_angles[camn] / len(label_names_right)
+
+            min_angle = 0
+            for label_right in label_names_right:
+                label = label_right.replace('right', '')
+                in_out = np.where(abs(angles[camn][label] - mean_angles[camn]) > np.pi / 2)
+                angles_out[camn][label] = [s if i in in_out[0] else np.nan for i, s in enumerate(angles[camn][label])]
+                min_angle = np.min([np.min(angles[camn][label]), min_angle])
+
+        if show_plot:
+            fig1, axs1 = plt.subplots(nb_cam, 1)
+            fig1.suptitle('Angle of the line btw right and left wing part - Before unscrambling')
+            for i, camn in enumerate(range(1, nb_cam + 1)):
+                axs1[i].clear()
+                for label_right in label_names_right:
+                    label = label_right.replace('right', '')
+                    frames = list(range(0, len(angles_out[camn][label])))
+
+                    axs1[i].scatter(frames, angles[camn][label], label=label, marker='.')
+                    axs1[i].scatter(frames, angles_out[camn][label], c='r', label='out', marker='*')
+
+                axs1[i].plot(mean_angles[camn], 'k', label='mean')
+                axs1[i].set_ylabel('angle (rad)')
+                axs1[i].set_xlabel('frames - ' + label_right)
+
+        for i, frame in enumerate(skeleton2d[obj_name]['frames']):
+            for label_right in label_names_right:
+                label_left = label_right.replace('right', 'left')
+                label = label_right.replace('right', '')
+
+                #if all([np.isnan(angles_out[camn][label][i]) for camn in range(1, nb_cam + 1)]): continue
+
+                xs_coord, ys_coord, frames_coord, likelihood_coords = [], [], [], []
+                for camn in range(1, nb_cam + 1):
+                    if not np.isnan(angles_out[camn][label][i]):  # Switch side
+                        skeleton2d[obj_name][label_right][camn]['x'][i], skeleton2d[obj_name][label_left][camn]['x'][i] = \
+                            skeleton2d[obj_name][label_left][camn]['x'][i], skeleton2d[obj_name][label_right][camn]['x'][i]
+                        skeleton2d[obj_name][label_right][camn]['y'][i], skeleton2d[obj_name][label_left][camn]['y'][i] = \
+                            skeleton2d[obj_name][label_left][camn]['y'][i], skeleton2d[obj_name][label_right][camn]['y'][i]
+                        skeleton2d[obj_name][label_right][camn]['likelihood'][i], skeleton2d[obj_name][label_left][camn]['likelihood'][i] = \
+                            skeleton2d[obj_name][label_left][camn]['likelihood'][i], skeleton2d[obj_name][label_right][camn]['likelihood'][i]
+
+                    x_right = skeleton2d[obj_name][label_right][camn]['x'][i].copy()
+                    y_right = skeleton2d[obj_name][label_right][camn]['y'][i].copy()
+                    x_left = skeleton2d[obj_name][label_left][camn]['x'][i].copy()
+                    y_left = skeleton2d[obj_name][label_left][camn]['y'][i].copy()
+
+                    likelihood_right = skeleton2d[obj_name][label_right][camn]['likelihood'][i]
+                    likelihood_left = skeleton2d[obj_name][label_left][camn]['likelihood'][i]
+
+                    likelihood_coords.append([likelihood_right, likelihood_left])
+                    xs_coord.append([x_right, x_left])
+                    ys_coord.append([y_right, y_left])
+                    frames_coord.append([frame, frame])
+
+                xs_pose, ys_pose, zs_pose, frames_pose, indexes, rmses, = \
+                    reconstructor.recon_objs(xs_coord, ys_coord, frames_coord, with_missing=False, sort_by_rmse=False)
+
+                # if len(xs_pose) == 0:
+                #     xs_pose, ys_pose, zs_pose = [[np.nan, np.nan]], [[np.nan, np.nan]], [[np.nan, np.nan]]
+                #     indexes = [[np.array([0, 0, 0]), np.array([1, 1, 1])]]
+                #
+                # else:
+                #     rmses
+                #     # order rmse
+                #
+                # cpt = 0
+                # while len(xs_pose[0]) > cpt:
+                #     # Remove duplicate usage of points and give priority to first rows (with lowest rmse)
+                #     # should have max two rows in the end (one per side)
+                #
+                #     comparison_indexes = np.array([False] * nb_cam)  # False if different from previous rows
+                #     for j in range(1, cpt + 1):
+                #         comparison_indexes += np.array(
+                #             [indexes[0][cpt][k] == indexes[0][cpt - j][k] for k in range(0, nb_cam)])
+                #
+                #     # Remove coordinates because it use at least one 2d points that was used already
+                #     if comparison_indexes.any():
+                #         xs_pose[0] = [s for k, s in enumerate(xs_pose[0]) if k != cpt]
+                #         ys_pose[0] = [s for k, s in enumerate(ys_pose[0]) if k != cpt]
+                #         indexes[0] = [s for k, s in enumerate(indexes[0]) if k != cpt]
+                #     else:
+                #         cpt += 1
+                #
+                # if len(xs_pose[0]) == 1:
+                #     xs_pose[0].append(np.nan), ys_pose[0].append(np.nan), zs_pose[0].append(np.nan)
+                #     indexes[0].append([abs(index - 1) for index in indexes[0]][0])
+                #
+                # # if np.nansum(indexes[0][0]) > np.nansum(indexes[0][1]) \
+                # #         and indexes[0][1][1] == 0:  # use top cam (#2) as reference for identifying side
+                # if indexes[0][1][1] == 0:  # use top cam (#2) as reference for identifying side
+                #     indexes[0][0], indexes[0][1] = indexes[0][1], indexes[0][0]
+                #     xs_pose[0].reverse(), ys_pose[0].reverse(), zs_pose[0].reverse()
+
+                if len(xs_pose) == 0:
+                    xs_pose, ys_pose, zs_pose = [[np.nan, np.nan]], [[np.nan, np.nan]], [[np.nan, np.nan]]
+                    indexes = [[np.array([0, 0, 0]), np.array([1, 1, 1])]]
+
+                elif len(xs_pose[0]) == 1:
+                    xs_pose[0].append(np.nan), ys_pose[0].append(np.nan), zs_pose[0].append(np.nan)
+                    indexes[0].append([abs(index - 1) for index in indexes[0]][0])
+                #
+                # elif len(xs_pose[0]) > 2:
+                #     indexes_top_right = np.array([s for s in indexes[0] if s[top_camn - 1] == 0.0]).astype(int)
+                #     indexes_top_left = np.array([s for s in indexes[0] if s[top_camn - 1] == 1.0]).astype(int)
+                #     rmses_top_right = np.array([rmses[0][i] for i, s in enumerate(indexes[0]) if s[top_camn - 1] == 0.0])
+                #     rmses_top_left = np.array([rmses[0][i] for i, s in enumerate(indexes[0]) if s[top_camn - 1] == 1.0])
+                #
+                #     indexes = [[np.array([0, 0, 0]), np.array([1, 1, 1])]]
+                #     xs_pose_new, ys_pose_new, zs_pose_new = [[np.nan, np.nan]], [[np.nan, np.nan]], [[np.nan, np.nan]]
+                #     if len(rmses_top_right) > 1 and len(rmses_top_left) > 1:
+                #         combi = list(itertools.product(list(range(0, len(rmses_top_right))), list(range(0, len(rmses_top_left)))))
+                #
+                #         new_combi = []
+                #         for j in range(0, len(combi)):  # Keep only the combinations that are compatible
+                #             if (indexes_top_right[combi[j][0]] + indexes_top_left[combi[j][1]] == [1, 1, 1]).all():
+                #                 new_combi.append(combi[j])
+                #
+                #         if not len(new_combi) == 0:
+                #             rmse_combi = [np.sqrt(rmses_top_right[s[0]] ** 2 + rmses_top_left[s[1]] ** 2) for s in new_combi]
+                #             best_combi = new_combi[np.argsort(rmse_combi)[0]]
+                #
+                #             indexes = [[indexes_top_right[best_combi[0]], indexes_top_left[best_combi[1]]]]
+                #
+                #         else:
+                #             in_sort_right = np.argsort(rmses_top_right)
+                #             in_sort_left = np.argsort(rmses_top_left)
+                #
+                #             if rmses_top_right[in_sort_right[0]] < rmses_top_left[in_sort_left[0]]:
+                #                 indexes = [[indexes_top_right[in_sort_right[0]], abs(indexes_top_right[in_sort_right[0]] -1)]]
+                #             else:
+                #                 indexes = [[abs(indexes_top_left[in_sort_left[0]] -1), indexes_top_left[in_sort_left[0]]]]
+                #
+                #         xs_pose_new[0][0], ys_pose_new[0][0], zs_pose_new[0][0] = \
+                #             xs_pose[0][indexes[0][0][0]], ys_pose[0][indexes[0][0][1]], zs_pose[0][indexes[0][0][2]]
+                #         xs_pose_new[0][1], ys_pose_new[0][1], zs_pose_new[0][1] = \
+                #             xs_pose[0][indexes[0][1][0]], ys_pose[0][indexes[0][1][1]], zs_pose[0][indexes[0][1][2]]
+                #
+                #     elif not len(rmses_top_right) == 0 and len(rmses_top_left) == 0:
+                #         in_sort_right = np.argsort(rmses_top_right)
+                #         in_sort_left = np.argsort(rmses_top_left)
+                #
+                #         if len(rmses_top_right) == 0:
+                #             indexes[0][0] = abs(indexes_top_left[in_sort_left[0]] - 1)
+                #         else:
+                #             indexes[0][0] = indexes_top_right[in_sort_right[0]]
+                #             xs_pose_new[0][0], ys_pose_new[0][0], zs_pose_new[0][0] = \
+                #                 xs_pose[0][indexes[0][0][0]], ys_pose[0][indexes[0][0][1]], zs_pose[0][indexes[0][0][2]]
+                #
+                #         if len(rmses_top_left) == 0:
+                #             indexes[0][1] = abs(indexes_top_right[in_sort_right[0]] - 1)
+                #         else:
+                #             indexes[0][1] = indexes_top_left[in_sort_left[0]]
+                #             xs_pose_new[0][1], ys_pose_new[0][1], zs_pose_new[0][1] = \
+                #                 xs_pose[0][indexes[0][1][0]], ys_pose[0][indexes[0][1][1]], zs_pose[0][indexes[0][1][2]]
+                #
+                #     xs_pose, ys_pose, zs_pose = xs_pose_new, ys_pose_new, zs_pose_new
+                #
+                # if indexes[0][1][top_camn-1] == 0:  # use top cam as reference for identifying side
+                #     indexes[0][0], indexes[0][1] = indexes[0][1], indexes[0][0]
+                #     xs_pose[0].reverse(), ys_pose[0].reverse(), zs_pose[0].reverse()
+                #
+                # if (indexes[0] != np.array([0, 0, 0])).any():
+                #     for j, camn in enumerate(range(1, nb_cam + 1)):
+                #         if not np.isnan(indexes[0][0][j]) and not np.isnan(indexes[0][1][j]):
+                #             in_right = int(indexes[0][0][j])
+                #             skeleton2d[obj_name][label_right][camn]['x'][i] = xs_coord[j][in_right]
+                #             skeleton2d[obj_name][label_right][camn]['y'][i] = ys_coord[j][in_right]
+                #             skeleton2d[obj_name][label_right][camn]['likelihood'][i] = likelihood_coords[j][in_right]
+                #
+                #             in_left = int(indexes[0][1][j])
+                #             skeleton2d[obj_name][label_left][camn]['x'][i] = xs_coord[j][in_left]
+                #             skeleton2d[obj_name][label_left][camn]['y'][i] = ys_coord[j][in_left]
+                #             skeleton2d[obj_name][label_left][camn]['likelihood'][i] = likelihood_coords[j][in_left]
+
+                skeleton3d[obj_name][label_right]['x'][i], skeleton3d[obj_name][label_right]['y'][i], \
+                skeleton3d[obj_name][label_right]['z'][i] = xs_pose[0][0], ys_pose[0][0], zs_pose[0][0]
+
+                skeleton3d[obj_name][label_left]['x'][i], skeleton3d[obj_name][label_left]['y'][i], \
+                skeleton3d[obj_name][label_left]['z'][i] = xs_pose[0][1], ys_pose[0][1], zs_pose[0][1]
+
+            for label in other_label_names:
+                xs_coord, ys_coord, frames_coord = [], [], []
+                for camn in range(1, nb_cam + 1):
+                    xs_coord.append([skeleton2d[obj_name][label][camn]['x'][i].copy()])
+                    ys_coord.append([skeleton2d[obj_name][label][camn]['y'][i].copy()])
+                    frames_coord.append([frame])
+
+                xs_pose, ys_pose, zs_pose, frames_pose, indexes, rmses, = \
+                    reconstructor.recon_objs(xs_coord, ys_coord, frames_coord, with_missing=False)
+
+                if len(xs_pose) == 0:
+                    skeleton3d[obj_name][label]['x'][i], skeleton3d[obj_name][label]['y'][i], \
+                    skeleton3d[obj_name][label]['z'][i] = np.nan, np.nan, np.nan
+                else:
+                    skeleton3d[obj_name][label]['x'][i], skeleton3d[obj_name][label]['y'][i], \
+                    skeleton3d[obj_name][label]['z'][i] = xs_pose[0][0], ys_pose[0][0], zs_pose[0][0]
+
+        if show_plot:
+            fig2, axs2 = plt.subplots(nb_cam, 1)
+            fig2.suptitle('Angle of the line btw right and left wing part - After unscrambling')
+            for i, camn in enumerate(range(1, nb_cam + 1)):
+                axs2[i].clear()
+                for label_right in label_names_right:
+                    label_left = label_right.replace('right', 'left')
+                    x_right = skeleton2d[obj_name][label_right][camn]['x']
+                    y_right = skeleton2d[obj_name][label_right][camn]['y']
+                    x_left = skeleton2d[obj_name][label_left][camn]['x']
+                    y_left = skeleton2d[obj_name][label_left][camn]['y']
+
+                    angles = np.arctan2((y_right - y_left), (x_right - x_left))
+                    #angles = np.arctan((y_right - y_left) / (x_right - x_left))
+                    #angles[~np.isnan(angles)] = np.unwrap(angles[~np.isnan(angles)] * 2, discont=2 * np.pi * 0.8) / 2
+                    angles = np.mod(angles, 2*np.pi)
+
+                    axs2[i].plot(angles, label=label_right.replace('right', ''))
+                axs2[i].set_ylabel('angle (rad)')
+                axs2[i].set_xlabel('frames - ' + label_right)
+            plt.legend
+            plt.show()
+
+
+def reverse_processes_2d_points(skeleton2d, process_dict, threshold_likelihood, img_size, save_path, show_plot=False):
+
+    nb_cam = len(process_dict.keys())
+
+    [width_img, height_img] = img_size
+    for camn in range(1, nb_cam + 1):
+        process_names = [process_dict[camn][process_num]['fn'] for process_num in process_dict[camn].keys()]
+
+        for obj_name in skeleton2d['obj_names']:
+            if 'stitch' in process_names or 'rotate' in process_names:
+                if 'crop' in process_names:
+                    crop_num = process_names.index('crop')
+                    crop_num = crop_num[-1] if isinstance(crop_num, list) else crop_num
+                    width_crop = process_dict[camn][crop_num]['kwargs']['width_crop']
+                    height_crop = process_dict[camn][crop_num]['kwargs']['height_crop']
+
+                else:
+                    width_crop, height_crop = width_img, height_img
+
+            if 'crop' in process_names:
+                crop_paths = os.path.join(save_path, '_Cropped')
+                crop_path = crop_paths[process_dict[camn][0]['camn']]
+                csv_path = glob.glob(os.path.join(crop_path, process_dict[camn][0]['rec_names'][camn],
+                                                  '*' + obj_name + '-2d_points.csv'))
+
+                pts_csv = np.genfromtxt(csv_path[0], delimiter=',', skip_header=0, names=True)
+
+                temp_set = set(skeleton2d[obj_name]['frames'])
+                in_frame = [i for i, val in enumerate(pts_csv['frame']) if val in temp_set]
+
+                width_crop = process_dict[camn][crop_num]['kwargs']['width_crop']
+                height_crop = process_dict[camn][crop_num]['kwargs']['height_crop']
+
+                x_crop = pts_csv['x_px'][in_frame] - width_crop / 2
+                y_crop = pts_csv['y_px'][in_frame] - height_crop / 2
+
+                for i in range(0, len(in_frame)):
+                    x_crop[i] = np.max([np.min([x_crop[i], width_img - width_crop]), 0])
+                    y_crop[i] = np.max([np.min([y_crop[i], height_img - height_crop]), 0])
+
+            for label in skeleton2d[obj_name]['label_names']:
+                if 'stitch' in process_names:
+                    skeleton2d[obj_name][label][camn]['x'] += - width_crop * (camn - 1)
+
+                if 'rotate' in process_names and \
+                        camn in process_dict[camn][process_names.index('rotate')]['kwargs']['camn_to_process']:
+                    x = skeleton2d[obj_name][label][camn]['x'].copy() - width_crop / 2
+                    y = skeleton2d[obj_name][label][camn]['y'].copy() - height_crop / 2
+                    teta = np.deg2rad(process_dict[camn][process_names.index('rotate')]['kwargs']['degrees'])
+
+                    skeleton2d[obj_name][label][camn]['x'] = x * np.cos(teta) - y * np.sin(teta) + width_crop / 2
+                    skeleton2d[obj_name][label][camn]['y'] = x * np.sin(teta) + y * np.cos(teta) + height_crop / 2
+
+                if 'crop' in process_names:
+                    skeleton2d[obj_name][label][camn]['x'] += x_crop
+                    skeleton2d[obj_name][label][camn]['y'] += y_crop
+
+            if show_plot:
+                raw_rec_path = os.path.join(process_dict[camn][0]['paths'][process_dict[camn][0]['camn']],
+                                            process_dict[camn][0]['rec_names'][process_dict[camn][0]['camn']])
+
+                all_files = sorted(os.listdir(raw_rec_path), key=lambda s: s[s.rfind('.') - nb_leading_zeros:s.rfind('.')])
+                all_images = [s for s in all_files if '.{0}'.format(image_format) in s]
+
+                fig = plt.figure()
+                for i, image in enumerate(all_images):
+                    if i % 500: continue  # plot every 100 frames
+                    ax = fig.add_subplot(111)
+                    plt.clf()
+                    plt.imshow(Image.open(os.path.join(raw_rec_path, image)))
+                    # ax.scatter(x_crop + width_crop/2, y_crop + height_crop/2, marker='o')
+                    for label in skeleton2d[obj_name]['label_names']:
+                        # if 'wing' not in label: continue
+                        if skeleton2d[obj_name][label][camn]['likelihood'][i] > threshold_likelihood:
+                            ax.scatter(skeleton2d[obj_name][label][camn]['x'][i],
+                                       skeleton2d[obj_name][label][camn]['y'][i], 1)
+                    ax.set_xlabel('x (m)')
+                    ax.set_ylabel('y (m)')
+                    plt.pause(0.1)
+                    # plt.show()
+
+
+def test_unwrap(skeleton2d, dlt_path, reconstructor_settings: Reconstructor3DSettings):
+    reconstructor = Reconstructor3D.read_dlt_coefs(dlt_path)
+    reconstructor.settings = reconstructor_settings
+    nb_cam = reconstructor.nb_cam
+
+    # Unscamble case where side is wrong on only a minority of cam views
+    skeleton3d = {'obj_names': skeleton2d['obj_names'], 'model_name': skeleton2d['model_name']}
+    for obj_name in skeleton3d['obj_names']:
+        label_names_right = [s for s in skeleton2d[obj_name]['label_names'] if 'right' in s]
+
+        skeleton3d[obj_name] = {'frames': skeleton2d[obj_name]['frames'],
+                                     'label_names': skeleton2d[obj_name]['label_names']}
+        for label in skeleton3d[obj_name]['label_names']:
+            skeleton3d[obj_name][label] = {'x': np.nan * np.ones(np.size(skeleton3d[obj_name]['frames'])),
+                                                'y': np.nan * np.ones(np.size(skeleton3d[obj_name]['frames'])),
+                                                'z': np.nan * np.ones(np.size(skeleton3d[obj_name]['frames']))}
+
+        for i, frame in enumerate(skeleton3d[obj_name]['frames']):
+            for label in skeleton3d[obj_name]['label_names']:
+                xs_coord, ys_coord, frames_coord = [], [], []
+                for camn in range(1, nb_cam + 1):
+                    xs_coord.append([skeleton2d[obj_name][label][camn]['x'][i].copy()])
+                    ys_coord.append([skeleton2d[obj_name][label][camn]['y'][i].copy()])
+                    frames_coord.append([frame])
+
+                xs_pose, ys_pose, zs_pose, frames_pose, indexes, rmses, = \
+                    reconstructor.recon_objs(xs_coord, ys_coord, frames_coord, with_missing=False)
+
+                if len(xs_pose) == 0:
+                    skeleton3d[obj_name][label]['x'][i], skeleton3d[obj_name][label]['y'][i], \
+                    skeleton3d[obj_name][label]['z'][i] = np.nan, np.nan, np.nan
+                else:
+                    skeleton3d[obj_name][label]['x'][i], skeleton3d[obj_name][label]['y'][i], \
+                    skeleton3d[obj_name][label]['z'][i] = xs_pose[0][0], ys_pose[0][0], zs_pose[0][0]
+
+        fig1, axs1 = plt.subplots(nb_cam, 1)
+        for i, camn in enumerate(range(1, nb_cam + 1)):
+            axs1[i].clear()
+            for label_right in label_names_right:
+                label_left = label_right.replace('right', 'left')
+                x_right = skeleton2d[obj_name][label_right][camn]['x']
+                y_right = skeleton2d[obj_name][label_right][camn]['y']
+                x_left = skeleton2d[obj_name][label_left][camn]['x']
+                y_left = skeleton2d[obj_name][label_left][camn]['y']
+
+                angle = np.arctan((y_right - y_left) / (x_right - x_left))
+                angle[~np.isnan(angle)] = np.unwrap(angle[~np.isnan(angle)] * 2, discont=2 * np.pi * 0.8) / 2
+
+                angle2 = np.arctan2((y_right - y_left), (x_right - x_left))
+                median_diff_angle = np.nanmedian(angle2 - angle)
+                if abs(abs(median_diff_angle) - np.pi) < 0.2:
+                    if median_diff_angle > 0:
+                        angle2 -= np.pi
+                    elif median_diff_angle < 0:
+                        angle2 += np.pi
+
+                in_diff = np.array([False] * len(angle))
+                is_not_nan = ~np.isnan(angle) & ~np.isnan(angle2)
+                in_diff[is_not_nan] = abs(angle2[is_not_nan] - angle[is_not_nan]) > np.pi / 2
+
+                axs1[i].plot(skeleton2d[obj_name]['frames'], angle)  # angle of the line btw the two sides
+                axs1[i].plot(np.array(skeleton2d[obj_name]['frames'])[in_diff], angle2[in_diff], marker='*',
+                             linestyle='None')
+
+            axs1[i].set_ylabel('angle btw right and left (rad)')
+            axs1[i].set_xlabel('frames - ' + label_right)
+
+        plt.show()
+
+
+def recon3d_dlc(skeleton2d, dlt_path, reconstructor_settings: Reconstructor3DSettings):
+    reconstructor = Reconstructor3D.read_dlt_coefs(dlt_path)
+    reconstructor.settings = reconstructor_settings
+
+    # Unscamble case where side is wrong on only a minority of cam views
+    skeleton3d = {'obj_names': skeleton2d['obj_names'], 'model_name': skeleton2d['model_name']}
+    for obj_name in skeleton3d['obj_names']:
+        # label_names_right = [s for s in skeleton2d[obj_name]['label_names'] if 'right' in s]
+        # other_label_names = [s for s in skeleton2d[obj_name]['label_names'] if
+        #                      'right' not in s or 'left' not in s]
+
+        skeleton3d[obj_name] = {'frames': skeleton2d[obj_name]['frames'],
+                                     'label_names': skeleton2d[obj_name]['label_names']}
+        for label in skeleton3d[obj_name]['label_names']:
+            skeleton3d[obj_name][label] = {'x': np.nan * np.ones(np.size(skeleton2d[obj_name]['frames'])),
+                                                'y': np.nan * np.ones(np.size(skeleton2d[obj_name]['frames'])),
+                                                'z': np.nan * np.ones(np.size(skeleton2d[obj_name]['frames']))}
+
+        for i, frame in enumerate(skeleton2d[obj_name]['frames']):
+            for label in skeleton3d[obj_name]['label_names']:
+                xs_coord, ys_coord, frames_coord = [], [], []
+
+                nb_cam = len(skeleton2d[obj_name][label].keys())
+                for camn in range(1, nb_cam + 1):
+                    xs_coord.append([skeleton2d[obj_name][label][camn]['x'][i].copy()])
+                    ys_coord.append([skeleton2d[obj_name][label][camn]['y'][i].copy()])
+                    frames_coord.append([frame])
+
+                xs_pose, ys_pose, zs_pose, frames_pose, indexes, rmses, = \
+                    reconstructor.recon_objs(xs_coord, ys_coord, frames_coord, with_missing=False)
+
+                if len(xs_pose) == 0:
+                    skeleton3d[obj_name][label]['x'][i], skeleton3d[obj_name][label]['y'][i], \
+                    skeleton3d[obj_name][label]['z'][i] = np.nan, np.nan, np.nan
+                else:
+                    skeleton3d[obj_name][label]['x'][i], skeleton3d[obj_name][label]['y'][i], \
+                    skeleton3d[obj_name][label]['z'][i] = xs_pose[0][0], ys_pose[0][0], zs_pose[0][0]
\ No newline at end of file
diff --git a/process/images_preprocessing.py b/process/images_preprocessing.py
new file mode 100644
index 0000000000000000000000000000000000000000..87dbad4e0093b75f07a92c1c797b7acb6f18bbb3
--- /dev/null
+++ b/process/images_preprocessing.py
@@ -0,0 +1,158 @@
+import os
+import cv2
+
+import numpy as np
+from PIL import Image
+
+from typing import List
+
+import images.utils as img_utils
+from images.read import read_image
+
+from images.utils import find_common_substrings
+
+
+def crop_image(img, coords, height, width):
+
+    x_pxs, y_pxs = np.array([]), np.array([])
+    for x_name in [s for s in coords.keys() if 'x_' in s]:
+        y_name = x_name.replace('x_', 'y_')
+
+        x_pxs = np.append(x_pxs, coords[x_name])
+        y_pxs = np.append(y_pxs, coords[y_name])
+
+    assert x_pxs.size and y_pxs.size
+
+    return img_utils.crop(img, x_pxs, y_pxs, height, width)
+
+
+def crop_all_images(coords_objs, image_names, image_path, image_save_path, height, width):
+
+    obj_names = coords_objs.keys()
+
+    for ind_img, image_name in enumerate(image_names):
+
+        img = read_image(os.path.join(image_path, image_name))
+
+        img_cropped = []
+        for obj_name in obj_names:
+            coords_obj = {key: coords_objs[obj_name][key][ind_img] for key in coords_objs[obj_name].keys()}
+            
+            img_cropped.append(crop_image(img, coords_obj, height, width))
+
+            image_save_name = image_name[:image_name.rfind('.')] + '-' + obj_name + image_name[image_name.rfind('.'):]
+            img_cropped[-1].save(os.path.join(image_save_path, image_save_name))
+            img_cropped[-1].close()
+
+        img.close()
+
+
+def stitch_all_images(image_names, image_paths, frames, image_save_paths, main_camn):
+    # TODO make this function work with an MultiImageSequence (size: nb_cam*nb_frame)
+    #  or list of images (size: nb_cam), not an image_path, image_format ...
+    #  + use this function in multiprocesses in BatchProcessing
+
+    nb_cam = len(image_names.keys())
+
+    frames_set = set()
+    for camn in range(1, nb_cam + 1):
+        frames_set = frames_set & set(frames[camn]) if len(frames_set) > 0 else set(frames[camn])
+
+    frames_set = np.sort(list(frames_set))
+
+    index_i = {}
+    for camn in range(1, nb_cam + 1):
+        index_i[camn] = [frames[camn].index(frame) for frame in frames_set]
+
+    if not os.path.exists(image_save_paths[main_camn]): os.makedirs(image_save_paths[main_camn])
+
+    for i, _ in enumerate(index_i[main_camn]):
+        imgs = {}
+        for camn in range(1, nb_cam + 1):
+            imgs[camn] = Image.open(os.path.join(image_paths[camn], image_names[camn][index_i[camn][i]]))
+
+        img = img_utils.stitch_images(imgs, nb_cam)
+
+        if image_names[main_camn][index_i[main_camn][i]].find('.jpg') != -1 and img.mode != 'RGB': img = img.convert('RGB')
+
+        img.save(os.path.join(image_save_paths[main_camn], image_names[main_camn][index_i[main_camn][i]]), compression='lzw')
+
+
+def stitch_images_multiprocessing(img, image_names, image_rec_paths, frames, image_save_paths, obj_names, multi_processes_num,
+                                  multi_processes_nums, main_camn):
+
+    # TODO Merge this function with stitch_images, make them work on a MultiImageSequence
+
+    nb_cam = len(image_names.keys())
+
+    frames_set = set()
+    for camn in range(1, nb_cam + 1):
+        frames_set = frames_set & set(frames[camn]) if len(frames_set) > 0 else set(frames[camn])
+
+    frames_set = np.sort(list(frames_set))
+
+    index_i = {}
+    for camn in range(1, nb_cam + 1):
+        index_i[camn] = [frames[camn].index(frame) for frame in frames_set]
+
+    for obj_name in obj_names:
+        # if obj_name == 'obj0' and 'from_fn_name' in kwargs.keys(): break
+
+        for camn in range(1, nb_cam + 1):
+            if obj_name not in img[camn].keys():
+                img[camn][obj_name] = {}
+
+        for i, _ in enumerate(index_i[main_camn]):
+            imgs = {}
+            for camn in range(1, nb_cam + 1):
+                if i not in img[camn][obj_name].keys():
+                    img[camn][obj_name][i] = read_image(os.path.join(image_rec_paths[camn], image_names[camn][i]))
+
+                imgs[camn] = img[camn][obj_name][i]
+                img[camn][obj_name][i] = []
+
+            img[main_camn][obj_name][i] = img_utils.stitch_images(imgs, nb_cam)
+
+        if multi_processes_num == multi_processes_nums[-1]:
+            for i, image_name in enumerate(image_names[camn]):
+                if 'obj0' == obj_name:
+                    image_save_name = image_name
+                else:
+                    image_save_name = image_name[:image_name.rfind('.')] + '-' + obj_name + image_name[image_name.rfind('.'):]
+
+                if image_name.find('.jpg') != -1 and img[main_camn][obj_name][i].mode != 'RGB':
+                    img[main_camn][obj_name][i] = img[main_camn][obj_name][i].convert('RGB')
+
+                img[main_camn][obj_name][i].save(os.path.join(image_save_paths[main_camn], image_save_name))
+                img[main_camn][obj_name][i].close()
+
+    return img
+
+
+def save_avi(all_image_names: List[str], image_path: str, rec_name: str, save_path: str, frame_rate: int = 24,
+             step_frame: int = 1, lossless: bool = False) -> None:
+
+    obj_names = np.unique([s[s.rfind('-obj')+1:s.rfind('.')] for s in all_image_names])
+
+    for obj_name in obj_names:
+        image_names = sorted([i for i in all_image_names if obj_name in i], key=lambda s: s[len(rec_name):s.rfind('.')])
+        save_name = rec_name + '-' + obj_name + '.avi'
+
+        img = cv2.imread(os.path.join(image_path, image_names[0]), cv2.IMREAD_UNCHANGED)
+        is_rgb = len(img.shape) == 3
+        if is_rgb:  height_img, width_img, _ = img.shape
+        else: height_img, width_img = img.shape
+
+        # commun: 'MP42', 'XVID' or lossless: 'HFYU'
+        codec = cv2.VideoWriter_fourcc(*'HFYU') if lossless else cv2.VideoWriter_fourcc(*'MP42')
+        writer = cv2.VideoWriter(os.path.join(save_path, save_name), codec, frame_rate, (width_img, height_img,))
+
+        for i in np.arange(0, len(image_names), step_frame):
+            img_16bit = cv2.imread(os.path.join(image_path, image_names[i]), cv2.IMREAD_UNCHANGED)
+            if is_rgb: writer.write(img_16bit)
+            else: writer.write(cv2.cvtColor(img_16bit, cv2.COLOR_GRAY2BGR))
+            #cv2.imshow(obj_name, img_16bit)
+            #cv2.waitKey(1)
+
+        writer.release()
+
diff --git a/process/point_tracking.py b/process/point_tracking.py
new file mode 100644
index 0000000000000000000000000000000000000000..d0a73c834a06393e26f97c42d22ef6fb8a64c8e1
--- /dev/null
+++ b/process/point_tracking.py
@@ -0,0 +1,155 @@
+import glob
+import os
+
+import numpy as np
+import matplotlib.pyplot as plt
+from PIL import Image
+
+from images.imagesequence import ImageSequence
+
+from point_tracker.reconstructor3d import Reconstructor3D
+from point_tracker.tracker2d import Tracker2D, BlobDetectorSettings, BackgroundSubtractorSettings
+from point_tracker.reconstructor3d import Reconstructor3DSettings
+
+
+def track_2d_objects(image_sequence: ImageSequence, save_name: str, save_path: str,
+                     back_subtractor_settings: BackgroundSubtractorSettings,
+                     blob_detector_settings: BlobDetectorSettings, show_plot: bool = False):
+
+    image_names = image_sequence.get_names()
+    image_path = image_sequence.get_path()
+
+    frames = image_sequence.get_numbers()
+
+    tracker = Tracker2D()
+    tracker.get_back_subtractor(back_subtractor_settings)
+    tracker.get_blob_detector(blob_detector_settings)
+
+    tracker.load_images(image_names, image_path, frames)
+
+    tracker.do_tracking()
+
+    if show_plot:
+        fig = plt.figure()
+        ax = fig.add_subplot(111)
+        plt.imshow(Image.open(os.path.join(image_path, image_names[0])))
+        ax.scatter(tracker.points['x'], tracker.points['y'])
+        ax.set_xlabel('x (m)')
+        ax.set_ylabel('y (m)')
+        plt.show()
+
+    tracker.save_csv(save_name, save_path)
+
+
+def interp_2d_coords(frames, csv_name, csv_path, save_path, image_size, show_plot=False):
+    width_img, height_img = image_size
+
+    old_obj_dict = np.genfromtxt(os.path.join(csv_path, csv_name), delimiter=',', skip_header=0, names=True)
+
+    # is_not_nan = ~np.isnan(old_obj_dict['x_px']) & ~np.isnan(old_obj_dict['y_px'])
+    # old_obj_dict = old_obj_dict[is_not_nan]
+
+    x_px = np.interp(frames, old_obj_dict['frame'], old_obj_dict['x_px'])
+    y_px = np.interp(frames, old_obj_dict['frame'], old_obj_dict['y_px'])
+
+    for i, _ in enumerate(frames):
+        if x_px[i] > width_img: x_px[i] = width_img
+        if y_px[i] > height_img: y_px[i] = height_img
+        if x_px[i] < 0: x_px[i] = 0
+        if y_px[i] < 0: y_px[i] = 0
+
+    np.savetxt(os.path.join(save_path, csv_name), np.c_[frames, x_px, y_px], delimiter=',', header='frame,x_px,y_px')
+    obj_dict = {'frame': frames, 'x_px': x_px, 'y_px': y_px}
+
+    if show_plot:
+        fig = plt.figure()
+        ax = fig.add_subplot(111)
+        ax.scatter(obj_dict['x_px'], obj_dict['y_px'], marker='*')
+        ax.scatter(old_obj_dict['x_px'], old_obj_dict['y_px'], marker='x')
+        ax.set_xlabel('x (m)')
+        ax.set_ylabel('y (m)')
+        plt.xlim(0, width_img)
+        plt.ylim(height_img, 0)
+        plt.show()
+
+    return obj_dict
+
+
+def interp_2d_coords_of_objs(frames, csv_path, save_path, image_size, show_plot=False):
+
+    csv_names = glob.glob(os.path.join(csv_path, '*-obj*-2d_points.csv'))
+    csv_names = [os.path.basename(os.path.normpath(csv_name)) for csv_name in csv_names]
+
+    obj_names = [csv_name[csv_name.rfind('-obj') + 1:csv_name.find('-2d_points.csv')] for csv_name in csv_names]
+
+    coords_objs = {}
+    for i, csv_name in enumerate(csv_names):
+        if len(obj_names) == 0:
+            coords_objs[obj_names[i]] = {}
+
+        else:
+            coords_objs[obj_names[i]] = \
+                interp_2d_coords(frames, csv_name, csv_path, save_path, image_size, show_plot)
+
+    return coords_objs
+
+
+def reconstruct_3d_points(pts_dict, dlt_path, save_names, save_paths,
+                          reconstructor_settings: Reconstructor3DSettings, save_csv: bool = True):
+
+    reconstructor = Reconstructor3D.read_dlt_coefs(dlt_path)
+    reconstructor.settings = reconstructor_settings
+
+    nb_cam = reconstructor.nb_cam
+
+    xs_coord = np.array([pts_dict[camn]['x_px'] for camn in range(1, nb_cam + 1)])
+    ys_coord = np.array([pts_dict[camn]['y_px'] for camn in range(1, nb_cam + 1)])
+    frames_coord = np.array([pts_dict[camn]['frame'] for camn in range(1, nb_cam + 1)])
+
+    xs_pose, ys_pose, zs_pose, frames_pose, indexes, rmses, = \
+        reconstructor.recon_objs(xs_coord, ys_coord, frames_coord, with_missing=False)
+
+    save_names_list = [save_names[camn] for camn in range(1, nb_cam + 1)]
+    save_paths_list = [save_paths[camn] for camn in range(1, nb_cam + 1)]
+    if save_csv: reconstructor.save_points_csv(save_names_list, save_paths_list, save_type='all_frames')
+
+    return xs_pose, ys_pose, zs_pose, frames_pose, indexes, rmses
+
+
+def reconstruct_3d_tracks(pts_dict, dlt_path, save_names, save_paths, reconstructor_settings: Reconstructor3DSettings,
+                          show_plot=False, save_csv=True):
+
+    reconstructor = Reconstructor3D.read_dlt_coefs(dlt_path)
+    reconstructor.settings = reconstructor_settings
+    nb_cam = reconstructor.nb_cam
+
+    xs_coord = np.array([pts_dict[camn]['x_px'] for camn in range(1, nb_cam + 1)])
+    ys_coord = np.array([pts_dict[camn]['y_px'] for camn in range(1, nb_cam + 1)])
+    frames_coord = np.array([pts_dict[camn]['frame'] for camn in range(1, nb_cam + 1)])
+
+    xs_pose, ys_pose, zs_pose, frames_pose, indexes_pts, _, = \
+        reconstructor.recon_objs(xs_coord, ys_coord, frames_coord, with_missing=False)
+
+    tracks_dict = reconstructor.recon_tracks(xs_pose, ys_pose, zs_pose, frames_pose, indexes_pts)
+
+    if not xs_pose or not tracks_dict['obj']: return
+
+    save_names_list = [save_names[camn] for camn in range(1, nb_cam + 1)]
+    save_paths_list = [save_paths[camn] for camn in range(1, nb_cam + 1)]
+    if save_csv: reconstructor.save_tracks_csv(save_names_list, save_paths_list)
+
+    if show_plot:
+        xs_pose, ys_pose, zs_pose = np.concatenate(xs_pose), np.concatenate(ys_pose), np.concatenate(zs_pose)
+
+        fig = plt.figure()
+        ax = fig.add_subplot(111, projection='3d')
+        ax.scatter(xs_pose, ys_pose, zs_pose, marker='+')
+        for nb_obj in tracks_dict['obj'].keys():
+            ax.scatter(tracks_dict['obj'][nb_obj]['x'], tracks_dict['obj'][nb_obj]['y'],
+                       tracks_dict['obj'][nb_obj]['z'], marker='o')
+        ax.set_xlabel('x (m)')
+        ax.set_ylabel('y (m)')
+        ax.set_zlabel('z (m)')
+        plt.show()
+
+    return tracks_dict
diff --git a/process/skeleton_fitting.py b/process/skeleton_fitting.py
new file mode 100644
index 0000000000000000000000000000000000000000..1030d189481354830daee0d2381b6953a26cfbd6
--- /dev/null
+++ b/process/skeleton_fitting.py
@@ -0,0 +1,1431 @@
+import copy, glob, os, time
+from functools import partial
+from multiprocessing import Pool
+
+import numpy as np
+from PIL import Image, ImageDraw
+from matplotlib import pyplot as plt
+from scipy import signal, optimize
+from sympy import symbols, solve
+
+from camera import calib
+from images.read import read_image
+from process.smoothing_filtering import filter_interp_params
+from skeleton_fitter.utils import length_from_vector, reproject_skeleton3d_to2d, angle_from_vectors
+from skeleton_fitter.optimiser import optim_fit_body_params, optim_fit_limbs_params
+
+# TODO remove dependencies to insect body/wing/hybrid functions
+#  use: import importlib
+#       animal_type = 'insect'
+#       skeleton_type = ['body_slim', 'wings', 'hybrid_wings_body_slim']
+#       module = importlib.import_module('skeleton_fitter.skeleton_modules.' + animal_type + '_' + skeleton_type)
+import skeleton_fitter.skeleton_modules.insect_body_slim as body
+import skeleton_fitter.skeleton_modules.insect_wings as wings
+import skeleton_fitter.skeleton_modules.insect_hybrid_wings_body_slim as hybrid
+
+
+def load_skeleton2d_from_csv(model_name, paths, main_camn):
+
+    nb_cam = len(paths.keys())
+
+    csv_paths = glob.glob(os.path.join(paths[main_camn], '*-2d_points.csv'))
+    obj_names = np.unique([csv_path[len(paths[main_camn]) + 1:csv_path.index('-')] for csv_path in csv_paths])
+
+    skeleton2d = {'obj_names': obj_names, 'model_name': model_name}
+    for obj_name in obj_names:
+        label_names = np.unique([csv_path[csv_path.index('-') + 1:csv_path.index('-2d_points.csv')] for csv_path in csv_paths])
+
+        skeleton2d[obj_name] = {'frames': [], 'label_names': label_names}
+        for label in label_names:
+            skeleton2d[obj_name][label] = {1: {}}
+            for camn in range(1, nb_cam + 1):
+                pts_csv = np.genfromtxt(os.path.join(paths[camn], obj_name + '-' + label + '-2d_points.csv'),
+                                        delimiter=',', skip_header=0, names=True)
+
+                skeleton2d[obj_name]['frames'] = \
+                    np.unique(np.concatenate((skeleton2d[obj_name]['frames'], pts_csv['frame'])))
+                skeleton2d[obj_name][label][camn] = \
+                    {'x': pts_csv['x_px'], 'y': pts_csv['y_px'], 'likelihood': pts_csv['likelihood']}
+
+    return skeleton2d
+
+
+def load_skeleton3d_from_csv(model_name, paths, main_camn):
+
+    nb_cam = len(paths.keys())
+
+    csv_paths = glob.glob(os.path.join(paths[main_camn], '*-3d_points.csv'))
+    obj_names = np.unique([csv_path[len(paths[main_camn]) + 1:csv_path.index('-')] for csv_path in csv_paths])
+
+    if len(csv_paths) == 0:
+        raise ValueError("Couldn't find any *-3d_points.csv file (path: {0})".format(os.path.join(paths[main_camn])))
+
+    skeleton3d = {'obj_names': obj_names, 'model_name': model_name}
+    for obj_name in obj_names:
+        obj_csv_paths = [csv_path for csv_path in csv_paths if obj_name in csv_path]
+        label_names = np.unique([csv_path[csv_path.index(obj_name + '-') + len(obj_name) +1:
+                                          csv_path.index('-3d_points.csv')] for csv_path in obj_csv_paths])
+
+        skeleton3d[obj_name] = {'frames': [], 'label_names': label_names}
+        for label in label_names:
+            skeleton3d[obj_name][label] = {1: {}}
+            for camn in range(1, nb_cam + 1):
+                pts_csv = np.genfromtxt(os.path.join(paths[camn], obj_name + '-' + label + '-3d_points.csv'),
+                                        delimiter=',', skip_header=0, names=True)
+
+                skeleton3d[obj_name]['frames'] = \
+                    np.unique(np.concatenate((skeleton3d[obj_name]['frames'], pts_csv['frame'])))
+                skeleton3d[obj_name][label] = {'x': pts_csv['x'], 'y': pts_csv['y'], 'z': pts_csv['z']}
+
+    return skeleton3d
+
+
+def save_skeleton_params_in_csv(skeleton3d, wings_params_init, body_params, wings_params, save_path):
+    for obj_name in skeleton3d['obj_names']:
+        body_params_names = body_params[obj_name].keys()
+
+        params_list, params_names = [skeleton3d[obj_name]['frames']], 'frame'
+        for param_name in body_params_names:
+            params_list.append(body_params[obj_name][param_name])
+            params_names += ',' + param_name
+
+        for side in wings_params_init.keys():
+            for param_name in wings_params[obj_name][side].keys():
+                if param_name in body_params_names: continue
+                params_list.append(wings_params[obj_name][side][param_name])
+                params_names += ',' + param_name + '_' + side
+
+        np.savetxt(os.path.join(save_path, 'skeleton_parameters-' + obj_name + '.csv'),
+                   np.c_[np.transpose(params_list)], delimiter=',', header=params_names)
+
+
+def save_obj_metric_in_csv(skeleton3d, wings_params_init, list_dict, list_name, save_path):
+    for obj_name in skeleton3d['obj_names']:
+        params_list, params_names = [skeleton3d[obj_name]['frames']], 'frame'
+        if isinstance(list_dict[obj_name], dict):
+            for side in wings_params_init.keys():
+                params_list.append(list_dict[obj_name][side])
+                params_names += ',' + list_name + '_' + side
+        else:
+            params_list.append(list_dict[obj_name])
+            params_names += ',' + list_name
+
+        np.savetxt(os.path.join(save_path, list_name + '-' + obj_name + '.csv'),
+                   np.c_[np.transpose(params_list)], delimiter=',', header=params_names)
+
+
+class SkeletonFitting:
+    def __init__(self):
+        self.nb_process = 5
+
+        self.cutoff_frequency = 100  # Hz (for Butterworth filter on body params)
+
+        self.skeleton2d, self.skeleton3d = {}, {}  # TODO should get these from arguments
+
+        # TODO remove following hybrid skeleton
+        self.hybrid_skeleton2d = copy.deepcopy(self.skeleton2d)
+        self.hybrid_skeleton3d = copy.deepcopy(self.skeleton3d)
+
+        # TODO Only use one module at a time (either body, wings or hybrid)
+        #  + loop through module name using AnimalSettings class (from fly.py)
+        self.body_sets = body.ModuleSettings()
+        self.wings_sets = wings.ModuleSettings()
+        self.hybrid_sets = hybrid.ModuleSettings()
+
+    def load_params_from_csv(self, path):
+        obj_names = self.skeleton2d['obj_names']
+
+        body_params, wings_params = {}, {}
+        for obj_name in obj_names:
+            body_params[obj_name], wings_params[obj_name] = {}, {}
+            params_csv = np.genfromtxt(os.path.join(path, 'skeleton_parameters-' + obj_name + '.csv'),
+                                       delimiter=',', skip_header=0, names=True)
+
+            for body_param_name in self.body_sets.params_init.keys():
+                body_params[obj_name][body_param_name] = params_csv[body_param_name]
+
+            for side in ['right', 'left']:
+                wings_params[obj_name][side] = {}
+                for wing_param_name in self.wings_sets.params_init[side].keys():
+                    if wing_param_name in self.body_sets.params_init.keys():
+                        wings_params[obj_name][side][wing_param_name] = params_csv[wing_param_name]
+                    else:
+                        wings_params[obj_name][side][wing_param_name] = params_csv[wing_param_name + '_' + side]
+
+        return body_params, wings_params
+
+    def estimate_body_wings_params(self, interpolate_nans=False, filter_high_freq=False, show_plot=False):
+        body_params_labels = self.body_sets.params_init.keys()
+        wing_params_labels = self.wings_sets.params_init['right'].keys()
+
+        if len(self.skeleton3d['obj_names']) == 0:
+            raise ValueError('obj_names is empty')
+
+        self.body_params, self.wings_params, self.mean_body_params, self.mean_wings_params = {}, {}, {}, {}
+        for obj_name in self.skeleton3d['obj_names']:
+            frames = self.skeleton3d[obj_name]['frames']
+
+            self.body_params[obj_name], self.wings_params[obj_name] = {}, {}
+            self.mean_body_params[obj_name], self.mean_wings_params[obj_name] = {}, {}
+            for label in body_params_labels:
+                self.body_params[obj_name][label] = np.nan * np.ones(frames.shape)
+
+            for side in ['right', 'left']:
+                self.wings_params[obj_name][side] = {}
+                for label in wing_params_labels:
+                    self.wings_params[obj_name][side][label] = np.nan * np.ones(frames.shape)
+
+            for i, frame in enumerate(frames):
+                #for label in self.skeleton3d[obj_name]['label_names']:
+
+                body_skeleton3d, wings_skeleton3d = {}, {'right': {}, 'left': {}}
+                for body_label in self.body_sets.labels:
+                    body_skeleton3d[body_label] = np.array([self.skeleton3d[obj_name][body_label]['x'][i],
+                                                            self.skeleton3d[obj_name][body_label]['y'][i],
+                                                            self.skeleton3d[obj_name][body_label]['z'][i]])
+
+                if self.wings_sets.keep_init_aspect_ratio: init_aspect_ratio = {}
+
+                for side in ['right', 'left']:
+                    for wing_label in self.wings_sets.labels:
+                        label = side + '_wing_' + wing_label
+                        wings_skeleton3d[side][wing_label] = \
+                            np.array([self.skeleton3d[obj_name][label]['x'][i],
+                                      self.skeleton3d[obj_name][label]['y'][i],
+                                      self.skeleton3d[obj_name][label]['z'][i]])
+
+                    if self.wings_sets.keep_init_aspect_ratio:
+                        span_v = self.wings_skeleton3d_init[side]['tip'] - self.wings_skeleton3d_init[side]['hinge']
+                        chord_v = self.wings_skeleton3d_init[side]['leading_edge_q2'] - self.wings_skeleton3d_init[side]['trailing_edge_q2']
+                        init_aspect_ratio[side] = np.round(length_from_vector(span_v), 9) / np.round(length_from_vector(chord_v), 9)
+                        init_aspect_ratio[side] = init_aspect_ratio[side] * np.ones(np.shape(self.wings_params[obj_name][side]['span']))
+
+                    # for leg_name in leg_names:
+                    #     for leg_label in leg_labels:
+                    #         label = side + '_leg_' + leg_name + '_' + leg_label
+                    #         body_skeleton3d[label] = np.array([self.skeleton3d[obj_name][label]['x'][i],
+                    #                                            self.skeleton3d[obj_name][label]['y'][i],
+                    #                                            self.skeleton3d[obj_name][label]['z'][i]])
+
+                body_params = body.estimate_params_from_skeleton3d(body_skeleton3d, self.body_sets.params_init)
+                for label in body_params_labels:
+                    self.body_params[obj_name][label][i] = body_params[label]
+
+                for side in ['right', 'left']:
+                    wing_param = wings.estimate_params_from_skeleton3d(wings_skeleton3d[side], body_params,
+                                                                       self.wings_sets.params_init[side], side)
+
+                    for label in wing_params_labels:
+                        self.wings_params[obj_name][side][label][i] = wing_param[label]
+
+            if self.wings_sets.keep_init_aspect_ratio:
+                for side in ['right', 'left']:
+                    self.wings_params[obj_name][side]['aspect_ratio'] = init_aspect_ratio[side]
+                    self.wings_params[obj_name][side]['chord'] = self.wings_params[obj_name][side]['span']/init_aspect_ratio[side]
+
+            # if show_plot:
+            #     # Plot body and wings params that will be kept cst
+            #     fig1, axs1 = plt.subplots(len(self.body_sets.labels_to_keep_cst), 1)
+            #     for i, label in enumerate(self.body_sets.labels_to_keep_cst):
+            #         y = self.body_params[obj_name][label]
+            #         axs1[i].plot(frames, y, '.', label='estim')
+            #         axs1[i].plot(frames, np.nanmean(y) * np.ones(frames.shape), '-', label='mean')
+            #         axs1[i].plot(frames, np.nanmedian(y) * np.ones(frames.shape), '-', label='median')
+            #         axs1[i].plot(frames, stats.mode(y)[0] * np.ones(frames.shape), '-', label='mode')
+            #
+            #         axs1[i].set_ylabel(label)
+            #     axs1[i].set_xlabel('frames')
+            #     fig1.suptitle('Body parameters in function of time')
+            #     fig1.legend()
+            #
+            #     fig2, axs2 = plt.subplots(len(self.wings_sets.labels_to_keep_cst), 1)
+            #     for i, label in enumerate(self.wings_sets.labels_to_keep_cst):
+            #         for side in ['right', 'left']:
+            #             y = self.wings_params[obj_name][side][label]
+            #             axs2[i].plot(frames, y, '.', label='estim_' + side)
+            #             axs2[i].plot(frames, np.nanmean(y) * np.ones(frames.shape), '-', label='mean_' + side)
+            #             axs2[i].plot(frames, np.nanmedian(y) * np.ones(frames.shape), '-', label='median_' + side)
+            #             axs2[i].plot(frames, stats.mode(y)[0] * np.ones(frames.shape), '-', label='mode_' + side)
+            #         axs2[i].set_ylabel(label)
+            #     axs2[i].set_xlabel('frames')
+            #     fig2.suptitle('Wings parameters in function of time')
+            #     fig2.legend()
+            #
+            #     plt.show()
+
+            # Compute median value of body and wings length (should be constant for one animal)
+            for label in self.body_sets.labels_to_keep_cst:
+                self.body_params[obj_name][label] = \
+                    np.nanmedian(self.body_params[obj_name][label]) * np.ones(frames.shape)
+                 # print('>>> mean {0}: {1}'.format(label, self.body_params[obj_name][label][0]))
+
+            self.body_params[obj_name] = filter_interp_params(self.cutoff_frequency, self.frame_rate,
+                                                              self.body_params[obj_name], body_params_labels,
+                                                              interpolate_nans, filter_high_freq, show_plot)
+
+            for label in self.wings_sets.labels_to_keep_cst:
+                mean_over_sides = np.nanmedian([np.nanmedian(self.wings_params[obj_name][side][label]) for side in ['right', 'left']])
+                for side in ['right', 'left']:
+                    self.wings_params[obj_name][side][label] = mean_over_sides * np.ones(frames.shape)
+                # print('>>> mean {0}: {1} (right and left)'.format(label, self.wings_params[obj_name]['right'][label][0]))
+
+            for side in ['right', 'left']:
+                self.wings_params[obj_name][side] = \
+                    filter_interp_params(self.cutoff_frequency, self.frame_rate, self.wings_params[obj_name][side],
+                                         ['x_hinge', 'y_hinge', 'z_hinge'], False, filter_high_freq, show_plot)
+                self.wings_params[obj_name][side] = \
+                    filter_interp_params(self.cutoff_frequency, self.frame_rate, self.wings_params[obj_name][side],
+                                         wing_params_labels, interpolate_nans, False, show_plot)
+
+            # Copy all body_params in wings_params
+            for side in ['right', 'left']:
+                for label in body_params_labels:
+                    self.wings_params[obj_name][side][label] = self.body_params[obj_name][label].copy()
+
+        return self.body_params, self.wings_params
+
+    def estimate_wing_hinges(self, interpolate_nans=False, filter_high_freq=False, show_plot=False):
+        wing_params_labels = self.wings_sets.params_init['right'].keys()
+
+        dpt = 3
+        x, y, z = symbols('x y z')
+
+        if len(self.skeleton3d['obj_names']) == 0:
+            raise ValueError('obj_names is empty')
+
+        for obj_name in self.skeleton3d['obj_names']:
+            frames = self.skeleton3d[obj_name]['frames']
+
+            if show_plot:
+                init_hinges = {}
+                for side in ['right', 'left']:
+                    init_hinges[side] = {'x': self.wings_params[obj_name][side]['x_hinge'].copy(),
+                                         'y': self.wings_params[obj_name][side]['y_hinge'].copy(),
+                                         'z': self.wings_params[obj_name][side]['z_hinge'].copy()}
+
+            for side in ['right', 'left']:
+                for label in wing_params_labels:
+                    self.wings_params[obj_name][side][label] = np.nan * np.ones(frames.shape)
+
+            for side in ['right', 'left']:
+                a = np.ones(np.shape(frames)) * np.nan
+                b, c, d = a.copy(), a.copy(), a.copy()
+                for i, frame in enumerate(frames):
+                    x_coords = np.ones((len(self.wings_sets.labels), 1)) * np.nan
+                    y_coords, z_coords = x_coords.copy(), x_coords.copy()
+                    for j, wing_label in enumerate(self.wings_sets.labels):
+                        label = side + '_wing_' + wing_label
+
+                        x_coords[j] = self.skeleton3d[obj_name][label]['x'][i]
+                        y_coords[j] = self.skeleton3d[obj_name][label]['y'][i]
+                        z_coords[j] = self.skeleton3d[obj_name][label]['z'][i]
+
+                    if len(x_coords) < 3: continue
+                    params = minimize_perp_distance(x_coords, y_coords, z_coords)
+                    a[i], b[i], c[i], d[i] = params
+
+                    if i >= 2*dpt:
+                        eq = [a[i - 2*dpt] * x + b[i - 2*dpt] * y + c[i - 2*dpt] * z + d[i - 2*dpt],
+                              a[i - 1*dpt] * x + b[i - 1*dpt] * y + c[i - 1*dpt] * z + d[i - 1*dpt],
+                              a[i] * x + b[i] * y + c[i] * z + d[i]]
+                        sol = solve(eq, dict=True)
+
+                        if len(sol) > 0 and x in sol[0].keys() and y in sol[0].keys() and z in sol[0].keys():
+                            self.wings_params[obj_name][side]['x_hinge'][i] = sol[0][x]
+                            self.wings_params[obj_name][side]['y_hinge'][i] = sol[0][y]
+                            self.wings_params[obj_name][side]['z_hinge'][i] = sol[0][z]
+
+            for side in ['right', 'left']:
+                self.wings_params[obj_name][side] = \
+                    filter_interp_params(self.cutoff_frequency, self.frame_rate, self.wings_params[obj_name][side],
+                                         ['x_hinge', 'y_hinge', 'z_hinge'], interpolate_nans, filter_high_freq,
+                                         show_plot)
+
+            if show_plot:
+                fig, axs = plt.subplots(1, 3)
+                for i, coord in enumerate(['x', 'y', 'z']):
+                    axs[i].clear()
+                    for side in ['right', 'left']:
+                        axs[i].scatter(frames, init_hinges[side][coord], marker='o', label='init_' + side)
+                        axs[i].scatter(frames, self.wings_params[obj_name][side][coord + '_hinge'], marker='+', label='est_' + side)
+                    axs[i].set_ylabel(coord + ' pos (m)')
+                    axs[i].set_xlabel('frames')
+                plt.legend()
+                plt.show()
+
+        return self.wings_params
+
+    def optim_fit_skeleton(self, animal_name, body_param_names, wing_param_names, res_method, opt_method, rec_paths,
+                           save_paths, dlt_path, show_plot=False):
+        dlt_coefs = calib.read_dlt_coefs(dlt_path)
+
+        main_camn = 1
+        save_path = save_paths[main_camn]
+
+        body_params_init, wings_params_init = self.estimate_body_wings_params(interpolate_nans=True,
+                                                                              filter_high_freq=True)
+        #wings_params_init = self.estimate_wing_hinges(interpolate_nans=True, filter_high_freq=True)
+        save_skeleton_params_in_csv(self.skeleton3d, self.wings_sets.params_init, body_params_init, wings_params_init, save_path)
+
+        start = time.time()
+        body_params, nb_visible_pts_body, rmse_body, nb_iterations_body = \
+            self.optim_fit_all_hybrid_params(animal_name, body_params_init, wings_params_init, body_param_names,
+                                             res_method, opt_method, dlt_coefs, rec_paths, interpolate_nans=False,
+                                             filter_high_freq=True)
+        print('>> optim hybrid done! (time elapsed: {0:.4f} s)'.format(time.time() - start))
+
+        # body_params, nb_visible_pts_body, rmse_body, nb_iterations_body = \
+        #     self.optim_fit_all_body_params(animal_name, body_params_init, body_param_names, res_method, opt_method, dlt_coefs, rec_paths,
+        #                                    interpolate_nans=False, filter_high_freq=True)
+        # #body_params = body_params_init
+        # print('>> optim body done! (time elapsed: {0:.4f} s)'.format(time.time() - start))
+
+        # self.unscramble_sides3d_dlc(body_params)
+        # self.save2d_dlc(save_paths)
+        # self.save3d_dlc(save_paths)
+
+        start = time.time()
+        wings_params, nb_visible_pts_wings, rmse_wings, nb_iterations_wings = \
+            self.optim_fit_all_wings_params(animal_name, body_params, wings_params_init, wing_param_names, res_method,
+                                            opt_method, dlt_coefs, rec_paths, interpolate_nans=False,
+                                            filter_high_freq=True)
+        print('>> optim wings done! (time elapsed: {0:.4f} s)'.format(time.time() - start))
+
+        #body_params, wings_params = self.correct_body_wings_params(body_params, wings_params, angles_to_correct, rec_paths, dlt_coefs)
+
+        if show_plot:
+            for obj_name in body_params.keys():
+                frames = list(range(0, len(body_params[obj_name][body_param_names[0]])))
+
+                # Plot body params
+                fig1, axs1 = plt.subplots(len(body_param_names), 1)
+                for i, label in enumerate(body_param_names):
+                    axs1[i].plot(frames, body_params_init[obj_name][label], '-', label='init')
+                    axs1[i].plot(frames, body_params[obj_name][label], '-', label='optim')
+                    axs1[i].set_ylabel(label)
+                axs1[i].set_xlabel('frames')
+                fig1.suptitle('Body parameters in function of time')
+                fig1.legend()
+
+                # Plot wings params
+                fig2, axs2 = plt.subplots(len(wing_param_names), 1)
+                for i, label in enumerate(wing_param_names):
+                    for side in wings_params[obj_name].keys():
+                        axs2[i].plot(frames, wings_params_init[obj_name][side][label], '-', label='init_' + side)
+                        axs2[i].plot(frames, wings_params[obj_name][side][label], '-', label='optim_' + side)
+                    axs2[i].set_ylabel(label)
+                axs2[i].set_xlabel('frames')
+                fig2.suptitle('Wings parameters in function of time')
+                fig2.legend()
+
+            plt.show()
+
+        save_skeleton_params_in_csv(self.skeleton3d, self.wings_sets.params_init, body_params, wings_params, save_path)
+
+        save_obj_metric_in_csv(self.skeleton3d, self.wings_sets.params_init, nb_visible_pts_body, 'nb_visible_pts-body', save_path)
+        save_obj_metric_in_csv(self.skeleton3d, self.wings_sets.params_init, nb_visible_pts_wings, 'nb_visible_pts-wings', save_path)
+        save_obj_metric_in_csv(self.skeleton3d, self.wings_sets.params_init, nb_iterations_body, 'nb_iterations-body', save_path)
+        save_obj_metric_in_csv(self.skeleton3d, self.wings_sets.params_init, nb_iterations_wings, 'nb_iterations-wings', save_path)
+        save_obj_metric_in_csv(self.skeleton3d, self.wings_sets.params_init, rmse_body, 'rmse-body', save_path)
+        save_obj_metric_in_csv(self.skeleton3d, self.wings_sets.params_init, rmse_wings, 'rmse-wings', save_path)
+
+    def optim_fit_all_body_params(self, animal_name, body_params_init, param_names, res_method, opt_method, dlt_coefs,
+                                  rec_paths, interpolate_nans=False, filter_high_freq=False, show_plot=False):
+
+        res_method = 'body_' + res_method
+
+        nb_cam = len(rec_paths.keys())
+
+        body_param_ests, nb_visible_pts, rmse, nb_iterations = {}, {}, {}, {}
+        obj_names = self.skeleton2d['obj_names'] if '2d' in res_method else self.skeleton3d['obj_names']
+        for obj_name in obj_names:
+            frames = self.skeleton2d[obj_name]['frames'] if '2d' in res_method else self.skeleton3d[obj_name]['frames']
+            # frames = [frame for i, frame in enumerate(frames) if i % 100 == 0.0]
+
+            shape_frame = np.array(frames).shape
+            nb_iterations[obj_name] = np.zeros(shape_frame)
+            rmse[obj_name], nb_visible_pts[obj_name] = np.ones(shape_frame) * np.nan, np.ones(shape_frame) * np.nan
+            if obj_name in body_params_init.keys():
+                body_param_ests[obj_name] = copy.deepcopy(body_params_init[obj_name])
+            else:
+                body_param_ests[obj_name] = {}
+                for label in self.body_sets.params_init.keys():
+                    body_param_ests[obj_name][label] = np.ones(shape_frame) * self.body_sets.params_init[label]
+
+            if show_plot and not self.multiprocessing:
+                fig, axs = plt.subplots(1, nb_cam)
+                all_images = {}
+                for camn in range (1, nb_cam +1):
+                    all_files = sorted(os.listdir(rec_paths[camn]), key=lambda s: s[s.rfind('.') - nb_leading_zeros:s.rfind('.')])
+                    all_images[camn] = [s for s in all_files if '.{0}'.format(image_format) in s]
+
+            if self.multiprocessing: args = [(frame,) for frame in frames]
+
+            #for i, frame in enumerate(tqdm.tqdm(frames)):  # to print progress bar
+            for i, frame in enumerate(frames):
+                if obj_name in body_params_init.keys():
+                    body_params_init_frame = {}
+                    for label in self.body_sets.params_init.keys():
+                        body_params_init_frame[label] = body_params_init[obj_name][label][i]
+                else:
+                    body_params_init_frame = self.body_sets.params_init
+
+                nb_points = len(self.body_skeleton3d_init.keys())
+                nb_visible_pts[obj_name][i] = nb_points
+                if '3d' in res_method:
+                    body_skeleton3d = {}
+                    for body_label in self.body_skeleton3d_init.keys():
+                        body_skeleton3d[body_label] = \
+                            np.array([self.skeleton3d[obj_name][body_label]['x'][i],
+                                      self.skeleton3d[obj_name][body_label]['y'][i],
+                                      self.skeleton3d[obj_name][body_label]['z'][i]])
+
+                        if np.isnan(body_skeleton3d[body_label]).any(): nb_visible_pts[obj_name][i] -= 1
+
+                elif '2d' in res_method:
+                    body_skeleton2d = {}
+                    for body_label in self.body_skeleton3d_init.keys():
+                        body_skeleton2d[body_label] = {1: np.array([])}
+                        for camn in range(1, nb_cam + 1):
+
+                            if self.skeleton2d[obj_name][body_label][camn]['likelihood'][i] > self.body_sets.threshold_likelihood:
+                                body_skeleton2d[body_label][camn] = \
+                                    np.array([self.skeleton2d[obj_name][body_label][camn]['x'][i],
+                                              self.skeleton2d[obj_name][body_label][camn]['y'][i],
+                                              self.skeleton2d[obj_name][body_label][camn]['likelihood'][i]])
+                            else:
+                                body_skeleton2d[body_label][camn] = \
+                                    np.array([np.nan, np.nan, self.skeleton2d[obj_name][body_label][camn]['likelihood'][i]])
+
+                                nb_visible_pts[obj_name][i] -= 1 / nb_cam
+
+                return_nan = nb_visible_pts[obj_name][i] <= self.body_sets.threshold_nb_pts
+
+                if not self.multiprocessing:
+                    if '3d' in res_method:
+                        body_param_ests_frame, rmse[obj_name][i], nb_iterations[obj_name][i] = \
+                            optim_fit_body_params(animal_name, self.body_skeleton3d_init, body_skeleton3d, body_params_init_frame,
+                                                  param_names, res_method, opt_method, self.body_sets.bounds_init, return_nan)
+
+                    elif '2d' in res_method:
+                        body_param_ests_frame, rmse[obj_name][i], nb_iterations[obj_name][i] = \
+                            optim_fit_body_params(animal_name, self.body_skeleton3d_init, body_skeleton2d, body_params_init_frame,
+                                                  param_names, res_method, opt_method, self.body_sets.bounds_init, return_nan, dlt_coefs=dlt_coefs)
+
+                    for label in body_param_ests_frame.keys():
+                        body_param_ests[obj_name][label][i] = body_param_ests_frame[label]
+
+                    if show_plot and (frame % 20 == 0.0 or frame == 1):
+                        body_skeleton3d_init = copy.deepcopy(self.body_skeleton3d_init)
+                        body_skeleton3d_init = body.build_skeleton3d(body_skeleton3d_init, body_params_init_frame)
+                        body_skeleton3d_init = body.rotate_skeleton3d(body_skeleton3d_init, body_params_init_frame)
+                        body_skeleton3d_init = body.translate_skeleton3d(body_skeleton3d_init, body_params_init_frame)
+                        body_skeleton2d_init = reproject_skeleton3d_to2d(body_skeleton3d_init, dlt_coefs)
+
+                        body_skeleton3d_new = copy.deepcopy(self.body_skeleton3d_init)
+                        body_skeleton3d_new = body.build_skeleton3d(body_skeleton3d_new, body_param_ests_frame)
+                        body_skeleton3d_new = body.rotate_skeleton3d(body_skeleton3d_new, body_param_ests_frame)
+                        body_skeleton3d_new = body.translate_skeleton3d(body_skeleton3d_new, body_param_ests_frame)
+                        body_skeleton2d_new = reproject_skeleton3d_to2d(body_skeleton3d_new, dlt_coefs)
+
+                        # Compute reprojection of 3d reconstruction from dlc 2d points
+                        body_skeleton3d_repro2d = {}
+                        for label in self.body_skeleton3d_init.keys():
+                            body_skeleton3d_repro2d[label] = {1: []}
+                            for j, camn in enumerate(range(1, nb_cam + 1)):
+                                uv = calib.find2d(1, dlt_coefs[j], np.array([[self.skeleton3d[obj_name][label]['x'][i],
+                                                                                 self.skeleton3d[obj_name][label]['y'][i], self.skeleton3d[obj_name][label]['z'][i]]]))
+                                body_skeleton3d_repro2d[label][camn] = np.array([uv[0][0], uv[0][1], 1.0])
+
+                        if '3d' in res_method:
+                            body_skeleton2d = reproject_skeleton3d_to2d(body_skeleton3d, dlt_coefs)
+
+                        # Plot 2d repojection of body skeleton
+                        for j, camn in enumerate(range(1, nb_cam +1)):
+                            center_of_mass_xy = \
+                                (body_skeleton2d_new['torso_abdomen_joint'][camn] * body_param_ests_frame['ratio_com_torso'] \
+                                 + body_skeleton2d_new['head_torso_joint'][camn] * (1 - body_param_ests_frame['ratio_com_torso']))
+
+                            if np.isnan(center_of_mass_xy).any(): continue
+
+                            axs[j].clear()
+                            axs[j].imshow(Image.open(os.path.join(rec_paths[camn], all_images[camn][i])))
+                            for label in body_skeleton2d_new.keys():
+                                if body_skeleton2d[label][camn][2] > self.body_sets.threshold_likelihood:
+                                    axs[j].scatter(body_skeleton2d[label][camn][0],
+                                                   body_skeleton2d[label][camn][1], marker='.', color='k')
+                                    axs[j].scatter(body_skeleton3d_repro2d[label][camn][0],
+                                                   body_skeleton3d_repro2d[label][camn][1], marker='o', facecolors='none', edgecolors='k')
+                                axs[j].scatter(body_skeleton2d_init[label][camn][0],
+                                               body_skeleton2d_init[label][camn][1], marker='o', facecolors='none', edgecolors='b')
+                                axs[j].scatter(body_skeleton2d_new[label][camn][0],
+                                               body_skeleton2d_new[label][camn][1], marker='+', color='r')
+
+                            axs[j].set_xlim((center_of_mass_xy[0] - 100, center_of_mass_xy[0] + 100))
+                            axs[j].set_ylim((center_of_mass_xy[1] + 100, center_of_mass_xy[1] - 100))
+
+                        plt.pause(0.05)
+                        # plt.show()
+
+                        # param_to_print = ['pitch_a', 'roll_a', 'yaw_a', 'ratio_body']
+                        # for label in param_to_print:
+                        #     print('{0}: init: {1} and est: {2}'.format(label, body_params_init_frame[label], body_param_ests_frame[label]))
+
+                else:
+                    if '3d' in res_method:
+                        args[i] = (self.body_skeleton3d_init, body_skeleton3d, body_params_init_frame, param_names,
+                                    res_method, opt_method, self.body_sets.bounds_init, return_nan,)
+
+                    elif '2d' in res_method:
+                        args[i] = (self.body_skeleton3d_init, body_skeleton2d, body_params_init_frame, param_names,
+                                    res_method, opt_method, self.body_sets.bounds_init, return_nan,)
+
+            if self.multiprocessing:
+                pool_body = Pool(self.nb_process)
+                if '3d' in res_method:
+                    results = pool_body.starmap(optim_fit_body_params, args)
+
+                elif '2d' in res_method:
+                    results = pool_body.starmap(partial(optim_fit_body_params, dlt_coefs=dlt_coefs), args)
+
+                pool_body.close()
+                for i, frame in enumerate(frames):
+                    body_param_ests_frame, rmse[obj_name][i], nb_iterations[obj_name][i] = results[i]
+                    for label in body_param_ests_frame.keys():
+                        body_param_ests[obj_name][label][i] = body_param_ests_frame[label]
+
+            body_param_ests[obj_name] = filter_interp_params(self.cutoff_frequency, self.frame_rate,
+                                                             body_param_ests[obj_name], param_names, interpolate_nans,
+                                                             filter_high_freq, show_plot)
+
+        return body_param_ests, nb_visible_pts, rmse, nb_iterations
+
+    def optim_fit_all_wings_params(self, animal_name, body_params_init, wings_params_init, param_names, res_method, opt_method,
+                                   dlt_coefs, rec_paths, interpolate_nans=False, filter_high_freq=False, show_plot=False):
+
+        res_method = 'wing_' + res_method
+
+        nb_cam = len(rec_paths.keys())
+
+        obj_names = self.skeleton2d['obj_names'] if '2d' in res_method else self.skeleton3d['obj_names']
+        for obj_name in obj_names:
+            if obj_name in wings_params_init.keys():
+                for side in self.wings_sets.params_init.keys():
+                    for label in body_params_init[obj_name].keys():
+                        wings_params_init[obj_name][side][label] = body_params_init[obj_name][label]
+
+        wings_params_ests, nb_visible_pts, rmse, nb_iterations = copy.deepcopy(wings_params_init), {}, {}, {}
+        for obj_name in obj_names:
+            frames = self.skeleton2d[obj_name]['frames'] if '2d' in res_method else self.skeleton3d[obj_name]['frames']
+            # frames = [frame for i, frame in enumerate(frames) if i % 100 == 0.0]
+
+            if show_plot and not self.multiprocessing:
+                fig, axs = plt.subplots(1, nb_cam)
+                all_images = {}
+                for camn in range(1, nb_cam + 1):
+                    all_files = sorted(os.listdir(rec_paths[camn]), key=lambda s: s[s.rfind('.') - nb_leading_zeros:s.rfind('.')])
+                    all_images[camn] = [s for s in all_files if '.{0}'.format(image_format) in s]
+
+            if self.multiprocessing: args = [(frame,) for frame in frames]
+
+            shape_frame = np.array(frames).shape
+            rmse[obj_name] = {'right': np.ones(shape_frame) * np.nan, 'left': np.ones(shape_frame) * np.nan}
+            nb_iterations[obj_name] = {'right': np.zeros(shape_frame), 'left': np.zeros(shape_frame)}
+            nb_visible_pts[obj_name] = {'right': np.ones(shape_frame) * np.nan, 'left': np.ones(shape_frame) * np.nan}
+            #for i, frame in enumerate(tqdm.tqdm(frames)):   # to print progress bar
+            for i, frame in enumerate(frames):
+                if obj_name in wings_params_init.keys():
+                    wings_params_init_frame = {}
+                    for side in self.wings_sets.params_init.keys():
+                        wings_params_init_frame[side] = {}
+                        for label in self.wings_sets.params_init[side].keys():
+                            wings_params_init_frame[side][label] = wings_params_init[obj_name][side][label][i]
+                else:
+                    wings_params_init_frame = self.wings_sets.params_init
+
+                nb_points = len(self.body_skeleton3d_init.keys())
+                for side in self.wings_skeleton3d_init.keys():
+                    nb_visible_pts[obj_name][side][i] = nb_points
+
+                if '3d' in res_method:
+                    wings_skeleton3d = {'right': {}, 'left': {}}
+                    for side in self.wings_skeleton3d_init.keys():
+                        for wing_label in self.wings_skeleton3d_init[side].keys():
+                            label = side + '_wing_' + wing_label
+                            wings_skeleton3d[side][wing_label] = \
+                                np.array([self.skeleton3d[obj_name][label]['x'][i],
+                                          self.skeleton3d[obj_name][label]['y'][i],
+                                          self.skeleton3d[obj_name][label]['z'][i]])
+
+                            if np.isnan(wings_skeleton3d[side][wing_label]).any():
+                                nb_visible_pts[obj_name][side][i] -= 1
+
+                elif '2d' in res_method:
+                    wings_skeleton2d = {'right': {}, 'left': {}}
+                    for side in self.wings_skeleton3d_init.keys():
+                        for wing_label in self.wings_skeleton3d_init[side].keys():
+
+                            wings_skeleton2d[side][wing_label] = {1: []}
+                            for camn in range(1, nb_cam + 1):
+                                label = side + '_wing_' + wing_label
+
+                                if self.skeleton2d[obj_name][label][camn]['likelihood'][i] > self.wings_sets.threshold_likelihood:
+                                    wings_skeleton2d[side][wing_label][camn] = \
+                                        np.array([self.skeleton2d[obj_name][label][camn]['x'][i],
+                                                  self.skeleton2d[obj_name][label][camn]['y'][i],
+                                                  self.skeleton2d[obj_name][label][camn]['likelihood'][i]])
+                                else:
+                                    wings_skeleton2d[side][wing_label][camn] = \
+                                        np.array([np.nan, np.nan, self.skeleton2d[obj_name][label][camn]['likelihood'][i]])
+
+                                    nb_visible_pts[obj_name][side][i] -= 1 / nb_cam
+
+                body_params_frame = {}
+                for label in body_params_init[obj_name].keys():
+                    body_params_frame[label] = body_params_init[obj_name][label][i]
+
+                body_skeleton3d = copy.deepcopy(self.body_skeleton3d_init)
+                body_skeleton3d = body.build_skeleton3d(body_skeleton3d, body_params_frame)
+                body_skeleton3d = body.rotate_skeleton3d(body_skeleton3d, body_params_frame)
+                body_skeleton3d = body.translate_skeleton3d(body_skeleton3d, body_params_frame)
+
+                return_nan = [False, False]
+                for j, side in enumerate(self.wings_skeleton3d_init.keys()):
+                    [wings_params_init_frame[side]['x_hinge'], wings_params_init_frame[side]['y_hinge'],
+                     wings_params_init_frame[side]['z_hinge']] = body_skeleton3d['{0}_wing_hinge'.format(side)]
+
+                    return_nan[j] = nb_visible_pts[obj_name][side][i] <= self.wings_sets.threshold_nb_pts
+                    if return_nan[j]:
+                        for label in param_names:
+                            wings_params_ests[obj_name][side][label][i] = np.nan
+                        continue
+
+                    for label in ['yaw_a', 'pitch_a', 'roll_a', 'x_com', 'y_com', 'z_com']:
+                        wings_params_init_frame[side][label] = body_params_frame[label]
+
+                if not self.multiprocessing:
+                    if 'geo' in res_method:
+                        if '3d' in res_method:
+                            wings_params_est_frame, rmse_frame, nb_iterations_frame = \
+                                optim_fit_limbs_params(animal_name, self.wings_geometry3d_init, wings_skeleton3d, wings_params_init_frame,
+                                                       param_names, res_method, opt_method, self.wings_sets.bounds_init, return_nan)
+
+                        elif '2d' in res_method:
+                            wings_params_est_frame, rmse_frame, nb_iterations_frame = \
+                                optim_fit_limbs_params(animal_name, self.wings_geometry3d_init, wings_skeleton2d, wings_params_init_frame,
+                                                       param_names, res_method, opt_method, self.wings_sets.bounds_init, return_nan,
+                                                       dlt_coefs=dlt_coefs)
+                    else:
+                        if '3d' in res_method:
+                            wings_params_est_frame, rmse_frame, nb_iterations_frame = \
+                                optim_fit_limbs_params(animal_name, self.wings_skeleton3d_init, wings_skeleton3d, wings_params_init_frame,
+                                                       param_names, res_method, opt_method, self.wings_sets.bounds_init, return_nan)
+
+                        elif '2d' in res_method:
+                            wings_params_est_frame, rmse_frame, nb_iterations_frame = \
+                                optim_fit_limbs_params(animal_name, self.wings_skeleton3d_init, wings_skeleton2d, wings_params_init_frame,
+                                                       param_names, res_method, opt_method, self.wings_sets.bounds_init, return_nan,
+                                                       dlt_coefs=dlt_coefs)
+
+                    for side in self.wings_skeleton3d_init.keys():
+                        rmse[obj_name][side][i] = rmse_frame[side]
+                        nb_iterations[obj_name][side][i] = nb_iterations_frame[side]
+                        for label in wings_params_est_frame[side].keys():
+                            wings_params_ests[obj_name][side][label][i] = wings_params_est_frame[side][label]
+
+                    if show_plot and frame % 20 == 0.0:
+                        body_skeleton2d_new = reproject_skeleton3d_to2d(body_skeleton3d, dlt_coefs)
+
+                        wings_skeleton3d_new = copy.deepcopy(self.wings_skeleton3d_init)
+                        wings_skeleton3d_init = copy.deepcopy(self.wings_skeleton3d_init)
+                        wings_skeleton2d_new, wings_skeleton2d_init = {'right': {}, 'left': {}}, {'right': {}, 'left': {}}
+                        wings_skeleton3d_repro2d = {'right': {}, 'left': {}}
+                        if '3d' in res_method: wings_skeleton2d = copy.deepcopy(wings_skeleton2d_new)
+                        for side in self.wings_skeleton3d_init.keys():
+                            wings_skeleton3d_init[side] = wings.build_skeleton3d(wings_skeleton3d_init[side], wings_params_init_frame[side])
+                            wings_skeleton3d_init[side] = wings.rotate_and_translate_skeleton3d(wings_skeleton3d_init[side], wings_params_init_frame[side], side)
+                            wings_skeleton2d_init[side] = reproject_skeleton3d_to2d(wings_skeleton3d_init[side], dlt_coefs)
+
+                            wings_skeleton3d_new[side] = wings.build_skeleton3d(wings_skeleton3d_new[side], wings_params_est_frame[side])
+                            wings_skeleton3d_new[side] = wings.rotate_and_translate_skeleton3d(wings_skeleton3d_new[side], wings_params_est_frame[side], side)
+                            wings_skeleton2d_new[side] = reproject_skeleton3d_to2d(wings_skeleton3d_new[side], dlt_coefs)
+
+                            # Compute reprojection of 3d reconstruction from dlc 2d points
+                            for label in self.wings_skeleton3d_init[side].keys():
+                                wings_skeleton3d_repro2d[side][label] = {1: []}
+                                for j, camn in enumerate(range(1, nb_cam + 1)):
+                                    label2 = side + '_wing_' + label
+                                    uv = calib.find2d(1, dlt_coefs[j], np.array([[self.skeleton3d[obj_name][label2]['x'][i],
+                                                                                     self.skeleton3d[obj_name][label2]['y'][i], self.skeleton3d[obj_name][label2]['z'][i]]]))
+                                    wings_skeleton3d_repro2d[side][label][camn] = np.array([uv[0][0], uv[0][1], 1.0])
+
+                            if '3d' in res_method:
+                                wings_skeleton2d[side] = reproject_skeleton3d_to2d(wings_skeleton3d[side], dlt_coefs)
+
+                        for j, camn in enumerate(range(1, nb_cam + 1)):
+                            center_of_mass_xy = \
+                                (body_skeleton2d_new['torso_abdomen_joint'][camn] * body_params_frame['ratio_com_torso'] \
+                                 + body_skeleton2d_new['head_torso_joint'][camn] * (1 - body_params_frame['ratio_com_torso']))
+
+                            if np.isnan(center_of_mass_xy).any(): continue
+
+                            axs[j].clear()
+                            axs[j].imshow(Image.open(os.path.join(rec_paths[camn], all_images[camn][i])))
+                            for side in self.wings_skeleton3d_init.keys():
+                                for label in wings_skeleton2d_new[side].keys():
+                                    if wings_skeleton2d[side][label][camn][2] > self.wings_sets.threshold_likelihood:
+                                        axs[j].scatter(wings_skeleton2d[side][label][camn][0],
+                                                       wings_skeleton2d[side][label][camn][1], marker='.', color='k')
+                                        axs[j].scatter(wings_skeleton3d_repro2d[side][label][camn][0],
+                                                       wings_skeleton3d_repro2d[side][label][camn][1], marker='o', facecolors='none', edgecolors='k')
+                                    axs[j].scatter(wings_skeleton2d_init[side][label][camn][0],
+                                                   wings_skeleton2d_init[side][label][camn][1], marker='o', facecolors='none', edgecolors='b')
+                                    axs[j].scatter(wings_skeleton2d_new[side][label][camn][0],
+                                                   wings_skeleton2d_new[side][label][camn][1], marker='+', color='r')
+
+                            axs[j].set_xlim((center_of_mass_xy[0] - 100, center_of_mass_xy[0] + 100))
+                            axs[j].set_ylim((center_of_mass_xy[1] + 100, center_of_mass_xy[1] - 100))
+
+                        plt.pause(0.05)
+                        #plt.show()
+
+                        # param_to_print = ['stroke_a', 'deviation_a', 'rotation_a', 'ratio_wing']
+                        # for label in param_to_print:
+                        #     print('{0}: init: {1} and est: {2}'.format(label, wings_params_init_frame[side][label],
+                        #                                                wings_params_est_frame[side][label]))
+
+                else:
+                    if 'geo' in res_method:
+                        if '3d' in res_method:
+                            args[i] = (self.wings_geometry3d_init, wings_skeleton3d, wings_params_init_frame, param_names,
+                                       res_method, opt_method, self.wings_sets.bounds_init, return_nan)
+
+                        elif '2d' in res_method:
+                            args[i] = (self.wings_geometry3d_init, wings_skeleton2d, wings_params_init_frame, param_names,
+                                       res_method, opt_method, self.wings_sets.bounds_init, return_nan)
+                    else:
+                        if '3d' in res_method:
+                            args[i] = (self.wings_skeleton3d_init, wings_skeleton3d, wings_params_init_frame, param_names,
+                                       res_method, opt_method, self.wings_sets.bounds_init, return_nan)
+
+                        elif '2d' in res_method:
+                            args[i] = (self.wings_skeleton3d_init, wings_skeleton2d, wings_params_init_frame, param_names,
+                                       res_method, opt_method, self.wings_sets.bounds_init, return_nan)
+
+            if self.multiprocessing:
+                pool_wings = Pool(self.nb_process)
+                if '3d' in res_method:
+                    results = pool_wings.starmap(optim_fit_limbs_params, args)
+
+                elif '2d' in res_method:
+                    results = pool_wings.starmap(partial(optim_fit_limbs_params, dlt_coefs=dlt_coefs), args)
+
+                pool_wings.close()
+                for i, frame in enumerate(frames):
+                    wings_params_est_frame, rmse_frame, nb_iterations_frame = results[i]
+                    for side in self.wings_skeleton3d_init.keys():
+                        rmse[obj_name][side][i] = rmse_frame[side]
+                        nb_iterations[obj_name][side][i] = nb_iterations_frame[side]
+                        for label in wings_params_est_frame[side].keys():
+                            wings_params_ests[obj_name][side][label][i] = wings_params_est_frame[side][label]
+
+            for side in self.wings_skeleton3d_init.keys():
+                wings_params_ests[obj_name][side] = \
+                    filter_interp_params(self.cutoff_frequency, self.frame_rate, wings_params_ests[obj_name][side],
+                                         ['x_hinge', 'y_hinge', 'z_hinge'], False, filter_high_freq, show_plot)
+                wings_params_ests[obj_name][side] = \
+                    filter_interp_params(self.cutoff_frequency, self.frame_rate, wings_params_ests[obj_name][side],
+                                         param_names, interpolate_nans, False, show_plot)
+
+        return wings_params_ests, nb_visible_pts, rmse, nb_iterations
+
+    def optim_fit_all_hybrid_params(self, animal_name, body_params_init, wings_params_init, param_names, res_method,
+                                    opt_method, dlt_coefs, rec_paths, interpolate_nans=False, filter_high_freq=False,
+                                    first_optim_wings=False, show_plot=False):
+
+        nb_cam = len(rec_paths.keys())
+
+        hybrid_params_init = copy.deepcopy(body_params_init)
+        if '2d' in res_method: self.hybrid_skeleton2d = copy.deepcopy(self.skeleton2d)
+        else: self.hybrid_skeleton3d = copy.deepcopy(self.skeleton3d)
+
+        if first_optim_wings:
+            start = time.time()
+            # First optim on wing to estimate wing tips and hinges positions + deviation_a
+            wing_param_names = ['stroke_a', 'deviation_a', 'rotation_a', 'x_hinge', 'y_hinge', 'z_hinge']
+            wings_params, _, _ = \
+                self.optim_fit_all_wings_params(body_params_init, wings_params_init, wing_param_names, res_method,
+                                                opt_method, dlt_coefs, rec_paths, interpolate_nans=False, filter_high_freq=True, show_plot=show_plot)
+
+            obj_names = self.skeleton2d['obj_names'] if '2d' in res_method else self.skeleton3d['obj_names']
+            for obj_name in obj_names:
+
+                frames = self.skeleton2d[obj_name]['frames'] if '2d' in res_method else self.skeleton3d[obj_name]['frames']
+                for i, frame in enumerate(frames):
+
+                    wings_params_frame = {'right': {}, 'left': {}}
+                    for side in self.wings_skeleton3d_init.keys():
+                        for label in wings_params[obj_name][side].keys():
+                            wings_params_frame[side][label] = wings_params[obj_name][side][label][i]
+
+                    wings_skeleton3d_new = copy.deepcopy(self.wings_skeleton3d_init)
+                    wings_skeleton2d_new = {'right': {}, 'left': {}}
+                    for side in self.wings_skeleton3d_init.keys():
+                        wings_skeleton3d_new[side] = wings.build_skeleton3d(wings_skeleton3d_new[side], wings_params_frame[side])
+                        wings_skeleton3d_new[side] = \
+                            wings.rotate_and_translate_skeleton3d(wings_skeleton3d_new[side], wings_params_frame[side], side)
+
+                        if '2d' in res_method:
+                            wings_skeleton2d_new[side] = reproject_skeleton3d_to2d(wings_skeleton3d_new[side], dlt_coefs)
+
+                        for label in ['tip', 'hinge']:
+                            wing_label = side + '_wing_' + label
+
+                            if '2d' in res_method:
+                                for camn in range(1, nb_cam + 1):
+                                    self.hybrid_skeleton2d[obj_name][wing_label][camn]['x'][i] = wings_skeleton2d_new[side][label][camn][0]
+                                    self.hybrid_skeleton2d[obj_name][wing_label][camn]['y'][i] = wings_skeleton2d_new[side][label][camn][1]
+                                    self.hybrid_skeleton2d[obj_name][wing_label][camn]['likelihood'][i] = 1.0
+
+                            else:
+                                self.hybrid_skeleton3d[obj_name][wing_label]['x'][i] = wings_skeleton3d_new[side][label][0]
+                                self.hybrid_skeleton3d[obj_name][wing_label]['y'][i] = wings_skeleton3d_new[side][label][1]
+                                self.hybrid_skeleton3d[obj_name][wing_label]['z'][i] = wings_skeleton3d_new[side][label][2]
+
+            print('>> first optim wing done! (time elapsed: {0:.4f} s)'.format(time.time() - start))
+
+        res_method = 'hybrid_' + res_method
+
+        hybrid_param_ests, nb_visible_pts, rmse, nb_iterations = {}, {}, {}, {}
+        obj_names = self.hybrid_skeleton2d['obj_names'] if '2d' in res_method else self.hybrid_skeleton3d['obj_names']
+        for obj_name in obj_names:
+            frames = self.hybrid_skeleton2d[obj_name]['frames'] if '2d' in res_method else self.hybrid_skeleton3d[obj_name]['frames']
+            # frames = [frame for i, frame in enumerate(frames) if i % 100 == 0.0]
+
+            # Low pass filter the position of wing tips and hinges + deviation_a and span
+            for param_name in ['deviation_a', 'span']:
+                hybrid_params_init[obj_name]['wbaverage_' + param_name] = \
+                    np.mean([np.transpose(filter_interp(wings_params_init[obj_name]['right'][param_name],
+                                                        self.frame_rate, self.cutoff_frequency)),
+                             np.transpose(filter_interp(wings_params_init[obj_name]['left'][param_name],
+                                                        self.frame_rate, self.cutoff_frequency))], axis=0)
+
+            for side in self.wings_sets.params_init.keys():
+                for label in ['tip', 'hinge']:
+                    wing_label = side +'_wing_' + label
+                    if '2d' in res_method:
+                        for camn in range(1, nb_cam + 1):
+                            self.hybrid_skeleton2d[obj_name][wing_label][camn]['x'] = \
+                                filter_interp(self.hybrid_skeleton2d[obj_name][wing_label][camn]['x'],
+                                              self.frame_rate, self.cutoff_frequency)
+                            self.hybrid_skeleton2d[obj_name][wing_label][camn]['y'] = \
+                                filter_interp(self.hybrid_skeleton2d[obj_name][wing_label][camn]['y'],
+                                              self.frame_rate, self.cutoff_frequency)
+                            self.hybrid_skeleton2d[obj_name][wing_label][camn]['likelihood'] = \
+                                np.ones(np.size(self.hybrid_skeleton2d[obj_name][wing_label][camn]['x'],
+                                                self.frame_rate, self.cutoff_frequency))
+
+                    else:
+                        self.hybrid_skeleton3d[obj_name][wing_label]['x'] = \
+                            filter_interp(self.hybrid_skeleton3d[obj_name][wing_label]['x'],
+                                          self.frame_rate, self.cutoff_frequency)
+                        self.hybrid_skeleton3d[obj_name][wing_label]['y'] = \
+                            filter_interp(self.hybrid_skeleton3d[obj_name][wing_label]['y'],
+                                          self.frame_rate, self.cutoff_frequency)
+                        self.hybrid_skeleton3d[obj_name][wing_label]['z'] = \
+                            filter_interp(self.hybrid_skeleton3d[obj_name][wing_label]['z'],
+                                          self.frame_rate, self.cutoff_frequency)
+
+            shape_frame = np.array(frames).shape
+            nb_iterations[obj_name] = np.zeros(shape_frame)
+            nb_visible_pts[obj_name], rmse[obj_name] = np.ones(shape_frame) * np.nan, np.ones(shape_frame) * np.nan
+            if obj_name in hybrid_params_init.keys():
+                hybrid_param_ests[obj_name] = copy.deepcopy(hybrid_params_init[obj_name])
+            else:
+                hybrid_param_ests[obj_name] = {}
+                for label in self.hybrid_sets.params_init.keys():
+                    hybrid_param_ests[obj_name][label] = np.ones(shape_frame) * self.hybrid_sets.params_init[label]
+
+            if show_plot and not self.multiprocessing:
+                fig, axs = plt.subplots(1, nb_cam)
+                all_images = {}
+                for camn in range (1, nb_cam +1):
+                    all_files = sorted(os.listdir(rec_paths[camn]), key=lambda s: s[s.rfind('.') - nb_leading_zeros:s.rfind('.')])
+                    all_images[camn] = [s for s in all_files if '.{0}'.format(image_format) in s]
+
+            if self.multiprocessing: args = [(frame,) for frame in frames]
+
+            #for i, frame in enumerate(tqdm.tqdm(frames)):  # to print progress bar
+            for i, frame in enumerate(frames):
+                if obj_name in hybrid_params_init.keys():
+                    hybrid_params_init_frame = {}
+                    for label in self.hybrid_sets.params_init.keys():
+                        hybrid_params_init_frame[label] = hybrid_params_init[obj_name][label][i]
+                else:
+                    hybrid_params_init_frame = self.hybrid_sets.params_init
+
+                nb_points = len(self.hybrid_sets.skeleton3d_init.keys())
+                nb_visible_pts[obj_name][i] = nb_points
+                if '3d' in res_method:
+                    hybrid_skeleton3d = {}
+                    for hybrid_label in self.hybrid_sets.skeleton3d_init.keys():
+                        hybrid_skeleton3d[hybrid_label] = \
+                            np.array([self.hybrid_skeleton3d[obj_name][hybrid_label]['x'][i],
+                                      self.hybrid_skeleton3d[obj_name][hybrid_label]['y'][i],
+                                      self.hybrid_skeleton3d[obj_name][hybrid_label]['z'][i]])
+
+                        if np.isnan(hybrid_skeleton3d[hybrid_label]).any(): nb_visible_pts[obj_name][i] -= 1
+
+                elif '2d' in res_method:
+                    hybrid_skeleton2d = {}
+                    for hybrid_label in self.hybrid_sets.skeleton3d_init.keys():
+                        hybrid_skeleton2d[hybrid_label] = {1: np.array([])}
+                        for camn in range(1, nb_cam + 1):
+
+                            if self.hybrid_skeleton2d[obj_name][hybrid_label][camn]['likelihood'][i] > self.hybrid_sets.threshold_likelihood:
+                                hybrid_skeleton2d[hybrid_label][camn] = \
+                                    np.array([self.hybrid_skeleton2d[obj_name][hybrid_label][camn]['x'][i],
+                                              self.hybrid_skeleton2d[obj_name][hybrid_label][camn]['y'][i],
+                                              self.hybrid_skeleton2d[obj_name][hybrid_label][camn]['likelihood'][i]])
+                            else:
+                                hybrid_skeleton2d[hybrid_label][camn] = \
+                                    np.array([np.nan, np.nan, self.hybrid_skeleton2d[obj_name][hybrid_label][camn]['likelihood'][i]])
+
+                                nb_visible_pts[obj_name][i] -= 1 / nb_cam
+
+                return_nan = nb_visible_pts[obj_name][i] <= self.body_sets.threshold_nb_pts - 2 # to compensate for the extra wing tips
+
+                if not self.multiprocessing:
+                    if '3d' in res_method:
+                        hybrid_param_ests_frame, rmse[obj_name][i], nb_iterations[obj_name][i] = \
+                            optim_fit_body_params(animal_name, self.hybrid_sets.skeleton3d_init, hybrid_skeleton3d, hybrid_params_init_frame,
+                                                  param_names, res_method, opt_method, self.hybrid_bounds_init, return_nan)
+
+                    elif '2d' in res_method:
+                        hybrid_param_ests_frame, rmse[obj_name][i], nb_iterations[obj_name][i] = \
+                            optim_fit_body_params(animal_name, self.hybrid_sets.skeleton3d_init, hybrid_skeleton2d, hybrid_params_init_frame,
+                                                  param_names, res_method, opt_method, self.hybrid_bounds_init, return_nan, dlt_coefs=dlt_coefs)
+
+                    for label in hybrid_param_ests_frame.keys():
+                        hybrid_param_ests[obj_name][label][i] = hybrid_param_ests_frame[label]
+
+                    if show_plot and (frame % 20 == 0.0 or frame == 1):
+                        hybrid_skeleton3d_init = copy.deepcopy(self.hybrid_sets.skeleton3d_init)
+                        hybrid_skeleton3d_init = hybrid.build_skeleton3d(hybrid_skeleton3d_init, hybrid_params_init_frame)
+                        hybrid_skeleton3d_init = body.rotate_skeleton3d(hybrid_skeleton3d_init, hybrid_params_init_frame)
+                        hybrid_skeleton3d_init = body.translate_skeleton3d(hybrid_skeleton3d_init, hybrid_params_init_frame)
+                        hybrid_skeleton2d_init = reproject_skeleton3d_to2d(hybrid_skeleton3d_init, dlt_coefs)
+
+                        hybrid_skeleton3d_new = copy.deepcopy(self.hybrid_sets.skeleton3d_init)
+                        hybrid_skeleton3d_new = hybrid.build_skeleton3d(hybrid_skeleton3d_new, hybrid_param_ests_frame)
+                        hybrid_skeleton3d_new = body.rotate_skeleton3d(hybrid_skeleton3d_new, hybrid_param_ests_frame)
+                        hybrid_skeleton3d_new = body.translate_skeleton3d(hybrid_skeleton3d_new, hybrid_param_ests_frame)
+                        hybrid_skeleton2d_new = reproject_skeleton3d_to2d(hybrid_skeleton3d_new, dlt_coefs)
+
+                        # Compute reprojection of 3d reconstruction from dlc 2d points
+                        hybrid_skeleton3d_repro2d = {}
+                        for label in self.hybrid_sets.skeleton3d_init.keys():
+                            hybrid_skeleton3d_repro2d[label] = {1: []}
+                            for j, camn in enumerate(range(1, nb_cam + 1)):
+                                uv = calib.find2d(1, dlt_coefs[j], np.array([[self.hybrid_skeleton3d[obj_name][label]['x'][i],
+                                                                              self.hybrid_skeleton3d[obj_name][label]['y'][i], self.hybrid_skeleton3d[obj_name][label]['z'][i]]]))
+                                hybrid_skeleton3d_repro2d[label][camn] = np.array([uv[0][0], uv[0][1], 1.0])
+
+                        if '3d' in res_method:
+                            hybrid_skeleton2d = reproject_skeleton3d_to2d(hybrid_skeleton3d, dlt_coefs)
+
+                        # Plot 2d repojection of hybrid skeleton
+                        for j, camn in enumerate(range(1, nb_cam +1)):
+                            center_of_mass_xy = \
+                                (hybrid_skeleton2d_new['torso_abdomen_joint'][camn] * hybrid_param_ests_frame['ratio_com_torso'] \
+                                 + hybrid_skeleton2d_new['head_torso_joint'][camn] * (1 - hybrid_param_ests_frame['ratio_com_torso']))
+
+                            if np.isnan(center_of_mass_xy).any(): continue
+
+                            axs[j].clear()
+                            axs[j].imshow(Image.open(os.path.join(rec_paths[camn], all_images[camn][i])))
+                            for label in hybrid_skeleton2d_new.keys():
+                                if hybrid_skeleton2d[label][camn][2] > self.hybrid_sets.threshold_likelihood:
+                                    axs[j].scatter(hybrid_skeleton2d[label][camn][0],
+                                                   hybrid_skeleton2d[label][camn][1], marker='.', color='k')
+                                    axs[j].scatter(hybrid_skeleton3d_repro2d[label][camn][0],
+                                                   hybrid_skeleton3d_repro2d[label][camn][1], marker='o', facecolors='none', edgecolors='k')
+                                axs[j].scatter(hybrid_skeleton2d_init[label][camn][0],
+                                               hybrid_skeleton2d_init[label][camn][1], marker='o', facecolors='none', edgecolors='b')
+                                axs[j].scatter(hybrid_skeleton2d_new[label][camn][0],
+                                               hybrid_skeleton2d_new[label][camn][1], marker='+', color='r')
+
+                            axs[j].set_xlim((center_of_mass_xy[0] - 100, center_of_mass_xy[0] + 100))
+                            axs[j].set_ylim((center_of_mass_xy[1] + 100, center_of_mass_xy[1] - 100))
+
+                        plt.pause(0.05)
+                        # plt.show()
+
+                        # param_to_print = ['pitch_a', 'roll_a', 'yaw_a', 'ratio_hybrid']
+                        # for label in param_to_print:
+                        #     print('{0}: init: {1} and est: {2}'.format(label, hybrid_params_init_frame[label], hybrid_param_ests_frame[label]))
+
+                else:
+                    if '3d' in res_method:
+                        args[i] = (self.hybrid_sets.skeleton3d_init, hybrid_skeleton3d, hybrid_params_init_frame, param_names,
+                                   res_method, opt_method, self.hybrid_bounds_init, return_nan,)
+
+                    elif '2d' in res_method:
+                        args[i] = (self.hybrid_sets.skeleton3d_init, hybrid_skeleton2d, hybrid_params_init_frame, param_names,
+                                   res_method, opt_method, self.hybrid_bounds_init, return_nan,)
+
+            if self.multiprocessing:
+                pool_hybrid = Pool(self.nb_process)
+                if '3d' in res_method:
+                    results = pool_hybrid.starmap(optim_fit_body_params, args)
+
+                elif '2d' in res_method:
+                    results = pool_hybrid.starmap(partial(optim_fit_body_params, dlt_coefs=dlt_coefs), args)
+
+                pool_hybrid.close()
+                for i, frame in enumerate(frames):
+                    hybrid_param_ests_frame, rmse[obj_name][i], nb_iterations[obj_name][i] = results[i]
+                    for label in hybrid_param_ests_frame.keys():
+                        hybrid_param_ests[obj_name][label][i] = hybrid_param_ests_frame[label]
+
+            hybrid_param_ests[obj_name] = filter_interp_params(self.cutoff_frequency, self.frame_rate,
+                                                               hybrid_param_ests[obj_name], param_names,
+                                                               interpolate_nans, filter_high_freq, show_plot)
+
+        return hybrid_param_ests, nb_visible_pts, rmse, nb_iterations
+
+    def correct_body_wings_params(self, body_params, wings_params, angle_names_to_correct, rec_paths, dlt_coefs, show_plot=False):
+        # Will correct asymetrical wing angles (mean value should be the same on both side)
+        body_params_corrected = copy.deepcopy(body_params)
+        wings_params_corrected = copy.deepcopy(wings_params)
+
+        obj_names = self.skeleton2d['obj_names']
+        for obj_name in obj_names:
+
+            wings_params_mean = {} # Find angles error (btw stroke_a and deviation_a of both sides)
+            for side in self.wings_skeleton3d_init.keys():
+                wings_params_mean[side] = \
+                    filter_interp_params(self.cutoff_frequency, self.frame_rate, wings_params[obj_name][side],
+                                         angle_names_to_correct, False, True, show_plot)
+
+            if 'stroke_a' in angle_names_to_correct:
+                diff_stroke_a = wings_params_mean['right']['stroke_a'] - wings_params_mean['left']['stroke_a']
+                # body_params_corrected[obj_name]['yaw_a'] -= diff_stroke_a / 2
+                # wings_params_corrected[obj_name]['right']['stroke_a'] -= diff_stroke_a / 2
+                # wings_params_corrected[obj_name]['left']['stroke_a'] += diff_stroke_a / 2
+                # wings_params_corrected[obj_name]['right']['yaw_a'] -= diff_stroke_a / 2
+                # wings_params_corrected[obj_name]['left']['yaw_a'] -= diff_stroke_a / 2
+
+            if 'deviation_a' in angle_names_to_correct:
+                diff_deviation_a = wings_params_mean['right']['deviation_a'] - wings_params_mean['left']['deviation_a']
+                # body_params_corrected[obj_name]['roll_a'] -= diff_deviation_a / 2
+                # wings_params_corrected[obj_name]['right']['deviation_a'] -= diff_deviation_a / 2
+                # wings_params_corrected[obj_name]['left']['deviation_a'] += diff_deviation_a / 2
+                # wings_params_corrected[obj_name]['right']['roll_a'] -= diff_deviation_a / 2
+                # wings_params_corrected[obj_name]['left']['roll_a'] -= diff_deviation_a / 2
+
+            if 'rotation_a' in angle_names_to_correct:
+                raise ValueError('There is not reason for rotation_a to be corrected!'
+                                 'Any asymmetry btw sides cannot results from estimation error of the pitch angle')
+
+            if show_plot:
+                # Plot wing tips pos
+                frames = self.skeleton2d[obj_name]['frames']
+                wing_tips, wing_hinges = {'right': {}, 'left': {}}, {'right': {}, 'left': {}}
+                for side in self.wings_skeleton3d_init.keys():
+                    for coord in ['x', 'y', 'z']:
+                        wing_tips[side][coord], wing_hinges[side][coord] = np.ones(np.size(frames)) * np.nan, np.ones(
+                            np.size(frames)) * np.nan
+
+                tip_hinge_a = np.ones(np.size(frames)) * np.nan
+                tip_torso_a = np.ones(np.size(frames)) * np.nan
+                for i, frame in enumerate(frames):
+                    body_params_frame, wings_params_frame = {}, {'right': {}, 'left': {}}
+                    for label in body_params[obj_name].keys():
+                        body_params_frame[label] = body_params[obj_name][label][i]
+
+                    body_skeleton3d_est = copy.deepcopy(self.body_skeleton3d_init)
+                    body_skeleton3d_est = body.build_skeleton3d(body_skeleton3d_est, body_params_frame)
+                    body_skeleton3d_est = body.rotate_skeleton3d(body_skeleton3d_est, body_params_frame)
+                    body_skeleton3d_est = body.translate_skeleton3d(body_skeleton3d_est, body_params_frame)
+
+                    wings_skeleton3d_mean = copy.deepcopy(self.wings_skeleton3d_init)
+                    for side in self.wings_skeleton3d_init.keys():
+                        for label in wings_params[obj_name][side].keys():
+                            wings_params_frame[side][label] = wings_params_mean[side][label][i]
+
+                        wings_skeleton3d_mean[side] = wings.build_skeleton3d(wings_skeleton3d_mean[side],
+                                                                             wings_params_frame[side])
+                        wings_skeleton3d_mean[side] = wings.rotate_and_translate_skeleton3d(wings_skeleton3d_mean[side],
+                                                                                            wings_params_frame[side],
+                                                                                            side)
+
+                        wing_tips[side]['x'][i], wing_tips[side]['y'][i], wing_tips[side]['z'][i] = \
+                            wings_skeleton3d_mean[side]['tip'][0], wings_skeleton3d_mean[side]['tip'][1], \
+                            wings_skeleton3d_mean[side]['tip'][2]
+                        wing_hinges[side]['x'][i], wing_hinges[side]['y'][i], wing_hinges[side]['z'][i] = \
+                            wings_skeleton3d_mean[side]['hinge'][0], wings_skeleton3d_mean[side]['hinge'][1], \
+                            wings_skeleton3d_mean[side]['hinge'][2]
+
+                        tip_vect = np.array(
+                            [wing_tips['right']['x'][i], wing_tips['right']['y'][i], wing_tips['right']['z'][i]]) \
+                                   - np.array(
+                            [wing_tips['left']['x'][i], wing_tips['left']['y'][i], wing_tips['left']['z'][i]])
+                        hinge_vect = np.array(
+                            [wing_hinges['right']['x'][i], wing_hinges['right']['y'][i], wing_hinges['right']['z'][i]]) \
+                                     - np.array(
+                            [wing_hinges['left']['x'][i], wing_hinges['left']['y'][i], wing_hinges['left']['z'][i]])
+
+                        torso_vect = body_skeleton3d_est['head_torso_joint'] - body_skeleton3d_est[
+                            'torso_abdomen_joint']
+
+                        tip_hinge_a[i] = angle_from_vectors(tip_vect, hinge_vect)
+                        tip_torso_a[i] = angle_from_vectors(tip_vect, torso_vect)
+
+                fig_test, axs_test = plt.subplots(1, 3)
+                fig_test.suptitle('Average position of wing tip over time')
+                for side in self.wings_skeleton3d_init.keys():
+                    axs_test[0].plot(wing_tips[side]['x'] - body_params[obj_name]['x_com'], label=side + '_tip')
+                    axs_test[1].plot(wing_tips[side]['y'] - body_params[obj_name]['y_com'], label=side + '_tip')
+                    axs_test[2].plot(wing_tips[side]['z'] - body_params[obj_name]['z_com'], label=side + '_tip')
+
+                    axs_test[0].plot(wing_hinges[side]['x'] - body_params[obj_name]['x_com'], label=side + '_hinge')
+                    axs_test[1].plot(wing_hinges[side]['y'] - body_params[obj_name]['y_com'], label=side + '_hinge')
+                    axs_test[2].plot(wing_hinges[side]['z'] - body_params[obj_name]['z_com'], label=side + '_hinge')
+
+                    axs_test[0].set_ylabel('x (m)'), axs_test[1].set_ylabel('y (m)'), axs_test[2].set_ylabel('z (m)')
+
+                axs_test[2].set_xlabel('frames')
+                fig_test.legend()
+
+                fig_test2, axs_test2 = plt.subplots(1, 2)
+                fig_test2.suptitle('Average angle btw  wing tip vector and torso vector over time')
+                axs_test2[0].plot(tip_hinge_a, label='tip_hinge_a')
+                axs_test2[0].plot(diff_stroke_a, label='diff_stroke_a')
+                axs_test2[0].plot(diff_deviation_a, label='diff_deviation_a')
+                # axs_test2[0].plot(diff_rotation_a, label='diff_rotation_a')
+                axs_test2[1].plot(tip_torso_a, label='tip_torso_a')
+                axs_test2[0].set_ylabel('angle (deg)')
+
+                fig_test2.legend()
+
+                self.plot_skeleton2d(body_params_corrected, wings_params_corrected, rec_paths, dlt_coefs)
+
+        return body_params_corrected, wings_params_corrected
+
+    def plot_skeleton2d(self, body_params, wings_params, rec_paths, dlt_coefs, modulo=1, save_images=False,
+                        save_paths={}, show_plot=False, show_body=True, show_wings=True):
+
+        # TODO remove the folowing data in code
+        camns_to_rotate = [2]
+        degrees = 270
+        main_camn = 1
+
+        nb_cam = len(rec_paths.keys())
+
+        obj_names = self.skeleton2d['obj_names']
+        for obj_name in obj_names:
+            if not show_body and not show_wings: break
+
+            if show_plot:
+                fig, axs = plt.subplots(1, nb_cam, figsize=(15, 20))
+                plt.axis('off')
+
+            all_images = {}
+            for camn in range(1, nb_cam + 1):
+                all_files = sorted(os.listdir(rec_paths[camn]), key=lambda s: s[s.rfind('.') - nb_leading_zeros:s.rfind('.')])
+                all_images[camn] = [s for s in all_files if '.{0}'.format(image_format) in s]
+
+            frames = self.skeleton2d[obj_name]['frames']
+            for i, frame in enumerate(frames):
+                if not frame % modulo == 0.0 and not frame == 1: continue
+
+                body_skeleton2d_dlc = {}
+                for body_label in self.skeleton2d[obj_name]['label_names']:
+                    body_skeleton2d_dlc[body_label] = {1: np.array([])}
+                    for camn in range(1, nb_cam + 1):
+                        if self.skeleton2d[obj_name][body_label][camn]['likelihood'][i] > self.body_sets.threshold_likelihood:
+                            body_skeleton2d_dlc[body_label][camn] = \
+                                np.array([self.skeleton2d[obj_name][body_label][camn]['x'][i],
+                                          self.skeleton2d[obj_name][body_label][camn]['y'][i],
+                                          self.skeleton2d[obj_name][body_label][camn]['likelihood'][i]])
+                        else:
+                            body_skeleton2d_dlc[body_label][camn] = \
+                                np.array([np.nan, np.nan, self.skeleton2d[obj_name][body_label][camn]['likelihood'][i]])
+
+                if show_body:
+                    body_params_frame = {}
+                    for label in body_params[obj_name].keys():
+                        body_params_frame[label] = body_params[obj_name][label][i]
+
+                    body_skeleton3d = copy.deepcopy(self.body_skeleton3d_init)
+                    body_skeleton3d = body.build_skeleton3d(body_skeleton3d, body_params_frame)
+                    body_skeleton3d = body.rotate_skeleton3d(body_skeleton3d, body_params_frame)
+                    body_skeleton3d = body.translate_skeleton3d(body_skeleton3d, body_params_frame)
+                    body_skeleton2d = reproject_skeleton3d_to2d(body_skeleton3d, dlt_coefs)
+
+                if show_wings:
+                    wings_skeleton3d = copy.deepcopy(self.wings_skeleton3d_init)
+
+                    wings_params_frame = {'right': {}, 'left': {}}
+                    wings_skeleton2d, wings_skeleton2d_dlc = {'right': {}, 'left': {}}, {'right': {}, 'left': {}}
+                    for side in self.wings_skeleton3d_init.keys():
+                        for label in wings_params[obj_name][side].keys():
+                            wings_params_frame[side][label] = wings_params[obj_name][side][label][i]
+
+                        wings_skeleton3d[side] = wings.build_skeleton3d(wings_skeleton3d[side], wings_params_frame[side])
+                        wings_skeleton3d[side] = wings.rotate_and_translate_skeleton3d(wings_skeleton3d[side], wings_params_frame[side], side)
+                        wings_skeleton2d[side] = reproject_skeleton3d_to2d(wings_skeleton3d[side], dlt_coefs)
+
+                        for wing_label in self.wings_skeleton3d_init[side].keys():
+                            wings_skeleton2d_dlc[side][wing_label] = {1: []}
+                            for camn in range(1, nb_cam + 1):
+                                label = side + '_wing_' + wing_label
+                                if self.skeleton2d[obj_name][label][camn]['likelihood'][i] > self.wings_sets.threshold_likelihood:
+                                    wings_skeleton2d_dlc[side][wing_label][camn] = \
+                                        np.array([self.skeleton2d[obj_name][label][camn]['x'][i],
+                                                  self.skeleton2d[obj_name][label][camn]['y'][i],
+                                                  self.skeleton2d[obj_name][label][camn]['likelihood'][i]])
+                                else:
+                                    wings_skeleton2d_dlc[side][wing_label][camn] = \
+                                        np.array([np.nan, np.nan, self.skeleton2d[obj_name][label][camn]['likelihood'][i]])
+
+                prev_img = None
+                for j, camn in enumerate(range(1, nb_cam + 1)):
+                    center_of_mass_xy = \
+                        (body_skeleton2d['torso_abdomen_joint'][camn] * body_params_frame['ratio_com_torso'] \
+                         + body_skeleton2d['head_torso_joint'][camn] * (1 - body_params_frame['ratio_com_torso']))
+
+                    if np.isnan(center_of_mass_xy).any(): continue
+
+                    if show_plot or save_images:
+                        img = read_image(os.path.join(rec_paths[camn], all_images[camn][i]))
+
+                    if show_plot:
+                        axs[j].clear()
+                        axs[j].imshow(img)
+
+                        if show_body:
+                            for label in body_skeleton2d.keys():
+                                if body_skeleton2d_dlc[label][camn][2] > self.body_sets.threshold_likelihood:
+                                    axs[j].scatter(body_skeleton2d_dlc[label][camn][0],
+                                                   body_skeleton2d_dlc[label][camn][1], marker='.', color='k')
+                                axs[j].scatter(body_skeleton2d[label][camn][0],
+                                               body_skeleton2d[label][camn][1], marker='+', color='r')
+
+                        if show_wings:
+                            for side in self.wings_skeleton3d_init.keys():
+                                for label in wings_skeleton2d[side].keys():
+                                    if wings_skeleton2d_dlc[side][label][camn][2] > self.wings_sets.threshold_likelihood:
+                                        axs[j].scatter(wings_skeleton2d_dlc[side][label][camn][0],
+                                                       wings_skeleton2d_dlc[side][label][camn][1], marker='.', color='k')
+                                    axs[j].scatter(wings_skeleton2d[side][label][camn][0],
+                                                   wings_skeleton2d[side][label][camn][1], marker='+', color='r')
+
+                        axs[j].set_xlim((center_of_mass_xy[0] - 100, center_of_mass_xy[0] + 100))
+                        axs[j].set_ylim((center_of_mass_xy[1] + 100, center_of_mass_xy[1] - 100))
+
+                        if camn == nb_cam:
+                            plt.pause(0.05)
+                            # fig.savefig(os.path.join(save_paths[main_camn], all_images[camn][i]),
+                            #             bbox_inches='tight', transparent=True, pad_inches=0)
+
+                    if save_images:
+                        img = img.convert('RGB')
+                        draw = ImageDraw.Draw(img)
+                        if show_body:
+                            body_line = []
+                            for label in body_skeleton2d.keys():
+                                if body_skeleton2d_dlc[label][camn][2] > self.body_sets.threshold_likelihood:
+                                    draw.point((body_skeleton2d_dlc[label][camn][0], body_skeleton2d_dlc[label][camn][1]), fill='blue')
+                                draw.point((body_skeleton2d[label][camn][0], body_skeleton2d[label][camn][1]), fill='red')
+
+                                if not 'hinge' in label:
+                                    body_line.append((body_skeleton2d[label][camn][0], body_skeleton2d[label][camn][1]))
+                            draw.line(body_line, fill='red', width=2)
+
+                        if show_wings:
+                            for side in self.wings_skeleton3d_init.keys():
+                                wing_line = []
+                                for label in wings_skeleton2d[side].keys():
+                                    if wings_skeleton2d_dlc[side][label][camn][2] > self.wings_sets.threshold_likelihood:
+                                        draw.point((wings_skeleton2d_dlc[side][label][camn][0], wings_skeleton2d_dlc[side][label][camn][1]), fill='blue')
+
+                                    draw.point((wings_skeleton2d[side][label][camn][0], wings_skeleton2d[side][label][camn][1]), fill='red')
+                                    wing_line.append((wings_skeleton2d[side][label][camn][0], wings_skeleton2d[side][label][camn][1]))
+
+                                frst_label = list(wings_skeleton2d[side].keys())[0]
+                                wing_line.append((wings_skeleton2d[side][frst_label][camn][0], wings_skeleton2d[side][frst_label][camn][1]))
+                                draw.line(wing_line, fill='red', width=2)
+
+                        left, right = int(center_of_mass_xy[0] - 100), int(center_of_mass_xy[0] + 100)
+                        top, bottom = int(center_of_mass_xy[1] - 100), int(center_of_mass_xy[1] + 100)
+                        img = img.crop((left, top, right, bottom))
+
+                        if camn in camns_to_rotate:
+                            img = img.rotate(degrees)
+                        # img.show()
+                        # img.save(os.path.join(save_paths[camn], all_images[camn][i]), compression='lzw')
+                        # img.close()
+
+                        # Stitch various cam views
+                        if prev_img is None:
+                            prev_img = img.copy()
+
+                        else:
+                            dst = Image.new('RGB', (prev_img.width + img.width, prev_img.height))
+                            dst.paste(prev_img, (0, 0))
+                            dst.paste(img, (prev_img.width, 0))
+                            prev_img = dst.copy()
+
+                if not prev_img is None:
+                    save_name = all_images[main_camn][i][:-len(image_format) - 1] \
+                                + '-' + obj_name + '.{0}'.format(image_format)
+                    prev_img.save(os.path.join(save_paths[main_camn], save_name), compression='lzw')
+                    prev_img.close()
+                    img.close()
+
+
+def filter_interp(y, frame_rate, cutoff_frequency):
+    y_filtered = y.copy()
+    is_nan, x = np.isnan(y), lambda z: z.nonzero()[0]
+
+    if sum(~is_nan) == 0: return y_filtered # only nans
+    if sum(is_nan) > 0:  # interpolation needed for filter_high_freq
+        y_filtered[is_nan] = np.interp(x(is_nan), x(~is_nan), y_filtered[~is_nan])
+
+    w = cutoff_frequency / (frame_rate / 2)  # Normalize the frequency
+    b, a = signal.butter(2, w, 'low')
+
+    y_filtered = signal.filtfilt(b, a, y_filtered)
+    y_filtered[is_nan] = np.nan
+
+    return y_filtered
+
+
+def minimize_perp_distance(x, y, z):
+    def model(params, xyz):
+        a, b, c, d = params
+        x, y, z = xyz
+        length_squared = a ** 2 + b ** 2 + c ** 2
+        return ((a * x + b * y + c * z + d) ** 2 / length_squared).sum()
+
+    def unit_length(params):
+        a, b, c, d = params
+        return a ** 2 + b ** 2 + c ** 2 - 1
+
+    # Initial guess of params using first three points
+    p1 = np.squeeze(np.transpose([x[0], y[0], z[0]]))
+    p2 = np.squeeze(np.transpose([x[1], y[1], z[1]]))
+    p3 = np.squeeze(np.transpose([x[2], y[2], z[2]]))
+
+    v1, v2 = p3 - p1, p2 - p1  # These two vectors are in the plane
+    cp = np.cross(v1, v2)  # the cross product is a vector normal to the plane
+    d = np.dot(cp, p3)  # This evaluates a * x3 + b * y3 + c * z3 which equals d
+    a, b, c = cp
+
+    # constrain the vector perpendicular to the plane be of unit length
+    cons = ({'type': 'eq', 'fun': unit_length})
+    sol = optimize.minimize(model, (a, b, c, d), args=[x, y, z], constraints=cons)
+
+    return tuple(sol.x)
\ No newline at end of file
diff --git a/process/smoothing_filtering.py b/process/smoothing_filtering.py
new file mode 100644
index 0000000000000000000000000000000000000000..73ecb606ac89f9a649f4872cb1c99bd369c9a4fd
--- /dev/null
+++ b/process/smoothing_filtering.py
@@ -0,0 +1,57 @@
+import copy
+
+import numpy as np
+import matplotlib.pyplot as plt
+
+from scipy import signal
+
+
+def filter_interp_params(cutoff_frequency, frame_rate, params, param_names, interpolate_nans, filter_high_freq,
+                         show_plot=False):
+    if not interpolate_nans and not filter_high_freq: return params
+    new_params = copy.deepcopy(params)
+
+    if show_plot: fig, axs = plt.subplots(len(param_names), 1)
+
+    for i, label in enumerate(param_names):
+        is_nan, x = np.isnan(new_params[label]), lambda z: z.nonzero()[0]
+        if sum(~is_nan) == 0: continue  # only nans
+
+        if show_plot: axs[i].plot(new_params[label], label='raw')
+
+        if '_a' in label:
+            # fig2 = plt.figure()
+            # ax2 = fig2.add_subplot(111)
+            # ax2.plot(new_params[label], label='raw')
+
+            new_params[label][~is_nan] = np.unwrap(np.deg2rad(new_params[label][~is_nan]))
+            # ax2.plot(np.rad2deg(new_params[label]), label='unwrapped')
+
+        if sum(is_nan) > 0:  # interpolation needed for filter_high_freq
+            new_params[label][is_nan] = np.interp(x(is_nan), x(~is_nan), new_params[label][~is_nan])
+
+        if filter_high_freq and np.nanstd(new_params[label]) > 10e-6:
+            w = cutoff_frequency / (frame_rate / 2)  # Normalize the frequency
+            b, a = signal.butter(2, w, 'low')
+
+            new_params[label] = signal.filtfilt(b, a, new_params[label])
+
+            if not interpolate_nans and sum(is_nan) > 0:  # to fill back with nan if didnt want to interpolate
+                new_params[label][is_nan] = np.nan
+
+        if '_a' in label:  # Wrap angles on [-pi, pi)
+            new_params[label] = np.rad2deg((new_params[label] + np.pi) % (2 * np.pi) - np.pi)
+            # ax2.plot(new_params[label], label='wrapped')
+            # ax2.set_ylabel(label)
+            # ax2.set_xlabel('frames')
+
+        if show_plot:
+            axs[i].plot(new_params[label], label='filt')
+            axs[i].set_ylabel(label)
+            axs[i].set_xlabel('frames')
+
+    if show_plot:
+        plt.legend()
+        # plt.show()
+
+    return new_params
\ No newline at end of file
diff --git a/process/utils.py b/process/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..4241fe065841be7ef9d650ed45dc822933b84876
--- /dev/null
+++ b/process/utils.py
@@ -0,0 +1,296 @@
+import os, re, copy
+
+import inspect
+from typing import List, Dict
+
+from difflib import SequenceMatcher
+from datetime import datetime
+
+import numpy as np
+
+
+def match_folders_by_name(folder_names: List[str],
+                          threshold_ratio_diff: float = None,
+                          threshold_nb_diff_char: int = None) -> List[List[str]]:
+    """
+    Matches list of folder names together by checking how similar their folder names are and associate them together
+    The best matches will be found by comparing to the smaller list of folders to avoid matching one to two folders.
+
+    Args:
+        folder_names: List[list[]] of folder names
+        threshold_ratio_diff: Will check the similarity percentage (between 0 and 1)
+        threshold_nb_diff_char: Will check how many characters are different between the file names.
+
+    Returns:
+        matched_folder_names: List of lists (# of recordings * # of cameras) containing the matched folder names.
+
+    Raises:
+        ValueError: If both threshold type are given instead of only one
+        TypeError: thresholds have to be either a positive int or a float between 0.0 and 1.0.
+    """
+
+    if threshold_ratio_diff is not None and threshold_nb_diff_char is not None:
+        raise ValueError("Only one threshold has to be procured")
+
+    elif threshold_ratio_diff is None and threshold_nb_diff_char is None:
+        threshold_ratio_diff = 1.0
+
+    if threshold_ratio_diff is not None:
+        assert type(threshold_ratio_diff) is float
+        assert 0.0 <= threshold_ratio_diff <= 1.0
+
+    elif threshold_nb_diff_char is not None:
+        if type(threshold_nb_diff_char) is float and threshold_nb_diff_char.is_integer():
+            threshold_nb_diff_char = int(threshold_nb_diff_char)
+        assert threshold_nb_diff_char >= 0
+
+    else:
+        raise TypeError('Threshold has to be either a positive int or a float between 0.0 and 1.0')
+
+    nb_directories = len(folder_names)
+    nb_folders = [len(x) for x in folder_names]
+    i_min = np.argmin(nb_folders)
+
+    matched_folder_names, ratios = [[]] * len(folder_names), [[]] * len(folder_names)
+    is_similar_enough = [[]] * len(folder_names)
+    for i in range(nb_directories):
+
+        if i == i_min:
+            matched_folder_names[i] = folder_names[i_min]
+            ratios[i] = [1.0] * len(folder_names[i_min])
+
+        else:
+            matches = [max([(SequenceMatcher(a=f1, b=f2).ratio(), f2) for f2 in folder_names[i]]) for f1 in folder_names[i_min]]
+
+            matched_folder_names[i] = [folder_name for ratio, folder_name in matches]
+            ratios[i] = [ratio for ratio, folder_name in matches]
+
+        if threshold_ratio_diff is not None:
+            is_similar_enough[i] = [ratio >= threshold_ratio_diff for ratio in ratios[i]]
+
+        elif threshold_nb_diff_char is not None:
+            nb_char_diffs = [round((1-ratio)*len(folder_names[i_min][j])) for j, ratio in enumerate(ratios[i])]
+            is_similar_enough[i] = [nb_char_diff <= threshold_nb_diff_char for nb_char_diff in nb_char_diffs]
+
+    matched_folder_names = [list(x) for x in zip(*matched_folder_names)]
+    is_similar_enough = [list(x) for x in zip(*is_similar_enough)]
+
+    matched_folder_names = [x for i, x in enumerate(matched_folder_names) if all(is_similar_enough[i])]
+
+    return matched_folder_names
+
+
+def match_recordings_by_name(paths: List[str],
+                             threshold_ratio_diff: float = None,
+                             threshold_nb_diff_char: int = None) -> List[List[str]]:
+    """
+    Matches the recordings together (minimum 2) by checking how similar their folder names are and associate them together
+    The best matches will be found by comparing to the smaller list of folders to avoid matching one to two other folders.
+
+    Args:
+        paths: List of directory paths (one per camera) each containing folders (to be matched) with recorded images
+        threshold_ratio_diff: Will check the similarity percentage (between 0 and 1)
+        threshold_nb_diff_char: Will check how many characters are different between the file names.
+
+    Returns:
+        matched_folder_names: List of lists (# of recordings * # of cameras) containing the matched folder names.
+    """
+
+    folder_names = get_list_of_folder_names(paths)
+
+    if threshold_nb_diff_char is not None:
+        return match_folders_by_name(folder_names, threshold_nb_diff_char=threshold_nb_diff_char)
+
+    else:
+        return match_folders_by_name(folder_names, threshold_ratio_diff=threshold_ratio_diff)
+
+
+def match_folders_by_date(folder_names: List[str],
+                          expr: str = r"\d{8}_\d{6}",
+                          format_date_str: str ='%Y%m%d_%H%M%S',
+                          threshold_s: int = 30) -> List[List[str]]:
+    """
+    Matches list of folder names together by checking how similar their folder names are and associate them together
+    The best matches will be found by comparing to the smaller list of folders to avoid matching one to two folders.
+
+    Args:
+        folder_names: List[list[]] of folder names
+        expr: Regular expression pattern matching recording folders.
+        format_date_str: format of the date and time in the folder names.
+        threshold_s: Will check if the date in the folder names differ from less than threshold in second
+        
+    Returns:
+        matched_folder_names: List of lists (# of recordings * # of cameras) containing the matched folder names.
+
+    Raises:
+        TypeError: threshold_s has to be a positive int or a float between 0.0 and 1.0.
+    """
+
+    if type(threshold_s) is float and threshold_s.is_integer():
+        threshold_s = int(threshold_s)
+    
+    if not type(threshold_s) is int and threshold_s >= 0:
+        raise TypeError('Threshold has to be either a positive int or a float between 0.0 and 1.0')
+
+    pattern = re.compile(expr)
+
+    dates = [[]] * len(folder_names)
+    for i in range(len(folder_names)):
+
+        new_folder_names, new_dates = [], []
+        for j in range(len(folder_names[i])):
+            match = re.search(pattern, folder_names[i][j])
+
+            if match:
+                new_folder_names.append(folder_names[i][j])
+                new_dates.append(datetime.strptime(match.group(), format_date_str))
+
+        folder_names[i] = new_folder_names
+        dates[i] = new_dates
+
+    nb_folders = [len(x) for x in folder_names]
+    i_min = np.argmin(nb_folders)
+
+    matched_folder_names, diff_time_s = [[]] * len(folder_names), [[]] * len(folder_names)
+    is_similar_enough = [[]] * len(folder_names)
+    for i in range(len(folder_names)):
+
+        if i == i_min:
+            matched_folder_names[i] = folder_names[i_min]
+            diff_time_s[i] = [0] * len(folder_names[i_min])
+
+        else:
+            matches = [min([(abs((d1 - d2).total_seconds()), folder_names[i][j])
+                            for j, d2 in enumerate(dates[i])]) for d1 in dates[i_min]]
+            matched_folder_names[i] = [folder_name for diff_time_s, folder_name in matches]
+            diff_time_s[i] = [diff_s for diff_s, folder_name in matches]
+
+        is_similar_enough[i] = [x <= threshold_s for x in diff_time_s[i]]
+
+    matched_folder_names = [list(x) for x in zip(*matched_folder_names)]
+    is_similar_enough = [list(x) for x in zip(*is_similar_enough)]
+
+    matched_folder_names = [x for i, x in enumerate(matched_folder_names) if all(is_similar_enough[i])]
+
+    return matched_folder_names
+
+
+def match_recordings_by_date(paths: List[str],
+                             expr: str = r"\d{8}_\d{6}",
+                             format_date_str: str ='%Y%m%d_%H%M%S',
+                             threshold_s: int = 30) -> List[List[str]]:
+    """
+    Matches the recordings together (minimum 2) by checking how similar the date in their folder names are
+    and associate them together. The best matches will be found by comparing to the smaller list of folders
+    to avoid matching one to two other folders.
+
+    Args:
+        paths: List of directory paths (one per camera) each containing folders (to be matched) with recorded images
+        expr: Regular expression pattern matching recording folders.
+        format_date_str: format of the date and time in the folder names.
+        threshold_s: Will check if the date in the folder names differ from less than threshold in second
+
+    Returns:
+        matched_folder_names: List of lists (# of recordings * # of cameras) containing the matched folder names.
+    """
+
+    folder_names = get_list_of_folder_names(paths)
+
+    return match_folders_by_date(folder_names, expr, format_date_str, threshold_s)
+
+
+def get_unmatched_folders(folder_names: List[List[str]],
+                          matched_folder_names: List[List[str]]) -> List[List[str]]:
+
+    if len(folder_names) == len(matched_folder_names[0]):
+        matched_folder_names = [list(x) for x in zip(*matched_folder_names)]
+
+    unmatched_folder_names = [[]] * len(folder_names)
+    for i in range(len(folder_names)):
+
+        unmatched_folder_names[i] = [folder_names[i][j] for j in range(len(folder_names[i]))
+                                     if folder_names[i][j] not in matched_folder_names[i]]
+
+    return unmatched_folder_names
+
+
+def get_unmatched_recordings(paths: List[str],
+                             matched_folder_names: List[List[str]]) -> List[List[str]]:
+
+    folder_names = get_list_of_folder_names(paths)
+
+    unmatched_folder_names = get_unmatched_folders(folder_names, matched_folder_names)
+
+    return unmatched_folder_names
+
+
+def get_list_of_folder_names(paths: List[str], keep_only_folder_with_images: bool = True) -> List[List[str]]:
+
+    folder_names = [[]] * len(paths)
+    for ind_cam, directory in enumerate(paths):
+        folders = os.listdir(directory)
+        folder_names[ind_cam] = [os.path.basename(os.path.normpath(folder)) for folder in folders]
+
+        if keep_only_folder_with_images:
+
+            ind_folder_to_remove = []
+            for ind_folder, folder_name in enumerate(folder_names[ind_cam]):
+                if not does_folder_contains_images(os.path.join(directory, folder_name)):
+                    ind_folder_to_remove.append(ind_folder)
+
+            if ind_folder_to_remove:
+                [folder_names[ind_cam].pop(i) for i in ind_folder_to_remove[::-1]]
+
+    return folder_names
+
+
+def get_image_names_in_directory(directory: str, expr: str = r"\d+(?=\.(tif|png|jpg|bmp))") -> List[str]:
+    """
+
+        Args:
+            directory: The path to a directory containing images.
+            expr: Regular expression pattern matching image files.
+    """
+
+    if not os.path.exists(directory): return []
+
+    contents = os.listdir(directory)
+    pattern = re.compile(expr)
+
+    image_names = []
+    for file_name in contents:
+        match = re.search(pattern, file_name)
+        if match:
+            image_names.append(os.path.basename(os.path.normpath(file_name)))
+
+    return image_names
+
+
+def does_folder_contains_images(directory: str, expr: str = r"\d+(?=\.(tif|png|jpg|bmp))") -> bool:
+    """
+
+        Args:
+            directory: The path to a directory containing images.
+            expr: Regular expression pattern matching image files.
+    """
+
+    image_names = get_image_names_in_directory(directory, expr)
+
+    return len(image_names) != 0
+
+
+def convert_kwargs_to_be_writen_in_yaml(kwargs: Dict[any, any]) -> Dict[any, any]:
+
+    new_kwargs = copy.deepcopy(kwargs)
+    for key in kwargs.keys():
+        if hasattr(new_kwargs[key], '__dict__'):
+            new_kwargs[key] = new_kwargs[key].__dict__
+
+        elif type(new_kwargs[key]) is dict:
+            new_kwargs = convert_kwargs_to_be_writen_in_yaml(new_kwargs)
+
+    return new_kwargs
+
+
+def find_char(s, ch):
+    return [i for i, ltr in enumerate(s) if ltr == ch]
\ No newline at end of file
diff --git a/processes.yaml b/processes.yaml
index 60023f51a05ec7c33dbfcb6affb69b3c22b981f3..b8611176b2ed592fc3ed20cff78911b67ae2eb78 100644
--- a/processes.yaml
+++ b/processes.yaml
@@ -23,7 +23,7 @@
 5:
   fn: rotate
   kwargs:
-    camns:
+    camn_to_process:
     - 2
     degrees: 270
     from_fn_name: crop
diff --git a/requirements.txt b/requirements.txt
index 83014c999fc427c516502497419ea97d1bd14615..d9095b26516e9a4923001ae18fb11f52f9c1ade3 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -8,6 +8,8 @@ scipy==1.4.1
 pandas~=1.0.4
 setuptools~=57.0.0
 
+pytest~=7.0.1
+
 PyYAML~=3.13
 pytest~=7.0.1
 
diff --git a/scripts/gen_dlt_coefs.py b/scripts/gen_dlt_coefs.py
index a70469965b469391e6c4965fa95791c5b64f9c27..bf23bfc40e7038e9ab32162038af41207da8f7ba 100644
--- a/scripts/gen_dlt_coefs.py
+++ b/scripts/gen_dlt_coefs.py
@@ -1,8 +1,8 @@
 import sys, os
 import numpy as np
-from cam import calib
+from camera import calib
 
-from point_tracker.reconstructor3d import gen_dlt
+from camera.calib import read_dlt_coefs
 
 corePath = os.path.join("/home", "user", "Desktop", "John", "_Python", "dlc_flytracker", "camera")
 sys.path.append(corePath)
@@ -57,14 +57,16 @@ dlt_path = os.path.join(calib_path, dlt_name)
 # gen_dlt(3, img_size, xyz_path, xy_path, dlt_path, points_to_remove=[1, 3, 4, 11, 13, 20], plot_error_analysis=False)  # small
 
 # Load dlt_coefs
-dlt_coefs = np.zeros((nb_cam, 12))
+dlt_coefs = np.zeros((nb_cam, 11))
 dlt_csv = np.genfromtxt(dlt_path, delimiter=',', skip_header=0, names=True)
 dlt_csv.dtype.names = ['cam{0}'.format(camn) for camn in range(1, nb_cam + 1)]
 for i, camn in enumerate(range(1, nb_cam + 1)):
     dlt_coefs[i] = np.append(np.array(dlt_csv['cam{0}'.format(camn)]), 1)
 
+dlt_coefs = read_dlt_coefs(dlt_path)
+
 # Reconstruct 3d and reproject to 2d
-obj_coord = calib.recon3D(3, nb_cam, dlt_coefs, pts_cams)
+obj_coord = calib.recon3d(3, nb_cam, dlt_coefs, pts_cams)
 repro_pts_cams = calib.find2d(nb_cam, dlt_coefs, np.array([obj_coord]))
 mean_repro_err = np.sqrt(np.mean(np.sum((repro_pts_cams - pts_cams) ** 2, 1)))
 
diff --git a/scripts/process_all_escapes.py b/scripts/process_all_escapes.py
index fb4aea8dff9f0e2256aa55d747cc76ea48e761c5..bcba8bb2256de221631d1ad833bdb7126119b6d6 100644
--- a/scripts/process_all_escapes.py
+++ b/scripts/process_all_escapes.py
@@ -1,5 +1,5 @@
 import os, time, yaml
-from images.process import ImagesProcessing
+from process.batch_processing import BatchProcessing
 
 from convert_xypts import *
 
@@ -21,7 +21,7 @@ wing_param_names = ['stroke_a', 'deviation_a', 'rotation_a']
 
 img_size = (1024, 1024)
 
-img_process = ImagesProcessing(3, dlt_path)
+img_process = BatchProcessing()
 img_process.cam_paths = {1: '/media/user/MosquitoEscape_Photron1/Photron1',
                          2: '/media/user/MosquitoEscape_Photron2/Photron2',
                          3: '/media/user/MosquitoEscape_Photron3/Photron3'}
@@ -81,27 +81,27 @@ for i, date in enumerate(dates_to_process):
     # rec_names_to_process = non_processed_rec_names
 
     # # Do cropping, rotating, stitching, etc at once and save as .avi (save much time and space)
-    # img_process.do_batch('multi_processes', rec_names=rec_names_to_process, yaml_path='processes_updated.yaml', delete_previous=True)
+    # img_process.do_batch('multi_processes', rec_names_to_process=rec_names_to_process, yaml_path='processes_updated.yaml', delete_previous=True)
 
     # # Do 2d tracking (blob detection) on images in cam_save_paths/_Sample
-    # img_process.do_batch('track2d', rec_names=rec_names_to_process, from_fn_name='sample')
+    # img_process.do_batch('track2d', rec_names_to_process=rec_names_to_process, from_fn_name='sample')
     #
     # # Do 3d reconstruction of tracks
-    # img_process.do_batch('recon3d', rec_names=rec_names_to_process, from_fn_name='sample')
+    # img_process.do_batch('recon3d', rec_names_to_process=rec_names_to_process, from_fn_name='sample', dlt_path=dlt_path)
 
     # # Track features using DeepLabCut
-    # img_process.do_batch('analyse_dlc', rec_names=rec_names_to_process, from_fn_name='save_avi', cfg_path=dlc_cfg_path,
-    #                     shuffle=shuffle, trainingsetindex=0, batchsize=5, save_avi=False, model_name=model_name, delete_previous=True)
+    # img_process.do_batch('analyse_dlc', rec_names_to_process=rec_names_to_process, from_fn_name='save_avi', cfg_path=dlc_cfg_path,
+    #                     shuffle=shuffle, trainingset_index=0, batch_size=5, save_avi=False, model_name=model_name, delete_previous=True)
 
     # # Load (+ filtering and unscrambling) 2d coords from DLC + Reverse processes (unstitch, rotate back, uncrop) + Reconstruct 3d coord
-    # img_process.do_batch('load_dlc', rec_names=rec_names_to_process, from_fn_name='analyse_dlc', model_name=model_name,
-    #                       tracker_method='skeleton')
+    # img_process.do_batch('load_dlc', rec_names_to_process=rec_names_to_process, from_fn_name='analyse_dlc', model_name=model_name,
+    #                       tracker_method='skeleton', dlt_path=dlt_path)
 
     # Optimize fit of skeleton to find body and wings anglJues
-    img_process.multiprocessing = True
+    img_process.do_multiprocessing = True
     img_process.threshold_likelihood = 0.85
-    img_process.do_batch('fit_skeleton', rec_names=rec_names_to_process, from_fn_name='load_dlc', model_name=model_name,
-                         csv_path=csv_wings_geo_path, body_param_names=body_param_names, wing_param_names=wing_param_names,
-                         animal_name="fly", res_method=res_method, opt_method=opt_method)
+    img_process._do_batch('fit_skeleton', rec_names_to_process=rec_names_to_process, from_fn_name='load_dlc', model_name=model_name,
+                          csv_path=csv_wings_geo_path, body_param_names=body_param_names, wing_param_names=wing_param_names,
+                          animal_name="fly", res_method=res_method, opt_method=opt_method, dlt_path=dlt_path)
 
     print('> All processes as been processed (total time elapsed: {0:.4f} s)'.format(time.time() - start))
diff --git a/scripts/process_all_escapes_strobe.py b/scripts/process_all_escapes_strobe.py
index 66a59b77769e2c4aa0c2fee0db605deb714eee7c..35fc5b47397a866a5c5ba13d34895cba5eab1751 100644
--- a/scripts/process_all_escapes_strobe.py
+++ b/scripts/process_all_escapes_strobe.py
@@ -1,5 +1,5 @@
 import time, yaml, os
-from images.process import ImagesProcessing
+from process.batch_processing import BatchProcessing
 
 from convert_xypts import *
 
@@ -10,7 +10,7 @@ xyz_path = os.path.join(os.getcwd(), 'data/calib/xyz_calibration_device_big.csv'
 csv_wings_geo_path = os.path.join(os.getcwd(), 'acoluzzii_wing.csv')
 dlc_cfg_path = '/home/user/Desktop/Antoine/_maDLC/config.yaml'
 
-img_process = ImagesProcessing(3, dlt_path)
+img_process = BatchProcessing()
 img_process.cam_paths = {1: '/media/user/MosquitoEscape_Photron1/Photron1',
                          2: '/media/user/MosquitoEscape_Photron2/Photron2',
                          3: '/media/user/MosquitoEscape_Photron3/Photron3'}
@@ -50,30 +50,30 @@ for i, date in enumerate(dates_to_process):
     #rec_names_to_process = {1: ['cam1_20200303_030117'], 2: ['cam2_20200303_030120'], 3: ['cam3_20200303_030120']}
 
     # # Do cropping, rotating, stitching, etc at once and save as .avi (save much time and space)
-    # img_process.do_batch('multi_processes', rec_names=rec_names_to_process, yaml_path='processes-strobe.yaml', delete_previous=True)
-    # img_process.do_batch('stitch', rec_names=rec_names_to_process, from_fn_name='save_strobe')
+    # img_process.do_batch('multi_processes', rec_names_to_process=rec_names_to_process, yaml_path='processes-strobe.yaml', delete_previous=True)
+    # img_process.do_batch('stitch', rec_names_to_process=rec_names_to_process, from_fn_name='save_strobe')
 
-    # # Convert images to 8 bits and reduce framerate (save in cam_save_paths/_Sample)
-    # img_process.do_batch('sample', rec_names=rec_names_to_process, step_frame=50, delete_previous=True)
+    # # Convert images to 8 bits and reduce frame_rate (save in cam_save_paths/_Sample)
+    # img_process.do_batch('sample', rec_names_to_process=rec_names_to_process, step_frame=50, delete_previous=True)
     #
     # # Do 2d tracking (blob detection) on images in cam_save_paths/_Sample
-    # img_process.do_batch('track2d', rec_names=rec_names_to_process, from_fn_name='sample')
+    # img_process.do_batch('track2d', rec_names_to_process=rec_names_to_process, from_fn_name='sample')
     #
     # # Do 3d reconstruction of tracks
-    # img_process.do_batch('recon3d', rec_names=rec_names_to_process, from_fn_name='sample', dlt_path=dlt_path)
+    # img_process.do_batch('recon3d', rec_names_to_process=rec_names_to_process, from_fn_name='sample', dlt_path=dlt_path)
 
     # Generate and save stroboscopic images
-    img_process.do_batch('save_strobe', rec_names=rec_names_to_process, from_fn_name='sample', step_frame=5, radius=60, shape='circle')
-    # img_process.do_batch('save_strobe', rec_names=rec_names_to_process, from_fn_name='sample', step_frame=5, radius=60,
-    #                      shape='strip_height', camns=[2])
-    # img_process.do_batch('save_strobe', rec_names=rec_names_to_process, from_fn_name='sample', step_frame=5, radius=60,
-    #                      shape='strip_width', camns=[1, 3])
+    img_process._do_batch('save_strobe', rec_names_to_process=rec_names_to_process, from_fn_name='sample', step_frame=5, radius=60, shape='circle')
+    # img_process.do_batch('save_strobe', rec_names_to_process=rec_names_to_process, from_fn_name='sample', step_frame=5, radius=60,
+    #                      shape='strip_height', camn_to_process=[2])
+    # img_process.do_batch('save_strobe', rec_names_to_process=rec_names_to_process, from_fn_name='sample', step_frame=5, radius=60,
+    #                      shape='strip_width', camn_to_process=[1, 3])
 
     # Rotate view 2 (to always have piston coming from right side)
-    img_process.do_batch('rotate', rec_names=rec_names_to_process, from_fn_name='save_strobe', camns=[2], degrees=270)
+    img_process._do_batch('rotate', rec_names_to_process=rec_names_to_process, from_fn_name='save_strobe', camn_to_process=[2], degrees=270)
 
     # Stitch all views together and save in cam_save_paths/_Stitched
-    img_process.do_batch('stitch', rec_names=rec_names_to_process, from_fn_name='save_strobe')
+    img_process._do_batch('stitch', rec_names_to_process=rec_names_to_process, from_fn_name='save_strobe')
 
     print('> All processes as been processed (total time elapsed: {0:.4f} s)'.format(time.time() - start))
     #exit()
\ No newline at end of file
diff --git a/scripts/process_mating_events.py b/scripts/process_mating_events.py
index 90127bd5a97855f28f28cb434a873a39a3332120..703018a1b9dd4652fe457d8e99c3c5f30a4a8a0a 100644
--- a/scripts/process_mating_events.py
+++ b/scripts/process_mating_events.py
@@ -1,7 +1,7 @@
-from images.process import ImagesProcessing
+from process.batch_processing import BatchProcessing
 import numpy as np
 
-img_process = ImagesProcessing(3)
+img_process = BatchProcessing()
 
 img_process.cam_paths = {1: 'F:\\Photron1', 2: 'Y:\\', 3: 'Z:\\'}
 img_process.cam_save_paths = {1: 'F:\\Photron1\\_Process', 2: 'Y:\\_Process', 3: 'Z:\\_Process'}
@@ -23,10 +23,10 @@ if len(rec_cnt_be_found) > 0:
     exit()
 
 rec_to_delete = (list(set(img_process.all_folders_rec_cam[mating_events['camn'][0]]) - set(rec_mating_names)))
-img_process.do_batch('delete', rec_names=rec_names_to_process, to_del_names=rec_to_delete)
+img_process._do_batch('delete', rec_names_to_process=rec_names_to_process, to_del_names=rec_to_delete)
 
 # Delete all without correspondings recording for camera 2 and 3
 img_process.main_cam = 2
-img_process.do_batch('delete', rec_names=rec_names_to_process, to_del_names=[])
+img_process._do_batch('delete', rec_names_to_process=rec_names_to_process, to_del_names=[])
 img_process.main_cam = 3
-img_process.do_batch('delete', rec_names=rec_names_to_process, to_del_names=[])
\ No newline at end of file
+img_process._do_batch('delete', rec_names_to_process=rec_names_to_process, to_del_names=[])
\ No newline at end of file
diff --git a/scripts/process_selection.py b/scripts/process_selection.py
index a375d2592edf6ae433928a246ee2895330e2c1cc..6246d3621d677e8b469d9420c7505ade93fb2e03 100644
--- a/scripts/process_selection.py
+++ b/scripts/process_selection.py
@@ -1,5 +1,5 @@
 import os, time, yaml
-from images.process import ImagesProcessing
+from process.batch_processing import BatchProcessing
 import numpy as np
 
 selection_csv = 'selection_escapes.csv'
@@ -12,7 +12,7 @@ csv_wings_geo_path = os.path.join(os.getcwd(), '../acoluzzii_wing.csv')
 dlc_cfg_path = '/home/user/Desktop/Antoine/_maDLC/config.yaml'
 
 
-img_process = ImagesProcessing(3, dlt_path)
+img_process = BatchProcessing()
 img_process.cam_paths = {1: '/media/user/MosquitoEscape_Photron1/Photron1',
                          2: '/media/user/MosquitoEscape_Photron2/Photron2',
                          3: '/media/user/MosquitoEscape_Photron3/Photron3'}
@@ -89,55 +89,56 @@ for i, date in enumerate(dates_to_process):
     if len(rec_names_to_process[camn]) == 0: continue
 
     # # Do cropping, rotating, stitching, etc at once and save as .avi (save much time and space)
-    # img_process.do_batch('multi_processes', rec_names=rec_names_to_process, yaml_path='config.yaml', delete_previous=True)
+    # img_process.do_batch('multi_processes', rec_names_to_process=rec_names_to_process, yaml_path='config.yaml', delete_previous=True)
 
-    # # Convert images to 8 bits and reduce framerate (save in cam_save_paths/_Sample)
-    # img_process.do_batch('sample', rec_names=rec_names_to_process, step_frame=50, delete_previous=True)
+    # # Convert images to 8 bits and reduce frame_rate (save in cam_save_paths/_Sample)
+    # img_process.do_batch('sample', rec_names_to_process=rec_names_to_process, step_frame=50, delete_previous=True)
 
     # # Do 2d tracking (blob detection) on images in cam_save_paths/_Sample
-    # img_process.do_batch('track2d', rec_names=rec_names_to_process, from_fn_name='sample')
+    # img_process.do_batch('track2d', rec_names_to_process=rec_names_to_process, from_fn_name='sample')
 
     # # Do 3d reconstruction of tracks
-    # img_process.do_batch('recon3d', rec_names=rec_names_to_process, from_fn_name='sample')
+    # img_process.do_batch('recon3d', rec_names_to_process=rec_names_to_process, from_fn_name='sample', dlt_path=dlt_path)
 
     # # Crop all frames and save in cam_save_paths/_Cropped
-    # img_process.do_batch('crop', rec_names=rec_names_to_process, from_fn_name='sample', height_crop=200, width_crop=200, delete_previous=True)
+    # img_process.do_batch('crop', rec_names_to_process=rec_names_to_process, from_fn_name='sample', height_crop=200, width_crop=200, delete_previous=True)
     #
     # # Rotate view 2 (to always have piston coming from right side)
-    # img_process.do_batch('rotate', rec_names=rec_names_to_process, from_fn_name='crop', camns=[2], degrees=270)
+    # img_process.do_batch('rotate', rec_names_to_process=rec_names_to_process, from_fn_name='crop', camn_to_process=[2], degrees=270)
     #
     # # Upsizing of the images (increase resolution)
-    # #img_process.do_batch('resize', rec_names=rec_names_to_process, resize_ratio=2, from_fn_name='crop', delete_previous=True)
+    # #img_process.do_batch('resize', rec_names_to_process=rec_names_to_process, resize_ratio=2, from_fn_name='crop', delete_previous=True)
     #
     # # Enhance images (increase sharpness and contrast)
-    # # img_process.do_batch('enhance_sharpness', factor=2.0, rec_names=rec_names_to_process, from_fn_name='resize', delete_previous=True)
-    # #img_process.do_batch('enhance_contrast', factor=1.5, rec_names=rec_names_to_process, from_fn_name='enhance_sharpness')
+    # # img_process.do_batch('enhance_sharpness', factor=2.0, rec_names_to_process=rec_names_to_process, from_fn_name='resize', delete_previous=True)
+    # #img_process.do_batch('enhance_contrast', factor=1.5, rec_names_to_process=rec_names_to_process, from_fn_name='enhance_sharpness')
     #
     # # Stitch all views together and save in cam_save_paths/_Stitched
-    # img_process.do_batch('stitch', rec_names=rec_names_to_process, from_fn_name='crop', delete_previous=True)
+    # img_process.do_batch('stitch', rec_names_to_process=rec_names_to_process, from_fn_name='crop', delete_previous=True)
 
     # # Save recordings in .avi
-    # img_process.do_batch('save_avi', rec_names=rec_names_to_process, from_fn_name='stitch', lossless=False, delete_previous=True)
+    # img_process.do_batch('save_avi', rec_names_to_process=rec_names_to_process, from_fn_name='stitch', lossless=False, delete_previous=True)
 
     # # Track features using DeepLabCut
-    # img_process.do_batch('analyse_dlc', rec_names=rec_names_to_process, from_fn_name='save_avi', cfg_path=dlc_cfg_path,
-    #                      shuffle=shuffle, trainingsetindex=0, batchsize=5, save_avi=False, model_name=model_name,
+    # img_process.do_batch('analyse_dlc', rec_names_to_process=rec_names_to_process, from_fn_name='save_avi', cfg_path=dlc_cfg_path,
+    #                      shuffle=shuffle, trainingset_index=0, batch_size=5, save_avi=False, model_name=model_name,
     #                      delete_previous=True)
     #
     # # Load (+ filtering and unscrambling) 2d coords from DLC + Reverse processes (unstitch, rotate back, uncrop) + Reconstruct 3d coord
-    # img_process.do_batch('load_dlc', rec_names=rec_names_to_process, from_fn_name='analyse_dlc', model_name=model_name,
-    #                      tracker_method='skeleton')
+    # img_process.do_batch('load_dlc', rec_names_to_process=rec_names_to_process, from_fn_name='analyse_dlc', model_name=model_name,
+    #                      tracker_method='skeleton', dlt_path=dlt_path)
 
     # Optimize fit of skeleton to find body and wings angles
-    img_process.multiprocessing = True
+    img_process.do_multiprocessing = True
     img_process.threshold_likelihood = 0.8
-    img_process.do_batch('fit_skeleton', rec_names=rec_names_to_process, from_fn_name='load_dlc', model_name=model_name,
-                         csv_path=csv_wings_geo_path, body_param_names=body_param_names, wing_param_names=wing_param_names,
-                         animal_name="fly", res_method=res_method, opt_method=opt_method, angles_to_correct=['stroke_a', 'deviation_a'])
+    img_process._do_batch('fit_skeleton', rec_names_to_process=rec_names_to_process, from_fn_name='load_dlc', model_name=model_name,
+                          csv_path=csv_wings_geo_path, body_param_names=body_param_names, wing_param_names=wing_param_names,
+                          animal_name="fly", res_method=res_method, opt_method=opt_method, dlt_path=dlt_path,
+                          angles_to_correct=['stroke_a', 'deviation_a'])
 
     # Save .png of skeleton fit on raw images
-    img_process.do_batch('plot_skeleton', rec_names=rec_names_to_process, from_fn_name='analyse_dlc', model_name=model_name,
-                         csv_path=csv_wings_geo_path, save_avi=True)
+    img_process._do_batch('plot_skeleton', rec_names_to_process=rec_names_to_process, from_fn_name='analyse_dlc', model_name=model_name,
+                          csv_path=csv_wings_geo_path, dlt_path=dlt_path, save_avi=True)
 
     print('> All processes as been processed (total time elapsed: {0:.4f} s)'.format(time.time() - start))
 
diff --git a/analysis/plot/__init__.py b/skeleton_fitter/animals/__init__.py
similarity index 100%
rename from analysis/plot/__init__.py
rename to skeleton_fitter/animals/__init__.py
diff --git a/skeleton_fitter/cost_functions/fly.py b/skeleton_fitter/animals/fly.py
similarity index 61%
rename from skeleton_fitter/cost_functions/fly.py
rename to skeleton_fitter/animals/fly.py
index 094cd749dc9a40cd0831621959163f427baf000b..34606675f4888efb08a6c077b2c6befe6fbf0bcf 100644
--- a/skeleton_fitter/cost_functions/fly.py
+++ b/skeleton_fitter/animals/fly.py
@@ -6,6 +6,35 @@ from skeleton_fitter.skeleton_modules import insect_hybrid_wings_body_slim
 from skeleton_fitter.skeleton_modules import insect_wings
 
 
+class AnimalSettings:
+    # TODO make class with animal characteristics
+
+    """
+    An animal consist of multiple modules (one body + an even number of limbs (right and left))
+    for example: a fly consist of one body: insect_body_slim, and multiple limbs: 2*insect_wing + 6*insect_leg
+
+    """
+
+    body_module_name = ['insect_body_slim']
+    limb_module_names = ['insect_wing', 'insect_leg']
+    limb_numbers = [2, 6]
+
+
+
+
+
+class AnimalHybridSettings:
+    # TODO make class with animal hybrid characteristics + explain why hybrid exist and when to use it
+
+    body_module_name = ['insect_hybrid_wings_body_slim']
+    limb_module_names = ['insect_wing', 'insect_leg']
+    limb_numbers = [2, 6]
+
+
+
+
+
+
 def cost_function(param_values, param_names, init_skeleton3d, init_params, raw_skeleton, options):
     """
     Cost function (sum of residuals) to fit body or wings skeleton (in init_skeleton3d) to points (in raw_skeleton)
@@ -19,7 +48,7 @@ def cost_function(param_values, param_names, init_skeleton3d, init_params, raw_s
         options: dict with various optional parameters
 
     Returns:
-        sum_res: Sum of residuals when comparing init_skeleton3d (after being transformed with param_values) to raw_skeleton
+        sum_res: Sum of residuals when comparing init_skeleton3d (after transformed with param_values) to raw_skeleton
     """
     new_params = copy.deepcopy(init_params)
     for i, label in enumerate(param_names):
@@ -29,39 +58,39 @@ def cost_function(param_values, param_names, init_skeleton3d, init_params, raw_s
     if 'body' in options['res_method'] or 'hybrid' in options['res_method']:
         body_skeleton3d = copy.deepcopy(init_skeleton3d)
         if 'body' in options['res_method']:
-            body_skeleton3d = insect_body_slim.build_body_skeleton3d(body_skeleton3d, new_params)
+            body_skeleton3d = insect_body_slim.build_skeleton3d(body_skeleton3d, new_params)
 
         elif 'hybrid' in options['res_method']:
-            body_skeleton3d = insect_hybrid_wings_body_slim.build_hybrid_skeleton3d(body_skeleton3d, new_params)
+            body_skeleton3d = insect_hybrid_wings_body_slim.build_skeleton3d(body_skeleton3d, new_params)
 
-        body_skeleton3d = insect_body_slim.rotate_body_skeleton3d(body_skeleton3d, new_params)
-        body_skeleton3d = insect_body_slim.translate_body_skeleton3d(body_skeleton3d, new_params)
+        body_skeleton3d = insect_body_slim.rotate_skeleton3d(body_skeleton3d, new_params)
+        body_skeleton3d = insect_body_slim.translate_skeleton3d(body_skeleton3d, new_params)
 
         if '3d' in options['res_method']:
-            residuals = insect_body_slim.residuals_body3d(body_skeleton3d, raw_skeleton, options['default_residual'])
+            residuals = insect_body_slim.residuals3d(body_skeleton3d, raw_skeleton, options['default_residual'])
 
         elif '2d' in options['res_method']:
-            residuals = insect_body_slim.residuals_body2d(body_skeleton3d, raw_skeleton, options['default_residual'], options['dlt_coefs'])
+            residuals = insect_body_slim.residuals2d(body_skeleton3d, raw_skeleton, options['default_residual'], options['dlt_coefs'])
 
     elif 'wing' in options['res_method']:
         if 'geo' in options['res_method']:
             wing_geometry3d = copy.deepcopy(init_skeleton3d)
-            wing_geometry3d = insect_wings.build_wing_geometry3d(wing_geometry3d, new_params)
-            wing_geometry3d = insect_wings.rotate_and_translate_wing_geometry3d(wing_geometry3d, new_params, options['side'])
+            wing_geometry3d = insect_wings.build_geometry3d(wing_geometry3d, new_params)
+            wing_geometry3d = insect_wings.rotate_and_translate_geometry3d(wing_geometry3d, new_params, options['side'])
 
             if '3d' in options['res_method']:
-                residuals = insect_wings.residuals_wing3d_geometry3d(wing_geometry3d, raw_skeleton, options['default_residual'])
+                residuals = insect_wings.residuals3d_geometry3d(wing_geometry3d, raw_skeleton, options['default_residual'])
             elif '2d' in options['res_method']:
-                residuals = insect_wings.residuals_wing2d_geometry3d(wing_geometry3d, raw_skeleton, options['default_residual'], options['dlt_coefs'])
+                residuals = insect_wings.residuals2d_geometry3d(wing_geometry3d, raw_skeleton, options['default_residual'], options['dlt_coefs'])
         else:
             wing_skeleton3d = copy.deepcopy(init_skeleton3d)
-            wing_skeleton3d = insect_wings.build_wing_skeleton3d(wing_skeleton3d, new_params)
-            wing_skeleton3d = insect_wings.rotate_and_translate_wing_skeleton3d(wing_skeleton3d, new_params, options['side'])
+            wing_skeleton3d = insect_wings.build_skeleton3d(wing_skeleton3d, new_params)
+            wing_skeleton3d = insect_wings.rotate_and_translate_skeleton3d(wing_skeleton3d, new_params, options['side'])
 
             if '3d' in options['res_method']:
-                residuals = insect_wings.residuals_wing3d(wing_skeleton3d, raw_skeleton, options['default_residual'])
+                residuals = insect_wings.residuals3d(wing_skeleton3d, raw_skeleton, options['default_residual'])
             elif '2d' in options['res_method']:
-                residuals = insect_wings.residuals_wing2d(wing_skeleton3d, raw_skeleton, options['default_residual'], options['dlt_coefs'])
+                residuals = insect_wings.residuals2d(wing_skeleton3d, raw_skeleton, options['default_residual'], options['dlt_coefs'])
     else:
         print('WARN: {0} is not a valid method!'.format(options['res_method']))
 
@@ -71,7 +100,7 @@ def cost_function(param_values, param_names, init_skeleton3d, init_params, raw_s
         return np.sum(residuals ** 2)
 
 
-# Useless??
+# TODO check if following is useless
 # def replace_outliers(data, m=2.):
 #     data = np.array(data)
 #
@@ -88,10 +117,10 @@ if __name__ == '__main__':
 
     csv_wings_geo_path = os.path.join(os.getcwd(), '../data/acoluzzii_wing.csv')
 
-    body_params_init = insect_body_slim.init_body_params()
-    wings_params_init = insect_wings.init_wings_params(body_params_init)
+    body_params_init = insect_body_slim.init_params()
+    wings_params_init = insect_wings.init_params(body_params_init)
 
-    body_skeleton3d_init = insect_body_slim.init_body_skeleton3d(body_params_init)
+    body_skeleton3d_init = insect_body_slim.init_skeleton3d(body_params_init)
     wings_skeleton3d_init, wings_geometry3d_init = insect_wings.init_wings_skeleton_geometry3d_from_csv(wings_params_init, csv_wings_geo_path)
 
     body_params = copy.deepcopy(body_params_init)
@@ -100,13 +129,13 @@ if __name__ == '__main__':
     body_params['roll_a'] = -5.0
     [body_params['x_com'], body_params['y_com'], body_params['z_com']] = [0.0, 5.0, 3.0]
 
-    wings_params = insect_wings.init_wings_params(body_params)
+    wings_params = insect_wings.init_params(body_params)
     wings_params['right']['stroke_a'] = -55.0
     wings_params['right']['deviation_a'] = 10.0
     wings_params['right']['rotation_a'] = 60.0
 
-    body_skeleton3d_1 = insect_body_slim.rotate_body_skeleton3d(body_skeleton3d_init, body_params)
-    body_skeleton3d_1 = insect_body_slim.translate_body_skeleton3d(body_skeleton3d_1, body_params)
+    body_skeleton3d_1 = insect_body_slim.rotate_skeleton3d(body_skeleton3d_init, body_params)
+    body_skeleton3d_1 = insect_body_slim.translate_skeleton3d(body_skeleton3d_1, body_params)
     #plot_body(body_skeleton3d_1)
 
     wings_skeleton3d_1, wings_geometry1 = copy.deepcopy(wings_skeleton3d_init), copy.deepcopy(wings_geometry3d_init)
@@ -116,10 +145,10 @@ if __name__ == '__main__':
         [wings_params[side]['x_com'], wings_params[side]['y_com'], wings_params[side]['z_com']] = \
             [body_params['x_com'], body_params['y_com'], body_params['z_com']]
 
-        wings_skeleton3d_1[side] = insect_wings.rotate_and_translate_wing_skeleton3d(wings_skeleton3d_init[side], wings_params[side], side)
-        wings_geometry1[side] = insect_wings.rotate_and_translate_wing_geometry3d(wings_geometry3d_init[side], wings_params[side], side)
+        wings_skeleton3d_1[side] = insect_wings.rotate_and_translate_skeleton3d(wings_skeleton3d_init[side], wings_params[side], side)
+        wings_geometry1[side] = insect_wings.rotate_and_translate_geometry3d(wings_geometry3d_init[side], wings_params[side], side)
 
-    insect_wings.plot_wings(wings_skeleton3d_1)
+    insect_wings.plot_multi(wings_skeleton3d_1)
     insect_wings.plot_body_and_wings(body_skeleton3d_1, wings_skeleton3d_1)
 
     # Estimate body and wings parameters
diff --git a/skeleton_fitter/cost_functions/__init__.py b/skeleton_fitter/cost_functions/__init__.py
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/skeleton_fitter/skeleton_modules/insect_body_slim.py b/skeleton_fitter/skeleton_modules/insect_body_slim.py
index c935f7961d1ff324747d610845f1eaee95a75a2d..b1f641b3956e5f78ce00c60d568cbd81bdac92f0 100644
--- a/skeleton_fitter/skeleton_modules/insect_body_slim.py
+++ b/skeleton_fitter/skeleton_modules/insect_body_slim.py
@@ -1,14 +1,40 @@
 import copy
 import numpy as np
 
-import matplotlib.pyplot as plt
+from dataclasses import dataclass
 
+import matplotlib.pyplot as plt
 from scipy.spatial.transform import Rotation as R
 
 from skeleton_fitter.utils import reproject_skeleton3d_to2d, angle_from_vectors, length_from_vector, set_axes3d_equal
+import skeleton_fitter.skeleton_modules.insect_body_slim as body
+
+
+@dataclass
+class ModuleSettings:
+    threshold_likelihood = 0.9
+    threshold_nb_pts = 4
+
+    params_init = body.init_params()
+    bounds_init = body.init_bounds()
+
+    skeleton2d_init = body.init_skeleton2d(params_init)
+    skeleton3d_init = body.init_skeleton3d(params_init)
+
+    labels = list(skeleton3d_init.keys())
+    # labels = ['proboscis_tip', 'proboscis_head_joint', 'head_torso_joint', 'right_wing_hinge',
+    #           'left_wing_hinge', 'torso_abdomen_joint', 'abdomen_middle', 'abdomen_tip']
+
+    @staticmethod
+    def from_dict(dictionary):
+        module_sets = ModuleSettings()
+        for key, value in dictionary.items():
+            setattr(module_sets, key, value)
+
+        return module_sets
 
 
-def init_body_params():
+def init_params():
     """
 
     Returns:
@@ -41,7 +67,7 @@ def init_body_params():
     return body_params
 
 
-def init_body_bounds():
+def init_bounds():
     """
 
     Returns:
@@ -71,7 +97,7 @@ def init_body_bounds():
     return body_bounds
 
 
-def init_body_skeleton3d(body_params):
+def init_skeleton3d(body_params):
     """
 
     Returns:
@@ -88,12 +114,12 @@ def init_body_skeleton3d(body_params):
     body_skeleton3d['abdomen_middle'] = np.array([-1.5, 0.0, 0.0])
     body_skeleton3d['abdomen_tip'] = np.array([-1.5, 0.0, 0.0])
 
-    body_skeleton3d = build_body_skeleton3d(body_skeleton3d, body_params)
+    body_skeleton3d = build_skeleton3d(body_skeleton3d, body_params)
 
     return body_skeleton3d
 
 
-def build_body_skeleton3d(init_body_skeleton3d, body_params):
+def build_skeleton3d(init_body_skeleton3d, body_params):
     """
 
     Args:
@@ -153,7 +179,7 @@ def build_body_skeleton3d(init_body_skeleton3d, body_params):
     return body_skeleton3d
 
 
-def rotate_body_skeleton3d(body_skeleton3d, body_params):
+def rotate_skeleton3d(body_skeleton3d, body_params):
     """
 
     Args:
@@ -176,7 +202,7 @@ def rotate_body_skeleton3d(body_skeleton3d, body_params):
     return body_skeleton3d_world
 
 
-def translate_body_skeleton3d(body_skeleton3d, body_params):
+def translate_skeleton3d(body_skeleton3d, body_params):
     """
 
     Args:
@@ -198,7 +224,7 @@ def translate_body_skeleton3d(body_skeleton3d, body_params):
     return body_skeleton3d_world
 
 
-def estimate_body_parameters_from_skeleton3d(body_skeleton3d_world, init_body_params):
+def estimate_params_from_skeleton3d(body_skeleton3d_world, init_body_params):
     """
     Estimate parameters of the transformations/rotations needed to get body_skeleton3d_world
 
@@ -278,7 +304,7 @@ def estimate_body_parameters_from_skeleton3d(body_skeleton3d_world, init_body_pa
     return body_params
 
 
-def get_copy_body_params_zero(body_params, label_to_zero=['yaw_a', 'pitch_a', 'roll_a']):
+def get_copy_params_zero(body_params, label_to_zero=['yaw_a', 'pitch_a', 'roll_a']):
     """
 
     Args:
@@ -296,7 +322,7 @@ def get_copy_body_params_zero(body_params, label_to_zero=['yaw_a', 'pitch_a', 'r
     return new_body_params
 
 
-def plot_body(body_skeleton3d):
+def plot(body_skeleton3d):
     fig = plt.figure()
     ax = fig.add_subplot(111, projection='3d')
     for label in body_skeleton3d.keys():
@@ -309,7 +335,7 @@ def plot_body(body_skeleton3d):
     plt.show()
 
 
-def residuals_body2d(body_skeleton3d, body_skeleton2d, default_residual, dlt_coefs):
+def residuals2d(body_skeleton3d, body_skeleton2d, default_residual, dlt_coefs):
     """
     Compute residuals when comparing body_skeleton3d and body_skeleton2d
 
@@ -339,7 +365,7 @@ def residuals_body2d(body_skeleton3d, body_skeleton2d, default_residual, dlt_coe
     return residuals.flatten()
 
 
-def residuals_body3d(body_skeleton3d_1, body_skeleton3d_2, default_residual):
+def residuals3d(body_skeleton3d_1, body_skeleton3d_2, default_residual):
     """
     Compute residuals when comparing body_skeleton3d_1 and body_skeleton3d_2
 
diff --git a/skeleton_fitter/skeleton_modules/insect_hybrid_wings_body_slim.py b/skeleton_fitter/skeleton_modules/insect_hybrid_wings_body_slim.py
index 98067e1698a40d461d313bd86eaf5b56d939aa27..62559f8d66029c9ffb17b69ee634f01d846a99f9 100644
--- a/skeleton_fitter/skeleton_modules/insect_hybrid_wings_body_slim.py
+++ b/skeleton_fitter/skeleton_modules/insect_hybrid_wings_body_slim.py
@@ -1,45 +1,63 @@
 import numpy as np
 
+from dataclasses import dataclass
+
 import matplotlib.pyplot as plt
 
 from skeleton_fitter.utils import set_axes3d_equal
-from skeleton_fitter.skeleton_modules.insect_body_slim import init_body_params, init_body_bounds, init_body_skeleton3d, \
-    build_body_skeleton3d
+import skeleton_fitter.skeleton_modules.insect_body_slim as body
+
+
+@dataclass
+class ModuleSettings:
+    threshold_likelihood = 0.9
+    threshold_nb_pts = 4
+
+    hybrid_params_init = hybrid.init_params()
+    hybrid_skeleton3d_init = hybrid.init_skeleton3d(hybrid_params_init)
+
+    @staticmethod
+    def from_dict(dictionary):
+        module_sets = ModuleSettings()
+        for key, value in dictionary.items():
+            setattr(module_sets, key, value)
+
+        return module_sets
 
 
-def init_hybrid_params():
+def init_params():
     """
 
         Returns:
             hybrid_params:
     """
-    hybrid_params = init_body_params()
+    hybrid_params = body.init_params()
     hybrid_params['wbaverage_span'] = 0.0025
     hybrid_params['wbaverage_deviation_a'] = 0.0
 
     return hybrid_params
 
 
-def init_hybrid_bounds():
+def init_bounds():
     """
 
         Returns:
             hybrid_bounds:
     """
-    hybrid_bounds = init_body_bounds()
+    hybrid_bounds = body.init_bounds()
     hybrid_bounds['wbaverage_span'] = (0.0005, 0.01)
     hybrid_bounds['wbaverage_deviation_a'] = (-45.0, 45.0)
 
     return hybrid_bounds
 
 
-def init_hybrid_skeleton3d(hybrid_params):
+def init_skeleton3d(hybrid_params):
     """
 
         Returns:
             hybrid_skeleton3d:
     """
-    hybrid_skeleton3d = init_body_skeleton3d(hybrid_params)
+    hybrid_skeleton3d = body.init_skeleton3d(hybrid_params)
     hybrid_skeleton3d['right_wing_tip'] = \
         hybrid_skeleton3d['right_wing_hinge'] \
         + np.array([0.0, - hybrid_params['wbaverage_span'] * np.cos(np.deg2rad(hybrid_params['wbaverage_deviation_a'])),
@@ -57,7 +75,7 @@ def init_hybrid_skeleton3d(hybrid_params):
     return hybrid_skeleton3d
 
 
-def build_hybrid_skeleton3d(init_hybrid_skeleton3d, hybrid_params):
+def build_skeleton3d(init_hybrid_skeleton3d, hybrid_params):
     """
 
     Args:
@@ -68,7 +86,7 @@ def build_hybrid_skeleton3d(init_hybrid_skeleton3d, hybrid_params):
         hybrid_skeleton3d:
     """
 
-    hybrid_skeleton3d = build_body_skeleton3d(init_hybrid_skeleton3d, hybrid_params)
+    hybrid_skeleton3d = body.build_skeleton3d(init_hybrid_skeleton3d, hybrid_params)
     hybrid_skeleton3d['right_wing_tip'] = \
         hybrid_skeleton3d['right_wing_hinge'] \
         + np.array([0.0, - hybrid_params['wbaverage_span'] * np.cos(np.deg2rad(hybrid_params['wbaverage_deviation_a'])),
@@ -82,7 +100,7 @@ def build_hybrid_skeleton3d(init_hybrid_skeleton3d, hybrid_params):
     return hybrid_skeleton3d
 
 
-def plot_body_and_wings(body_skeleton3d, wings_skeleton3d):
+def plot(body_skeleton3d, wings_skeleton3d):
     fig = plt.figure()
     ax = fig.add_subplot(111, projection='3d')
     for label in body_skeleton3d.keys():
diff --git a/skeleton_fitter/skeleton_modules/insect_wings.py b/skeleton_fitter/skeleton_modules/insect_wings.py
index 69e585889f641fa37c98c09a39c4488c9330dad7..e2b4a10f630caeb2719d6b1bd603abefa9d20e0f 100644
--- a/skeleton_fitter/skeleton_modules/insect_wings.py
+++ b/skeleton_fitter/skeleton_modules/insect_wings.py
@@ -2,6 +2,8 @@ import copy
 import pandas as pd
 import numpy as np
 
+from dataclasses import dataclass
+
 import matplotlib.pyplot as plt
 
 from scipy.spatial.transform import Rotation as R
@@ -11,7 +13,40 @@ from skeleton_fitter.utils import reproject_skeleton3d_to2d, reproject_geometry3
     length_from_vector, set_axes3d_equal
 
 
-def init_wings_params(body_params):
+@dataclass
+class ModuleSettings:
+    threshold_likelihood = 0.9
+    threshold_nb_pts = 4
+
+    keep_init_aspect_ratio = True
+
+    params_init = wings.init_params(body_params_init)
+    bounds_init = wings.init_bounds(body_bounds_init)
+    skeleton3d_init = wings.init_skeleton3d(params_init)
+
+    body_params_init = body.init_params()
+    body_bounds_init = body.init_bounds()
+    body_skeleton3d_init = body.init_skeleton3d(body_params_init)
+
+    labels = list(skeleton3d_init.keys())
+    # labels = ['hinge', 'leading_edge_q1', 'leading_edge_q2', 'leading_edge_q3', 'tip', 'trailing_edge_q3',
+    #           'trailing_edge_q2', 'trailing_edge_q1']
+    body_labels = list(body_skeleton3d_init.keys())
+
+    labels_to_keep_cst = ['span', 'chord', 'aspect_ratio']
+    body_labels_to_keep_cst = ['proboscis_l', 'torso_l', 'torso_r', 'abdomen_l',
+                               'proboscis_torso_a', 'torso_abdomen_a', 'ratio_com_torso', 'ratio_proboscis_head']
+
+    @staticmethod
+    def from_dict(dictionary):
+        module_sets = ModuleSettings()
+        for key, value in dictionary.items():
+            setattr(module_sets, key, value)
+
+        return module_sets
+
+
+def init_params(body_params):
     """
 
     Args:
@@ -43,7 +78,7 @@ def init_wings_params(body_params):
     return wings_params
 
 
-def init_wing_bounds(body_bounds):
+def init_bounds(body_bounds):
     """
 
     Args:
@@ -121,8 +156,8 @@ def init_wings_skeleton_geometry3d(wings_params):
             len(angle) / len(wings_skeleton3d[side].keys()))
         wings_geometry3d[side]['index_prev_labels'] = np.sort(wings_geometry3d[side]['index_prev_labels'])
 
-        wings_skeleton3d[side] = build_wing_skeleton3d(wings_geometry3d[side], wings_params[side])
-        wings_geometry3d[side] = build_wing_geometry3d(wings_geometry3d[side], wings_params[side])
+        wings_skeleton3d[side] = build_skeleton3d(wings_geometry3d[side], wings_params[side])
+        wings_geometry3d[side] = build_geometry3d(wings_geometry3d[side], wings_params[side])
 
     return wings_skeleton3d, wings_geometry3d
 
@@ -201,13 +236,13 @@ def init_wings_skeleton_geometry3d_from_csv(wings_params, csv_path):
                    'index_prev_labels': index_prev_labels}}
 
     for side in wings_geometry3d.keys():
-        wings_skeleton3d[side] = build_wing_skeleton3d(wings_skeleton3d[side], wings_params[side])
-        wings_geometry3d[side] = build_wing_geometry3d(wings_geometry3d[side], wings_params[side])
+        wings_skeleton3d[side] = build_skeleton3d(wings_skeleton3d[side], wings_params[side])
+        wings_geometry3d[side] = build_geometry3d(wings_geometry3d[side], wings_params[side])
 
     return wings_skeleton3d, wings_geometry3d
 
 
-def build_wing_skeleton3d(init_wing_skeleton3d, wing_params):
+def build_skeleton3d(init_wing_skeleton3d, wing_params):
     """
 
     Args:
@@ -239,7 +274,7 @@ def build_wing_skeleton3d(init_wing_skeleton3d, wing_params):
     return wing_skeleton3d
 
 
-def build_wing_geometry3d(wing_geometry3d, wing_params):
+def build_geometry3d(wing_geometry3d, wing_params):
     """
 
     Args:
@@ -272,7 +307,7 @@ def build_wing_geometry3d(wing_geometry3d, wing_params):
     return wing_geometry3d
 
 
-def rotate_and_translate_wing_skeleton3d(wing_skeleton3d, wing_params, side):
+def rotate_and_translate_skeleton3d(wing_skeleton3d, wing_params, side):
     """
 
     Args:
@@ -317,7 +352,7 @@ def rotate_and_translate_wing_skeleton3d(wing_skeleton3d, wing_params, side):
     return wing_skeleton3d_world
 
 
-def translate_wing_skeleton3d(wing_skeleton3d, wing_params):
+def translate_skeleton3d(wing_skeleton3d, wing_params):
     """
 
     Args:
@@ -340,7 +375,7 @@ def translate_wing_skeleton3d(wing_skeleton3d, wing_params):
     return wing_skeleton3d_world
 
 
-def rotate_and_translate_wing_geometry3d(wing_geometry3d, wing_params, side):
+def rotate_and_translate_geometry3d(wing_geometry3d, wing_params, side):
     """
 
     Args:
@@ -380,7 +415,7 @@ def rotate_and_translate_wing_geometry3d(wing_geometry3d, wing_params, side):
     return wing_geometry3d_world
 
 
-def translate_wing_geometry3d(wing_geometry3d, wing_params):
+def translate_geometry3d(wing_geometry3d, wing_params):
     """
 
     Args:
@@ -401,7 +436,7 @@ def translate_wing_geometry3d(wing_geometry3d, wing_params):
     return wing_geometry3d_world
 
 
-def estimate_wing_parameters_from_skeleton3d(wing_skeleton3d_world, body_params, init_wing_params, side):
+def estimate_params_from_skeleton3d(wing_skeleton3d_world, body_params, init_wing_params, side):
     """
     Estimate parameters of the transformations/rotations needed to get wing_skeleton3d_world
 
@@ -479,7 +514,7 @@ def estimate_wing_parameters_from_skeleton3d(wing_skeleton3d_world, body_params,
     return wing_params
 
 
-def residuals_wing2d(wing_skeleton3d, wing_skeleton2d, default_residual, dlt_coefs):
+def residuals2d(wing_skeleton3d, wing_skeleton2d, default_residual, dlt_coefs):
     """
     Compute residuals when comparing wing_skeleton3d to wing_skeleton2d
 
@@ -508,7 +543,7 @@ def residuals_wing2d(wing_skeleton3d, wing_skeleton2d, default_residual, dlt_coe
     return residuals.flatten()
 
 
-def residuals_wing3d(wing_skeleton3d_1, wing_skeleton3d_2, default_residual):
+def residuals3d(wing_skeleton3d_1, wing_skeleton3d_2, default_residual):
     """
     Compute residuals when comparing wing_skeleton3d_1 to wing_skeleton3d_2
 
@@ -530,7 +565,7 @@ def residuals_wing3d(wing_skeleton3d_1, wing_skeleton3d_2, default_residual):
     return residuals
 
 
-def residuals_wing2d_geometry3d(wing_geometry3d, wing_skeleton2d, default_residual, dlt_coefs):
+def residuals2d_geometry3d(wing_geometry3d, wing_skeleton2d, default_residual, dlt_coefs):
     """
     Compute residuals when comparing wing_geometry3d to wing_skeleton2d
 
@@ -578,7 +613,7 @@ def residuals_wing2d_geometry3d(wing_geometry3d, wing_skeleton2d, default_residu
     return residuals.flatten()
 
 
-def residuals_wing3d_geometry3d(wing_geometry3d, wing_skeleton3d, default_residual):
+def residuals3d_geometry3d(wing_geometry3d, wing_skeleton3d, default_residual):
     """
     Compute residuals when comparing wing_geometry3d to wing_skeleton3d
 
@@ -619,7 +654,7 @@ def residuals_wing3d_geometry3d(wing_geometry3d, wing_skeleton3d, default_residu
     return residuals
 
 
-def plot_wing(wing_skeleton3d):
+def plot(wing_skeleton3d):
     fig = plt.figure()
     ax = fig.add_subplot(111, projection='3d')
     for label in wing_skeleton3d.keys():
@@ -633,7 +668,7 @@ def plot_wing(wing_skeleton3d):
     plt.show()
 
 
-def plot_wings(wings_skeleton3d):
+def plot_multi(wings_skeleton3d):
     fig = plt.figure()
     ax = fig.add_subplot(111, projection='3d')
     for side in ['right', 'left']:
@@ -648,7 +683,7 @@ def plot_wings(wings_skeleton3d):
     plt.show()
 
 
-def plot_wings_geometry3d(wings_geometry3d):
+def plot_multi_geometry3d(wings_geometry3d):
     fig = plt.figure()
     ax = fig.add_subplot(111, projection='3d')
     for side in ['right', 'left']:
@@ -662,7 +697,7 @@ def plot_wings_geometry3d(wings_geometry3d):
     plt.show()
 
 
-def get_copy_wings_params_zero(wings_params, label_to_zero=['stroke_a', 'deviation_a', 'rotation_a']):
+def get_copy_multi_params_zero(wings_params, label_to_zero=['stroke_a', 'deviation_a', 'rotation_a']):
     """
 
     Args:
diff --git a/skeleton_fitter/utils.py b/skeleton_fitter/utils.py
index 1c5c3cbb5506ef32b5f36afa3d5e7356d50ce26f..acba887cf510773eadc62c678c29b66a88a8585a 100644
--- a/skeleton_fitter/utils.py
+++ b/skeleton_fitter/utils.py
@@ -1,6 +1,6 @@
 import numpy as np
 
-from cam import calib
+from camera import calib
 
 
 def reproject_skeleton3d_to2d(skeleton3d, dlt_coefs):
@@ -13,6 +13,7 @@ def reproject_skeleton3d_to2d(skeleton3d, dlt_coefs):
     Returns:
         skeleton2d_from3d:
     """
+
     nb_cam = len(dlt_coefs)
 
     skeleton2d_from3d = {}
diff --git a/tests/processes_preprocessing.yaml b/tests/processes_preprocessing.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..78e0e8b3bfa2a43c9d2c7ef5bef9319d2fa25c96
--- /dev/null
+++ b/tests/processes_preprocessing.yaml
@@ -0,0 +1,67 @@
+0:
+  fn: initialization
+  image_sizes:
+    1: [256, 256]
+    2: [256, 256]
+    3: [256, 256]
+  last_frame: 5451
+  missing_frames:
+    1: []
+    2: []
+    3: []
+  nb_cam: 3
+  paths: {1: ../data/examples/mosquito_escapes/cam1, 2: ../data/examples/mosquito_escapes/cam2,
+    3: ../data/examples/mosquito_escapes/cam3}
+  process_path: ''
+1:
+  fn: sample
+  kwargs:
+    camn_to_process: [1, 2, 3]
+    delete_previous: true
+    step_frame: 50
+2:
+  fn: track2d
+  kwargs:
+    back_subtractor_settings: {dist2Threshold: 50, shadow_threshold: 1, type: KNN,
+      varThreshold: 12}
+    blob_detector_settings: {area_max: 1000, area_min: 10, aspect_ratio_min: 0.8,
+      eucli_dist_max: 5.0, inertia_ratio_max: 1.0, inertia_ratio_min: 0.0, threshold_max: 255,
+      threshold_min: 1}
+    camn_to_process: [1, 2, 3]
+    from_fn_name: sample
+3:
+  fn: recon3d
+  kwargs:
+    camn_to_process: [1, 2, 3]
+    dlt_path: ../data/calib/examples/mosquito_escapes/20200215_DLTcoefs-py.csv
+    from_fn_name: sample
+    reconstructor_settings: {image_ratio: 4.0, threshold_delay_tracks: 10, threshold_distance_tracks: 0.02,
+      threshold_length_tracks: 5, threshold_mean_repro_err: 5.0}
+4:
+  fn: crop
+  kwargs:
+    camn_to_process: [1, 2, 3]
+    delete_previous: true
+    from_fn_name: sample
+    height_crop: 50
+    width_crop: 50
+5:
+  fn: rotate
+  kwargs:
+    camn_to_process: [2]
+    degrees: 270
+    from_fn_name: crop
+6:
+  fn: stitch
+  kwargs:
+    camn_to_process: [1, 2, 3]
+    delete_previous: true
+    from_fn_name: crop
+7:
+  fn: save_avi
+  kwargs:
+    camn_to_process: [1, 2, 3]
+    delete_previous: true
+    frame_rate: 24
+    from_fn_name: stitch
+    lossless: false
diff --git a/tests/test_calib.py b/tests/test_calib.py
index d75a2a2164cd88413573176c13407bb8e4c194db..321ad04778d8de72eb0ba16eb43bea951d1e017a 100644
--- a/tests/test_calib.py
+++ b/tests/test_calib.py
@@ -1,6 +1,7 @@
+import os
 import numpy as np
 
-from cam import calib
+from camera import calib
 
 
 def test_dlt2d_1views():
@@ -15,18 +16,19 @@ def test_dlt2d_1views():
     uv1 = [[1302, 1147], [1110, 976], [1411, 863], [1618, 1012]]
 
     # Use only one view to perform a 2D calibration of the camera with 4 points of the square:')
-    nd = 2
-    nc = 1
+    nb_dim = 2
+    nb_cam = 1
 
     # Camera calibration parameters based on view #1 and error of the calibration of view #1 (in pixels):')
-    L1, err1 = calib.calib(nd, xy, uv1)
+    dlt_coef1, repro_err1 = calib.calib(nb_dim, xy, uv1)
 
-    assert err1 <= max_mean_err_px
+    assert len(dlt_coef1) == 8
+    assert repro_err1 <= max_mean_err_px
 
     # Reconstruction of the same 4 points based on one view and the camera calibration parameters:
     xy1 = np.zeros((len(xy), 2))
     for i in range(len(uv1)):
-        xy1[i, :] = calib.recon3D(nd, nc, L1, uv1[i])
+        xy1[i, :] = calib.recon3d(nb_dim, nb_cam, dlt_coef1, uv1[i])
 
     # Mean error of the point reconstruction using the DLT (error in cm):
     mean_err = np.mean(np.sqrt(np.sum((np.array(xy1) - np.array(xy)) ** 2, 1)))
@@ -49,21 +51,22 @@ def test_dlt2d_2views():
     uv2 = [[1094, 1187], [1130, 956], [1514, 968], [1532, 1187]]
 
     # Use 2 views to perform a 2D calibration of the camera with 4 points of the square:
-    nd = 2
-    nc = 2
+    nb_dim = 2
+    nb_cam = 2
 
     # Camera calibration parameters based on view #1 and error of the calibration of view #1 (in pixels)
-    L1, err1 = calib.calib(nd, xy, uv1)
-    L2, err2 = calib.calib(nd, xy, uv2)
+    dlt_coef1, repro_err1 = calib.calib(nb_dim, xy, uv1)
+    dlt_coef2, repro_err2 = calib.calib(nb_dim, xy, uv2)
 
-    assert err1 <= max_mean_err_px
-    assert err2 <= max_mean_err_px
+    assert len(dlt_coef1) == 8
+    assert repro_err1 <= max_mean_err_px
+    assert repro_err2 <= max_mean_err_px
 
     # Reconstruction of the same 4 points based on 2 views and the camera calibration parameters:
     xy12 = np.zeros((len(xy), 2))
-    L12 = [L1, L2]
+    dlt_coefs = [dlt_coef1, dlt_coef2]
     for i in range(len(uv1)):
-        xy12[i, :] = calib.recon3D(nd, nc, L12, [uv1[i], uv2[i]])
+        xy12[i, :] = calib.recon3d(nb_dim, nb_cam, dlt_coefs, [uv1[i], uv2[i]])
 
     # Mean error of the point reconstruction using the DLT (error in cm):
     mean_err = np.mean(np.sqrt(np.sum((np.array(xy12) - np.array(xy)) ** 2, 1)))
@@ -90,25 +93,26 @@ def test_dlt3d():
            [1661, 1520]]
 
     # Use 4 views to perform a 3D calibration of the camera with 8 points of the cube:
-    nd = 3
-    nc = 4
+    nb_dim = 3
+    nb_cam = 4
 
     # Camera calibration parameters based on view #1 and error of the calibration of view #1 (in pixels)
-    L1, err1 = calib.calib(nd, xyz, uv1)
-    L2, err2 = calib.calib(nd, xyz, uv2)
-    L3, err3 = calib.calib(nd, xyz, uv3)
-    L4, err4 = calib.calib(nd, xyz, uv4)
+    dlt_coef1, repro_err1 = calib.calib(nb_dim, xyz, uv1)
+    dlt_coef2, repro_err2 = calib.calib(nb_dim, xyz, uv2)
+    dlt_coef3, repro_err3 = calib.calib(nb_dim, xyz, uv3)
+    dlt_coef4, repro_err4 = calib.calib(nb_dim, xyz, uv4)
 
-    assert err1 <= max_mean_err_px
-    assert err2 <= max_mean_err_px
-    assert err3 <= max_mean_err_px
-    assert err4 <= max_mean_err_px
+    assert len(dlt_coef1) == 11
+    assert repro_err1 <= max_mean_err_px
+    assert repro_err2 <= max_mean_err_px
+    assert repro_err3 <= max_mean_err_px
+    assert repro_err4 <= max_mean_err_px
 
     # Reconstruction of the same 8 points based on 4 views and the camera calibration parameters:
     xyz1234 = np.zeros((len(xyz), 3))
-    L1234 = [L1, L2, L3, L4]
+    L1234 = [dlt_coef1, dlt_coef2, dlt_coef3, dlt_coef4]
     for i in range(len(uv1)):
-        xyz1234[i, :] = calib.recon3D(nd, nc, L1234, [uv1[i], uv2[i], uv3[i], uv4[i]])
+        xyz1234[i, :] = calib.recon3d(nb_dim, nb_cam, L1234, [uv1[i], uv2[i], uv3[i], uv4[i]])
 
     # Mean error of the point reconstruction using the DLT (error in cm):
     mean_err = np.mean(np.sqrt(np.sum((np.array(xyz1234) - np.array(xyz)) ** 2, 1)))
@@ -118,19 +122,46 @@ def test_dlt3d():
         'is larger than {0} ({1})'.format(max_mean_err_cm, mean_err)
 
     # 2D reprojection of the 8 points
-    uv1_repro = calib.find2d(1, L1, xyz1234)
-    uv2_repro = calib.find2d(1, L2, xyz1234)
-    uv3_repro = calib.find2d(1, L3, xyz1234)
-    uv4_repro = calib.find2d(1, L4, xyz1234)
+    uv1_repro = calib.find2d(1, dlt_coef1, xyz1234)
+    uv2_repro = calib.find2d(1, dlt_coef2, xyz1234)
+    uv3_repro = calib.find2d(1, dlt_coef3, xyz1234)
+    uv4_repro = calib.find2d(1, dlt_coef4, xyz1234)
 
     # Mean error of the point reprojection using the DLT (error in px):
-    err1_repro = np.sqrt(np.mean(np.sum((uv1 - uv1_repro) ** 2, 1)))
-    err2_repro = np.sqrt(np.mean(np.sum((uv2 - uv2_repro) ** 2, 1)))
-    err3_repro = np.sqrt(np.mean(np.sum((uv3 - uv3_repro) ** 2, 1)))
-    err4_repro = np.sqrt(np.mean(np.sum((uv4 - uv4_repro) ** 2, 1)))
-
-    assert err1_repro <= max_mean_err_px
-    assert err2_repro <= max_mean_err_px
-    assert err3_repro <= max_mean_err_px
-    assert err4_repro <= max_mean_err_px
+    repro_err1 = np.sqrt(np.mean(np.sum((uv1 - uv1_repro) ** 2, 1)))
+    repro_err2 = np.sqrt(np.mean(np.sum((uv2 - uv2_repro) ** 2, 1)))
+    repro_err3 = np.sqrt(np.mean(np.sum((uv3 - uv3_repro) ** 2, 1)))
+    repro_err4 = np.sqrt(np.mean(np.sum((uv4 - uv4_repro) ** 2, 1)))
+
+    assert repro_err1 <= max_mean_err_px
+    assert repro_err2 <= max_mean_err_px
+    assert repro_err3 <= max_mean_err_px
+    assert repro_err4 <= max_mean_err_px
+
+
+def test_gen_dlt_coefs_and_save_in_csv() -> None:
+    max_rmse = 10
+
+    img_height = 1024
+    from_matlab = True
+
+    coord_calib_csv_path = '../data/calib/examples/mosquito_escapes/xyz_calibration_device.csv'
+    pts_calib_csv_path = '../data/calib/examples/mosquito_escapes/20200306_xypts.csv'
+
+    dlt_coefs, rmse_errs = calib.gen_dlt_coefs_from_paths(coord_calib_csv_path, pts_calib_csv_path, from_matlab=from_matlab, img_height=img_height)
+
+    assert all(rmse_errs <= max_rmse)
+
+    calib.save_dlt_coefs(dlt_coefs, '../data/calib/examples/mosquito_escapes/20200306_DLTcoefs-py.csv')
+
+
+def main():
+    test_dlt2d_1views
+    test_dlt2d_2views
+    test_dlt3d
+    test_gen_dlt_coefs_and_save_in_csv
+
+
+if __name__ == "__main__":
+    main()
 
diff --git a/tests/test_images.py b/tests/test_images.py
index 4a195543feac74c6c78d0b882cf5da4e875f69db..a8af6cef1c7fe93fdaa2fc1474881464442d2105 100644
--- a/tests/test_images.py
+++ b/tests/test_images.py
@@ -1,9 +1,132 @@
-from images import process
+import os.path
 
+import matplotlib.pyplot as plt
+import numpy as np
 
-def test_process():
-    # TODO! Make test_process
-    print('TODO')
-    # assert "something" is True, "error message"
-    # assert tracker2d.test() is True, "error message"
+from images import Image, ImageSequence, MultiImageSequence
 
+
+def test_image_write() -> None:
+    file_image = "../data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013501.jpg"
+    file_dest = "/cam1_20200216_0759013501_write_test.jpg"
+
+    image = Image(file=file_image)
+
+    image_dup = image.copy()
+    image_dup.write(file_dest)
+
+    image_written = Image(file=file_dest)
+
+    print(image == image_written)
+
+
+# def test_image_file() -> None:
+#     file_image = "../data/examples/mosquito_escape/cam1_20200216_075901-selection/cam1_20200216_0759013501.jpg"
+#     image = Image(file=file_image, use_caching=True)
+#     print(image._data._cache)
+#
+#     plt.figure()
+#     plt.imshow(image.gradient(return_components=True)[1])
+#     plt.show()
+#
+#     print(image._data._cache)
+#
+#     image.clear_cache()
+#
+#     print(image._data._cache)
+
+
+# def test_image_array() -> None:
+#     file_image = "../data/examples/mosquito_escape/cam1_20200216_075901-selection/cam1_20200216_0759013501.jpg"
+#
+#     shape = (1024, 1024)
+#     image = Image(shape=shape)
+#     image_file = Image(file=file_image)
+#
+#     image_add = image + image_file
+#     image_add = image_add - image_file
+#     image_add = image_add + 1.2
+#
+#     plt.figure()
+#     plt.imshow((image_add / 2).get())
+#     plt.colorbar()
+#     plt.show()
+
+
+def test_image_normalise() -> None:
+    shape = (10, 10)
+    data = np.ones(shape)
+    data[0, 0] = -2.0
+
+    image = Image(data=data)
+    print(image.normalise(minimum=0.0, maximum=1.0).get())
+
+
+def test_image_logical() -> None:
+    test = np.full((10, 10), True, dtype=np.bool_)
+    test[2, 4] = False
+
+    image = Image(data=test, type="bw")
+    print(image.get())
+
+    image.write("test.tif", type_file="tiff_bw")
+
+
+def test_load_image_grey() -> None:
+    file_grey = "../data/examples/mosquito_escapes/cam1/20200216_075901-selection/cam1_20200216_0759013501.jpg"
+
+    image = Image(file=file_grey, type="grey")
+
+    plt.figure()
+    plt.imshow(image.get())
+    plt.show()
+
+
+def test_image_sequence() -> None:
+    seq = ImageSequence.from_directory("../data/examples/mosquito_escapes/cam1/cam1_20200216_075901-sample_binned")
+
+    plt.figure()
+    plt.imshow(seq.median().get())
+    plt.show()
+
+
+def test_multi_image_sequence() -> None:
+    path = '../data/examples/mosquito_escapes/'
+    seq1 = ImageSequence.from_directory(os.path.join(path, 'cam1/cam1_20200216_075901-sample_binned'), use_caching=False)
+    seq2 = ImageSequence.from_directory(os.path.join(path, 'cam2/cam2_20200216_075905-sample_binned'), use_caching=False)
+    seq3 = ImageSequence.from_directory(os.path.join(path, 'cam3/cam3_20200216_075905-sample_binned'), use_caching=False)
+
+    seqs = {'cam1': seq1, 'cam2': seq2, 'cam3': seq3}
+    frame_rates = {'cam1': 12500, 'cam2': 12500, 'cam3': 12500}
+    shifts = {'cam1': 0, 'cam2': 0, 'cam3': 0}
+
+    multi_seq = MultiImageSequence(seqs, frame_rates, shifts)
+
+    assert multi_seq.nb_sequences == 3
+
+    # Or
+    paths = [os.path.join(path, 'cam1/cam1_20200216_075901-sample_binned'),
+             os.path.join(path, 'cam2/cam2_20200216_075905-sample_binned'),
+             os.path.join(path, 'cam3/cam3_20200216_075905-sample_binned')]
+
+    multi_seq = MultiImageSequence.from_directory(paths, 12500)
+
+    assert multi_seq.nb_sequences == 3
+
+    assert multi_seq.get_path() == paths
+    assert multi_seq.names[0] == 'cam1_20200216_075901-sample_binned'
+
+
+def main():
+    # test_image_file()
+    # test_image_array()
+    # test_image_write()
+    # test_image_normalise()
+    # test_image_logical()
+    test_load_image_grey()
+    test_image_sequence()
+    test_multi_image_sequence()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/tests/test_point_tracker.py b/tests/test_point_tracker.py
index 7629978050773466a52271b80e1b07abd42d9b7e..ad1ecc493e4118a9c9efa3ec3ec696f774a4f0e6 100644
--- a/tests/test_point_tracker.py
+++ b/tests/test_point_tracker.py
@@ -1,7 +1,8 @@
-import os, sys
+import os
 import numpy as np
 
-from point_tracker import tracker2d, reconstructor3d
+from point_tracker import Tracker2D, Reconstructor3D
+from camera.calib import read_dlt_coefs
 
 
 def test_tracker2d():
@@ -9,6 +10,8 @@ def test_tracker2d():
     # What to assert?
     print('TODO make test_tracker2d that use minimum data')
 
+    tracker = Tracker2D()
+
     # image_format = 'tif'
     # nb_cam = 3
     #
@@ -16,7 +19,6 @@ def test_tracker2d():
     # image_path = os.path.join(os.getcwd(), '../data/examples/mosquito_escape/')
     #
     # tracker = tracker2d.Tracker2D()
-    # tracker.initialize()
     #
     # for camn in range(1, nb_cam + 1):
     #     print(os.path.join(image_path, rec_names[camn]))
@@ -31,47 +33,22 @@ def test_tracker2d():
 def test_reconstructor3d():
     max_rmse = 10
 
-    nb_cam = 3
-    img_size = (1024, 1024)
-
-    coord_calib_csv = np.genfromtxt(os.path.join(os.getcwd(),
-                                                 '../data/calib/examples/mosquito_escape/xyz_calibration_device.csv'),
-                                    delimiter=',', skip_header=0, names=True)
-    pts_calib_csv = np.genfromtxt(os.path.join(os.getcwd(),
-                                               '../data/calib/examples/mosquito_escape/20200306_xypts.csv'),
-                                  delimiter=',', skip_header=0, names=True)
-
     rec_names = {1: 'cam1_20200303_030117', 2: 'cam2_20200303_030120', 3: 'cam3_20200303_030120'}
-    image_path = os.path.join(os.getcwd(), '../data/examples/mosquito_escape/')
+    image_paths = {1: os.path.join(os.getcwd(), '../data/examples/mosquito_escapes/cam1/'),
+                   2: os.path.join(os.getcwd(), '../data/examples/mosquito_escapes/cam2/'),
+                   3: os.path.join(os.getcwd(), '../data/examples/mosquito_escapes/cam3/')}
+    calib_path = '../data/calib/examples/mosquito_escapes/20200306_DLTcoefs-py.csv'
 
-    coord_calib = {'x': np.array(coord_calib_csv['x']), 'y': np.array(coord_calib_csv['y']),
-                   'z': np.array(coord_calib_csv['z'])}
-
-    reconstructor = reconstructor3d.Reconstructor3D(nb_cam, img_size)
-
-    # generate calibration (DLT coefficients)
-    pts_calib = {}
-    for camn in range(1, nb_cam + 1):
-        pts_calib[camn] = {'x': np.array(pts_calib_csv['cam{0}_X'.format(camn)]),
-                           'y': np.array(pts_calib_csv['cam{0}_Y'.format(camn)])}
+    dlt_coefs = read_dlt_coefs(calib_path)
 
-        # Fill img_pts_calib with pixel coordinates from Matlab tracking (have to transform coordinate system)
-        img_pts_calib = np.array([pts_calib[camn]['x'], img_size[1] - pts_calib[camn]['y']], np.float32).T
+    nb_cam = len(rec_names.keys())
 
-        # Remove np.nan
-        index = ~np.isnan(img_pts_calib).any(axis=1)
-        img_pts_calib = img_pts_calib[index]
-
-        obj_coord_calib = np.array([coord_calib['x'][index], coord_calib['y'][index], coord_calib['z'][index]]).T
-
-        _, rmse = reconstructor.gen_dltcoef(camn, obj_coord_calib, img_pts_calib)
-
-        assert rmse <= max_rmse, 'mean reprojection error of cam{0} is {1:.2f} pixels (> {2:.2f}'.format(camn, rmse, max_rmse)
+    reconstructor = Reconstructor3D(dlt_coefs)
 
     # From .csv file, fill xs_pose, ys_pose and frames_pose (2d coordinates of blobs)
     pts_csv = {}
     for camn in range(1, nb_cam + 1):
-        pts_csv[camn] = np.genfromtxt(os.path.join(image_path, rec_names[camn], rec_names[camn] + '-2d_points.csv'),
+        pts_csv[camn] = np.genfromtxt(os.path.join(image_paths[camn], rec_names[camn], rec_names[camn] + '-2d_points.csv'),
                                       delimiter=',', skip_header=0, names=True)
 
     xs_coord = np.array([pts_csv[camn]['x_px'] for camn in range(1, nb_cam + 1)])
@@ -81,12 +58,12 @@ def test_reconstructor3d():
     xs_pose, ys_pose, zs_pose, frames_pose, indexes_pts, _, = reconstructor.recon_objs(xs_coord, ys_coord, frames_coord)
     tracks_dict = reconstructor.recon_tracks(xs_pose, ys_pose, zs_pose, frames_pose, indexes_pts)
 
-    for num_obj in tracks_dict['obj'].keys():
+    for nb_obj in tracks_dict['obj'].keys():
 
-        xs_coord_repro, ys_coord_repro = reconstructor.get_2d_reprojection(tracks_dict['obj'][num_obj]['x'],
-                                                                           tracks_dict['obj'][num_obj]['y'],
-                                                                           tracks_dict['obj'][num_obj]['z'])
-        index = np.array(tracks_dict['obj'][num_obj]['index_pts']).T
+        xs_coord_repro, ys_coord_repro = reconstructor.get_2d_reprojection(tracks_dict['obj'][nb_obj]['x'],
+                                                                           tracks_dict['obj'][nb_obj]['y'],
+                                                                           tracks_dict['obj'][nb_obj]['z'])
+        index = np.array(tracks_dict['obj'][nb_obj]['index_pts']).T
 
         for i in range(0, nb_cam):
 
@@ -100,5 +77,12 @@ def test_reconstructor3d():
             assert rmse_y_repro <= max_rmse, 'Mean reprojection y error of tracks is {0:.2f} pixels (> {1:.2f}'.format(rmse_x_repro, max_rmse)
 
 
+def main():
+    test_tracker2d
+    test_reconstructor3d
+
+
+if __name__ == "__main__":
+    main()
 
 
diff --git a/tests/test_process_batch.py b/tests/test_process_batch.py
new file mode 100644
index 0000000000000000000000000000000000000000..bc3ce53a86d96e7985b203b3b1617b2c83449703
--- /dev/null
+++ b/tests/test_process_batch.py
@@ -0,0 +1,139 @@
+import os
+
+from process.batch_processing import BatchProcessing
+from point_tracker.tracker2d import BlobDetectorSettings, BackgroundSubtractorSettings
+from point_tracker.reconstructor3d import Reconstructor3DSettings
+
+
+def test_batch_processing_images_preprocessing() -> None:
+
+    recording_paths = ['../data/examples/mosquito_escapes/cam1',
+                       '../data/examples/mosquito_escapes/cam2',
+                       '../data/examples/mosquito_escapes/cam3']
+    save_path = '_Process'
+    dlt_path = os.path.join(os.getcwd(), '../data/calib/examples/mosquito_escapes/20200215_DLTcoefs-py.csv')
+
+    rec_names_to_process = ['20200216_075901-selection']  # names of the recordings to process
+    # (Should be the name of the folder with the images of the main camera (by default: #1))
+
+    framerate = 12500
+
+    img_process = BatchProcessing.from_directory_by_names(recording_paths, framerate, save_path,
+                                                          type_image="grey", threshold_nb_diff_char=2)
+    # img_process = BatchProcessing.from_directory_by_dates(recording_paths, framerate, save_path, type_image="grey",
+    #                                                       expr=r"\d{8}_\d{6}", format_date_str='%Y%m%d_%H%M%S', threshold_s=90)
+
+    # -----------------------------------------------------------------------------------------------------------------------
+    # Will prepare recordings to be used by Deeplabcut (do each step separately)
+    # Convert images to 8 bits and reduce frame_rate (save in cam_save_paths/_Sample)
+    img_process.sample_images(50, rec_names_to_process=rec_names_to_process, delete_previous=True)
+
+    # Do 2d tracking (blob detection) on images in cam_save_paths/_Sample
+    blob_detec_sets = BlobDetectorSettings(area_min=10, area_max=1000)
+    img_process.track2d(blob_detector_settings=blob_detec_sets,
+                        rec_names_to_process=rec_names_to_process, from_fn_name='sample')
+
+    # Do 3d reconstruction of tracks
+    reconstructor_sets = Reconstructor3DSettings(image_ratio=4.0)
+    # use image_ratio=4.0 because the images to analysed have been binned compared to the images used for the calibration)
+    img_process.recon3d(dlt_path, reconstructor_settings=reconstructor_sets,
+                        rec_names_to_process=rec_names_to_process, from_fn_name='sample')
+
+    # Crop all frames and save in cam_save_paths/_Cropped
+    img_process.crop_images('sample', 50, 50, rec_names_to_process=rec_names_to_process, delete_previous=True)
+
+    # Rotate view 2 (to always have piston coming from right side)
+    img_process.rotate_images(270, rec_names_to_process=rec_names_to_process, from_fn_name='crop', camn_to_process=[2])
+
+    # Stitch all views together and save in cam_save_paths/_Stitched
+    img_process.stitch_images(rec_names_to_process=rec_names_to_process, from_fn_name='crop', delete_previous=True)
+
+    # Save each recording in .avi
+    img_process.save_avi(rec_names_to_process=rec_names_to_process, from_fn_name='stitch', delete_previous=True)
+
+
+def test_batch_multiprocessing_images_preprocessing() -> None:
+
+    recording_paths = ['../data/examples/mosquito_escapes/cam1',
+                       '../data/examples/mosquito_escapes/cam2',
+                       '../data/examples/mosquito_escapes/cam3']
+    save_path = '_Process'
+
+    framerate = 12500
+
+    img_process = BatchProcessing.from_directory_by_names(recording_paths, framerate, save_path,
+                                                          type_image="grey", threshold_nb_diff_char=2)
+
+    rec_names_to_process = ['20200216_075901-selection']
+
+    # -----------------------------------------------------------------------------------------------------------------------
+    # Will prepare recordings to be used by Deeplabcut (do everything automatically by using 'multi_processes')
+    # Do cropping, rotating, stitching, etc at once and save as .avi (save much time and space)
+    img_process.multi_processes('processes_preprocessing.yaml', rec_names_to_process=rec_names_to_process, delete_previous=True)
+
+
+def test_batch_processing_dlc_analysis() -> None:
+    # TODO check test_batch_processing_dlc_analysis with deeplabcut installed
+
+    # -----------------------------------------------------------------------------------------------------------------------
+    # Run Deeplabcut analysis + load tracking result (filtering, unscrambling, and reverse processes) + Reconstruct 3d coord
+    dlc_cfg_path = '/home/user/Desktop/Antoine/_maDLC/config.yaml'
+    dlt_path = os.path.join(os.getcwd(), '../data/calib/examples/mosquito_escapes/to_delete/20200615_DLTcoefs-py.csv')
+
+    rec_names_to_process = ['20200216_075901-selection']  # names of the recordings to process
+
+    shuffle = 31
+    iteration = 70000
+    model_name = 'DLC_resnet50_MatingKinematicsshuffle{0}_{1}'.format(shuffle, iteration)
+
+    img_process = BatchProcessing.from_directory_by_dates()
+
+    # Track features using DeepLabCut
+    img_process.analyse_dlc(dlc_cfg_path, shuffle, 0, 5, model_name, save_avi=False,
+                            rec_names_to_process=rec_names_to_process, from_fn_name='save_avi', delete_previous=True)
+
+    # TODO split the following step into several (Loading, Reverse processes and finally 3d reconstruction)??
+    # Load (+ filtering and unscrambling) 2d coords from DLC + Reverse processes (un-stitch, rotate back, un-crop) + Reconstruct 3d coord
+
+    reconstructor_sets = Reconstructor3DSettings(image_ratio=4.0)
+    # use image_ratio=4.0 because the images to analysed have been binned compared to the images used for the calibration)
+    img_process.load_dlc(model_name, 'skeleton', dlt_path, reconstructor_settings=reconstructor_sets,
+                         rec_names_to_process=rec_names_to_process, from_fn_name='analyse_dlc')
+
+
+def test_batch_processing_fit_skeleton() -> None:
+    # -----------------------------------------------------------------------------------------------------------------------
+    # Fit 3D skeleton to 2D coordinate from Deeplabcut
+
+    dlt_path = os.path.join(os.getcwd(), '../data/calib/examples/mosquito_escapes/to_delete/20200615_DLTcoefs-py.csv')
+    #csv_wings_geo_path =
+
+    rec_names_to_process = ['20200216_075901-selection']
+
+    shuffle = 31
+    iteration = 70000
+    model_name = 'DLC_resnet50_MatingKinematicsshuffle{0}_{1}'.format(shuffle, iteration)
+
+    res_method = '3d'  # '2d', '3d' or '2d_geo'
+    opt_method = 'leastsq'  # 'powell' or 'leastsq' or 'least_squares'
+    body_param_names = ['yaw_a', 'pitch_a', 'roll_a', 'x_com', 'y_com', 'z_com']
+    wing_param_names = ['stroke_a', 'deviation_a', 'rotation_a']
+
+    img_process = BatchProcessing.from_directory_by_dates()
+
+    # Optimize fit of skeleton to find body and wings angles
+    img_process.multiprocessing = True
+    img_process.threshold_likelihood = 0.85
+    img_process.fit_skeleton('fly', model_name, res_method, opt_method, dlt_path,
+                             rec_names_to_process=rec_names_to_process, from_fn_name='load_dlc')
+
+
+def main():
+    test_batch_processing_images_preprocessing()
+    test_batch_multiprocessing_images_preprocessing()
+    test_batch_processing_dlc_analysis()
+    test_batch_processing_fit_skeleton()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/tests/test_process_utils.py b/tests/test_process_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..9cccf12db4bfdf406710a341667d8def25c6a2dc
--- /dev/null
+++ b/tests/test_process_utils.py
@@ -0,0 +1,54 @@
+import process.utils as process_utils
+
+
+def test_match_folders() -> None:
+    folder_names = [['cam1_20220304_055123', 'cam1_20220304_101111', 'cam1_20220304_110140', 'cam1_20220304_120352'],
+                    ['cam2_20220304_055123', 'cam2_20220304_101115', 'cam2_20220304_120402'],
+                    ['cam3_20220304_055123', 'cam3_20220304_101111', 'cam3_20220304_111158', 'cam3_20220304_120352']]
+
+    matched_directories = process_utils.match_folders_by_name(folder_names)
+    assert matched_directories == []
+
+    matched_directories = process_utils.match_folders_by_name(folder_names, threshold_nb_diff_char=1)
+    assert matched_directories == [['cam1_20220304_055123', 'cam2_20220304_055123', 'cam3_20220304_055123']]
+
+    matched_directories = process_utils.match_folders_by_date(folder_names, threshold_s=60)
+    assert matched_directories == [['cam1_20220304_055123', 'cam2_20220304_055123', 'cam3_20220304_055123'],
+                                   ['cam1_20220304_101111', 'cam2_20220304_101115', 'cam3_20220304_101111'],
+                                   ['cam1_20220304_120352', 'cam2_20220304_120402', 'cam3_20220304_120352']]
+
+
+def test_match_recordings() -> None:
+    directories = ['../data/examples/mosquito_escapes/cam1/cam1_20200303_030117',
+                   '../data/examples/mosquito_escapes/cam2/cam2_20200303_030120',
+                   '../data/examples/mosquito_escapes/cam3/cam3_20200303_030120']
+
+    matched_directories = process_utils.match_recordings_by_date(directories, threshold_s=60)
+    assert len(matched_directories) == 3
+
+
+def test_get_unmatched_folder_names() -> None:
+    folder_names = [['cam1_20220304_055123', 'cam1_20220304_101111', 'cam1_20220304_110140', 'cam1_20220304_120352'],
+                    ['cam2_20220304_055123', 'cam2_20220304_101115', 'cam2_20220304_120402'],
+                    ['cam3_20220304_055123', 'cam3_20220304_101111', 'cam3_20220304_111158', 'cam3_20220304_120352']]
+
+    matched_folder_names = [['cam1_20220304_055123', 'cam2_20220304_055123', 'cam3_20220304_055123'],
+                            ['cam1_20220304_101111', 'cam2_20220304_101115', 'cam3_20220304_101111'],
+                            ['cam1_20220304_120352', 'cam2_20220304_120402', 'cam3_20220304_120352']]
+
+    unmatched_folder_names = process_utils.get_unmatched_folders(folder_names, matched_folder_names)
+
+    assert unmatched_folder_names == [['cam1_20220304_110140'], [], ['cam3_20220304_111158',]]
+
+
+def main():
+    test_match_folders
+    test_match_recordings
+    test_get_unmatched_folder_names
+
+
+if __name__ == "__main__":
+    main()
+
+
+
diff --git a/tests/test_skeleton_fitter.py b/tests/test_skeleton_fitter.py
index 7cb8fca7cd5d7e4cf6dc272ff0c6aa988dcb560c..0463ced52d091a846f46776b7854d1aa94b6eeab 100644
--- a/tests/test_skeleton_fitter.py
+++ b/tests/test_skeleton_fitter.py
@@ -18,30 +18,30 @@ def test_skeleton_fitter():
 
     dlt_coefs = [[-6.86546837e+02,  1.29528915e+04,  1.02377641e+02,  4.73021647e+02,
                   -7.28479582e+02,  1.16245489e+02, -1.31706744e+04,  5.05410355e+02,
-                  -1.75493289e+00, -2.16534662e-01, -8.74025031e-02,  1.00000000e+00],
+                  -1.75493289e+00, -2.16534662e-01, -8.74025031e-02],
                  [ 1.32835992e+04, -1.30629279e+02, -8.78227221e+02,  4.46517571e+02,
                   -1.21075194e+02, -1.31697555e+04, -8.05482186e+02,  4.24669476e+02,
-                   9.01983567e-02,  3.58804475e-02, -1.98002751e+00,  1.00000000e+00],
+                   9.01983567e-02,  3.58804475e-02, -1.98002751e+00],
                  [-1.28278296e+04, -7.90718730e+02,  9.37964723e+00,  4.30413100e+02,
                   -6.06304504e+01, -9.31664890e+02, -1.28878959e+04,  5.05777107e+02,
-                   1.83401606e-02, -2.16231985e+00, -3.49328202e-02,  1.00000000e+00]]
+                   1.83401606e-02, -2.16231985e+00, -3.49328202e-02]]
 
     res_methods = ['2d', '2d_geo', '3d']
     opt_methods = ['nelder-mead', 'powell', 'least_squares', 'leastsq']
 
     sides = ['left', 'right']
 
-    body_params_init = insect_body_slim.init_body_params()
-    wings_params_init = insect_wings.init_wings_params(body_params_init)
-    hybrid_params_init = insect_hybrid_wings_body_slim.init_hybrid_params()
+    body_params_init = insect_body_slim.init_params()
+    wings_params_init = insect_wings.init_params(body_params_init)
+    hybrid_params_init = insect_hybrid_wings_body_slim.init_params()
 
-    body_bounds = insect_body_slim.init_body_bounds()
-    wing_bounds = insect_wings.init_wing_bounds(body_bounds)
-    hybrid_bounds_init = insect_hybrid_wings_body_slim.init_body_bounds()
+    body_bounds = insect_body_slim.init_bounds()
+    wing_bounds = insect_wings.init_bounds(body_bounds)
+    hybrid_bounds_init = insect_hybrid_wings_body_slim.init_bounds()
 
-    body_skeleton3d_init = insect_body_slim.init_body_skeleton3d(body_params_init)
+    body_skeleton3d_init = insect_body_slim.init_skeleton3d(body_params_init)
     wings_skeleton3d_init, wings_geometry3d_init = insect_wings.init_wings_skeleton_geometry3d_from_csv(wings_params_init, csv_wings_geo_path)
-    hybrid_skeleton3d_init = insect_hybrid_wings_body_slim.init_hybrid_skeleton3d(hybrid_params_init)
+    hybrid_skeleton3d_init = insect_hybrid_wings_body_slim.init_skeleton3d(hybrid_params_init)
 
     # Parameters to generate fake 3d and 2d skeletons
     body_params = copy.deepcopy(body_params_init)
@@ -57,16 +57,16 @@ def test_skeleton_fitter():
 
     # Test all res_methods and opt_methods
     for res_method in res_methods:
-        body_skeleton = insect_body_slim.rotate_body_skeleton3d(body_skeleton3d_init, body_params)
-        body_skeleton = insect_body_slim.translate_body_skeleton3d(body_skeleton, body_params)
+        body_skeleton = insect_body_slim.rotate_skeleton3d(body_skeleton3d_init, body_params)
+        body_skeleton = insect_body_slim.translate_skeleton3d(body_skeleton, body_params)
 
-        hybrid_skeleton = insect_body_slim.rotate_body_skeleton3d(hybrid_skeleton3d_init, body_params)
-        hybrid_skeleton = insect_body_slim.translate_body_skeleton3d(hybrid_skeleton, body_params)
+        hybrid_skeleton = insect_body_slim.rotate_skeleton3d(hybrid_skeleton3d_init, body_params)
+        hybrid_skeleton = insect_body_slim.translate_skeleton3d(hybrid_skeleton, body_params)
 
         wings_skeleton, wings_geometry = copy.deepcopy(wings_skeleton3d_init), copy.deepcopy(wings_geometry3d_init)
         for side in sides:
-            wings_skeleton[side] = insect_wings.rotate_and_translate_wing_skeleton3d(wings_skeleton3d_init[side], wings_params[side], side)
-            wings_geometry[side] = insect_wings.rotate_and_translate_wing_geometry3d(wings_geometry3d_init[side], wings_params[side], side)
+            wings_skeleton[side] = insect_wings.rotate_and_translate_skeleton3d(wings_skeleton3d_init[side], wings_params[side], side)
+            wings_geometry[side] = insect_wings.rotate_and_translate_geometry3d(wings_geometry3d_init[side], wings_params[side], side)
 
         if '2d' in res_method:
             body_skeleton = utils.reproject_skeleton3d_to2d(body_skeleton, dlt_coefs)
@@ -123,6 +123,9 @@ def test_skeleton_fitter():
                 "Too high nb_iterations ({0} > {1}) when optimise fitting in {2} with {3}".format(hybrid_nb_iterations, max_nb_iterations, res_method, opt_method)
 
 
-if __name__ == '__main__':
-
+def main():
     test_skeleton_fitter()
+
+
+if __name__ == "__main__":
+    main()