add
Browse files- esim_py/CMakeLists.txt +20 -0
- esim_py/README.md +52 -0
- esim_py/include/.gitignore +1 -0
- esim_py/include/esim.h +123 -0
- esim_py/setup.py +66 -0
- esim_py/src/bindings.cpp +22 -0
- esim_py/src/esim.cpp +239 -0
esim_py/CMakeLists.txt
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
cmake_minimum_required(VERSION 3.3)
|
2 |
+
project(esim_py)
|
3 |
+
|
4 |
+
set(OpenCV_DIR "E:\\opencv\\build")
|
5 |
+
|
6 |
+
find_package(pybind11 REQUIRED)
|
7 |
+
find_package(OpenCV REQUIRED)
|
8 |
+
find_package(Eigen3 REQUIRED NO_MODULE)
|
9 |
+
find_package(Boost COMPONENTS system filesystem REQUIRED)
|
10 |
+
|
11 |
+
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
12 |
+
set(CMAKE_CXX_STANDARD 11)
|
13 |
+
|
14 |
+
include_directories(include ${EIGEN3_INCLUDE_DIR} ${OpenCV_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS})
|
15 |
+
|
16 |
+
add_library(libesim STATIC src/esim.cpp)
|
17 |
+
|
18 |
+
pybind11_add_module(esim_py src/bindings.cpp)
|
19 |
+
|
20 |
+
target_link_libraries(esim_py PRIVATE libesim ${OpenCV_LIBS} ${Boost_FILESYSTEM_LIBRARY} ${BOOST_SYSTEM_LIBRARY} Boost::filesystem Boost::system Eigen3::Eigen pybind11::module)
|
esim_py/README.md
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# esym\_py
|
2 |
+
|
3 |
+
This package exposes python bindings for ESIM which can be used within a training loop.
|
4 |
+
To test out if the installation was successful you can run
|
5 |
+
|
6 |
+
```bash
|
7 |
+
python tests/test.py
|
8 |
+
```
|
9 |
+
|
10 |
+
which should print a message if completed sucessfully.
|
11 |
+
|
12 |
+
The currently supported functions are listed in the example below:
|
13 |
+
```python
|
14 |
+
import esim_py
|
15 |
+
|
16 |
+
# constructor
|
17 |
+
esim = esim_py.EventSimulator(
|
18 |
+
contrast_threshold_pos, # contrast thesholds for positive
|
19 |
+
contrast_threshold_neg, # and negative events
|
20 |
+
refractory_period, # minimum waiting period (in sec) before a pixel can trigger a new event
|
21 |
+
log_eps, # epsilon that is used to numerical stability within the logarithm
|
22 |
+
use_log, # wether or not to use log intensity
|
23 |
+
)
|
24 |
+
|
25 |
+
# setter, useful within a training loop
|
26 |
+
esim.setParameters(contrast_threshold_pos, contrast_threshold_neg, refractory_period, log_eps, use_log)
|
27 |
+
|
28 |
+
# generate events from a sequence of images
|
29 |
+
events_from_images = esim.generateFromFolder(
|
30 |
+
path_to_image_folder, # absolute path to folder that stores images in numbered order
|
31 |
+
path_to_timestamps # absolute path to timestamps file containing one timestamp (in secs) for each
|
32 |
+
)
|
33 |
+
|
34 |
+
# generate events from a video
|
35 |
+
events_from_video = esim.generateFromVideo(
|
36 |
+
path_to_video_file, # absolute path to video storing images
|
37 |
+
path_to_timestamps # absolute path to timestamps file
|
38 |
+
)
|
39 |
+
|
40 |
+
# generate events from list of images and timestamps
|
41 |
+
events_list_of_images = esim.generateFromStampedImageSequence(
|
42 |
+
list_of_image_files, # list of absolute paths to images
|
43 |
+
list_of_timestamps # list of timestamps in ascending order
|
44 |
+
)
|
45 |
+
|
46 |
+
```
|
47 |
+
The example script `tests/plot_virtual_events.py` plots virtual events that are generated from images in `tests/data/images` with varying positive and negative contrast thresholds. To call it you need some additional pip packages:
|
48 |
+
|
49 |
+
```bash
|
50 |
+
pip install numpy matplotlib
|
51 |
+
python tests/plot_virtual_events.py
|
52 |
+
```
|
esim_py/include/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
eigen3.3.7
|
esim_py/include/esim.h
ADDED
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#pragma once
|
2 |
+
|
3 |
+
#include <vector>
|
4 |
+
|
5 |
+
#include <boost/filesystem.hpp>
|
6 |
+
#include <Eigen/Core>
|
7 |
+
#include <opencv2/core/core.hpp>
|
8 |
+
|
9 |
+
#include <pybind11/numpy.h>
|
10 |
+
#include <pybind11/pybind11.h>
|
11 |
+
|
12 |
+
namespace py = pybind11;
|
13 |
+
|
14 |
+
struct Event
|
15 |
+
{
|
16 |
+
Event(int x, int y, double t, int polarity)
|
17 |
+
: x_(x), y_(y), t_(t), polarity_(polarity)
|
18 |
+
{}
|
19 |
+
|
20 |
+
bool operator<(Event& other)
|
21 |
+
{
|
22 |
+
return t_ < other.t_;
|
23 |
+
}
|
24 |
+
|
25 |
+
int x_, y_;
|
26 |
+
double t_;
|
27 |
+
int polarity_;
|
28 |
+
};
|
29 |
+
|
30 |
+
/*
|
31 |
+
* The EventSimulator takes as input a sequence of stamped images,
|
32 |
+
* assumed to be sampled at a "sufficiently high" framerate,
|
33 |
+
* and simulates the principle of operation of an idea event camera
|
34 |
+
* with a constant contrast threshold C.
|
35 |
+
* Pixel-wise intensity values are linearly interpolated in time.
|
36 |
+
*
|
37 |
+
* The pixel-wise voltages are reset with the values from the first image
|
38 |
+
* which is passed to the simulator.
|
39 |
+
*/
|
40 |
+
class EventSimulator
|
41 |
+
{
|
42 |
+
public:
|
43 |
+
EventSimulator(float contrast_threshold_pos,
|
44 |
+
float contrast_threshold_neg,
|
45 |
+
float refractory_period,
|
46 |
+
float log_eps,
|
47 |
+
bool use_log_img);
|
48 |
+
|
49 |
+
Eigen::MatrixXd generateFromFolder(std::string image_folder, std::string timestamps_file_path);
|
50 |
+
Eigen::MatrixXd generateFromVideo(std::string video_path, std::string timestamps_file_path);
|
51 |
+
Eigen::MatrixXd generateFromStampedImageSequence(std::vector<std::string> image_paths, std::vector<double> timestamps);
|
52 |
+
|
53 |
+
Eigen::MatrixXd generateEventFromCVImage(py::array_t<float> input_array, double time);
|
54 |
+
void initialise(py::array_t<float> input_array, double time);
|
55 |
+
|
56 |
+
void setParameters(float contrast_threshold_pos,
|
57 |
+
float contrast_threshold_neg,
|
58 |
+
float refractory_period,
|
59 |
+
float log_eps,
|
60 |
+
bool use_log_img)
|
61 |
+
{
|
62 |
+
contrast_threshold_pos_ = contrast_threshold_pos;
|
63 |
+
contrast_threshold_neg_ = contrast_threshold_neg;
|
64 |
+
refractory_period_ = refractory_period;
|
65 |
+
log_eps_ = log_eps;
|
66 |
+
use_log_img_ = use_log_img;
|
67 |
+
}
|
68 |
+
|
69 |
+
cv::Mat pyArrayToCvMat(py::array_t<float> input_array) {
|
70 |
+
// Accessing the NumPy array data
|
71 |
+
py::buffer_info buf_info = input_array.request();
|
72 |
+
float* ptr = static_cast<float*>(buf_info.ptr);
|
73 |
+
|
74 |
+
// Assuming a 3-channel image, you may need to adjust channels and sizes accordingly
|
75 |
+
return cv::Mat(buf_info.shape[0], buf_info.shape[1], CV_32F, ptr).clone();
|
76 |
+
}
|
77 |
+
|
78 |
+
|
79 |
+
private:
|
80 |
+
void init(const cv::Mat &img, double time);
|
81 |
+
Eigen::MatrixXd vec_to_eigen_matrix(std::vector<Event>& events_vec)
|
82 |
+
{
|
83 |
+
Eigen::MatrixXd events(events_vec.size(), 4);
|
84 |
+
for (int i=0; i<events_vec.size(); i++)
|
85 |
+
{
|
86 |
+
Event& event = events_vec[i];
|
87 |
+
events(i,0) = event.x_;
|
88 |
+
events(i,1) = event.y_;
|
89 |
+
events(i,2) = event.t_;
|
90 |
+
events(i,3) = event.polarity_;
|
91 |
+
}
|
92 |
+
return events;
|
93 |
+
}
|
94 |
+
|
95 |
+
void imageCallback(const cv::Mat& img, double time, std::vector<Event>& events);
|
96 |
+
|
97 |
+
void read_directory_from_path(const std::string& name, std::vector<std::string>& v)
|
98 |
+
{
|
99 |
+
boost::filesystem::path p(name);
|
100 |
+
boost::filesystem::directory_iterator start(p);
|
101 |
+
boost::filesystem::directory_iterator end;
|
102 |
+
|
103 |
+
auto path_leaf_string = [](const boost::filesystem::directory_entry& entry) -> std::string {return entry.path().string();};
|
104 |
+
|
105 |
+
std::transform(start, end, std::back_inserter(v), path_leaf_string);
|
106 |
+
|
107 |
+
std::sort(v.begin(), v.end());
|
108 |
+
}
|
109 |
+
|
110 |
+
float contrast_threshold_pos_;
|
111 |
+
float contrast_threshold_neg_;
|
112 |
+
float refractory_period_;
|
113 |
+
float log_eps_;
|
114 |
+
bool use_log_img_;
|
115 |
+
|
116 |
+
bool is_initialized_;
|
117 |
+
double current_time_;
|
118 |
+
cv::Mat ref_values_;
|
119 |
+
cv::Mat last_img_;
|
120 |
+
cv::Mat last_event_timestamp_;
|
121 |
+
int image_height_;
|
122 |
+
int image_width_;
|
123 |
+
};
|
esim_py/setup.py
ADDED
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import platform
|
3 |
+
import re
|
4 |
+
import subprocess
|
5 |
+
import sys
|
6 |
+
|
7 |
+
from distutils.version import LooseVersion
|
8 |
+
from setuptools import setup, Extension
|
9 |
+
from setuptools.command.build_ext import build_ext
|
10 |
+
|
11 |
+
class CMakeExtension(Extension):
|
12 |
+
def __init__(self, name, sourcedir=''):
|
13 |
+
super().__init__(name, sources=[])
|
14 |
+
self.sourcedir = os.path.abspath(sourcedir)
|
15 |
+
|
16 |
+
|
17 |
+
class CMakeBuild(build_ext):
|
18 |
+
def run(self):
|
19 |
+
try:
|
20 |
+
out = subprocess.check_output(['cmake', '--version'])
|
21 |
+
except OSError:
|
22 |
+
raise RuntimeError("CMake must be installed to build the following extensions: " +
|
23 |
+
", ".join(e.name for e in self.extensions))
|
24 |
+
|
25 |
+
if platform.system() == "Windows":
|
26 |
+
cmake_version = LooseVersion(re.search(r'version\s*([\d.]+)', out.decode()).group(1))
|
27 |
+
if cmake_version < '3.1.0':
|
28 |
+
raise RuntimeError("CMake >= 3.1.0 is required on Windows")
|
29 |
+
|
30 |
+
for ext in self.extensions:
|
31 |
+
self.build_extension(ext)
|
32 |
+
|
33 |
+
def build_extension(self, ext):
|
34 |
+
extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name)))
|
35 |
+
cmake_args = ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir,
|
36 |
+
'-DPYTHON_EXECUTABLE=' + sys.executable]
|
37 |
+
|
38 |
+
cfg = 'Debug' if self.debug else 'Release'
|
39 |
+
build_args = ['--config', cfg]
|
40 |
+
|
41 |
+
cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg]
|
42 |
+
build_args += ['--', '-j']
|
43 |
+
|
44 |
+
env = os.environ.copy()
|
45 |
+
env['CXXFLAGS'] = '{} -DVERSION_INFO=\\"{}\\"'.format(env.get('CXXFLAGS', ''),
|
46 |
+
self.distribution.get_version())
|
47 |
+
env['CFLAGS'] = '{} -DVERSION_INFO=\\"{}\\"'.format(env.get('CFLAGS', ''),
|
48 |
+
self.distribution.get_version())
|
49 |
+
if not os.path.exists(self.build_temp):
|
50 |
+
os.makedirs(self.build_temp)
|
51 |
+
subprocess.check_call(['cmake', ext.sourcedir] + cmake_args, cwd=self.build_temp, env=env)
|
52 |
+
subprocess.check_call(['cmake', '--build', '.'] + build_args, cwd=self.build_temp)
|
53 |
+
|
54 |
+
|
55 |
+
|
56 |
+
setup(
|
57 |
+
name='esim_py',
|
58 |
+
version='0.0.1',
|
59 |
+
author='Daniel Gehrig',
|
60 |
+
author_email='[email protected]',
|
61 |
+
description='Python bindings for ESIM.',
|
62 |
+
long_description='',
|
63 |
+
ext_modules=[CMakeExtension('esim_py')],
|
64 |
+
cmdclass=dict(build_ext=CMakeBuild),
|
65 |
+
zip_safe=False,
|
66 |
+
)
|
esim_py/src/bindings.cpp
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#include <esim.h>
|
2 |
+
|
3 |
+
#include <pybind11/eigen.h>
|
4 |
+
#include <pybind11/pybind11.h>
|
5 |
+
#include <pybind11/stl.h>
|
6 |
+
|
7 |
+
|
8 |
+
namespace py = pybind11;
|
9 |
+
|
10 |
+
PYBIND11_MODULE(esim_py, m) {
|
11 |
+
m.doc() = "ESIM bindings";
|
12 |
+
|
13 |
+
py::class_<EventSimulator>(m, "EventSimulator")
|
14 |
+
.def(py::init<float,float,float,float,bool>())
|
15 |
+
.def("generateFromFolder", &EventSimulator::generateFromFolder, py::return_value_policy::reference_internal)
|
16 |
+
.def("generateFromVideo", &EventSimulator::generateFromVideo, py::return_value_policy::reference_internal)
|
17 |
+
.def("generateFromStampedImageSequence", &EventSimulator::generateFromStampedImageSequence, py::return_value_policy::reference_internal)
|
18 |
+
.def("setParameters", &EventSimulator::setParameters)
|
19 |
+
.def("generateEventFromCVImage", &EventSimulator::generateEventFromCVImage)
|
20 |
+
.def("init", &EventSimulator::initialise);
|
21 |
+
|
22 |
+
}
|
esim_py/src/esim.cpp
ADDED
@@ -0,0 +1,239 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#include <esim.h>
|
2 |
+
|
3 |
+
#include <fstream>
|
4 |
+
#include <iostream>
|
5 |
+
#include <algorithm>
|
6 |
+
|
7 |
+
#include <opencv2/core/eigen.hpp>
|
8 |
+
#include <opencv2/highgui/highgui.hpp>
|
9 |
+
#include <opencv2/imgproc/imgproc.hpp>
|
10 |
+
|
11 |
+
|
12 |
+
EventSimulator::EventSimulator(float contrast_threshold_pos,
|
13 |
+
float contrast_threshold_neg,
|
14 |
+
float refractory_period,
|
15 |
+
float log_eps,
|
16 |
+
bool use_log_img)
|
17 |
+
: contrast_threshold_pos_(contrast_threshold_pos), contrast_threshold_neg_(contrast_threshold_neg),
|
18 |
+
refractory_period_(refractory_period), log_eps_(log_eps), use_log_img_(use_log_img), is_initialized_(false)
|
19 |
+
{
|
20 |
+
|
21 |
+
}
|
22 |
+
|
23 |
+
Eigen::MatrixXd EventSimulator::generateFromVideo(std::string video_path, std::string timestamps_file_path)
|
24 |
+
{
|
25 |
+
std::ifstream timestamps_file(timestamps_file_path);
|
26 |
+
|
27 |
+
if(!timestamps_file.is_open())
|
28 |
+
throw std::runtime_error("unable to open the file " + timestamps_file_path);
|
29 |
+
|
30 |
+
cv::VideoCapture cap(video_path);
|
31 |
+
|
32 |
+
if ( !cap.isOpened() )
|
33 |
+
throw std::runtime_error("Cannot open the video file " + video_path);
|
34 |
+
|
35 |
+
std::string time_str;
|
36 |
+
double time;
|
37 |
+
|
38 |
+
std::vector<Event> events_vec;
|
39 |
+
|
40 |
+
cv::Mat img, log_img;
|
41 |
+
|
42 |
+
while (cap.read(img))
|
43 |
+
{
|
44 |
+
img.convertTo(img, CV_32F, 1.0/255);
|
45 |
+
cv::Mat log_img = img;
|
46 |
+
if (use_log_img_)
|
47 |
+
cv::log(img+log_eps_, log_img);
|
48 |
+
|
49 |
+
std::getline(timestamps_file, time_str);
|
50 |
+
time = std::stod(time_str);
|
51 |
+
|
52 |
+
imageCallback(log_img, time, events_vec);
|
53 |
+
}
|
54 |
+
|
55 |
+
// reset state to generate new events
|
56 |
+
is_initialized_ = false;
|
57 |
+
|
58 |
+
return vec_to_eigen_matrix(events_vec);
|
59 |
+
}
|
60 |
+
|
61 |
+
Eigen::MatrixXd EventSimulator::generateFromStampedImageSequence(std::vector<std::string> image_paths, std::vector<double> timestamps)
|
62 |
+
{
|
63 |
+
// check that timestamps are ascending
|
64 |
+
if (image_paths.size() != timestamps.size())
|
65 |
+
throw std::runtime_error("Number of image paths and number of timestamps should be equal. Got " + std::to_string(image_paths.size()) + " and " + std::to_string(timestamps.size()));
|
66 |
+
|
67 |
+
cv::Mat img, log_img;
|
68 |
+
double time;
|
69 |
+
|
70 |
+
std::vector<Event> events_vec;
|
71 |
+
|
72 |
+
for (int i=0; i<timestamps.size(); i++)
|
73 |
+
{
|
74 |
+
if ((i < timestamps.size()-1) && timestamps[i+1]<timestamps[i])
|
75 |
+
throw std::runtime_error("Timestamps must be sorted in ascending order.");
|
76 |
+
|
77 |
+
img = cv::imread(image_paths[i], cv::IMREAD_GRAYSCALE);
|
78 |
+
|
79 |
+
if(img.empty())
|
80 |
+
throw std::runtime_error("unable to open the image " + image_paths[i]);
|
81 |
+
|
82 |
+
img.convertTo(img, CV_32F, 1.0/255);
|
83 |
+
cv::Mat log_img = img;
|
84 |
+
if (use_log_img_)
|
85 |
+
cv::log(img+log_eps_, log_img);
|
86 |
+
|
87 |
+
time = timestamps[i];
|
88 |
+
|
89 |
+
imageCallback(log_img, time, events_vec);
|
90 |
+
}
|
91 |
+
|
92 |
+
// reset state to generate new events
|
93 |
+
is_initialized_ = false;
|
94 |
+
|
95 |
+
return vec_to_eigen_matrix(events_vec);
|
96 |
+
}
|
97 |
+
|
98 |
+
|
99 |
+
Eigen::MatrixXd EventSimulator::generateFromFolder(std::string image_folder, std::string timestamps_file_path)
|
100 |
+
{
|
101 |
+
std::vector<std::string> image_files;
|
102 |
+
read_directory_from_path(image_folder, image_files);
|
103 |
+
std::ifstream timestamps_file(timestamps_file_path);
|
104 |
+
|
105 |
+
if(!timestamps_file.is_open())
|
106 |
+
throw std::runtime_error("unable to open the file " + timestamps_file_path);
|
107 |
+
|
108 |
+
std::string time_str;
|
109 |
+
double time;
|
110 |
+
|
111 |
+
std::vector<Event> events_vec;
|
112 |
+
|
113 |
+
cv::Mat img, log_img;
|
114 |
+
|
115 |
+
for (const std::string& file : image_files)
|
116 |
+
{
|
117 |
+
img = cv::imread(file, cv::IMREAD_GRAYSCALE);
|
118 |
+
if(img.empty())
|
119 |
+
throw std::runtime_error("unable to open the image " + file);
|
120 |
+
|
121 |
+
img.convertTo(img, CV_32F, 1.0/255);
|
122 |
+
cv::Mat log_img = img;
|
123 |
+
if (use_log_img_)
|
124 |
+
cv::log(img+log_eps_, log_img);
|
125 |
+
|
126 |
+
std::getline(timestamps_file, time_str);
|
127 |
+
time = std::stod(time_str);
|
128 |
+
|
129 |
+
imageCallback(log_img, time, events_vec);
|
130 |
+
}
|
131 |
+
|
132 |
+
// reset state to generate new events
|
133 |
+
is_initialized_ = false;
|
134 |
+
|
135 |
+
return vec_to_eigen_matrix(events_vec);
|
136 |
+
}
|
137 |
+
|
138 |
+
|
139 |
+
void EventSimulator::initialise(py::array_t<float> input_array, double time) {
|
140 |
+
cv::Mat img = pyArrayToCvMat(input_array);
|
141 |
+
|
142 |
+
init(img, time);
|
143 |
+
}
|
144 |
+
|
145 |
+
|
146 |
+
Eigen::MatrixXd EventSimulator::generateEventFromCVImage(py::array_t<float> input_array, double time) {
|
147 |
+
cv::Mat log_img = pyArrayToCvMat(input_array);
|
148 |
+
|
149 |
+
std::vector<Event> events_vec;
|
150 |
+
|
151 |
+
imageCallback(log_img, time, events_vec);
|
152 |
+
|
153 |
+
return vec_to_eigen_matrix(events_vec);
|
154 |
+
}
|
155 |
+
|
156 |
+
|
157 |
+
void EventSimulator::init(const cv::Mat &img, double time)
|
158 |
+
{
|
159 |
+
is_initialized_ = true;
|
160 |
+
last_img_ = img;
|
161 |
+
ref_values_ = img;
|
162 |
+
|
163 |
+
last_event_timestamp_ = cv::Mat::zeros(img.size[0], img.size[1], CV_64F);
|
164 |
+
|
165 |
+
current_time_ = time;
|
166 |
+
image_width_ = img.size[1];
|
167 |
+
image_height_ = img.size[0];
|
168 |
+
}
|
169 |
+
|
170 |
+
|
171 |
+
void EventSimulator::imageCallback(const cv::Mat& img, double time, std::vector<Event>& events)
|
172 |
+
{
|
173 |
+
cv::Mat preprocessed_img = img;
|
174 |
+
|
175 |
+
if(!is_initialized_)
|
176 |
+
{
|
177 |
+
init(preprocessed_img, time);
|
178 |
+
return;
|
179 |
+
}
|
180 |
+
|
181 |
+
std::vector<Event> new_events;
|
182 |
+
|
183 |
+
static constexpr double kTolerance = 1e-6;
|
184 |
+
double delta_t = time - current_time_;
|
185 |
+
|
186 |
+
for (int y = 0; y < image_height_; ++y)
|
187 |
+
{
|
188 |
+
for (int x = 0; x < image_width_; ++x)
|
189 |
+
{
|
190 |
+
float& itdt = preprocessed_img.at<float>(y, x);
|
191 |
+
float& it = last_img_.at<float>(y, x);
|
192 |
+
float& prev_cross = ref_values_.at<float>(y, x);
|
193 |
+
|
194 |
+
if (std::fabs (it - itdt) > kTolerance)
|
195 |
+
{
|
196 |
+
float pol = (itdt >= it) ? +1.0 : -1.0;
|
197 |
+
float C = (pol > 0) ? contrast_threshold_pos_ : contrast_threshold_neg_;
|
198 |
+
|
199 |
+
float curr_cross = prev_cross;
|
200 |
+
bool all_crossings = false;
|
201 |
+
|
202 |
+
do
|
203 |
+
{
|
204 |
+
curr_cross += pol * C;
|
205 |
+
|
206 |
+
if ((pol > 0 && curr_cross > it && curr_cross <= itdt)
|
207 |
+
|| (pol < 0 && curr_cross < it && curr_cross >= itdt))
|
208 |
+
{
|
209 |
+
const double edt = (curr_cross - it) * delta_t / (itdt - it);
|
210 |
+
const double t = current_time_ + edt;
|
211 |
+
|
212 |
+
const double last_stamp_at_xy = last_event_timestamp_.at<double>(y,x);
|
213 |
+
|
214 |
+
const double dt = t - last_stamp_at_xy;
|
215 |
+
|
216 |
+
if(last_stamp_at_xy == 0 || dt >= refractory_period_)
|
217 |
+
{
|
218 |
+
new_events.emplace_back(x, y,t,pol);
|
219 |
+
last_event_timestamp_.at<double>(y,x) = t;
|
220 |
+
}
|
221 |
+
|
222 |
+
ref_values_.at<float>(y,x) = curr_cross;
|
223 |
+
}
|
224 |
+
else
|
225 |
+
{
|
226 |
+
all_crossings = true;
|
227 |
+
}
|
228 |
+
} while (!all_crossings);
|
229 |
+
} // end tolerance
|
230 |
+
} // end for each pixel
|
231 |
+
}
|
232 |
+
|
233 |
+
current_time_ = time;
|
234 |
+
last_img_ = preprocessed_img; // it is now the latest image
|
235 |
+
|
236 |
+
// need to sort the new events before inserting
|
237 |
+
std::sort(new_events.begin(), new_events.end());
|
238 |
+
events.insert(events.end(), new_events.begin(), new_events.end());
|
239 |
+
}
|