diff --git a/README.md b/README.md index e63ad30..fc13821 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ -
-

-
+![FicTrac: The webcam-based method for tracking spherical motion and generating fictive animal paths](https://user-images.githubusercontent.com/3844483/110451048-176e9300-80c4-11eb-8e1e-e96545d7d2ed.jpg) **FicTrac** is an open-source software library for reconstructing the fictive path of an animal walking on a patterned sphere. The software is fast, flexible, easy to use, and simplifies the setup of closed-loop tracking experiments. @@ -53,7 +51,7 @@ The FicTrac source code can be built for both Windows and Linux (e.g. Ubuntu) op 2. Linux only: 1. Run the following from terminal to install necessary build tools and dependencies: ``` - [Linux] sudo apt-get install gcc g++ git cmake curl unzip tar yasm pkg-config libgtk2.0-dev libavformat-dev libavcodec-dev libavresample-dev libswscale-dev libopencv-dev + sudo apt-get install gcc g++ git cmake curl unzip tar yasm pkg-config libgtk2.0-dev libavformat-dev libavcodec-dev libavresample-dev libswscale-dev libopencv-dev ``` 3. (Windows and Linux) Clone or download the [Vcpkg](https://github.com/Microsoft/vcpkg) repository and then follow the guide to install (make sure to perform the bootstrap and integration steps). 4. Using Vcpkg, install remaining dependencies: @@ -125,7 +123,7 @@ Before running FicTrac, you may configure your camera (frame rate, resolution, e Basler Pylon SDK 1. Download and install the latest [Pylon SDK](https://www.baslerweb.com/en/products/software/basler-pylon-camera-software-suite/). -2. When preparing the build files for FicTrac using Cmake, you will need to specify to use Pylon using the switch `-D PGR_USB3=ON` and depending on where you installed the SDK, you may also need to provide the SDK directory path using the switch `-D BASLER_DIR=...`. For example, for a Windows installation you would replace step 3 above with (replacing with the path to your vcpkg root directory): +2. When preparing the build files for FicTrac using Cmake, you will need to specify to use Pylon using the switch `-D BASLER_USB3=ON` and depending on where you installed the SDK, you may also need to provide the SDK directory path using the switch `-D BASLER_DIR=...`. For example, for a Windows installation you would replace step 3 above with (replacing with the path to your vcpkg root directory): ``` cmake -A x64 -D CMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake -D BASLER_USB3=ON -D BASLER_DIR="C:\path\to\Pylon" .. ``` diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f6e44f0..2e67e7a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -5,8 +5,8 @@ # https://docs.microsoft.com/en-us/azure/devops/pipelines/build/triggers?view=azure-devops&tabs=yaml trigger: -#- master -- develop +- master +#- develop # We can run multiple jobs in parallel. # see https://docs.microsoft.com/en-us/azure/devops/pipelines/process/phases diff --git a/doc/params.md b/doc/params.md index 6173d25..9876b6c 100644 --- a/doc/params.md +++ b/doc/params.md @@ -24,7 +24,7 @@ In the table below, the various possible parameters are listed. If nothing is li | thr_ratio | float | 1.25 | (0,inf) | Only if you need to | Adjusts the adaptive thresholding of the input image. Values > 1 will favour foreground regions (more white in thresholded image) and values < 1 will favour background regions (more black in thresholded image). | | thr_win_pc | float | 0.2 | \[0,1] | Only if you need to | Adjusts the size of the neighbourhood window to use for adaptive thresholding of the input image, specified as a percentage of the width of the tracking window. Larger values avoid over-segmentation, whilst smaller values make segmentation more robust to illumination gradients on the trackball. | | vid_codec | string | h264 | [h264,xvid,mpg4,mjpg,raw] | Only if you need to | Specifies the video codec to use when writing output videos (see `save_raw` and `save_debug`). | -| sphere_map_fn | string | | | Only if you need to | If specified, FicTrac will attempt to load a previously generated sphere surface map from this filename. | +| sphere_map_fn | string | | | Only if you need to | If specified, FicTrac will attempt to load a previously generated sphere surface map from this filename. Note that if you set this option, you should probably also set `opt_do_global` otherwise FicTrac may not find the initial sphere attitude. | | | | | | | | | opt_max_evals | int | 50 | (0,inf) | Probably not | Specifies the maximum number of minimisation iterations to perform each frame. Smaller values may improve tracking frame rate at the risk of finding sub-optimal matches. Number of optimisation iterations is printed to screen during tracking (its=...). | | opt_bound | float | 0.35 | (0,inf) | Probably not | Specifies the optimisation search range in radians. Larger values will facilitate more track ball rotation per frame, but result in slower tracking and also possibly lead to false matches. | diff --git a/include/typesvars.h b/include/typesvars.h index 9119f1c..9a01dee 100644 --- a/include/typesvars.h +++ b/include/typesvars.h @@ -15,8 +15,3 @@ const CmReal CM_PI = 3.14159265358979323846; const CmReal CM_PI_2 = 1.57079632679489661923; const CmReal CM_R2D = 180.0 / CM_PI; const CmReal CM_D2R = CM_PI / 180.0; - -//enum THR_MODE { -// ADAPT, -// NORM_PRIORS -//}; diff --git a/scripts/socket_client.py b/scripts/socket_client.py index 8a62b51..e7e8799 100644 --- a/scripts/socket_client.py +++ b/scripts/socket_client.py @@ -1,58 +1,75 @@ #!/usr/bin/env python3 import socket +import select HOST = '127.0.0.1' # The (receiving) host IP address (sock_host) PORT = ???? # The (receiving) host port (sock_port) +# TCP # Open the connection (ctrl-c / ctrl-break to quit) -with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock: # UDP -#with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: # TCP - sock.bind((HOST, PORT)) # UDP -# sock.connect((HOST, PORT)) # TCP - - data = "" +#with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: +# sock.connect((HOST, PORT)) + +# UDP +# Open the connection (ctrl-c / ctrl-break to quit) +with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock: + sock.bind((HOST, PORT)) + sock.setblocking(0) # Keep receiving data until FicTrac closes + data = "" + timeout_in_seconds = 1 while True: - # Receive one data frame - new_data = sock.recv(1024) - if not new_data: - break - - # Decode received data - data += new_data.decode('UTF-8') - - # Find the first frame of data - endline = data.find("\n") - line = data[:endline] # copy first frame - data = data[endline+1:] # delete first frame - - # Tokenise - toks = line.split(", ") - - # Check that we have sensible tokens - if ((len(toks) < 24) | (toks[0] != "FT")): - print('Bad read') - continue - - # Extract FicTrac variables - # (see https://github.com/rjdmoore/fictrac/blob/master/doc/data_header.txt for descriptions) - cnt = int(toks[1]) - dr_cam = [float(toks[2]), float(toks[3]), float(toks[4])] - err = float(toks[5]) - dr_lab = [float(toks[6]), float(toks[7]), float(toks[8])] - r_cam = [float(toks[9]), float(toks[10]), float(toks[11])] - r_lab = [float(toks[12]), float(toks[13]), float(toks[14])] - posx = float(toks[15]) - posy = float(toks[16]) - heading = float(toks[17]) - step_dir = float(toks[18]) - step_mag = float(toks[19]) - intx = float(toks[20]) - inty = float(toks[21]) - ts = float(toks[22]) - seq = int(toks[23]) + # Check to see whether there is data waiting + ready = select.select([sock], [], [], timeout_in_seconds) + + # Only try to receive data if there is data waiting + if ready[0]: + # Receive one data frame + new_data = sock.recv(1024) + + # Uh oh? + if not new_data: + break + + # Decode received data + data += new_data.decode('UTF-8') + + # Find the first frame of data + endline = data.find("\n") + line = data[:endline] # copy first frame + data = data[endline+1:] # delete first frame + + # Tokenise + toks = line.split(", ") + + # Check that we have sensible tokens + if ((len(toks) < 24) | (toks[0] != "FT")): + print('Bad read') + continue + + # Extract FicTrac variables + # (see https://github.com/rjdmoore/fictrac/blob/master/doc/data_header.txt for descriptions) + cnt = int(toks[1]) + dr_cam = [float(toks[2]), float(toks[3]), float(toks[4])] + err = float(toks[5]) + dr_lab = [float(toks[6]), float(toks[7]), float(toks[8])] + r_cam = [float(toks[9]), float(toks[10]), float(toks[11])] + r_lab = [float(toks[12]), float(toks[13]), float(toks[14])] + posx = float(toks[15]) + posy = float(toks[16]) + heading = float(toks[17]) + step_dir = float(toks[18]) + step_mag = float(toks[19]) + intx = float(toks[20]) + inty = float(toks[21]) + ts = float(toks[22]) + seq = int(toks[23]) + + # Do something ... + print(cnt) - # Do something ... - print(cnt) + else: + # Didn't find any data - try again + print('retrying...') diff --git a/src/Trackball.cpp b/src/Trackball.cpp index 33a0fa9..6b11d4e 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -864,12 +864,15 @@ void Trackball::updateSphere() uint8_t& map = _sphere_map.data[py * _sphere_map.step + px]; // update map tile - if (map == 128) { + if ((map == 0) || (map == 255)) { + // map tile frozen + good++; + } else if (map == 128) { // map tile previously unseen map = (proi[j] == 255) ? (128 + SPHERE_MAP_FIRST_HIT_BONUS) : (128 - SPHERE_MAP_FIRST_HIT_BONUS); } else { good++; - map = min(max((proi[j] == 255) ? (int(map) + 1) : (int(map) - 1), 0), 255); + map = (proi[j] == 255) ? (map + 1) : (map - 1); } // display diff --git a/test/3axis0.avi b/test/3axis0.avi deleted file mode 100644 index 6345e6c..0000000 Binary files a/test/3axis0.avi and /dev/null differ diff --git a/test/3axis0.cfg b/test/3axis0.cfg deleted file mode 100644 index dc2953b..0000000 --- a/test/3axis0.cfg +++ /dev/null @@ -1,22 +0,0 @@ -## FicTrac config file (build Mar 10 2020) -c2a_cnrs_xy : { 0, 0, 640, 0, 640, 640, 0, 640 } -c2a_r : { -0.000000, 0.000000, 1.570796 } -c2a_src : c2a_cnrs_xy -c2a_t : { -0.000000, -0.000000, 0.288675 } -do_display : y -max_bad_frames : -1 -opt_bound : 0.350000 -opt_do_global : n -opt_max_err : -1.000000 -opt_max_evals : 50 -opt_tol : 0.001000 -q_factor : 6 -roi_circ : { 250, 318, 268, 274, 301, 253, 357, 262, 387, 304, 378, 355, 309, 387 } -roi_ignr : { } -save_debug : n -save_raw : n -src_fn : 3axis0.avi -src_fps : -1.000000 -thr_ratio : 1.250000 -thr_win_pc : 0.250000 -vfov : 120 diff --git a/test/3axis0_gt.csv b/test/3axis0_gt.csv deleted file mode 100644 index 98cda58..0000000 --- a/test/3axis0_gt.csv +++ /dev/null @@ -1,100 +0,0 @@ -0,-0,0 --0.051106,0.19408,-0.12047 --0.076408,0.39394,-0.22629 --0.074381,0.59669,-0.31494 --0.043754,0.79869,-0.38395 -0.016309,0.99551,-0.43098 -0.10599,1.1819,-0.45397 -0.22456,1.352,-0.45129 -0.37012,1.4992,-0.42193 -0.53939,1.6165,-0.36574 -0.7275,1.6974,-0.28363 -0.92799,1.7356,-0.17777 -1.133,1.7264,-0.051613 -1.3335,1.6669,0.090127 -1.5202,1.5566,0.24176 -1.6839,1.3977,0.39702 -1.8163,1.1948,0.54954 -1.911,0.9548,0.69344 -1.9633,0.68624,0.82374 -1.9708,0.39836,0.93671 -1.9331,0.10051,1.03 -1.8516,-0.1985,1.1028 -1.7288,-0.49077,1.1552 -1.5683,-0.76959,1.1888 -1.3742,-1.0294,1.2058 -1.1508,-1.2659,1.2089 -0.90248,-1.4757,1.2014 -0.63344,-1.6562,1.187 -0.3479,-1.8054,1.1695 -0.049999,-1.922,1.1529 --0.25613,-2.0047,1.1412 --0.56619,-2.0527,1.1388 --0.87553,-2.0653,1.1499 --1.179,-2.042,1.179 --1.4705,-1.9826,1.2301 --1.7433,-1.8872,1.3072 --1.989,-1.7569,1.4136 --2.1983,-1.5934,1.5513 -2.2179,1.3156,-1.6166 -2.171,1.0421,-1.6904 -2.084,0.79183,-1.7846 -1.9622,0.56789,-1.8965 -1.8101,0.37261,-2.0233 -1.6314,0.20788,-2.1624 -1.4295,0.075339,-2.3108 -1.2068,-0.023423,-2.4655 -0.96562,-0.086745,-2.6234 -0.70777,-0.11277,-2.7807 -0.43478,-0.099389,-2.9332 -0.14807,-0.044155,-3.0758 -0.14483,-0.053407,3.0728 -0.40491,-0.17846,2.9063 -0.62427,-0.32131,2.7073 -0.80366,-0.47505,2.4824 -0.94557,-0.63394,2.2374 -1.0535,-0.79341,1.9769 -1.1316,-0.94994,1.7048 -1.1841,-1.1009,1.424 -1.2152,-1.2443,1.137 -1.2291,-1.3788,0.84584 -1.2298,-1.5036,0.55219 -1.2212,-1.6183,0.25754 -1.2073,-1.7225,-0.036713 -1.192,-1.8166,-0.32917 -1.1793,-1.9007,-0.61829 -1.1734,-1.9756,-0.90229 -1.179,-2.042,-1.179 -1.201,-2.1009,-1.4454 -1.2451,-2.1536,-1.6979 --1.2604,2.106,1.8474 --1.1987,1.889,1.7981 --1.1648,1.692,1.7067 --1.1555,1.5171,1.5814 --1.1678,1.3655,1.4291 --1.1989,1.2375,1.2554 --1.2457,1.1332,1.0648 --1.3059,1.0524,0.86078 --1.3769,0.99481,0.64645 --1.4565,0.9601,0.42426 --1.5427,0.94795,0.1963 --1.6335,0.95804,-0.035632 --1.7271,0.9901,-0.26996 --1.8216,1.0439,-0.50529 --1.9154,1.1193,-0.7403 --2.0067,1.216,-0.97379 --2.0937,1.334,-1.2046 --2.1747,1.4732,-1.4315 -2.1204,-1.5407,1.5596 -1.859,-1.4593,1.5032 -1.6105,-1.3742,1.4155 -1.3758,-1.2829,1.3043 -1.1555,-1.1835,1.1757 -0.95014,-1.0747,1.0347 -0.76026,-0.95536,0.88559 -0.58662,-0.82468,0.73195 -0.43011,-0.6823,0.577 -0.29183,-0.52814,0.42365 -0.17308,-0.3625,0.27462 -0.075274,-0.18608,0.13255 --3.9288e-15,-1.019e-13,-7.8575e-15 diff --git a/test/test.py b/test/test.py deleted file mode 100644 index f0010da..0000000 --- a/test/test.py +++ /dev/null @@ -1,64 +0,0 @@ -import sys -import csv -import numpy as np -import matplotlib.pyplot as plt -import glob -import subprocess -import os - -if (len(sys.argv) < 2): - print('Needs at least test name!') - sys.exit() - -test_name = sys.argv[1] - -print('Processing test {}'.format(test_name)) - -cfg_fn = test_name+'.cfg' - -print('Using cfg: {}'.format(cfg_fn)) - -gt_csv = test_name+'_gt.csv' - -print('Using gt: {}'.format(gt_csv)) - -if os.name == 'nt': - cmd = "..\\bin\\Release\\fictrac.exe" -else: - cmd = "..\\bin\\Release\\fictrac" - -subprocess.run([cmd, cfg_fn]) - -dat_list = glob.glob(test_name+'-*.dat') - -print('Found {} dat files matching pattern {}'.format(len(dat_list), test_name)) - -dat_fn = dat_list[-1] - -print('Using dat: {}'.format(dat_fn)) - -with open(gt_csv, 'r') as f: - tmp = list(csv.reader(f)) - gt = np.array([[float(i) for i in x] for x in tmp]) - -with open(dat_fn, 'r') as f: - tmp = list(csv.reader(f)) - dat = np.array([[float(i) for i in x] for x in tmp]) - dat = dat[:,8:11] - -err = np.linalg.norm(gt - dat, axis=1) -max_err = np.max(err) -end_err = err[-1] - -print('max_err: {} end_err: {}'.format(max_err, end_err)) - -plt.figure(1) -plt.plot(gt) -plt.plot(dat) -plt.grid() - -plt.figure(2) -plt.plot(err) -plt.grid() - -plt.show() diff --git a/test/test_3axis0.m b/test/test_3axis0.m deleted file mode 100644 index a35f5bb..0000000 --- a/test/test_3axis0.m +++ /dev/null @@ -1,46 +0,0 @@ -function [max_err, end_err] = test_3axis0(dat_fn) - -dat = load(dat_fn); -test = dat(:,9:11) - -rotx = @(t) [1 0 0; 0 cos(t) -sin(t) ; 0 sin(t) cos(t)] ; -roty = @(t) [cos(t) 0 sin(t) ; 0 1 0 ; -sin(t) 0 cos(t)] ; -rotz = @(t) [cos(t) -sin(t) 0 ; sin(t) cos(t) 0 ; 0 0 1] ; - -% rotate <360,1080,720>*clock -R = deg2rad([360,1080,720]); -t = linspace(0,100,100); - -rlist = zeros(100,3); -for i = 1:100 - dR = t(i) .* R; - - A = eye(3); - A = rotx(dR(1)) * A; - A = roty(dR(2)) * A; - A = rotz(dR(3)) * A; - - r = rotationMatrixToVector(A); - rlist(i,:) = r; -end - -% hmm.. -rlist(:,2) = -rlist(:,2); - -err = vecnorm(rlist - test, 2, 2); -max_err = max(err); -end_err = err(end); - -figure(1); -hold off; -plot(rlist); -hold on; -plot(test, 'x-'); -grid on; - -figure(2); -hold off; -plot(err); -grid on; - -csvwrite('3axis0_gt.csv', rlist);