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,323 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import enum
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Type, Optional, Dict
|
|
5
|
+
from typing_extensions import Self
|
|
6
|
+
|
|
7
|
+
import yaml
|
|
8
|
+
|
|
9
|
+
from autotrainer.core.logging import get_verbose_logger
|
|
10
|
+
from .animal_presence_configuration import GlobalAnimalPresenceConfig
|
|
11
|
+
from .external_doors_monitor_configuration import ExternalDoorsMonitorConfig
|
|
12
|
+
from .presence_detection_configuration import PresenceDetectionConfig
|
|
13
|
+
from .system_fault_config import SystemFaultConfig
|
|
14
|
+
from .system_maintenance_config import SystemMaintenanceConfig
|
|
15
|
+
from .. import build_kwargs_apply_mapping, make_camelize_representer, make_decamelize_constructor, Offset3DTuple
|
|
16
|
+
|
|
17
|
+
from ..analysis import HeadbarPressureConfiguration
|
|
18
|
+
from .load_cell_config import LoadCellConfiguration, LoadCellAutoTareConfiguration
|
|
19
|
+
from ..analysis.audio_spectrum_monitor import AudioSpectrumThrashMonitorConfig
|
|
20
|
+
from .alarm_configuration import EmergencyAlarmConfiguration
|
|
21
|
+
from ..analysis.auto_tunnel_fan_monitor import AutoTunnelSweepConfiguration
|
|
22
|
+
|
|
23
|
+
logger = get_verbose_logger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclasses.dataclass
|
|
27
|
+
class ShiftXYZTarget:
|
|
28
|
+
|
|
29
|
+
# should be in Diamond coordinate system
|
|
30
|
+
x: float = 1.5
|
|
31
|
+
y: float = -3
|
|
32
|
+
z: float = 1
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def default_shift_xyz_target() -> Offset3DTuple:
|
|
36
|
+
return Offset3DTuple(**dataclasses.asdict(ShiftXYZTarget()))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def default_tongue_eaten_shift() -> Offset3DTuple:
|
|
40
|
+
return Offset3DTuple(0, 0.5, 0)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclasses.dataclass
|
|
44
|
+
class _ShiftXYZBufferHandlerConfig:
|
|
45
|
+
|
|
46
|
+
minimum_reach_fail: int = 10 # minimum nbr of failed reach, to make the mean/an entire processing of them
|
|
47
|
+
target: Offset3DTuple = field(default_factory=default_shift_xyz_target)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclasses.dataclass
|
|
51
|
+
class ShiftXYZBufferHandlerConfig(_ShiftXYZBufferHandlerConfig):
|
|
52
|
+
|
|
53
|
+
def __init__(self, **kwargs):
|
|
54
|
+
for c in "xyz":
|
|
55
|
+
kwargs.pop(f"target_{c}", None) # old config
|
|
56
|
+
super().__init__(**kwargs)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclasses.dataclass
|
|
60
|
+
class ShiftXYZHandlerConfig:
|
|
61
|
+
|
|
62
|
+
use_tongue_eaten: bool = True
|
|
63
|
+
tongue_eaten_shift: Offset3DTuple = field(default_factory=default_tongue_eaten_shift)
|
|
64
|
+
|
|
65
|
+
use_reach_buffer: bool = True
|
|
66
|
+
selected: str = "ShiftXYZBufferHandler"
|
|
67
|
+
buffer: ShiftXYZBufferHandlerConfig = field(default_factory=ShiftXYZBufferHandlerConfig)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass
|
|
71
|
+
class AutoCloseGateOnIntersessionConfiguration:
|
|
72
|
+
|
|
73
|
+
enabled: bool = False # enabled/disabled
|
|
74
|
+
session_min_duration: float = 5 # do not try close gate if session duration shorter than this
|
|
75
|
+
delay_after_cage_enter: float = 2.5 # only close gate once this delay since cage enter has elapsed
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass
|
|
79
|
+
class HomeOnExcessiveDriftDistanceConfiguration:
|
|
80
|
+
"""Execute, when in monitoring (should equal to be in deliver position), home to reset motors if measured drift distance is higher than threshold"""
|
|
81
|
+
|
|
82
|
+
enabled: bool = False
|
|
83
|
+
excessive_distance_threshold: float = 5 # mm
|
|
84
|
+
|
|
85
|
+
min_samples: int = 30
|
|
86
|
+
# only considerate if/when nbr of samples is greater than this.
|
|
87
|
+
# This can compensate small unsync between inference results and motor status positions,
|
|
88
|
+
# when the start of the sampling would be done right after the pellet-arm finished moving.
|
|
89
|
+
# The current inference is giving us ~15 datapoints per second,
|
|
90
|
+
# and almost same for the motor status position: ~10 / sec.
|
|
91
|
+
# So this requires/takes ~2 seconds of duration to get what's necessary.
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass
|
|
95
|
+
class PelletUncoverConfiguration:
|
|
96
|
+
min_y_dcs: float = 0 # mm, minimum Y dcs for all hand parts to be "valid" for uncover
|
|
97
|
+
trigger_delay: float = 1 # seconds, duration before real active/trigger to uncover when it's "valid"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass
|
|
101
|
+
class PelletDeliveryConfiguration:
|
|
102
|
+
"""
|
|
103
|
+
Behavior model options related to pellet delivery.
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
is_enabled: bool = False
|
|
107
|
+
"""When disabled not automatic behavior movement will be peformed, but eventually on application start"""
|
|
108
|
+
|
|
109
|
+
is_pellet_cover_enabled: bool = False
|
|
110
|
+
"""If enabled: cover pellet when session starts, and wait uncover condition"""
|
|
111
|
+
|
|
112
|
+
# not really related to pellet delivery but has been here since start:
|
|
113
|
+
is_intersession_analysis_enabled: bool = False
|
|
114
|
+
is_intersession_pellet_shift_enabled: bool = True
|
|
115
|
+
|
|
116
|
+
max_pellets_per_session: int = 10 # actually unused
|
|
117
|
+
max_pellets_per_day: int = 50 # actually unused
|
|
118
|
+
max_pellet_missing_seconds: float = 1.0 # how long to wait before load pellet when pellet missing/not seen
|
|
119
|
+
# this help ensure we don't execute a load pellet if we get an incorrect pose_result with pellet seen == False,
|
|
120
|
+
# which can happen eventually (missed inference detection basically).
|
|
121
|
+
|
|
122
|
+
auto_correct_motors_drift: bool = False # attempt "live" motor drift correction -- DISABLED in code
|
|
123
|
+
|
|
124
|
+
use_triangle_pellet_distance_too_far: bool = False
|
|
125
|
+
"""If enabled then a triangle-pellet too far distance also trigger a load-pellet"""
|
|
126
|
+
triangle_pellet_expected_distance: float = 5 # mm
|
|
127
|
+
triangle_pellet_diff_too_far_threshold: float = 1 # mm
|
|
128
|
+
|
|
129
|
+
@classmethod
|
|
130
|
+
def from_version_zero(cls, content: dict) -> Self:
|
|
131
|
+
return cls(**build_kwargs_apply_mapping(content, (
|
|
132
|
+
*(f.name for f in dataclasses.fields(cls)),
|
|
133
|
+
('is_enabled', 'is_deliver_pellet_enabled'),
|
|
134
|
+
('is_pellet_cover_enabled', 'is_cover_pellet_enabled'),
|
|
135
|
+
), skip_remaining=True))
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class HeadClampReleaseMode(str, enum.Enum):
|
|
139
|
+
ACTIVITY = "Activity"
|
|
140
|
+
FIXED_DURATION = "Fixed duration"
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@dataclass
|
|
144
|
+
class HeadClampConfiguration:
|
|
145
|
+
"""
|
|
146
|
+
Behavior model options related to the head clamp magnet including standard intensity and auto-clamp actions.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
baseline_intensity: float = 0.0
|
|
150
|
+
auto_clamp_intensity: float = 100.0
|
|
151
|
+
auto_clamp_release_tone_freq: int = 7000
|
|
152
|
+
auto_clamp_release_tone_delay: float = 0.1
|
|
153
|
+
before_reengage_delay: float = 5 # how long to wait/delay before allow/execute a re-engage after a disengage.
|
|
154
|
+
|
|
155
|
+
prerelease_intensity: float = 70 # absolute % value
|
|
156
|
+
prerelease_duration: float = 0 # seconds, if 0 then this pre-release is disabled / does not occur.
|
|
157
|
+
|
|
158
|
+
release_mode: str = HeadClampReleaseMode.ACTIVITY.value
|
|
159
|
+
|
|
160
|
+
# HeadClampReleaseMode.ACTIVITY
|
|
161
|
+
auto_clamp_no_activity_release_delay: float = 30 # seconds
|
|
162
|
+
auto_clamp_release_load_count: int = 100_000
|
|
163
|
+
|
|
164
|
+
# HeadClampReleaseMode.FIXED_DURATION
|
|
165
|
+
fixed_duration_release_delay: float = 30 # seconds
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@classmethod
|
|
169
|
+
def from_version_zero(cls, content: dict) -> Self:
|
|
170
|
+
return cls(**build_kwargs_apply_mapping(
|
|
171
|
+
content,
|
|
172
|
+
tuple(f.name for f in dataclasses.fields(cls)),
|
|
173
|
+
skip_remaining=True,
|
|
174
|
+
))
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@dataclasses.dataclass
|
|
178
|
+
class AutoEndSessionConfiguration:
|
|
179
|
+
# enabled: bool = True
|
|
180
|
+
|
|
181
|
+
no_activity_delay_minutes: int = 1
|
|
182
|
+
"""This is only for the Nose part. If it's not seen that much duration consecutively,
|
|
183
|
+
then also auto-end session"""
|
|
184
|
+
|
|
185
|
+
animal_tunnel_no_activity_delay: float = 10 # seconds
|
|
186
|
+
"""Delay without tunnel animal activity (== any animal part seen *and* low load-cell variance)
|
|
187
|
+
which triggers auto-end session"""
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@dataclasses.dataclass
|
|
191
|
+
class BatchSessionRecordingConfiguration:
|
|
192
|
+
|
|
193
|
+
enabled: bool = False
|
|
194
|
+
|
|
195
|
+
maximum_batch_size: int = 0
|
|
196
|
+
"""If 0: no max batch size, otherwise, once batch is over the size: force batch session processing"""
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@dataclass
|
|
200
|
+
class _BehaviorConfiguration:
|
|
201
|
+
pellet_delivery: PelletDeliveryConfiguration = field(default_factory=PelletDeliveryConfiguration)
|
|
202
|
+
pellet_uncover: PelletUncoverConfiguration = field(default_factory=PelletUncoverConfiguration)
|
|
203
|
+
shift_xyz_handler: ShiftXYZHandlerConfig = field(default_factory=ShiftXYZHandlerConfig)
|
|
204
|
+
head_clamp: HeadClampConfiguration = field(default_factory=HeadClampConfiguration)
|
|
205
|
+
load_cell: LoadCellConfiguration = field(default_factory=LoadCellConfiguration)
|
|
206
|
+
headbar_pressure: HeadbarPressureConfiguration = field(default_factory=HeadbarPressureConfiguration)
|
|
207
|
+
auto_tare: LoadCellAutoTareConfiguration = field(default_factory=LoadCellAutoTareConfiguration)
|
|
208
|
+
audio: AudioSpectrumThrashMonitorConfig = field(default_factory=AudioSpectrumThrashMonitorConfig)
|
|
209
|
+
global_animal_presence: GlobalAnimalPresenceConfig = field(default_factory=GlobalAnimalPresenceConfig)
|
|
210
|
+
emergency_alarm: EmergencyAlarmConfiguration = field(default_factory=EmergencyAlarmConfiguration)
|
|
211
|
+
external_doors: ExternalDoorsMonitorConfig = field(default_factory=ExternalDoorsMonitorConfig)
|
|
212
|
+
topcam_presence_detection: PresenceDetectionConfig = field(default_factory=PresenceDetectionConfig)
|
|
213
|
+
auto_end_session: AutoEndSessionConfiguration = field(default_factory=AutoEndSessionConfiguration)
|
|
214
|
+
auto_tunnel_sweep: AutoTunnelSweepConfiguration = field(default_factory=AutoTunnelSweepConfiguration)
|
|
215
|
+
batch_session_recording: BatchSessionRecordingConfiguration = field(default_factory=BatchSessionRecordingConfiguration)
|
|
216
|
+
auto_close_gate_on_intersession: AutoCloseGateOnIntersessionConfiguration = field(default_factory=AutoCloseGateOnIntersessionConfiguration)
|
|
217
|
+
home_on_excessive_drift_distance: HomeOnExcessiveDriftDistanceConfiguration = field(default_factory=HomeOnExcessiveDriftDistanceConfiguration)
|
|
218
|
+
system_maintenance: SystemMaintenanceConfig = field(default_factory=SystemMaintenanceConfig)
|
|
219
|
+
system_fault: SystemFaultConfig = field(default_factory=SystemFaultConfig)
|
|
220
|
+
|
|
221
|
+
@classmethod
|
|
222
|
+
def from_version_zero(cls, content: Dict) -> Self:
|
|
223
|
+
configuration = cls()
|
|
224
|
+
|
|
225
|
+
if "head_fix" in content:
|
|
226
|
+
if "load_cell" in content["head_fix"]:
|
|
227
|
+
configuration.load_cell = LoadCellConfiguration.from_version_zero(content["head_fix"]["load_cell"])
|
|
228
|
+
if "headbar_pressure" in content["head_fix"]:
|
|
229
|
+
configuration.headbar_pressure = HeadbarPressureConfiguration.from_version_zero(
|
|
230
|
+
content["head_fix"]["headbar_pressure"]
|
|
231
|
+
)
|
|
232
|
+
if "auto_tare" in content["head_fix"]:
|
|
233
|
+
configuration.auto_tare = LoadCellAutoTareConfiguration.from_version_zero(
|
|
234
|
+
content["head_fix"]["auto_tare"])
|
|
235
|
+
|
|
236
|
+
if "behavior" in content:
|
|
237
|
+
configuration.head_clamp = HeadClampConfiguration.from_version_zero(content["behavior"])
|
|
238
|
+
configuration.pellet_delivery = PelletDeliveryConfiguration.from_version_zero(content["behavior"])
|
|
239
|
+
|
|
240
|
+
return configuration
|
|
241
|
+
|
|
242
|
+
@classmethod
|
|
243
|
+
def from_version_one(cls, content: Dict):
|
|
244
|
+
headclamp = content.get("head_clamp", {})
|
|
245
|
+
headclamp.pop('max_baseline_intensity')
|
|
246
|
+
headclamp.pop('baseline_intensity_increment')
|
|
247
|
+
baseline = headclamp.pop('min_baseline_intensity')
|
|
248
|
+
if baseline is not None:
|
|
249
|
+
headclamp['baseline_intensity'] = baseline
|
|
250
|
+
return cls(
|
|
251
|
+
load_cell=LoadCellConfiguration.from_version_one(content.get("load_cell", {})),
|
|
252
|
+
headbar_pressure=HeadbarPressureConfiguration(**content.get("headbar_pressure", {})),
|
|
253
|
+
auto_tare=LoadCellAutoTareConfiguration(**content.get("auto_tare", {})),
|
|
254
|
+
head_clamp=HeadClampConfiguration(**headclamp),
|
|
255
|
+
pellet_delivery=PelletDeliveryConfiguration(**content.get("pellet_delivery", {})),
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
@dataclasses.dataclass
|
|
260
|
+
class BehaviorConfiguration(_BehaviorConfiguration):
|
|
261
|
+
# NB: having to subclass _BehaviorConfiguration dataclass type to allow to customize init signature (and body):
|
|
262
|
+
|
|
263
|
+
def __init__(self,
|
|
264
|
+
*,
|
|
265
|
+
mouse_presence=None, # temporarily to be back-compatible with previous
|
|
266
|
+
**kwargs):
|
|
267
|
+
if mouse_presence is not None:
|
|
268
|
+
logger.notice("Dropping previous mouse_presence config, new default one will be used. dropped entry: %s",
|
|
269
|
+
mouse_presence)
|
|
270
|
+
super().__init__(**kwargs)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
_cls_2_tag = {
|
|
274
|
+
PelletDeliveryConfiguration: "PelletDeliveryConfiguration",
|
|
275
|
+
PelletUncoverConfiguration: "PelletUncoverConfiguration",
|
|
276
|
+
LoadCellConfiguration: "LoadCellConfiguration",
|
|
277
|
+
HeadClampConfiguration: "HeadClampConfiguration",
|
|
278
|
+
HeadbarPressureConfiguration: "HeadbarPressureConfiguration",
|
|
279
|
+
LoadCellAutoTareConfiguration: "LoadCellAutoTareConfiguration",
|
|
280
|
+
BehaviorConfiguration: "BehaviorConfiguration",
|
|
281
|
+
AudioSpectrumThrashMonitorConfig: "AudioMonitorConfiguration",
|
|
282
|
+
GlobalAnimalPresenceConfig: "AnimalPresenceConfiguration",
|
|
283
|
+
EmergencyAlarmConfiguration: "EmergencyAlarmConfiguration",
|
|
284
|
+
PresenceDetectionConfig: "PresenceDetectionConfiguration",
|
|
285
|
+
ExternalDoorsMonitorConfig: "ExternalDoorsMonitorConfiguration",
|
|
286
|
+
AutoEndSessionConfiguration: "AutoEndSessionConfiguration",
|
|
287
|
+
AutoTunnelSweepConfiguration: "AutoTunnelSweepConfiguration",
|
|
288
|
+
BatchSessionRecordingConfiguration: "BatchSessionRecordingConfiguration",
|
|
289
|
+
AutoCloseGateOnIntersessionConfiguration: "AutoCloseGateOnIntersessionConfiguration",
|
|
290
|
+
HomeOnExcessiveDriftDistanceConfiguration: "HomeOnExcessiveDriftDistance", # missed Configuration suffix
|
|
291
|
+
# ShiftXYZTarget: "ShiftXYZTarget", # replaced by Offset3dTuple.
|
|
292
|
+
ShiftXYZHandlerConfig: "ShiftXYZHandlerConfiguration",
|
|
293
|
+
ShiftXYZBufferHandlerConfig: "ShiftXYZBufferHandlerConfiguration",
|
|
294
|
+
SystemMaintenanceConfig: "SystemMaintenanceConfig",
|
|
295
|
+
SystemFaultConfig: "SystemFaultConfig",
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def add_behavior_configuration_representers(dumper: Type[yaml.SafeDumper]):
|
|
300
|
+
def add(klass, tagname):
|
|
301
|
+
dumper.add_representer(klass, make_camelize_representer(f"!{tagname}"))
|
|
302
|
+
|
|
303
|
+
for cls, tag in _cls_2_tag.items():
|
|
304
|
+
add(cls, tag)
|
|
305
|
+
|
|
306
|
+
from autotrainer.core.configuration import repr_offset3d_tuple
|
|
307
|
+
|
|
308
|
+
dumper.add_representer(ShiftXYZTarget, repr_offset3d_tuple)
|
|
309
|
+
# changed to use Offset3dTuple
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def add_behavior_configuration_constructors(safe_loader: Type[yaml.SafeLoader]):
|
|
313
|
+
|
|
314
|
+
def add(klass, tagname):
|
|
315
|
+
safe_loader.add_constructor(f"!{tagname}", make_decamelize_constructor(klass))
|
|
316
|
+
|
|
317
|
+
for cls, tag in _cls_2_tag.items():
|
|
318
|
+
add(cls, tag)
|
|
319
|
+
|
|
320
|
+
add(GlobalAnimalPresenceConfig, "MousePresenceConfiguration")
|
|
321
|
+
# keeping temporarily MousePresenceConfiguration, was renamed to AnimalPresenceConfiguration. Back-compatibility.
|
|
322
|
+
# todo: remove some when later.
|
|
323
|
+
#
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import urllib.parse
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from enum import Enum, IntEnum
|
|
5
|
+
from typing import Dict, Any
|
|
6
|
+
|
|
7
|
+
from typing_extensions import Self
|
|
8
|
+
|
|
9
|
+
import yaml
|
|
10
|
+
|
|
11
|
+
from autotrainer.core.logging import get_verbose_logger
|
|
12
|
+
from autotrainer.core import make_camelize_representer, make_decamelize_constructor
|
|
13
|
+
from autotrainer.core.configuration import SystemConfigurationDumper
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
logger = get_verbose_logger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CameraId(IntEnum):
|
|
20
|
+
Left = 0
|
|
21
|
+
Right = 1
|
|
22
|
+
Web = 2
|
|
23
|
+
|
|
24
|
+
def __str__(self) -> str:
|
|
25
|
+
# Used as part of video file and related naming conventions.
|
|
26
|
+
if self == CameraId.Left:
|
|
27
|
+
return "left"
|
|
28
|
+
elif self == CameraId.Right:
|
|
29
|
+
return "right"
|
|
30
|
+
elif self == CameraId.Web:
|
|
31
|
+
return "web"
|
|
32
|
+
else:
|
|
33
|
+
raise ValueError(f"Invalid camera id: {self}")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class CameraConfiguration:
|
|
38
|
+
id: CameraId = CameraId.Left
|
|
39
|
+
name: str = "(unnamed)"
|
|
40
|
+
is_enabled: bool = False
|
|
41
|
+
is_record_enabled: bool = False
|
|
42
|
+
record_mode: int = 0
|
|
43
|
+
record_prebuffer_duration: float = 1 # seconds, delay of pre-buffer to use/record when start-recording is executed
|
|
44
|
+
is_still_image_capture_enabled: bool = False
|
|
45
|
+
still_image_capture_interval: float = 5.0
|
|
46
|
+
scheme: str = ""
|
|
47
|
+
host: str = ""
|
|
48
|
+
port: int = 0
|
|
49
|
+
path: str = ""
|
|
50
|
+
params: Dict[str, Any] = field(default_factory=dict)
|
|
51
|
+
|
|
52
|
+
def __post_init__(self):
|
|
53
|
+
if not isinstance(self.id, CameraId):
|
|
54
|
+
self.id = CameraId(self.id)
|
|
55
|
+
if "%" in self.path:
|
|
56
|
+
# this could possibly be removed later when ~all present config files would be already fixed.
|
|
57
|
+
self.path = urllib.parse.unquote(self.path)
|
|
58
|
+
logger.info("Replaced %%-encoded path from configuration with unquoted one: %r", self.path)
|
|
59
|
+
self.path = re.sub(r"/+", "/", self.path) # sanitize
|
|
60
|
+
|
|
61
|
+
@classmethod
|
|
62
|
+
def from_version_zero(cls, name: str, content: dict) -> Self:
|
|
63
|
+
if name == "camera1":
|
|
64
|
+
camera_id = CameraId.Left
|
|
65
|
+
elif name == "camera2":
|
|
66
|
+
camera_id = CameraId.Right
|
|
67
|
+
else:
|
|
68
|
+
camera_id = CameraId.Web
|
|
69
|
+
|
|
70
|
+
if "camera" in content:
|
|
71
|
+
camera = content["camera"]
|
|
72
|
+
else:
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
known_params = ["scheme", "host", "path", "port"]
|
|
76
|
+
|
|
77
|
+
params = dict()
|
|
78
|
+
|
|
79
|
+
for key in camera:
|
|
80
|
+
if key not in known_params:
|
|
81
|
+
params[key] = camera[key]
|
|
82
|
+
|
|
83
|
+
return cls(
|
|
84
|
+
id=camera_id,
|
|
85
|
+
name=content.get("name", "(unnamed)"),
|
|
86
|
+
is_enabled=content.get("is_enabled", False),
|
|
87
|
+
is_record_enabled=content.get("is_record_enabled", False),
|
|
88
|
+
record_mode=content.get("record_mode", 0),
|
|
89
|
+
is_still_image_capture_enabled=content.get("is_still_image_capture_enabled", False),
|
|
90
|
+
still_image_capture_interval=content.get("still_image_capture_interval", 0.0),
|
|
91
|
+
scheme=camera.get("scheme", ""),
|
|
92
|
+
host=camera.get("host", ""),
|
|
93
|
+
port=camera.get("port", 0),
|
|
94
|
+
path=camera.get("path", ""),
|
|
95
|
+
params=params
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
camera_configuration_representer = make_camelize_representer("!CameraConfiguration")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def camera_id_representer(dumper: yaml.SafeDumper, obj):
|
|
103
|
+
return dumper.represent_data(int(obj))
|
|
104
|
+
|
|
105
|
+
SystemConfigurationDumper.add_representer(CameraId, camera_id_representer)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
camera_configuration_constructor = make_decamelize_constructor(CameraConfiguration)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
from typing_extensions import Self
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
from autotrainer.core import make_camelize_representer, make_decamelize_constructor
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class HardwareConfiguration:
|
|
12
|
+
tunnel_identifier: str = ""
|
|
13
|
+
pellet_identifier: str = ""
|
|
14
|
+
|
|
15
|
+
min_ack_timeout: Optional[float] = None # min device-ack-timeout
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
18
|
+
def from_version_zero(cls, content: dict) -> Self:
|
|
19
|
+
configuration = cls()
|
|
20
|
+
|
|
21
|
+
if "head_fix" in content:
|
|
22
|
+
configuration.tunnel_identifier = content["head_fix"].get("port", "")
|
|
23
|
+
if "pellet_delivery" in content:
|
|
24
|
+
configuration.pellet_identifier = content["pellet_delivery"].get("port", "")
|
|
25
|
+
|
|
26
|
+
return configuration
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
hardware_configuration_representer = make_camelize_representer("!HardwareConfiguration")
|
|
30
|
+
hardware_configuration_constructor = make_decamelize_constructor(HardwareConfiguration)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from typing_extensions import Self
|
|
4
|
+
|
|
5
|
+
from autotrainer.core import build_kwargs_apply_mapping, make_camelize_representer, make_decamelize_constructor
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class InferenceConfiguration:
|
|
10
|
+
pose_model_location: str = ""
|
|
11
|
+
is_enabled: bool = False
|
|
12
|
+
intersession_wait_time: float = 2.0
|
|
13
|
+
"""
|
|
14
|
+
The amount of time to wait for the video files to be available for processing. This is more of a system property
|
|
15
|
+
than an algorithm/behavior property.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def from_version_zero(cls, content: dict) -> Self:
|
|
20
|
+
return cls(**build_kwargs_apply_mapping(content, (
|
|
21
|
+
('pose_model_location', 'model'),
|
|
22
|
+
)))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
inference_configuration_representer = make_camelize_representer("!InferenceConfiguration")
|
|
26
|
+
inference_configuration_constructor = make_decamelize_constructor(InferenceConfiguration)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Dict, Any
|
|
3
|
+
from typing_extensions import Self
|
|
4
|
+
|
|
5
|
+
from autotrainer.core import build_kwargs_apply_mapping
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class LoadCellConfiguration:
|
|
10
|
+
# NB: this is the current/previous value used on agx001:
|
|
11
|
+
weight_active_threshold: float = 2 # grams ; if above then will become engaged if above for threshold_duration
|
|
12
|
+
weight_inactive_threshold: float = 2 # grams ;
|
|
13
|
+
# if below then will become disengaged if below for more than
|
|
14
|
+
|
|
15
|
+
threshold_duration: float = 0.25
|
|
16
|
+
# duration threshold for engaged or thrashing_detected, must remain during that delay to make the change
|
|
17
|
+
|
|
18
|
+
min_event_duration: float = 5.0
|
|
19
|
+
min_post_event_hold_duration: float = 2.0
|
|
20
|
+
# delay before inactive if was engaged/active for more than min_event_duration
|
|
21
|
+
|
|
22
|
+
thrashing_var_weight_threshold_min: float = 20 # grams
|
|
23
|
+
thrashing_var_weight_threshold_max: float = 30 # grams
|
|
24
|
+
thrashing_var_min_delay: float = 0.05 # seconds
|
|
25
|
+
thrashing_var_max_delay: float = 0.2 # seconds
|
|
26
|
+
thrashing_min_ptp_change_count: int = 3 # nbr of "ptp" change needed in a row during var_max_delay
|
|
27
|
+
|
|
28
|
+
# allow to filter out values smaller than min, or larger than max :
|
|
29
|
+
weight_min_filter: float = -500 # grams
|
|
30
|
+
weight_max_filter: float = 500 # grams
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def from_version_zero(cls, content: Dict[str, Any]) -> Self:
|
|
34
|
+
return cls(**build_kwargs_apply_mapping(content, (
|
|
35
|
+
('weight_active_threshold', 'load_trigger'),
|
|
36
|
+
('threshold_duration', 'min_load_on_duration'),
|
|
37
|
+
('min_post_event_hold_duration', 'min_load_off_duration'),
|
|
38
|
+
)))
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def from_version_one(cls, content: Dict[str, Any]) -> Self:
|
|
42
|
+
return cls(**build_kwargs_apply_mapping(content, (
|
|
43
|
+
('weight_active_threshold', 'threshold'),
|
|
44
|
+
)))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class LoadCellAutoTareConfiguration:
|
|
49
|
+
threshold: float = 0.1
|
|
50
|
+
range_threshold: float = 0.75
|
|
51
|
+
duration: float = 2.0
|
|
52
|
+
sample_rate: int = 100
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def from_version_zero(cls, content: dict) -> Self:
|
|
56
|
+
kwargs = {}
|
|
57
|
+
for k in {"threshold", "range_threshold", "duration", "sample_rate"}:
|
|
58
|
+
val = content.get(k)
|
|
59
|
+
if val is not None:
|
|
60
|
+
kwargs[k] = val
|
|
61
|
+
return cls(**kwargs)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import ClassVar
|
|
3
|
+
|
|
4
|
+
from typing_extensions import Self
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from autotrainer.core import make_camelize_representer, make_decamelize_constructor
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class PersistenceConfiguration:
|
|
12
|
+
|
|
13
|
+
DEFAULT_OUTPUT_PATH: ClassVar[Path] = Path("~/Documents/RawDataLocal") # must use .expanduser() on it
|
|
14
|
+
|
|
15
|
+
output_location: str = DEFAULT_OUTPUT_PATH.expanduser().as_posix()
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
18
|
+
def get_default_output_path(cls) -> Path:
|
|
19
|
+
return cls.DEFAULT_OUTPUT_PATH.expanduser()
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def from_version_zero(cls, content: dict) -> Self:
|
|
23
|
+
return cls(
|
|
24
|
+
output_location=content.get("output_location", "")
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
persistence_configuration_representer = make_camelize_representer("!PersistenceConfiguration")
|
|
29
|
+
persistence_configuration_constructor = make_decamelize_constructor(PersistenceConfiguration)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@dataclasses.dataclass
|
|
5
|
+
class PresenceDetectionConfig:
|
|
6
|
+
pc_threshold: float = 2.2 # percent - lower makes more sensible to noise
|
|
7
|
+
pc_high_exclude_threshold: float = 85 # percent ; this is to exclude too big % diff due to switch light ON/OFF.
|
|
8
|
+
mask_lower_zero: int = 8 # gray value. this is "smooth" the difference, to be less sensible to noise. from 0 -> 255
|
|
9
|
+
max_delay_skip_threshold: float = 0.5 # seconds, how much to keep/look at diff over previous frames
|