auto-trainer-core 2.0.1.dev353__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.dev353.dist-info/METADATA +18 -0
- auto_trainer_core-2.0.1.dev353.dist-info/RECORD +73 -0
- auto_trainer_core-2.0.1.dev353.dist-info/WHEEL +5 -0
- auto_trainer_core-2.0.1.dev353.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,326 @@
|
|
|
1
|
+
import collections
|
|
2
|
+
import csv
|
|
3
|
+
import ctypes
|
|
4
|
+
import dataclasses
|
|
5
|
+
import math
|
|
6
|
+
import statistics
|
|
7
|
+
import os
|
|
8
|
+
import threading
|
|
9
|
+
import time
|
|
10
|
+
from multiprocessing.sharedctypes import Synchronized
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from typing import Deque, Tuple, List
|
|
14
|
+
|
|
15
|
+
import cv2
|
|
16
|
+
import numpy
|
|
17
|
+
import numpy as np
|
|
18
|
+
from typing_extensions import Self
|
|
19
|
+
|
|
20
|
+
from autotrainer.core import ValueHolderDescriptor, get_perf_now, RawValueHolder
|
|
21
|
+
from autotrainer.core.configuration.presence_detection_configuration import PresenceDetectionConfig
|
|
22
|
+
from autotrainer.core.project.project_info import ProjectInfo
|
|
23
|
+
from autotrainer.core.logging import get_verbose_logger
|
|
24
|
+
from autotrainer.core.multiproc import get_mp_ctx, EmptyWithContext
|
|
25
|
+
|
|
26
|
+
logger = get_verbose_logger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclasses.dataclass
|
|
30
|
+
class PresenceDetectionAttrs:
|
|
31
|
+
|
|
32
|
+
pc_threshold = ValueHolderDescriptor()
|
|
33
|
+
# the fg mask sum (as percent vs max(100)) threshold above which the presence is assumed
|
|
34
|
+
_pc_threshold: Synchronized = None
|
|
35
|
+
|
|
36
|
+
pc_high_exclude_threshold = ValueHolderDescriptor()
|
|
37
|
+
# but if above this exclude threshold then don't trigger.
|
|
38
|
+
_pc_high_exclude_threshold: Synchronized = None
|
|
39
|
+
|
|
40
|
+
mask_lower_zero = ValueHolderDescriptor()
|
|
41
|
+
_mask_lower_zero: Synchronized = None
|
|
42
|
+
# zero all values in the frame below this value
|
|
43
|
+
|
|
44
|
+
max_delay_skip_threshold = ValueHolderDescriptor()
|
|
45
|
+
_max_delay_skip_threshold: Synchronized = None
|
|
46
|
+
# remove frames older than this only. all frames within this delay will be taken into account
|
|
47
|
+
|
|
48
|
+
# could be todo: allow compare with one frame with the xth previous one (second, or third, for instance),
|
|
49
|
+
# not only the very next one, that would/could allow detect slower movement/presence
|
|
50
|
+
|
|
51
|
+
last_absence_start_perf_c = ValueHolderDescriptor()
|
|
52
|
+
_last_absence_start_perf_c: Synchronized = None
|
|
53
|
+
|
|
54
|
+
last_presence_start_perf_c = ValueHolderDescriptor()
|
|
55
|
+
_last_presence_start_perf_c: Synchronized = None
|
|
56
|
+
|
|
57
|
+
presence_detected = ValueHolderDescriptor()
|
|
58
|
+
_presence_detected: Synchronized = None
|
|
59
|
+
|
|
60
|
+
movement_detected = ValueHolderDescriptor()
|
|
61
|
+
_movement_detected: Synchronized = None
|
|
62
|
+
|
|
63
|
+
pc_sum = ValueHolderDescriptor()
|
|
64
|
+
_pc_sum: Synchronized = None
|
|
65
|
+
|
|
66
|
+
def __post_init__(self):
|
|
67
|
+
ctx = get_mp_ctx()
|
|
68
|
+
lock = ctx.RLock()
|
|
69
|
+
used = False
|
|
70
|
+
if self._pc_threshold is None:
|
|
71
|
+
used = True
|
|
72
|
+
self._pc_threshold = ctx.Value(ctypes.c_double, PresenceDetectionConfig.pc_threshold, lock=lock)
|
|
73
|
+
if self._pc_high_exclude_threshold is None:
|
|
74
|
+
used = True
|
|
75
|
+
self._pc_high_exclude_threshold = ctx.Value(ctypes.c_double, PresenceDetectionConfig.pc_high_exclude_threshold, lock=lock)
|
|
76
|
+
if self._mask_lower_zero is None:
|
|
77
|
+
used = True
|
|
78
|
+
self._mask_lower_zero = ctx.Value(ctypes.c_double, PresenceDetectionConfig.mask_lower_zero, lock=lock)
|
|
79
|
+
if self._max_delay_skip_threshold is None:
|
|
80
|
+
used = True
|
|
81
|
+
self._max_delay_skip_threshold = ctx.Value(ctypes.c_double, PresenceDetectionConfig.max_delay_skip_threshold, lock=lock)
|
|
82
|
+
if self._last_absence_start_perf_c is None:
|
|
83
|
+
used = True
|
|
84
|
+
self._last_absence_start_perf_c = ctx.Value(ctypes.c_double, -math.inf, lock=lock)
|
|
85
|
+
if self._last_presence_start_perf_c is None:
|
|
86
|
+
used = True
|
|
87
|
+
self._last_presence_start_perf_c = ctx.Value(ctypes.c_double, -math.inf, lock=lock)
|
|
88
|
+
if self._presence_detected is None:
|
|
89
|
+
used = True
|
|
90
|
+
self._presence_detected = ctx.Value(ctypes.c_bool, False, lock=lock)
|
|
91
|
+
if self._movement_detected is None:
|
|
92
|
+
used = True
|
|
93
|
+
self._movement_detected = ctx.Value(ctypes.c_bool, False, lock=lock)
|
|
94
|
+
if self._pc_sum is None:
|
|
95
|
+
used = True
|
|
96
|
+
self._pc_sum = ctx.Value(ctypes.c_double, 0, lock=lock)
|
|
97
|
+
if not used:
|
|
98
|
+
lock = EmptyWithContext()
|
|
99
|
+
self._lock = lock
|
|
100
|
+
|
|
101
|
+
def _loop_over_value_holder_desc(self):
|
|
102
|
+
cls = self.__class__
|
|
103
|
+
return (
|
|
104
|
+
a
|
|
105
|
+
for a in (getattr(cls, k) for k in dir(cls))
|
|
106
|
+
if isinstance(a, ValueHolderDescriptor)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
def __eq__(self, other):
|
|
110
|
+
return all(getattr(self, a.name) == getattr(other, a.name)
|
|
111
|
+
for a in self._loop_over_value_holder_desc())
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def lock(self):
|
|
115
|
+
return self._lock
|
|
116
|
+
|
|
117
|
+
def to_local_value(self) -> Self:
|
|
118
|
+
"""Detach from the shared values"""
|
|
119
|
+
with self._lock:
|
|
120
|
+
dct = {
|
|
121
|
+
f"_{a.name}": RawValueHolder(getattr(self, a.name))
|
|
122
|
+
for a in self._loop_over_value_holder_desc()
|
|
123
|
+
}
|
|
124
|
+
return self.__class__(**dct)
|
|
125
|
+
|
|
126
|
+
def to_config(self) -> PresenceDetectionConfig:
|
|
127
|
+
return PresenceDetectionConfig(**{
|
|
128
|
+
field.name: getattr(self, field.name)
|
|
129
|
+
for field in dataclasses.fields(PresenceDetectionConfig)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
def load_config(self, cfg: PresenceDetectionConfig):
|
|
133
|
+
for field in dataclasses.fields(cfg):
|
|
134
|
+
setattr(self, field.name, getattr(cfg, field.name))
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class VideoDetection(threading.Thread):
|
|
138
|
+
|
|
139
|
+
def __init__(self, project_info: ProjectInfo, detection_attrs: PresenceDetectionAttrs):
|
|
140
|
+
self._project_info = project_info
|
|
141
|
+
self._attrs = detection_attrs
|
|
142
|
+
self._stop_requested = False
|
|
143
|
+
# using a deque with 8 entries,
|
|
144
|
+
# NB: is thread-safe for append/popleft. see (c)python doc.
|
|
145
|
+
self._next_frames: Deque[Tuple[float, numpy.ndarray, float]] = collections.deque(maxlen=10)
|
|
146
|
+
# not sure using a simple thread queue.Queue is not as good, possibly better (can wait on it)
|
|
147
|
+
self._prev_frame = None
|
|
148
|
+
self._prev_when = None
|
|
149
|
+
self._csv_header = ["Time", "Index", "PercentSum", "Motion"]
|
|
150
|
+
self._file_info = None
|
|
151
|
+
self._csv_writer = None
|
|
152
|
+
self._csv_writer_fh = None
|
|
153
|
+
self._got_first_frame = False
|
|
154
|
+
super().__init__(name="PresenceDetection", daemon=True)
|
|
155
|
+
|
|
156
|
+
def cancel(self):
|
|
157
|
+
self._stop_requested = True
|
|
158
|
+
|
|
159
|
+
def update_frame(self, when: float, frame: numpy.ndarray, perf_c: float):
|
|
160
|
+
if not self._got_first_frame:
|
|
161
|
+
self._got_first_frame = True
|
|
162
|
+
perf_now = get_perf_now()
|
|
163
|
+
self._attrs.last_absence_start_perf_c = perf_now
|
|
164
|
+
self._attrs.last_presence_start_perf_c = perf_now
|
|
165
|
+
# this allows to get good measurement from monitors using the detection result(s)
|
|
166
|
+
self._next_frames.append((when, frame, perf_c))
|
|
167
|
+
|
|
168
|
+
def _check_path(self):
|
|
169
|
+
csv_file_info = self._project_info.get_webcam_presence_file(when=datetime.now())
|
|
170
|
+
if csv_file_info == self._file_info:
|
|
171
|
+
return
|
|
172
|
+
if self._csv_writer_fh is not None:
|
|
173
|
+
self._csv_writer_fh.flush()
|
|
174
|
+
self._csv_writer_fh.close()
|
|
175
|
+
if csv_file_info is None:
|
|
176
|
+
self._csv_writer_fh = self._csv_writer = None
|
|
177
|
+
else:
|
|
178
|
+
f_path = Path(csv_file_info.file)
|
|
179
|
+
logger.verbose("Switching to %s", f_path)
|
|
180
|
+
prev_exist = f_path.is_file() and f_path.exists() and f_path.stat().st_size > 0
|
|
181
|
+
self._csv_writer_fh = f_path.open("a")
|
|
182
|
+
self._csv_writer = csv.DictWriter(self._csv_writer_fh, self._csv_header)
|
|
183
|
+
if not prev_exist:
|
|
184
|
+
self._csv_writer.writeheader()
|
|
185
|
+
self._file_info = csv_file_info
|
|
186
|
+
|
|
187
|
+
def run(self):
|
|
188
|
+
try:
|
|
189
|
+
self._run()
|
|
190
|
+
finally:
|
|
191
|
+
fh = self._csv_writer_fh
|
|
192
|
+
if fh is not None:
|
|
193
|
+
fh.flush()
|
|
194
|
+
fh.close()
|
|
195
|
+
|
|
196
|
+
def _run(self):
|
|
197
|
+
attrs = self._attrs
|
|
198
|
+
prev_pc_threshold = attrs.pc_threshold
|
|
199
|
+
prev_gray_frame = prev_frame_perf_c = None
|
|
200
|
+
prev_detected = None # attrs.presence_detected.value
|
|
201
|
+
prev_pc_sum = None
|
|
202
|
+
row_dict = dict.fromkeys(self._csv_header)
|
|
203
|
+
next_log_report = time.perf_counter() + 5
|
|
204
|
+
processed_count = 0
|
|
205
|
+
expired_count = 0
|
|
206
|
+
show_report = os.getenv("PRESENCE_DETECTION_SHOW_REPORT", "").lower() in {"y", "yes", "true", "1"}
|
|
207
|
+
delay_report = int(os.getenv("PRES_DET_DELAY_REPORT", "180"))
|
|
208
|
+
processed_times: List[float] = []
|
|
209
|
+
prev_pc_values = []
|
|
210
|
+
hist_values: List[Tuple[float, np.ndarray, float, float]] = []
|
|
211
|
+
# when, gray_frame, pc_norm, pc_unnorm
|
|
212
|
+
def update_hist(w, new_frame, pc_norm, pc_unnorm):
|
|
213
|
+
idx = len(hist_values) - 1
|
|
214
|
+
while idx >= 0:
|
|
215
|
+
w2 = hist_values[idx][0]
|
|
216
|
+
if w - w2 >= attrs.max_delay_skip_threshold:
|
|
217
|
+
del hist_values[:idx + 1] # need + 1 with range (:+1)
|
|
218
|
+
break
|
|
219
|
+
idx -= 1
|
|
220
|
+
hist_values.append((w, new_frame, pc_norm, pc_unnorm))
|
|
221
|
+
#
|
|
222
|
+
last_log_report = time.perf_counter()
|
|
223
|
+
while not self._stop_requested:
|
|
224
|
+
perf_now = time.perf_counter()
|
|
225
|
+
loop_start_perf_now = perf_now
|
|
226
|
+
if show_report and perf_now >= next_log_report:
|
|
227
|
+
actual_delay = perf_now - last_log_report
|
|
228
|
+
logger.debug("video presence detection: %s, frame/s=%.1f mean_proc_time=%.3f ; expired_count=%.1f/s ; values=%s",
|
|
229
|
+
prev_pc_values[-1][0] if len(prev_pc_values) > 0 else math.nan,
|
|
230
|
+
processed_count / actual_delay,
|
|
231
|
+
statistics.mean(processed_times) if processed_times else math.nan,
|
|
232
|
+
expired_count / actual_delay, prev_pc_values)
|
|
233
|
+
last_log_report = perf_now
|
|
234
|
+
next_log_report = perf_now + delay_report
|
|
235
|
+
processed_count = expired_count = 0
|
|
236
|
+
prev_pc_values.clear()
|
|
237
|
+
processed_times.clear()
|
|
238
|
+
try:
|
|
239
|
+
when_nanos, frame, frame_perf_c = self._next_frames.popleft()
|
|
240
|
+
except IndexError:
|
|
241
|
+
time.sleep(0.003) # current producer is 30 fps. let's use very smallish sleep
|
|
242
|
+
continue
|
|
243
|
+
#
|
|
244
|
+
when = when_nanos / 1e9 # convert to second timestamp
|
|
245
|
+
#
|
|
246
|
+
if frame.ndim > 2:
|
|
247
|
+
gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
|
248
|
+
else:
|
|
249
|
+
gray_frame = frame
|
|
250
|
+
# given we divide by fg_mask.size below to calculate a % value,
|
|
251
|
+
# it does not really matter to rescale to any size:
|
|
252
|
+
# gray_frame = cv2.resize(gray_frame, (256, 256), interpolation=cv2.INTER_LINEAR)
|
|
253
|
+
save_prev_perf_c = prev_frame_perf_c
|
|
254
|
+
save_prev_gray_frame = prev_gray_frame
|
|
255
|
+
prev_frame_perf_c = when
|
|
256
|
+
prev_gray_frame = gray_frame
|
|
257
|
+
if save_prev_gray_frame is None:
|
|
258
|
+
# (re)init frame
|
|
259
|
+
continue
|
|
260
|
+
if frame_perf_c - save_prev_perf_c >= attrs.max_delay_skip_threshold:
|
|
261
|
+
expired_count += 1
|
|
262
|
+
continue
|
|
263
|
+
# work on frame
|
|
264
|
+
cur_pc_threshold = attrs.pc_threshold
|
|
265
|
+
if cur_pc_threshold != prev_pc_threshold:
|
|
266
|
+
logger.debug("Using new pc_threshold: %s", cur_pc_threshold)
|
|
267
|
+
prev_pc_threshold = cur_pc_threshold
|
|
268
|
+
processed_count += 1
|
|
269
|
+
fg_mask = cv2.absdiff(save_prev_gray_frame, gray_frame)
|
|
270
|
+
fg_mask[fg_mask < attrs.mask_lower_zero] = 0
|
|
271
|
+
tot_sum_unnormalized = numpy.sum(fg_mask)
|
|
272
|
+
fg_mask[fg_mask >= attrs.mask_lower_zero] = 1
|
|
273
|
+
tot_sum_normalized = numpy.sum(fg_mask)
|
|
274
|
+
pc_normalized = 100 * tot_sum_normalized / fg_mask.size
|
|
275
|
+
pc_normalized = round(pc_normalized, 1)
|
|
276
|
+
# NB: not using the unnormalized value atm:
|
|
277
|
+
pc_unnormalized = (100 * tot_sum_unnormalized) / (fg_mask.size * 8 ** fg_mask.itemsize)
|
|
278
|
+
pc_unnormalized = round(pc_unnormalized, 1)
|
|
279
|
+
update_hist(when, gray_frame, pc_normalized, pc_unnormalized)
|
|
280
|
+
if pc_normalized != prev_pc_sum:
|
|
281
|
+
# NB: we have:
|
|
282
|
+
# fg_mask.size == 2073600 and fg_mask.itemsize == 1
|
|
283
|
+
if __debug__:
|
|
284
|
+
if pc_normalized is None or prev_pc_sum is None or round(pc_normalized, 0) != round(prev_pc_sum, 0):
|
|
285
|
+
logger.spam("pc_norm=%.2f pc_unnorm=%.2f hist=%s",
|
|
286
|
+
pc_normalized, pc_unnormalized, [(d1, d2, d3) for d1, _, d2, d3 in hist_values])
|
|
287
|
+
attrs.pc_sum = pc_normalized
|
|
288
|
+
prev_pc_sum = pc_normalized
|
|
289
|
+
#
|
|
290
|
+
cur_exclude_threshold = attrs.pc_high_exclude_threshold
|
|
291
|
+
is_detected = (
|
|
292
|
+
cur_pc_threshold <= pc_normalized < cur_exclude_threshold
|
|
293
|
+
)
|
|
294
|
+
self._check_path()
|
|
295
|
+
csv_writer = self._csv_writer
|
|
296
|
+
if csv_writer is not None:
|
|
297
|
+
row_dict.update(
|
|
298
|
+
Time=when,
|
|
299
|
+
Index=int(frame_perf_c * 1e9), # to be consistent with others csv data files using Time/Index
|
|
300
|
+
PercentSum=pc_normalized,
|
|
301
|
+
Motion=int(is_detected),
|
|
302
|
+
)
|
|
303
|
+
csv_writer.writerow(row_dict)
|
|
304
|
+
# now use everything available in hist_values:
|
|
305
|
+
is_detected = (
|
|
306
|
+
any(pc_normalized >= cur_pc_threshold
|
|
307
|
+
for _, _, pc_normalized, _ in hist_values)
|
|
308
|
+
) and not (
|
|
309
|
+
any(pc_normalized >= cur_exclude_threshold
|
|
310
|
+
for _, _, pc_normalized, _ in hist_values)
|
|
311
|
+
)
|
|
312
|
+
if is_detected != prev_detected:
|
|
313
|
+
logger.debug("presence detected: %.1f - %.1f ; hist=%s",
|
|
314
|
+
pc_normalized, pc_unnormalized, [(d1, d2, d3) for d1, _, d2, d3 in hist_values])
|
|
315
|
+
prev_detected = is_detected
|
|
316
|
+
with attrs.lock: # use lock to really ensure consistency
|
|
317
|
+
attrs.presence_detected = is_detected
|
|
318
|
+
if is_detected:
|
|
319
|
+
attrs.last_presence_start_perf_c = frame_perf_c
|
|
320
|
+
else:
|
|
321
|
+
attrs.last_absence_start_perf_c = frame_perf_c
|
|
322
|
+
prev_gray_frame = None # this will make us to get the following necessary next frames for next check
|
|
323
|
+
if show_report:
|
|
324
|
+
prev_pc_values.append((pc_normalized, pc_unnormalized))
|
|
325
|
+
processed_times.append(time.perf_counter() - loop_start_perf_now)
|
|
326
|
+
# end while not self._stop_requested
|