chris10 commited on
Commit
e6c4101
1 Parent(s): 750fc00
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='daniel.gehrig18@gmail.com',
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
+ }