dv-toolkit 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Kuga Maxx
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,18 @@
1
+ Metadata-Version: 2.4
2
+ Name: dv_toolkit
3
+ Version: 0.2.0
4
+ Summary: a generic and simple toolkit for processing event-based data
5
+ Author: Kuga Maxx
6
+ Author-email: KugaMaxx@outlook.com
7
+ Requires-Python: >=3.8
8
+ License-File: LICENSE
9
+ Requires-Dist: numpy>=1.24
10
+ Requires-Dist: dv_processing>=2.0
11
+ Requires-Dist: plotly>=5.17
12
+ Requires-Dist: matplotlib>=3.7
13
+ Dynamic: author
14
+ Dynamic: author-email
15
+ Dynamic: license-file
16
+ Dynamic: requires-dist
17
+ Dynamic: requires-python
18
+ Dynamic: summary
@@ -0,0 +1,268 @@
1
+ # DV Toolkit
2
+
3
+ ![](https://img.shields.io/github/v/tag/KugaMaxx/yam-toolkit?style=flat-square)
4
+ ![](https://img.shields.io/github/license/KugaMaxx/yam-toolkit?style=flat-square)
5
+
6
+ DV Toolkit is an extension of [dv-processing](https://dv-processing.inivation.com/),
7
+ providing a series of wrapped modules to help users to do processing and
8
+ analysis on event camera data. Any questions, please contact me with
9
+ [KugaMaxx@outlook.com](mailto:KugaMaxx@outlook.com).
10
+
11
+ <div align=center><img src="https://github.com/KugaMaxx/yam-toolkit/blob/main/assets/images/demonstrate.gif" alt="demonstrate" width="100%"></div>
12
+
13
+ ## Background
14
+
15
+ ### Why create this extension?
16
+
17
+ The dv-processing library provides convenience for developers to handle event-based data. However, there is still room for improvement in **data reading, slicing, and visualization** during actual usage. Therefore, this repository has made the following improvements:
18
+
19
+ + Enables fast interoperation between C++ and Python.
20
+ + Adds data alignment and slicing for events, frames, imus, and triggers.
21
+ + Adds offline data reading and processing, eliminating the need for online operations.
22
+ + Implements a more convenient visual interactive interface in Python.
23
+ + Provides a unified usage method for C++ and Python interfaces.
24
+
25
+ ## Installation
26
+
27
+ ### Preliminaries
28
+
29
+ Since our library is an extension of dv-processing, you need to install dv-processing first. The official installation tutorial is available [here](https://dv-processing.inivation.com/rel_1.7/installation.html), or you can follow the steps below to install it on Ubuntu 20.04:
30
+
31
+ + Install necessary dependencies that dv-processing required:
32
+
33
+ ```bash
34
+ sudo apt-get install libboost-all-dev libeigen3-dev libopencv-dev
35
+ sudo apt-get install pybind11-dev python3-dev python3-numpy
36
+ ```
37
+
38
+ + Add repository and install dv-processing:
39
+
40
+ ```bash
41
+ sudo add-apt-repository ppa:inivation-ppa/inivation
42
+ sudo apt-get update
43
+ sudo apt-get install libcaer-dev libfmt-dev liblz4-dev libzstd-dev libssl-dev libusb-1.0-0-dev
44
+ sudo apt-get install dv-processing
45
+ ```
46
+
47
+ ### Git submodule usage
48
+
49
+ Assuming you are working on a project in Git, you can use this repository as a
50
+ submodule. Here is an example of placing this repository as a dependency in the
51
+ `/external` folder.
52
+
53
+ + Add the repository as a submodule in your project:
54
+
55
+ ```bash
56
+ git submodule add git@github.com:KugaMaxx/yam-toolkit.git external/dv-toolkit
57
+ ```
58
+
59
+ + Use in your cmake project:
60
+
61
+ ```CMake
62
+ # Find dv-processing supports
63
+ find_package(dv-processing REQUIRED)
64
+
65
+ # Install toolkit supports.
66
+ add_subdirectory(external/dv-toolkit)
67
+
68
+ # link your targets against the library
69
+ target_link_libraries(your_target
70
+ dv::processing
71
+ dv::toolkit
72
+ ...)
73
+ ```
74
+
75
+ ### Python package usage
76
+
77
+ This repository also allows for Python integration, which can be used by binding
78
+ it as a Python package before usage. Here is an example in a conda environment
79
+ named `toolkit`. Please refer to the [README](https://github.com/KugaMaxx/yam-toolkit/blob/main/python/README.md) to get more information.
80
+
81
+ + Create conda environment and install:
82
+
83
+ ```bash
84
+ # recommend python ≥ 3.8
85
+ conda create -n toolkit
86
+
87
+ # activate environment
88
+ conda activate toolkit
89
+
90
+ # include pybind11 as submodule
91
+ git submodule update --init
92
+
93
+ # install as package
94
+ pip install .
95
+ ```
96
+
97
+ + Import the library in your script:
98
+
99
+ ```python
100
+ # must have
101
+ import dv_processing as dv
102
+
103
+ # introduce extension
104
+ import dv_toolkit as kit
105
+ ```
106
+
107
+ ## Getting started
108
+
109
+ ### Standard Type
110
+
111
+ The dv-toolkit library encapsulates `event`, `frame`, `imu`, and `trigger` into
112
+ addressable storage types, which supports add, erase and slice operations.
113
+
114
+ + `dv::toolkit::MonoCameraData` stores all basic storage types of event-based data.
115
+
116
+ ```C++
117
+ #include <dv-toolkit/core/core.hpp>
118
+
119
+ int main() {
120
+ namespace kit = dv::toolkit;
121
+
122
+ // Initialize MonoCameraData
123
+ kit::MonoCameraData data;
124
+
125
+ // Access immutable variables through functions, support types are:
126
+ // events, frames, imus and triggers
127
+ std::cout << data.events() << std::endl;
128
+ // "Storage is empty!"
129
+
130
+ // Emplace back event elements, the function arguments are:
131
+ // timestamp, x, y, polarity
132
+ kit::EventStorage store;
133
+ store.emplace_back(dv::now(), 0, 0, false);
134
+ store.emplace_back(dv::now(), 1, 1, true);
135
+ store.emplace_back(dv::now(), 2, 2, false);
136
+ store.emplace_back(dv::now(), 3, 3, true);
137
+
138
+ // Assignment value to MonoCameraData
139
+ data["events"] = store;
140
+
141
+ // Access mutable variables through std::get
142
+ std::cout << std::get<kit::EVTS>(data["events"]) << std::endl;
143
+ // "Storage containing 4 elements within ..."
144
+
145
+ return 0;
146
+ }
147
+ ```
148
+
149
+ ### I/O Operations
150
+
151
+ The dv-toolkit library provide convenient method to read and write standard
152
+ aedat4 files offline. It facilitates the repeated operation and processing on
153
+ data, avoiding the issue of reading and writing files over and over again.
154
+
155
+ + `dv::toolkit::MonoCameraReader` reads offline data from standard aedat4
156
+ files.
157
+
158
+ ```C++
159
+ #include <dv-toolkit/io/reader.hpp>
160
+
161
+ int main() {
162
+ namespace kit = dv::toolkit;
163
+
164
+ // Initialize reader
165
+ kit::io::MonoCameraReader reader("/path/to/aedat4");
166
+
167
+ // Get offline MonoCameraData
168
+ kit::MonoCameraData data = reader.loadData();
169
+
170
+ // Check all basic types
171
+ std::cout << data.events() << std::endl;
172
+ std::cout << data.frames() << std::endl;
173
+ std::cout << data.imus() << std::endl;
174
+ std::cout << data.triggers() << std::endl;
175
+
176
+ // Get camera resolution
177
+ // Can also use reader.getEventResolution()
178
+ const auto resolution = reader.getResolution("events");
179
+ if (resolution.has_value()) {
180
+ std::cout << *resolution << std::endl;
181
+ }
182
+
183
+ return 0;
184
+ }
185
+ ```
186
+
187
+ + `dv::toolkit::MonoCameraWrite` write data back to standard aedat4 files.
188
+
189
+ ```C++
190
+ #include <dv-toolkit/core/core.hpp>
191
+ #include <dv-toolkit/io/reader.hpp>
192
+ #include <dv-toolkit/io/writer.hpp>
193
+ #include <dv-toolkit/simulation/generator.hpp>
194
+
195
+ int main() {
196
+ namespace kit = dv::toolkit;
197
+
198
+ // Enable literal time expression from the chrono library
199
+ using namespace std::chrono_literals;
200
+
201
+ // Initialize MonoCameraData
202
+ kit::MonoCameraData data;
203
+
204
+ // Initialize resolution
205
+ const auto resolution = cv::Size(346, 260);
206
+
207
+ // Create sample events
208
+ data["events"] = kit::simulation::generateSampleEvents(resolution);
209
+
210
+ // Initialize MonoCameraWriter
211
+ kit::io::MonoCameraWriter writer("/path/to/file.aedat4", resolution);
212
+
213
+ // Write MonoCameraData
214
+ writer.writeData(data);
215
+
216
+ return 0;
217
+ }
218
+ ```
219
+
220
+ ### Unified stream slicing
221
+
222
+ Thanks to the redefined standard data structure, it is possible to achieve
223
+ unified slicing of various types of data by using dv-toolkit library.
224
+
225
+ + `dv::toolkit::MonoCameraSlicer` allows to slice by time or by number. Before registering each slicer, please ensure that the reference type of the slicer
226
+ is specified.
227
+
228
+ ```C++
229
+ #include <dv-toolkit/core/slicer.hpp>
230
+ #include <dv-toolkit/io/reader.hpp>
231
+
232
+ int main() {
233
+ namespace kit = dv::toolkit;
234
+
235
+ // Initialize reader
236
+ kit::io::MonoCameraReader reader("/path/to/aedat4");
237
+
238
+ // Get offline MonoCameraData
239
+ kit::MonoCameraData data = reader.loadData();
240
+
241
+ // Initialize slicer, it will have no jobs at this time
242
+ kit::MonoCameraSlicer slicer;
243
+
244
+ // Use this namespace to enable literal time expression from the chrono library
245
+ using namespace std::chrono_literals;
246
+
247
+ // Register this method to be called every 33 millisecond of events
248
+ slicer.doEveryTimeInterval("events", 33ms, [](const kit::MonoCameraData &mono) {
249
+ std::cout << mono.events() << std::endl;
250
+ });
251
+
252
+ // Register this method to be called every 2 elements of frames
253
+ slicer.doEveryNumberOfElements("frames", 2, [](const kit::MonoCameraData &mono) {
254
+ std::cout << mono.events() << std::endl;
255
+ });
256
+
257
+ // Now push the store into the slicer, the data contents within the store
258
+ // can be arbitrary, the slicer implementation takes care of correct slicing
259
+ // algorithm and calls the previously registered callbacks accordingly.
260
+ slicer.accept(data);
261
+
262
+ return 0;
263
+ }
264
+ ```
265
+
266
+ ## Acknowledgement
267
+
268
+ Special thanks to [Jinze Chen](mailto:chjz@mail.ustc.edu.cn).
@@ -0,0 +1,3 @@
1
+ [build-system]
2
+ requires = ["setuptools>=42", "wheel", "numpy>=1.24"]
3
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,2 @@
1
+ from .lib._lib_toolkit import *
2
+ from . import plot
@@ -0,0 +1,4 @@
1
+ from .player import OfflineMonoCameraPlayer
2
+
3
+
4
+ __all__ = ['OfflineMonoCameraPlayer']
@@ -0,0 +1,80 @@
1
+ from typing import Tuple
2
+ from datetime import timedelta
3
+
4
+ from ..lib import _lib_toolkit as kit
5
+
6
+ from .tools import mp_player
7
+ from .tools import go_player
8
+
9
+
10
+ class OfflineMonoCameraPlayer(object):
11
+ def __init__(self,
12
+ resolution: Tuple[int, int],
13
+ mode: str = "hybrid",
14
+ core: str = "matplotlib"):
15
+ if (len(resolution) == 2):
16
+ self.resolution = resolution
17
+ else:
18
+ raise ValueError("Resolution must be a tuple of length 2.")
19
+
20
+ if core == "matplotlib":
21
+ self.core = mp_player
22
+ elif core == "plotly":
23
+ self.core = go_player
24
+ else:
25
+ raise ValueError("Invalid player core. use 'matplotlib' or 'plotly'")
26
+
27
+ if mode in ["hybrid", "2d", "3d"]:
28
+ self.mode = mode
29
+ else:
30
+ raise ValueError("Invalid view mode. use '2d', '3d' or 'hybrid'")
31
+
32
+ def viewPerTimeInterval(self, data,
33
+ reference: str = "events",
34
+ interval: timedelta = timedelta(milliseconds=33)):
35
+ # slice data
36
+ slicer, packets = kit.MonoCameraSlicer(), list()
37
+ slicer.doEveryTimeInterval(
38
+ reference, interval, lambda data: packets.append(data)
39
+ )
40
+ slicer.accept(data)
41
+
42
+ # run
43
+ self._run(
44
+ self.core.Figure(),
45
+ self.core.Preset(self.resolution, packets)
46
+ )
47
+
48
+ def viewPerNumberInterval(self, data,
49
+ reference: str = "events",
50
+ interval: int = 15000):
51
+ # slice data
52
+ slicer, packets = kit.MonoCameraSlicer(), list()
53
+ slicer.doEveryNumberOfElements(
54
+ reference, interval, lambda data: packets.append(data)
55
+ )
56
+ slicer.accept(data)
57
+
58
+ # run
59
+ self._run(
60
+ self.core.Figure(),
61
+ self.core.Preset(self.resolution, packets)
62
+ )
63
+
64
+ def _run(self, figure, preset):
65
+ figure.set_ticks(preset.get_ticks())
66
+ if self.mode == "2d":
67
+ figure.set_subplot(rows=1, cols=1, specs=[[{"type": "2d"}]])
68
+ figure.append_trace(row=1, col=1, plot_func=preset.plot_2d_frame, set_func=preset.set_2d_plot)
69
+ figure.append_trace(row=1, col=1, plot_func=preset.plot_2d_event, set_func=preset.set_2d_plot)
70
+ elif self.mode == "3d":
71
+ figure.set_subplot(rows=1, cols=1, specs=[[{"type": "3d"}]])
72
+ figure.append_trace(row=1, col=1, plot_func=preset.plot_3d_frame, set_func=preset.set_3d_plot)
73
+ figure.append_trace(row=1, col=1, plot_func=preset.plot_3d_event, set_func=preset.set_3d_plot)
74
+ else:
75
+ figure.set_subplot(rows=1, cols=2, specs=[[{"type": "2d"}, {"type": "3d"}]])
76
+ figure.append_trace(row=1, col=1, plot_func=preset.plot_2d_frame, set_func=preset.set_2d_plot)
77
+ figure.append_trace(row=1, col=1, plot_func=preset.plot_2d_event, set_func=preset.set_2d_plot)
78
+ figure.append_trace(row=1, col=2, plot_func=preset.plot_3d_frame, set_func=preset.set_3d_plot)
79
+ figure.append_trace(row=1, col=2, plot_func=preset.plot_3d_event, set_func=preset.set_3d_plot)
80
+ figure.show()
@@ -0,0 +1,5 @@
1
+ from . import go_player
2
+ from . import mp_player
3
+
4
+
5
+ __all__ = ['go_player', 'mp_player']
@@ -0,0 +1,56 @@
1
+ import numpy as np
2
+ from abc import ABC, abstractmethod
3
+
4
+
5
+ def _unravel_index(index, shape):
6
+ row, col = np.unravel_index(index, shape)
7
+ return row, col
8
+
9
+
10
+ def _ravel_multi_index(row, col, shape):
11
+ ind = np.ravel_multi_index(np.array([row, col]), shape)
12
+ return ind
13
+
14
+
15
+ def _reformat_layout(list_of_dict, mapping: dict):
16
+ for _list in list_of_dict:
17
+ for _dict in _list:
18
+ _dict['type'] = mapping[_dict['type']]
19
+
20
+ return list_of_dict
21
+
22
+
23
+ def _visualize_events(events, size, mode="accumulate"):
24
+ counts = np.zeros(size)
25
+ _bins = [size[0], size[1]]
26
+ _range = [[0, size[0]], [0, size[1]]]
27
+
28
+ # w/ polar
29
+ if mode == 'polar':
30
+ counts = np.histogram2d(events.ys(), events.xs(),
31
+ weights=(-1) ** (1 + events.polarities()),
32
+ bins=_bins, range=_range)[0]
33
+ return counts
34
+
35
+ # w/o polar
36
+ elif mode == 'monopolar':
37
+ counts = np.histogram2d(events.ys(), events.xs(),
38
+ weights=(+1) ** (1 + events.polarities()),
39
+ bins=_bins, range=_range)[0]
40
+ return counts
41
+
42
+ # count before polar assignment
43
+ elif mode == 'accumulate':
44
+ counts = np.histogram2d(events.ys(), events.xs(),
45
+ weights=(+1) ** (1 + events.polarities()),
46
+ bins=_bins, range=_range)[0]
47
+ weight = np.zeros(size)
48
+ weight[events.ys(), events.xs()] = (-1) ** (1 + events.polarities())
49
+ return counts * weight
50
+
51
+ return counts
52
+
53
+ def _convert_to_rgba(image):
54
+ if len(image.shape) == 2:
55
+ image = np.stack((image,) * 3, axis=-1)
56
+ return np.concatenate((image, 255 * np.ones((*image.shape[:2], 1))), axis=2)
@@ -0,0 +1,247 @@
1
+ import numpy as np
2
+ import dv_processing as dv
3
+ from datetime import datetime
4
+ import plotly.graph_objects as go
5
+
6
+ from .func import _reformat_layout, _visualize_events
7
+
8
+
9
+ class Animator(object):
10
+ def __init__(self, canvas, update, ticks, fps=25):
11
+ self._canvas = canvas
12
+ self._frames = [update(i) for i in range(len(ticks))]
13
+ self._ticks = ticks
14
+ self._interval = int(1E3 / fps)
15
+ self._init_widgets()
16
+
17
+ def _init_widgets(self):
18
+ self._buttons = [{
19
+ "type": "buttons",
20
+ "buttons": [
21
+ {
22
+ "method": "animate", "label": "&#9654;",
23
+ "args": [None, {"frame": {"duration": self._interval},
24
+ "mode": "immediate", "fromcurrent": True}]
25
+ },
26
+ {
27
+ "method": "animate", "label": "&#9724;",
28
+ "args": [[None], {"frame": {"duration": self._interval},
29
+ "mode": "immediate", "fromcurrent": True}]
30
+ }
31
+ ],
32
+ "x": 0.1, "y": 0.0, "direction": "left", "pad": {"r": 10, "t": 70},
33
+ }]
34
+
35
+ self._sliders = [{
36
+ "steps": [
37
+ {
38
+ "method": "animate", "label": self._ticks[i],
39
+ "args": [[self._ticks[i]], {"frame": {"duration": self._interval},
40
+ "mode": "immediate", "fromcurrent": True}]
41
+ } for i, val in enumerate(self._frames)
42
+ ],
43
+ "x": 0.1, "y": 0.0, "len": 0.9, "pad": {"b": 10, "t": 50},
44
+ }]
45
+ self._canvas.layout.update(
46
+ sliders=self._sliders,
47
+ updatemenus=self._buttons,
48
+ template="plotly_white",
49
+ )
50
+
51
+ def run(self):
52
+ self._canvas.update(frames=self._frames)
53
+ self._canvas.show()
54
+
55
+
56
+ class Figure(object):
57
+ def __init__(self):
58
+ self._figure = go.Figure()
59
+ self._traces = {
60
+ "inds": list(),
61
+ "rows": list(),
62
+ "cols": list(),
63
+ "plot_func": list(),
64
+ "set_func": list(),
65
+ "kwargs": list(),
66
+ }
67
+
68
+ self._index = 0
69
+ self._nrows = 0
70
+ self._ncols = 0
71
+ self._shape = (0, 0)
72
+ self._layouts = None
73
+ self._ticks = range(1)
74
+
75
+ def set_ticks(self, ticks):
76
+ self._ticks = ticks
77
+
78
+ def set_subplot(self, rows, cols, specs):
79
+ self._nrows = rows
80
+ self._ncols = cols
81
+ self._shape = (rows, cols)
82
+ self._layouts = _reformat_layout(specs, {'2d': 'xy', '3d': 'scene'})
83
+
84
+ def append_trace(self, row, col, plot_func, set_func, **kwargs):
85
+ self._index = self._index + 1
86
+ self._traces["rows"].append(row)
87
+ self._traces["cols"].append(col)
88
+ self._traces["plot_func"].append(plot_func)
89
+ self._traces["set_func"].append(set_func)
90
+ self._traces["kwargs"].append(kwargs)
91
+ self._traces["inds"].append(self._index - 1)
92
+
93
+ def show(self):
94
+ self._traces["data"] = list()
95
+ for plot_func, kwargs in zip(self._traces["plot_func"], self._traces["kwargs"]):
96
+ self._traces["data"].append(plot_func(**kwargs))
97
+
98
+ self._figure.set_subplots(rows=self._nrows, cols=self._ncols, specs=self._layouts)
99
+ self._figure.add_traces(data=self._traces["data"],
100
+ rows=self._traces["rows"],
101
+ cols=self._traces["cols"])
102
+
103
+ for set_func, kwargs in zip(self._traces["set_func"], self._traces["kwargs"]):
104
+ self._figure.layout.update(set_func(**kwargs))
105
+
106
+ Animator(canvas=self._figure, update=self._update, ticks=self._ticks).run()
107
+
108
+ def _update(self, k):
109
+ data = list()
110
+ for obj, plot_func, kwargs in zip(self._traces["data"], self._traces["plot_func"], self._traces["kwargs"]):
111
+ data.append(plot_func(obj, k, **kwargs))
112
+ self._traces["data"] = data
113
+
114
+ return go.Frame(data=self._traces["data"],
115
+ traces=self._traces["inds"], name=self._ticks[k])
116
+
117
+
118
+ class Preset(object):
119
+ def __init__(self, size, packets):
120
+ """
121
+ Create a Figure() class with default settings
122
+ """
123
+ self._size = size
124
+ self._packets = packets
125
+ self._ticks = range(len(packets))
126
+
127
+ self._fr_cmap = 'gray'
128
+ self._ev_cmap = [[0.0, "rgba(223,73,63,1)"],
129
+ [0.5, "rgba(0,0,0,0)"],
130
+ [1.0, "rgba(46,102,153,1)"]]
131
+
132
+ # ---------------- 2d settings ----------------
133
+ def set_2d_plot(self, **kwargs):
134
+ return {
135
+ "xaxis": dict(range=[0, self._size[0]]),
136
+ "yaxis": dict(range=[0, self._size[1]],
137
+ scaleanchor="x",
138
+ scaleratio=1.),
139
+ }
140
+
141
+ def plot_2d_event(self, obj=None, i=None, **kwargs):
142
+ if i is None:
143
+ obj = go.Heatmap(
144
+ z=[], zmin=-1, zmax=1,
145
+ colorscale=self._ev_cmap,
146
+ showscale=False,
147
+ showlegend=False,
148
+ )
149
+ return obj
150
+
151
+ events = self._packets[i].events()
152
+ if not events.isEmpty():
153
+ image = _visualize_events(events, self._size[::-1])
154
+ obj = go.Heatmap(
155
+ z=np.flip(image, axis=0),
156
+ )
157
+ else:
158
+ obj = go.Heatmap(
159
+ z=np.zeros(self._size[::-1]),
160
+ )
161
+
162
+ return obj
163
+
164
+ def plot_2d_frame(self, obj=None, i=None, **kwargs):
165
+ if i is None:
166
+ obj = go.Heatmap(
167
+ z=[], zmin=0, zmax=255,
168
+ colorscale=self._fr_cmap,
169
+ showscale=False,
170
+ showlegend=False,
171
+ )
172
+ return obj
173
+
174
+ frames = self._packets[i].frames()
175
+ if not frames.isEmpty():
176
+ image = frames.front().image
177
+ obj = go.Heatmap(
178
+ z=np.flip(image, axis=0)
179
+ )
180
+ self._tmp_frame = frames.front().image
181
+
182
+ return obj
183
+
184
+ # ---------------- 3d settings ----------------
185
+ def set_3d_plot(self, **kwargs):
186
+ return {
187
+ "scene": dict(yaxis=dict(range=[0, self._size[0]]),
188
+ zaxis=dict(range=[self._size[1], 0]),
189
+ aspectratio=dict(x=1., y=self._size[0]/500, z=self._size[1]/500)),
190
+ }
191
+
192
+ def plot_3d_event(self, obj=None, i=None, **kwargs):
193
+ if i is None:
194
+ obj = go.Scatter3d(
195
+ x=[], y=[], z=[],
196
+ mode='markers',
197
+ marker=dict(
198
+ size=2,
199
+ cmin=0,
200
+ cmax=1,
201
+ colorscale=self._ev_cmap,
202
+ ),
203
+ showlegend=False,
204
+ )
205
+ return obj
206
+
207
+ events = self._packets[i].events()
208
+ if not events.isEmpty():
209
+ obj = go.Scatter3d(
210
+ x=[f"{datetime.fromtimestamp(ts * 1E-6).strftime('%H:%M:%S.%f')}"
211
+ for ts in events.timestamps()],
212
+ y=events.xs(), z=events.ys(),
213
+ marker=dict(color=events.polarities()),
214
+ )
215
+ else:
216
+ obj = go.Scatter3d(
217
+ x=[], y=[], z=[]
218
+ )
219
+ return obj
220
+
221
+ def plot_3d_frame(self, obj=None, i=None, **kwargs):
222
+ if i is None:
223
+ obj = go.Surface(
224
+ x=[], y=[], z=[],
225
+ surfacecolor=np.full((*self._size, 1), np.nan),
226
+ colorscale=self._fr_cmap,
227
+ cmin=0,
228
+ cmax=255,
229
+ showscale=False,
230
+ showlegend=False,
231
+ )
232
+ return obj
233
+
234
+ frames = self._packets[i].frames()
235
+ if not frames.isEmpty():
236
+ for frame in frames:
237
+ xx, yy = np.mgrid[0:self._size[0], 0:self._size[1]]
238
+ obj = go.Surface(
239
+ x=np.full(self._size, datetime.fromtimestamp(frame.timestamp * 1E-6).strftime('%H:%M:%S.%f')),
240
+ y=xx, z=yy,
241
+ surfacecolor=np.fliplr(np.rot90(frame.image, -1))
242
+ )
243
+
244
+ return obj
245
+
246
+ def get_ticks(self):
247
+ return self._ticks
@@ -0,0 +1,281 @@
1
+ import itertools
2
+ import numpy as np
3
+ import dv_processing as dv
4
+ from datetime import datetime
5
+
6
+ import matplotlib.pyplot as plt
7
+ import matplotlib.dates as mdates
8
+ import matplotlib.colors as mcolors
9
+ from matplotlib.animation import FuncAnimation
10
+ from matplotlib.widgets import Button, Slider, TextBox
11
+ from mpl_toolkits.axes_grid1 import make_axes_locatable
12
+
13
+ from .func import _reformat_layout, _ravel_multi_index, _visualize_events, _convert_to_rgba
14
+
15
+
16
+ class Animator(FuncAnimation):
17
+ """
18
+ Animated interactive plot using matplotlib
19
+ https://stackoverflow.com/questions/46325447/animated-interactive-plot-using-matplotlib
20
+ """
21
+
22
+ def __init__(self, canvas, update, ticks, fps=25) -> None:
23
+ self._canvas = canvas
24
+ self._frames = update
25
+ self._ticks = ticks
26
+ self._len = len(ticks) - 1
27
+ self._pos = -1
28
+ self._min = 0
29
+ self._max = self._len if self._len > 0 else 1
30
+ self._run = True
31
+ self._forward = True
32
+ self._interval = int(1E3 / fps)
33
+ self._init_widgets()
34
+ super().__init__(self._canvas, self._frames, frames=self._play(), interval=self._interval, save_count=self._len)
35
+
36
+ def _init_widgets(self):
37
+ # add another axes
38
+ playerax = self._canvas.add_axes([0.13, 0.05, 0.74, 0.04])
39
+ divider = make_axes_locatable(playerax)
40
+
41
+ # create button on axes
42
+ bax = divider.append_axes("right", size="100%", pad="15%")
43
+ sax = divider.append_axes("right", size="100%", pad="15%")
44
+ fax = divider.append_axes("right", size="100%", pad="15%")
45
+ ofax = divider.append_axes("right", size="100%", pad="15%")
46
+ sliderax = divider.append_axes("right", size="700%", pad="30%")
47
+ textax = divider.append_axes("right", size="200%", pad="30%")
48
+ self.button_oneback = Button(playerax, label='$\u29CF$')
49
+ self.button_backward = Button(bax, label='$\u25C0$')
50
+ self.button_pauseplay = Button(sax, label='$\u25A0$')
51
+ self.button_forward = Button(fax, label='$\u25B6$')
52
+ self.button_onenext = Button(ofax, label='$\u29D0$')
53
+ self.slider = Slider(sliderax, '', self._min, self._max, valstep=1)
54
+ self.text = TextBox(textax, '', color='1.', textalignment='center')
55
+
56
+ # create button functions
57
+ self.button_oneback.on_clicked(self._play_back_frame)
58
+ self.button_backward.on_clicked(self._play_backwards)
59
+ self.button_pauseplay.on_clicked(self._set_stop)
60
+ self.button_forward.on_clicked(self._play_forwards)
61
+ self.button_onenext.on_clicked(self._play_next_frame)
62
+ self.slider.on_changed(self._set_position)
63
+ self.slider.valtext.set_visible(False)
64
+
65
+ def run(self, path=None, fps=30):
66
+ if path is None:
67
+ plt.show()
68
+ else:
69
+ self.save(path, writer='imagemagick', fps=fps)
70
+
71
+ # ---------------- call back function ----------------
72
+ def _play(self):
73
+ while self._run:
74
+ self._one_step()
75
+ yield self._pos
76
+
77
+ def _one_step(self):
78
+ if self._len == 0:
79
+ self._pos = 0
80
+ self.event_source.stop()
81
+ elif 0 < self._pos < self._len:
82
+ self._pos += self._forward - (not self._forward)
83
+ elif self._pos <= 0:
84
+ self._pos += self._forward
85
+ elif self._pos >= self._len and not self._forward:
86
+ self._pos -= not self._forward
87
+ self.slider.set_val(self._pos)
88
+ self.text.set_val(f"{self._ticks[self._pos]}")
89
+ self._frames(self._pos)
90
+ self._canvas.canvas.draw_idle()
91
+
92
+ def _play_back_frame(self, event=None):
93
+ self._run, self._forward = False, False
94
+ self.event_source.stop()
95
+ self._one_step()
96
+
97
+ def _play_backwards(self, event=None):
98
+ self._run, self._forward = True, False
99
+ self.event_source.start()
100
+
101
+ def _set_stop(self, event=None):
102
+ if not self._run:
103
+ self._run = True
104
+ self.event_source.start()
105
+ elif self._run:
106
+ self._run = False
107
+ self.event_source.stop()
108
+
109
+ def _play_forwards(self, event=None):
110
+ self._run, self._forward = True, True
111
+ self.event_source.start()
112
+
113
+ def _play_next_frame(self, event=None):
114
+ self._run, self._forward = False, True
115
+ self.event_source.stop()
116
+ self._one_step()
117
+
118
+ def _set_position(self, i):
119
+ self._pos = int(self.slider.val)
120
+ self.text.set_val(f"{self._ticks[self._pos]}")
121
+ self._frames(self._pos)
122
+
123
+
124
+ class Figure(object):
125
+ def __init__(self):
126
+ self._figure = plt.figure(figsize=(16, 9))
127
+ self._traces = {
128
+ "inds": list(),
129
+ "rows": list(),
130
+ "cols": list(),
131
+ "plot_func": list(),
132
+ "set_func": list(),
133
+ "kwargs": list(),
134
+ "AxesSubplots": list(),
135
+ "AxesImages": list(),
136
+ }
137
+
138
+ self._index = 0
139
+ self._nrows = 0
140
+ self._ncols = 0
141
+ self._shape = (0, 0)
142
+ self._layouts = None
143
+ self._ticks = range(1)
144
+
145
+ def set_ticks(self, ticks):
146
+ self._ticks = ticks
147
+
148
+ def set_subplot(self, rows, cols, specs):
149
+ self._nrows = rows
150
+ self._ncols = cols
151
+ self._shape = (rows, cols)
152
+ self._layouts = _reformat_layout(specs, {'2d': None, '3d': '3d'})
153
+
154
+ def append_trace(self, row, col, plot_func, set_func, **kwargs):
155
+ self._index = self._index + 1
156
+ self._traces["rows"].append(row)
157
+ self._traces["cols"].append(col)
158
+ self._traces["plot_func"].append(plot_func)
159
+ self._traces["set_func"].append(set_func)
160
+ self._traces["kwargs"].append(kwargs)
161
+ self._traces["inds"].append(_ravel_multi_index(row-1, col-1, self._shape))
162
+
163
+ def show(self):
164
+ ax_list = list()
165
+ for ind, _layout in enumerate(list(itertools.chain(*self._layouts))):
166
+ ax_list.append(self._figure.add_subplot(*self._shape, ind+1, projection=_layout["type"]))
167
+
168
+ for ind, plot_func, set_func, kwargs \
169
+ in zip(self._traces["inds"], self._traces["plot_func"], self._traces["set_func"], self._traces["kwargs"]):
170
+ AxesSubplot = set_func(ax_list[ind], **kwargs)
171
+ self._traces["AxesSubplots"].append(AxesSubplot)
172
+ self._traces["AxesImages"].append(plot_func(AxesSubplot, **kwargs))
173
+
174
+ Animator(canvas=self._figure, update=self._update, ticks=self._ticks).run()
175
+
176
+ def _update(self, k):
177
+ for i in range(self._index):
178
+ plot_func = self._traces["plot_func"][i]
179
+ AxesSubplot, AxesImage = self._traces["AxesSubplots"][i], self._traces["AxesImages"][i]
180
+ self._traces["AxesImages"][i] = plot_func(AxesSubplot, AxesImage, k, **self._traces["kwargs"][i])
181
+
182
+
183
+ class Preset(object):
184
+ def __init__(self, size, packets) -> None:
185
+ """
186
+ Create a Figure() class with default settings
187
+ """
188
+ self._size = size
189
+ self._packets = packets
190
+ self._ticks = range(len(packets))
191
+
192
+ self._fr_cmap = plt.get_cmap('gray')
193
+ self._ev_cmap = mcolors.LinearSegmentedColormap.from_list("evCmap", [(0.871, 0.286, 0.247, 1.),
194
+ (0.000, 0.000, 0.000, 0.),
195
+ (0.180, 0.400, 0.600, 1.)])
196
+
197
+ # ---------------- 2d settings ----------------
198
+ def set_2d_plot(self, ax, **kwargs):
199
+ ax.set_xlim(0, self._size[0])
200
+ ax.set_ylim(0, self._size[1])
201
+ ax.set_xticks([])
202
+ ax.set_yticks([])
203
+ return ax
204
+
205
+ def plot_2d_event(self, ax, obj=None, i=None, **kwargs):
206
+ if i is None:
207
+ obj = ax.imshow(
208
+ np.zeros(self._size[::-1]),
209
+ vmin=-1, vmax=1,
210
+ cmap=self._ev_cmap
211
+ )
212
+ return obj
213
+
214
+ events = self._packets[i].events()
215
+ if not events.isEmpty():
216
+ image = _visualize_events(events, self._size[::-1])
217
+ obj.set_data(np.flip(image, axis=0))
218
+ else:
219
+ obj.set_data(np.zeros(self._size[::-1]))
220
+
221
+ return obj
222
+
223
+ def plot_2d_frame(self, ax, obj=None, i=None, **kwargs):
224
+ if i is None:
225
+ obj = ax.imshow(
226
+ np.zeros(self._size[::-1]),
227
+ vmin=0, vmax=255,
228
+ cmap=self._fr_cmap
229
+ )
230
+ return obj
231
+
232
+ frames = self._packets[i].frames()
233
+ if not frames.isEmpty():
234
+ image = frames.front().image
235
+ obj.set_data(np.flip(image, axis=0))
236
+
237
+ return obj
238
+
239
+ # ---------------- 3d settings ----------------
240
+ def set_3d_plot(self, ax, **kwargs):
241
+ ax.set_ylim3d(0, self._size[0])
242
+ ax.set_zlim3d(self._size[1], 0)
243
+ ax.set_box_aspect((500, self._size[0], self._size[1]))
244
+ ax.view_init(elev=45, azim=30, roll=0)
245
+ return ax
246
+
247
+ def plot_3d_event(self, ax, obj=None, i=None, **kwargs):
248
+ if i is None:
249
+ obj = ax.scatter([], [], [], c=[], s=1.0, vmin=0, vmax=1, cmap=self._ev_cmap)
250
+ return obj
251
+
252
+ events = self._packets[i].events()
253
+ if not events.isEmpty():
254
+ obj._offsets3d = (events.timestamps(), events.xs(), events.ys())
255
+ obj.set_array(events.polarities())
256
+ ax.set_xlim3d(events.getLowestTime(), events.getHighestTime())
257
+ ax.set_xticks(np.linspace(events.getLowestTime(), events.getHighestTime(), 5))
258
+ ax.set_xticklabels([f"{datetime.fromtimestamp(ts * 1E-6).strftime('%H:%M:%S')}s"
259
+ for ts in ax.get_xticks().tolist()])
260
+ else:
261
+ obj._offsets3d = ([], [], [])
262
+
263
+ return obj
264
+
265
+ def plot_3d_frame(self, ax, obj=None, i=None, **kwargs):
266
+ xx, yy = np.mgrid[0:self._size[0], 0:self._size[1]]
267
+ if i is None:
268
+ obj = ax.plot_surface(np.nan, xx, yy, facecolors=np.full((*self._size, 1), np.nan))
269
+ return obj
270
+
271
+ frames = self._packets[i].frames()
272
+ if not frames.isEmpty():
273
+ obj.remove()
274
+ for frame in frames:
275
+ image = np.fliplr(np.rot90(_convert_to_rgba(frame.image) / 255.0, -1))
276
+ obj = ax.plot_surface(frame.timestamp, xx, yy, facecolors=image)
277
+
278
+ return obj
279
+
280
+ def get_ticks(self):
281
+ return self._ticks
@@ -0,0 +1,18 @@
1
+ Metadata-Version: 2.4
2
+ Name: dv_toolkit
3
+ Version: 0.2.0
4
+ Summary: a generic and simple toolkit for processing event-based data
5
+ Author: Kuga Maxx
6
+ Author-email: KugaMaxx@outlook.com
7
+ Requires-Python: >=3.8
8
+ License-File: LICENSE
9
+ Requires-Dist: numpy>=1.24
10
+ Requires-Dist: dv_processing>=2.0
11
+ Requires-Dist: plotly>=5.17
12
+ Requires-Dist: matplotlib>=3.7
13
+ Dynamic: author
14
+ Dynamic: author-email
15
+ Dynamic: license-file
16
+ Dynamic: requires-dist
17
+ Dynamic: requires-python
18
+ Dynamic: summary
@@ -0,0 +1,17 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ setup.py
5
+ python/src/dv_toolkit/__init__.py
6
+ python/src/dv_toolkit.egg-info/PKG-INFO
7
+ python/src/dv_toolkit.egg-info/SOURCES.txt
8
+ python/src/dv_toolkit.egg-info/dependency_links.txt
9
+ python/src/dv_toolkit.egg-info/not-zip-safe
10
+ python/src/dv_toolkit.egg-info/requires.txt
11
+ python/src/dv_toolkit.egg-info/top_level.txt
12
+ python/src/dv_toolkit/plot/__init__.py
13
+ python/src/dv_toolkit/plot/player.py
14
+ python/src/dv_toolkit/plot/tools/__init__.py
15
+ python/src/dv_toolkit/plot/tools/func.py
16
+ python/src/dv_toolkit/plot/tools/go_player.py
17
+ python/src/dv_toolkit/plot/tools/mp_player.py
@@ -0,0 +1,4 @@
1
+ numpy>=1.24
2
+ dv_processing>=2.0
3
+ plotly>=5.17
4
+ matplotlib>=3.7
@@ -0,0 +1,2 @@
1
+ _lib_toolkit
2
+ dv_toolkit
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,207 @@
1
+ import os
2
+ import re
3
+ import subprocess
4
+ import sys
5
+ import platform
6
+ from pathlib import Path
7
+
8
+ from setuptools import Extension, setup, find_packages
9
+ from setuptools.command.build_ext import build_ext
10
+
11
+ global extra_cmake_args
12
+ extra_cmake_args = [
13
+ "-DTOOLKIT_ENABLE_SAMPLES=OFF",
14
+ "-DTOOLKIT_ENABLE_PYTHON=ON"
15
+ ]
16
+
17
+ # Convert distutils Windows platform specifiers to CMake -A arguments
18
+ PLAT_TO_CMAKE = {
19
+ "win32": "Win32",
20
+ "win-amd64": "x64",
21
+ "win-arm32": "ARM",
22
+ "win-arm64": "ARM64",
23
+ }
24
+
25
+
26
+ # A CMakeExtension needs a sourcedir instead of a file list.
27
+ # The name must be the _single_ output extension from the CMake build.
28
+ # If you need multiple extensions, see scikit-build.
29
+ class CMakeExtension(Extension):
30
+
31
+ def __init__(self, name: str, sourcedir: str = "") -> None:
32
+ super().__init__(name, sources=[])
33
+ self.sourcedir = os.fspath(Path(sourcedir).resolve())
34
+
35
+
36
+ class CMakeBuild(build_ext):
37
+
38
+ def build_extension(self, ext: CMakeExtension) -> None:
39
+ # Must be in this form due to bug in .resolve() only fixed in Python 3.10+
40
+ ext_fullpath = Path.cwd() / self.get_ext_fullpath(ext.name) # type: ignore[no-untyped-call]
41
+ extdir = ext_fullpath.parent.resolve()
42
+
43
+ # Using this requires trailing slash for auto-detection & inclusion of
44
+ # auxiliary "native" libs
45
+
46
+ debug = int(os.environ.get("DEBUG", 0)) if self.debug is None else self.debug
47
+ cfg = "Debug" if debug else "Release"
48
+
49
+ # CMake lets you override the generator - we need to check this.
50
+ # Can be set with Conda-Build, for example.
51
+ cmake_generator = os.environ.get("CMAKE_GENERATOR", "")
52
+
53
+ # Set Python_EXECUTABLE instead if you use PYBIND11_FINDPYTHON
54
+ # EXAMPLE_VERSION_INFO shows you how to pass a value into the C++ code
55
+ # from Python.
56
+ cmake_args = [
57
+ f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={extdir}{os.sep}dv_toolkit/lib",
58
+ f"-DPYTHON_EXECUTABLE={sys.executable}",
59
+ f"-DCMAKE_BUILD_TYPE={cfg}", # not used on MSVC, but no harm
60
+ ]
61
+ build_args = []
62
+ # Adding CMake arguments set as environment variable
63
+ # (needed e.g. to build for ARM OSx on conda-forge)
64
+ if "CMAKE_ARGS" in os.environ:
65
+ cmake_args += [item for item in os.environ["CMAKE_ARGS"].split(" ") if item]
66
+
67
+ # In this example, we pass in the version to C++. You might not need to.
68
+ # cmake_args += [f"-DEXAMPLE_VERSION_INFO={self.distribution.get_version()}"] # type: ignore[attr-defined]
69
+
70
+ # Pass Python3 executable path to CMake after external args (eg. VCPKG toolchain files)
71
+ cmake_args += [f"-DPython3_EXECUTABLE={sys.executable}"]
72
+
73
+ # Add custom CMake arguments needed for build features
74
+ global extra_cmake_args
75
+ cmake_args += extra_cmake_args
76
+
77
+ if self.compiler.compiler_type != "msvc":
78
+ # Using Ninja-build since it a) is available as a wheel and b)
79
+ # multithreads automatically. MSVC would require all variables be
80
+ # exported for Ninja to pick it up, which is a little tricky to do.
81
+ # Users can override the generator with CMAKE_GENERATOR in CMake
82
+ # 3.15+.
83
+ if not cmake_generator or cmake_generator == "Ninja":
84
+ try:
85
+ import ninja
86
+
87
+ ninja_executable_path = Path(ninja.BIN_DIR) / "ninja"
88
+ cmake_args += [
89
+ "-GNinja",
90
+ f"-DCMAKE_MAKE_PROGRAM:FILEPATH={ninja_executable_path}",
91
+ ]
92
+ except ImportError:
93
+ pass
94
+
95
+ else:
96
+ # Single config generators are handled "normally"
97
+ single_config = any(x in cmake_generator for x in {"NMake", "Ninja"})
98
+
99
+ # CMake allows an arch-in-generator style for backward compatibility
100
+ contains_arch = any(x in cmake_generator for x in {"ARM", "Win64"})
101
+
102
+ # Specify the arch if using MSVC generator, but only if it doesn't
103
+ # contain a backward-compatibility arch spec already in the
104
+ # generator name.
105
+ if not single_config and not contains_arch:
106
+ cmake_args += ["-A", PLAT_TO_CMAKE[self.plat_name]]
107
+
108
+ # Multi-config generators have a different way to specify configs
109
+ if not single_config:
110
+ cmake_args += [f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{cfg.upper()}={extdir}"]
111
+ build_args += ["--config", cfg]
112
+
113
+ if sys.platform.startswith("darwin"):
114
+ # Cross-compile support for macOS - respect ARCHFLAGS if set
115
+ archs = re.findall(r"-arch (\S+)", os.environ.get("ARCHFLAGS", ""))
116
+ if archs:
117
+ cmake_args += ["-DCMAKE_OSX_ARCHITECTURES={}".format(";".join(archs))]
118
+
119
+ # Set CMAKE_BUILD_PARALLEL_LEVEL to control the parallel build level
120
+ # across all generators.
121
+ if "CMAKE_BUILD_PARALLEL_LEVEL" not in os.environ:
122
+ # self.parallel is a Python 3 only way to set parallel jobs by hand
123
+ # using -j in the build_ext call, not supported by pip or PyPA-build.
124
+ if hasattr(self, "parallel") and self.parallel:
125
+ # CMake 3.12+ only.
126
+ build_args += [f"-j{self.parallel}"]
127
+
128
+ build_temp = Path(self.build_temp) / ext.name
129
+ if not build_temp.exists():
130
+ build_temp.mkdir(parents=True)
131
+
132
+ ret = subprocess.run(["cmake", ext.sourcedir, *cmake_args], cwd=build_temp, check=True)
133
+ print("Called cmake with: " + str(ret.args))
134
+ ret = subprocess.run(["cmake", "--build", ".", *build_args], cwd=build_temp, check=True)
135
+ print("Called cmake with: " + str(ret.args))
136
+
137
+
138
+ def parse_gplusplus_major_version(gpp_path):
139
+ try:
140
+ p = subprocess.Popen([gpp_path, "--version"], stdout=subprocess.PIPE)
141
+ out, _ = p.communicate()
142
+ if p.returncode == 0:
143
+ output_text = out.decode('UTF-8')
144
+ prog = re.compile(r"^(\S+)\s\(.*\)\s(\d+)\.\d+\.\d+.*")
145
+ result = prog.match(output_text)
146
+ if result is not None and result[1][0:3] == "g++":
147
+ return int(result[2])
148
+ except FileNotFoundError:
149
+ pass
150
+ return None
151
+
152
+
153
+ def verify_and_configure_compiler():
154
+ # This verify and check only relevant on linux
155
+ if platform.system() != 'Linux':
156
+ return
157
+
158
+ version = parse_gplusplus_major_version("g++")
159
+ if version is not None and version >= 10:
160
+ # default compiler seems fine, continue
161
+ return
162
+ try:
163
+ version = parse_gplusplus_major_version(os.environ["CXX"])
164
+ if version is not None and version >= 10:
165
+ # A preset compiler found, it seems suitable
166
+ return
167
+ except KeyError:
168
+ pass
169
+
170
+ version = parse_gplusplus_major_version("g++-10")
171
+ if version is not None and version >= 10:
172
+ global extra_cmake_args
173
+ print("Detected installation of g++10, it will be used for compilation")
174
+ extra_cmake_args += ["-DCMAKE_C_COMPILER=gcc-10", "-DCMAKE_CXX_COMPILER=g++-10"]
175
+ return
176
+
177
+ # None of the above worked. Print a warning.
178
+ print("Compatible GCC compiler was not found.", file=sys.stderr)
179
+ print("Compiler compatibility verification failed, compilation will use system compiler.", file=sys.stderr)
180
+ print("This might fail!", file=sys.stderr)
181
+
182
+
183
+ # Initial setup
184
+ verify_and_configure_compiler()
185
+
186
+ # The information here can also be placed in setup.cfg - better separation of
187
+ # logic and declaration, and simpler if you include description/version in a file.
188
+ setup(
189
+ name="dv_toolkit",
190
+ version="0.2.0",
191
+ author="Kuga Maxx",
192
+ author_email="KugaMaxx@outlook.com",
193
+ description="a generic and simple toolkit for processing event-based data",
194
+ long_description="",
195
+ ext_modules=[CMakeExtension("_lib_toolkit")],
196
+ cmdclass={"build_ext": CMakeBuild},
197
+ zip_safe=False,
198
+ python_requires=">=3.8",
199
+ packages=find_packages(where='python/src'),
200
+ package_dir={'':'python/src'},
201
+ install_requires=[
202
+ 'numpy>=1.24',
203
+ 'dv_processing>=2.0',
204
+ 'plotly>=5.17',
205
+ 'matplotlib>=3.7'
206
+ ]
207
+ )