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) + +## 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) + + + + +### 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()