auto-trainer-core 2.0.1.dev349__py3-none-any.whl
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.
- auto_trainer_core-2.0.1.dev349.dist-info/METADATA +19 -0
- auto_trainer_core-2.0.1.dev349.dist-info/RECORD +73 -0
- auto_trainer_core-2.0.1.dev349.dist-info/WHEEL +5 -0
- auto_trainer_core-2.0.1.dev349.dist-info/top_level.txt +1 -0
- autotrainer/core/__init__.py +319 -0
- autotrainer/core/analysis/__init__.py +6 -0
- autotrainer/core/analysis/alarm_monitor.py +484 -0
- autotrainer/core/analysis/audio_spectrum_data.py +27 -0
- autotrainer/core/analysis/audio_spectrum_monitor.py +134 -0
- autotrainer/core/analysis/auto_tunnel_fan_monitor.py +92 -0
- autotrainer/core/analysis/detector.py +179 -0
- autotrainer/core/analysis/external_doors_monitor.py +124 -0
- autotrainer/core/analysis/global_animal_presence_monitor.py +69 -0
- autotrainer/core/analysis/head_fix_measurement.py +15 -0
- autotrainer/core/analysis/headbar_pressure_monitor.py +158 -0
- autotrainer/core/analysis/load_cell_monitor.py +377 -0
- autotrainer/core/analysis/load_cell_tare_monitor.py +217 -0
- autotrainer/core/analysis/measurement_data.py +80 -0
- autotrainer/core/analysis/pellet_position_monitor.py +108 -0
- autotrainer/core/analysis/sensor_analysis.py +390 -0
- autotrainer/core/analysis/system_fault_monitor.py +81 -0
- autotrainer/core/analysis/system_maintenance_monitor.py +88 -0
- autotrainer/core/animal/__init__.py +1 -0
- autotrainer/core/animal/animal_subject.py +208 -0
- autotrainer/core/capture.py +25 -0
- autotrainer/core/configuration/__init__.py +88 -0
- autotrainer/core/configuration/alarm_configuration.py +55 -0
- autotrainer/core/configuration/animal_presence_configuration.py +17 -0
- autotrainer/core/configuration/behavior_configuration.py +323 -0
- autotrainer/core/configuration/camera_configuration.py +108 -0
- autotrainer/core/configuration/external_doors_monitor_configuration.py +9 -0
- autotrainer/core/configuration/hardware_configuration.py +30 -0
- autotrainer/core/configuration/inference_configuration.py +26 -0
- autotrainer/core/configuration/load_cell_config.py +61 -0
- autotrainer/core/configuration/persistence_configuration.py +29 -0
- autotrainer/core/configuration/presence_detection_configuration.py +9 -0
- autotrainer/core/configuration/system_configuration.py +211 -0
- autotrainer/core/configuration/system_fault_config.py +9 -0
- autotrainer/core/configuration/system_maintenance_config.py +17 -0
- autotrainer/core/diamond_triangle_config.py +150 -0
- autotrainer/core/event/__init__.py +54 -0
- autotrainer/core/event/api_event_kind.py +5 -0
- autotrainer/core/event/api_event_plugin.py +57 -0
- autotrainer/core/event/event_info.py +53 -0
- autotrainer/core/event/event_manager.py +296 -0
- autotrainer/core/event/event_manager_plugin.py +45 -0
- autotrainer/core/event/file_event_plugin.py +91 -0
- autotrainer/core/event/logger_event_plugin.py +43 -0
- autotrainer/core/fixed_array_multiqueue.py +263 -0
- autotrainer/core/fixed_array_queue.py +124 -0
- autotrainer/core/frame_index.py +34 -0
- autotrainer/core/interfaces.py +347 -0
- autotrainer/core/logging.py +714 -0
- autotrainer/core/message/__init__.py +26 -0
- autotrainer/core/message/audio_spectrum_message.py +21 -0
- autotrainer/core/message/measurement_message.py +61 -0
- autotrainer/core/message/message_handler.py +158 -0
- autotrainer/core/message/motor_configuration_message.py +128 -0
- autotrainer/core/message/system_command_kind.py +74 -0
- autotrainer/core/message/system_message_handler.py +122 -0
- autotrainer/core/message/system_status_message.py +112 -0
- autotrainer/core/multiproc.py +50 -0
- autotrainer/core/notification/__init__.py +2 -0
- autotrainer/core/notification/notification_center.py +80 -0
- autotrainer/core/notification/trigger_notification.py +27 -0
- autotrainer/core/observable_object.py +105 -0
- autotrainer/core/perf_monitor.py +77 -0
- autotrainer/core/pose_elements.py +196 -0
- autotrainer/core/project/__init__.py +19 -0
- autotrainer/core/project/project_info.py +486 -0
- autotrainer/core/queue_util.py +60 -0
- autotrainer/core/reach_event.py +38 -0
- autotrainer/core/video_detection.py +326 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import ctypes
|
|
2
|
+
import logging
|
|
3
|
+
import multiprocessing
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
from multiprocessing import RawArray, Value
|
|
7
|
+
from multiprocessing import synchronize
|
|
8
|
+
from multiprocessing.context import BaseContext
|
|
9
|
+
from typing import Tuple, List, Optional
|
|
10
|
+
|
|
11
|
+
import numpy
|
|
12
|
+
|
|
13
|
+
from autotrainer.core.logging import get_verbose_logger
|
|
14
|
+
from autotrainer.core.frame_index import FrameIndexCategory
|
|
15
|
+
from autotrainer.core.fixed_array_queue import BufferResult
|
|
16
|
+
from autotrainer.core.multiproc import get_mp_ctx
|
|
17
|
+
|
|
18
|
+
logger = get_verbose_logger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class FixedArrayMultiQueue:
|
|
22
|
+
"""
|
|
23
|
+
Alternative to multiprocessing.Queue.
|
|
24
|
+
|
|
25
|
+
FixedArrayMultiQueue is an alternative to multiprocessing.Queue that
|
|
26
|
+
* Explicitly queues batches of 2D data from one or more sources
|
|
27
|
+
* Pre-allocates RawArray instances to hold the data for performance
|
|
28
|
+
|
|
29
|
+
The class does not share the exact put/get_xyz interface of the standard Queue classes as it is not a drop-in
|
|
30
|
+
replacement for Queue in terms of behavior.
|
|
31
|
+
"""
|
|
32
|
+
def __init__(self,
|
|
33
|
+
depth: int,
|
|
34
|
+
cam_count: int,
|
|
35
|
+
frames_per_camera: int,
|
|
36
|
+
shape: Tuple[int, int],
|
|
37
|
+
*,
|
|
38
|
+
primary: int = 0,
|
|
39
|
+
name: str="noname",
|
|
40
|
+
mp_ctx: Optional[BaseContext] = None,
|
|
41
|
+
):
|
|
42
|
+
"""
|
|
43
|
+
:param depth: queue length before discarding old data if consumers can not keep up
|
|
44
|
+
:param cam_count: number of sources (e.g., cameras) providing 2D data (frames)
|
|
45
|
+
:param frames_per_camera: number of frames per source
|
|
46
|
+
:param shape: size of each frame
|
|
47
|
+
:param primary: indicates which source (index) defines when frames_per_camera is met and a buffer rotates
|
|
48
|
+
"""
|
|
49
|
+
if mp_ctx is None:
|
|
50
|
+
mp_ctx = get_mp_ctx()
|
|
51
|
+
|
|
52
|
+
self._name = name
|
|
53
|
+
self._barrier = mp_ctx.Barrier(cam_count)
|
|
54
|
+
# indexing: [buffer][camera][batch_frame]
|
|
55
|
+
self._buffers: List[List[List[RawArray]]] = []
|
|
56
|
+
|
|
57
|
+
self._depth = depth
|
|
58
|
+
self._cam_count = cam_count
|
|
59
|
+
self._frames_per_camera = frames_per_camera
|
|
60
|
+
self._primary = primary
|
|
61
|
+
|
|
62
|
+
# writers:
|
|
63
|
+
self._buffer_index = [0] * self._cam_count
|
|
64
|
+
self._batch_index = [0] * self._cam_count
|
|
65
|
+
|
|
66
|
+
# reader:
|
|
67
|
+
self._read_index = 0
|
|
68
|
+
self._read_sem_acquired = [False] * self._cam_count
|
|
69
|
+
|
|
70
|
+
self._shape = shape
|
|
71
|
+
self._byte_count = shape[0] * shape[1] # 1 frame byte count, no RGB, only 8-bit gray.
|
|
72
|
+
|
|
73
|
+
self._buff_views = []
|
|
74
|
+
for idx in range(self._depth):
|
|
75
|
+
buffer = []
|
|
76
|
+
view = []
|
|
77
|
+
for cdx in range(self._cam_count):
|
|
78
|
+
cam_buffer = []
|
|
79
|
+
cam_view = []
|
|
80
|
+
for bdx in range(self._frames_per_camera):
|
|
81
|
+
# TODO: could use a single rawarray per camera
|
|
82
|
+
cam_buffer.append(mp_ctx.RawArray(ctypes.c_ubyte, self._byte_count))
|
|
83
|
+
buffer.append(cam_buffer)
|
|
84
|
+
view.append(cam_view)
|
|
85
|
+
|
|
86
|
+
self._buffers.append(buffer)
|
|
87
|
+
self._buff_views.append(view)
|
|
88
|
+
|
|
89
|
+
# "free" buckets:
|
|
90
|
+
self._sem_free = [mp_ctx.Semaphore(self._depth) for _ in range(self._cam_count)]
|
|
91
|
+
# "busy" buckets:
|
|
92
|
+
self._sem_busy = [mp_ctx.Semaphore(0) for _ in range(self._cam_count)]
|
|
93
|
+
|
|
94
|
+
self._cams_tot_frames = [
|
|
95
|
+
mp_ctx.Value(ctypes.c_int64)
|
|
96
|
+
for _ in range(self._cam_count)
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
# a single shared array for all frames indices of all cameras:
|
|
100
|
+
self._frame_indices = mp_ctx.RawArray(ctypes.c_int64, self._depth * self._frames_per_camera * self._cam_count)
|
|
101
|
+
|
|
102
|
+
self._frame_indexing: List[int] = list(numpy.repeat(range(self._frames_per_camera), self._cam_count))
|
|
103
|
+
self._camera_indexing: List[int] = list(numpy.tile(range(self._cam_count), self._frames_per_camera))
|
|
104
|
+
|
|
105
|
+
self._next_counts_log_time = time.perf_counter()
|
|
106
|
+
self._overflow_count = 0
|
|
107
|
+
self._put_count = 0
|
|
108
|
+
|
|
109
|
+
def __str__(self):
|
|
110
|
+
return f"{self.__class__.__name__}({self._name!r})"
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def depth(self):
|
|
114
|
+
return self._depth
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def camera_count(self):
|
|
118
|
+
return self._cam_count
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def frames_per_camera(self):
|
|
122
|
+
return self._frames_per_camera
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def batch_size(self):
|
|
126
|
+
return self.camera_count * self.frames_per_camera
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def shape(self):
|
|
130
|
+
return self._shape
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def buffer_index(self) -> int:
|
|
134
|
+
return self._buffer_index[0]
|
|
135
|
+
|
|
136
|
+
def set_cam_tot_frames(self, cam_idx: int, tot_frames: int):
|
|
137
|
+
"""Set the total nbr of frames for cam_idx for eventual sync between camera writers"""
|
|
138
|
+
self._cams_tot_frames[cam_idx].value = tot_frames
|
|
139
|
+
|
|
140
|
+
def pad_to_batch_size(self, cam_idx: int, empty_frame, cnt_net_q_put, *, timeout: float=10):
|
|
141
|
+
"""Pad the queue so that all the cams are on same bucket, and the start of it."""
|
|
142
|
+
barrier_wait = self._barrier.wait
|
|
143
|
+
#
|
|
144
|
+
p_before = time.perf_counter()
|
|
145
|
+
barrier_wait(timeout=timeout)
|
|
146
|
+
p_now = time.perf_counter()
|
|
147
|
+
timeout -= p_now - p_before
|
|
148
|
+
# set the tot_frames in different sync_barrier session than the next get_cam_missing_frames
|
|
149
|
+
self.set_cam_tot_frames(cam_idx, cnt_net_q_put)
|
|
150
|
+
p_before = p_now
|
|
151
|
+
barrier_wait(timeout=timeout)
|
|
152
|
+
p_now = time.perf_counter()
|
|
153
|
+
timeout -= p_now - p_before
|
|
154
|
+
missing = self.get_cam_missing_frames(cam_idx)
|
|
155
|
+
p_before = p_now
|
|
156
|
+
barrier_wait(timeout=timeout)
|
|
157
|
+
logger.verbose("padding %s frames (put=%s) to sync with others writers",
|
|
158
|
+
missing, cnt_net_q_put)
|
|
159
|
+
for _ in range(missing):
|
|
160
|
+
p_now = time.perf_counter()
|
|
161
|
+
timeout -= p_now - p_before
|
|
162
|
+
p_before = p_now
|
|
163
|
+
self.put_block(empty_frame, cam_idx, FrameIndexCategory.PADDING,
|
|
164
|
+
timeout=timeout)
|
|
165
|
+
|
|
166
|
+
def get_cam_missing_frames(self, cam_idx: int) -> int:
|
|
167
|
+
"""Returns the number of missing frame for camera idx,
|
|
168
|
+
to fill in the batch up to the max camera writer"""
|
|
169
|
+
tot_puts = [self._cams_tot_frames[cdx].value for cdx in range(self._cam_count)]
|
|
170
|
+
max_tot_put = max(tot_puts)
|
|
171
|
+
reminder_max = max_tot_put % self._frames_per_camera
|
|
172
|
+
missing_max = (self._frames_per_camera - reminder_max) % self._frames_per_camera
|
|
173
|
+
max_tot_put += missing_max
|
|
174
|
+
res = max_tot_put - tot_puts[cam_idx]
|
|
175
|
+
logger.debug("get_cam_missing_frames(%s): res=%s tot_puts=%s", cam_idx, res, tot_puts)
|
|
176
|
+
return res
|
|
177
|
+
|
|
178
|
+
def put_block(self, content: numpy.ndarray, camera: int, frame_idx: int, *, timeout: float=10):
|
|
179
|
+
if self.put(content, camera, frame_idx, timeout=timeout) != BufferResult.Ok:
|
|
180
|
+
raise RuntimeError(f"Timeout waiting space in queue for cam-{camera}")
|
|
181
|
+
|
|
182
|
+
def put(self, content: numpy.ndarray, camera: int, frame_idx: Optional[int],
|
|
183
|
+
*, block=True, timeout=0.01) -> BufferResult:
|
|
184
|
+
batch_index = self._batch_index[camera] # 0 ... up to frames per camera - 1
|
|
185
|
+
if batch_index == 0:
|
|
186
|
+
if not self._sem_free[camera].acquire(block, timeout):
|
|
187
|
+
return BufferResult.Overflow
|
|
188
|
+
buffer_index = self._buffer_index[camera] # 0 ... up to depth - 1
|
|
189
|
+
cur_view = memoryview(self._buffers[buffer_index][camera][batch_index]).cast("B")
|
|
190
|
+
# reshape does not copy if not necessary
|
|
191
|
+
cur_view[:] = content.reshape(-1)
|
|
192
|
+
if frame_idx is not None:
|
|
193
|
+
b = numpy.frombuffer(
|
|
194
|
+
memoryview(self._frame_indices).cast("B"), "int64", len(self._frame_indices)
|
|
195
|
+
).reshape((self._cam_count, self._depth, self._frames_per_camera))[camera]
|
|
196
|
+
b[buffer_index][batch_index] = frame_idx
|
|
197
|
+
self._put_count += 1
|
|
198
|
+
#
|
|
199
|
+
batch_index = self._batch_index[camera] = (batch_index + 1) % self._frames_per_camera
|
|
200
|
+
if batch_index == 0:
|
|
201
|
+
self._sem_busy[camera].release()
|
|
202
|
+
self._buffer_index[camera] = (1 + buffer_index) % self._depth
|
|
203
|
+
|
|
204
|
+
return BufferResult.Ok # if not is_overflow else BufferResult.Overflow
|
|
205
|
+
|
|
206
|
+
def get_output(self, output: numpy.ndarray, frames_indices: Optional[numpy.ndarray] = None, *, timeout: float=0.01) -> bool:
|
|
207
|
+
"""Get the next available "output" : i.e: 1 batch of frames_per_camera * nbr_cameras"""
|
|
208
|
+
for cdx in range(self._cam_count):
|
|
209
|
+
if not self._read_sem_acquired[cdx]:
|
|
210
|
+
p0 = time.perf_counter()
|
|
211
|
+
if not self._sem_busy[cdx].acquire(timeout=timeout):
|
|
212
|
+
return False
|
|
213
|
+
p1 = time.perf_counter()
|
|
214
|
+
timeout -= p1 - p0
|
|
215
|
+
self._read_sem_acquired[cdx] = True
|
|
216
|
+
read_idx_value = self._read_index
|
|
217
|
+
buffer = self._buffers[read_idx_value]
|
|
218
|
+
for idx, cdx in enumerate(self._camera_indexing): # cdx: 0 1 0 1 0 1 0 1
|
|
219
|
+
v = numpy.frombuffer(
|
|
220
|
+
buffer[cdx][self._frame_indexing[idx]], "uint8", self._byte_count
|
|
221
|
+
).reshape(self.shape)
|
|
222
|
+
# NB: current predict model expects an RGB frame,
|
|
223
|
+
# we have so to copy 3 times the current gray image/frame into the 3 planes:
|
|
224
|
+
# for fn in range(3):
|
|
225
|
+
# output[idx, :, :, fn] = v
|
|
226
|
+
# we do that after releasing the sema free below
|
|
227
|
+
output[idx, :, :, 0] = v
|
|
228
|
+
|
|
229
|
+
if frames_indices is not None:
|
|
230
|
+
# copy frames indices
|
|
231
|
+
frames_indices[:, :] = numpy.frombuffer(
|
|
232
|
+
memoryview(self._frame_indices).cast("B"), "int64", len(self._frame_indices)
|
|
233
|
+
).reshape(
|
|
234
|
+
(self._cam_count, self._depth, self._frames_per_camera)
|
|
235
|
+
)[:, read_idx_value, :]
|
|
236
|
+
|
|
237
|
+
# put back the used/copied bucket as free:
|
|
238
|
+
for cdx in range(self._cam_count):
|
|
239
|
+
self._sem_free[cdx].release()
|
|
240
|
+
self._read_sem_acquired[cdx] = False
|
|
241
|
+
|
|
242
|
+
# make the copy for rgb after having released sema_free
|
|
243
|
+
for idx, cdx in enumerate(self._camera_indexing):
|
|
244
|
+
for fn in (1, 2):
|
|
245
|
+
output[idx, :, :, fn] = output[idx, :, :, 0]
|
|
246
|
+
|
|
247
|
+
# don't forget to:
|
|
248
|
+
self._read_index = (read_idx_value + 1) % self._depth
|
|
249
|
+
return True
|
|
250
|
+
|
|
251
|
+
def put_frame_index_category(self, frame, frame_idx: FrameIndexCategory, *,
|
|
252
|
+
cam_idx: Optional[int] = None, timeout: float = 10):
|
|
253
|
+
logger.verbose("putting frame index category %s", frame_idx)
|
|
254
|
+
for _ in range(self._frames_per_camera):
|
|
255
|
+
for cdx in range(self._cam_count):
|
|
256
|
+
if cam_idx is None or cam_idx == cdx:
|
|
257
|
+
t0 = time.perf_counter()
|
|
258
|
+
# put_block() will take care to raise if timeout occurs
|
|
259
|
+
self.put_block(frame, cdx, frame_idx, timeout=timeout)
|
|
260
|
+
timeout -= time.perf_counter() - t0
|
|
261
|
+
|
|
262
|
+
def get_frame_indices(self):
|
|
263
|
+
return list(self._frame_indices)
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import ctypes
|
|
2
|
+
import logging
|
|
3
|
+
import multiprocessing
|
|
4
|
+
import queue
|
|
5
|
+
import time
|
|
6
|
+
from enum import IntEnum
|
|
7
|
+
from multiprocessing import RawArray, Value
|
|
8
|
+
from multiprocessing.context import BaseContext
|
|
9
|
+
from typing import List, Optional, Tuple
|
|
10
|
+
|
|
11
|
+
import numpy
|
|
12
|
+
|
|
13
|
+
from autotrainer.core.multiproc import get_mp_ctx
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class BufferResult(IntEnum):
|
|
19
|
+
Ok = 0
|
|
20
|
+
Overflow = 1
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class FixedArrayQueue:
|
|
24
|
+
def __init__(self, depth: int, shape: Tuple[int, int], name: str="noname", *,
|
|
25
|
+
mp_ctx: Optional[BaseContext] = None,
|
|
26
|
+
):
|
|
27
|
+
if mp_ctx is None:
|
|
28
|
+
mp_ctx = get_mp_ctx()
|
|
29
|
+
|
|
30
|
+
self._name = name
|
|
31
|
+
# indexing: [buffer]
|
|
32
|
+
self._buffers: List[RawArray] = []
|
|
33
|
+
self._is_dirty: List[Value] = []
|
|
34
|
+
|
|
35
|
+
self._depth = depth
|
|
36
|
+
self._shape = shape
|
|
37
|
+
self._byte_count = shape[0] * shape[1]
|
|
38
|
+
|
|
39
|
+
self._buffer_index = 0
|
|
40
|
+
self._read_index = 0
|
|
41
|
+
|
|
42
|
+
self._buff_views = []
|
|
43
|
+
for idx in range(self._depth):
|
|
44
|
+
self._buffers.append(mp_ctx.RawArray(ctypes.c_ubyte, self._byte_count))
|
|
45
|
+
self._is_dirty.append(mp_ctx.Value(ctypes.c_bool, False))
|
|
46
|
+
|
|
47
|
+
self._next_counts_log_time = time.time()
|
|
48
|
+
self._overflow_count = 0
|
|
49
|
+
self._put_count = 0
|
|
50
|
+
|
|
51
|
+
def __str__(self):
|
|
52
|
+
return f"{self.__class__.__name__}({self._name!r})"
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def shape(self):
|
|
56
|
+
return self._shape
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def buffer_index(self) -> int:
|
|
60
|
+
return self._buffer_index
|
|
61
|
+
|
|
62
|
+
def put(self, content: numpy.ndarray):
|
|
63
|
+
return self._put(content)
|
|
64
|
+
|
|
65
|
+
def _put(self, content: numpy.ndarray):
|
|
66
|
+
self._buff_views.clear()
|
|
67
|
+
for idx in range(self._depth):
|
|
68
|
+
self._buff_views.append(memoryview(self._buffers[idx]).cast("B"))
|
|
69
|
+
self.put = self._put = self._put_view
|
|
70
|
+
return self._put_view(content)
|
|
71
|
+
|
|
72
|
+
def _put_view(self, content: numpy.ndarray) -> BufferResult:
|
|
73
|
+
buffer_index = self._buffer_index
|
|
74
|
+
is_overflow = self._is_dirty[buffer_index].value
|
|
75
|
+
if is_overflow:
|
|
76
|
+
self._overflow_count += 1
|
|
77
|
+
return BufferResult.Overflow
|
|
78
|
+
|
|
79
|
+
self._put_count += 1
|
|
80
|
+
|
|
81
|
+
# t = time.time()
|
|
82
|
+
# if t > self._next_counts_log_time:
|
|
83
|
+
# self._next_counts_log_time += 10
|
|
84
|
+
# logger.info("%s: put=%s overflow=%s", self, self._put_count, self._overflow_count)
|
|
85
|
+
# self._put_count = self._overflow_count = 0
|
|
86
|
+
|
|
87
|
+
self._buff_views[buffer_index][:] = content.reshape(-1) # content.flatten()
|
|
88
|
+
|
|
89
|
+
self._is_dirty[buffer_index].value = True
|
|
90
|
+
|
|
91
|
+
buffer_index += 1
|
|
92
|
+
buffer_index %= self._depth
|
|
93
|
+
self._buffer_index = buffer_index
|
|
94
|
+
|
|
95
|
+
return BufferResult.Ok if not is_overflow else BufferResult.Overflow
|
|
96
|
+
|
|
97
|
+
def get(self, block: bool = True, timeout: float = 0.01) -> numpy.ndarray:
|
|
98
|
+
perf_timeout = time.perf_counter() + timeout
|
|
99
|
+
while True:
|
|
100
|
+
read_index = self._read_index
|
|
101
|
+
if self._is_dirty[read_index].value:
|
|
102
|
+
break
|
|
103
|
+
if time.perf_counter() > perf_timeout:
|
|
104
|
+
raise queue.Empty
|
|
105
|
+
time.sleep(0.005)
|
|
106
|
+
|
|
107
|
+
buffer = self._buffers[read_index]
|
|
108
|
+
|
|
109
|
+
v = numpy.frombuffer(buffer, "uint8", self._byte_count).reshape(self.shape)
|
|
110
|
+
output = v.copy() # numpy.frombuffer() returns a view
|
|
111
|
+
|
|
112
|
+
self._is_dirty[read_index].value = False
|
|
113
|
+
|
|
114
|
+
self._read_index = (read_index + 1) % self._depth
|
|
115
|
+
|
|
116
|
+
return output
|
|
117
|
+
|
|
118
|
+
# noinspection PyMethodMayBeStatic
|
|
119
|
+
def empty(self) -> bool:
|
|
120
|
+
return True
|
|
121
|
+
|
|
122
|
+
# noinspection PyMethodMayBeStatic
|
|
123
|
+
def qsize(self) -> int:
|
|
124
|
+
return 0
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class FrameIndexCategory(enum.IntEnum):
|
|
5
|
+
|
|
6
|
+
SWITCH_TO_ONLINE = -5 # not anymore used, replaced by InferenceCommandMessageKind.SetOfflineToLive
|
|
7
|
+
PADDING = -4
|
|
8
|
+
EOF_OFFLINE_PROCESSING = -3
|
|
9
|
+
EOF_RECORDING = -2
|
|
10
|
+
|
|
11
|
+
ONLINE_NO_RECORDING = -1 # NB: DO NOT CHANGE VALUE OF THIS ONE
|
|
12
|
+
|
|
13
|
+
RECORDING_OR_OFFLINE_PROCESSING = 1
|
|
14
|
+
# NB: positive or zero frane index means recording online or offline reprocessing.
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def is_signaling_index(cls, frame_idx) -> bool:
|
|
18
|
+
return frame_idx < cls.ONLINE_NO_RECORDING
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def _missing_(cls, value):
|
|
22
|
+
if isinstance(value, str):
|
|
23
|
+
value = int(value)
|
|
24
|
+
if isinstance(value, float):
|
|
25
|
+
v = int(value)
|
|
26
|
+
if v != value:
|
|
27
|
+
return super()._missing_(value)
|
|
28
|
+
value = v
|
|
29
|
+
if value >= 0:
|
|
30
|
+
return FrameIndexCategory.RECORDING_OR_OFFLINE_PROCESSING
|
|
31
|
+
for k, m in cls._member_map_.items():
|
|
32
|
+
if m.value == value:
|
|
33
|
+
return m
|
|
34
|
+
return super()._missing_(value)
|