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.
Files changed (73) hide show
  1. auto_trainer_core-2.0.1.dev349.dist-info/METADATA +19 -0
  2. auto_trainer_core-2.0.1.dev349.dist-info/RECORD +73 -0
  3. auto_trainer_core-2.0.1.dev349.dist-info/WHEEL +5 -0
  4. auto_trainer_core-2.0.1.dev349.dist-info/top_level.txt +1 -0
  5. autotrainer/core/__init__.py +319 -0
  6. autotrainer/core/analysis/__init__.py +6 -0
  7. autotrainer/core/analysis/alarm_monitor.py +484 -0
  8. autotrainer/core/analysis/audio_spectrum_data.py +27 -0
  9. autotrainer/core/analysis/audio_spectrum_monitor.py +134 -0
  10. autotrainer/core/analysis/auto_tunnel_fan_monitor.py +92 -0
  11. autotrainer/core/analysis/detector.py +179 -0
  12. autotrainer/core/analysis/external_doors_monitor.py +124 -0
  13. autotrainer/core/analysis/global_animal_presence_monitor.py +69 -0
  14. autotrainer/core/analysis/head_fix_measurement.py +15 -0
  15. autotrainer/core/analysis/headbar_pressure_monitor.py +158 -0
  16. autotrainer/core/analysis/load_cell_monitor.py +377 -0
  17. autotrainer/core/analysis/load_cell_tare_monitor.py +217 -0
  18. autotrainer/core/analysis/measurement_data.py +80 -0
  19. autotrainer/core/analysis/pellet_position_monitor.py +108 -0
  20. autotrainer/core/analysis/sensor_analysis.py +390 -0
  21. autotrainer/core/analysis/system_fault_monitor.py +81 -0
  22. autotrainer/core/analysis/system_maintenance_monitor.py +88 -0
  23. autotrainer/core/animal/__init__.py +1 -0
  24. autotrainer/core/animal/animal_subject.py +208 -0
  25. autotrainer/core/capture.py +25 -0
  26. autotrainer/core/configuration/__init__.py +88 -0
  27. autotrainer/core/configuration/alarm_configuration.py +55 -0
  28. autotrainer/core/configuration/animal_presence_configuration.py +17 -0
  29. autotrainer/core/configuration/behavior_configuration.py +323 -0
  30. autotrainer/core/configuration/camera_configuration.py +108 -0
  31. autotrainer/core/configuration/external_doors_monitor_configuration.py +9 -0
  32. autotrainer/core/configuration/hardware_configuration.py +30 -0
  33. autotrainer/core/configuration/inference_configuration.py +26 -0
  34. autotrainer/core/configuration/load_cell_config.py +61 -0
  35. autotrainer/core/configuration/persistence_configuration.py +29 -0
  36. autotrainer/core/configuration/presence_detection_configuration.py +9 -0
  37. autotrainer/core/configuration/system_configuration.py +211 -0
  38. autotrainer/core/configuration/system_fault_config.py +9 -0
  39. autotrainer/core/configuration/system_maintenance_config.py +17 -0
  40. autotrainer/core/diamond_triangle_config.py +150 -0
  41. autotrainer/core/event/__init__.py +54 -0
  42. autotrainer/core/event/api_event_kind.py +5 -0
  43. autotrainer/core/event/api_event_plugin.py +57 -0
  44. autotrainer/core/event/event_info.py +53 -0
  45. autotrainer/core/event/event_manager.py +296 -0
  46. autotrainer/core/event/event_manager_plugin.py +45 -0
  47. autotrainer/core/event/file_event_plugin.py +91 -0
  48. autotrainer/core/event/logger_event_plugin.py +43 -0
  49. autotrainer/core/fixed_array_multiqueue.py +263 -0
  50. autotrainer/core/fixed_array_queue.py +124 -0
  51. autotrainer/core/frame_index.py +34 -0
  52. autotrainer/core/interfaces.py +347 -0
  53. autotrainer/core/logging.py +714 -0
  54. autotrainer/core/message/__init__.py +26 -0
  55. autotrainer/core/message/audio_spectrum_message.py +21 -0
  56. autotrainer/core/message/measurement_message.py +61 -0
  57. autotrainer/core/message/message_handler.py +158 -0
  58. autotrainer/core/message/motor_configuration_message.py +128 -0
  59. autotrainer/core/message/system_command_kind.py +74 -0
  60. autotrainer/core/message/system_message_handler.py +122 -0
  61. autotrainer/core/message/system_status_message.py +112 -0
  62. autotrainer/core/multiproc.py +50 -0
  63. autotrainer/core/notification/__init__.py +2 -0
  64. autotrainer/core/notification/notification_center.py +80 -0
  65. autotrainer/core/notification/trigger_notification.py +27 -0
  66. autotrainer/core/observable_object.py +105 -0
  67. autotrainer/core/perf_monitor.py +77 -0
  68. autotrainer/core/pose_elements.py +196 -0
  69. autotrainer/core/project/__init__.py +19 -0
  70. autotrainer/core/project/project_info.py +486 -0
  71. autotrainer/core/queue_util.py +60 -0
  72. autotrainer/core/reach_event.py +38 -0
  73. 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,9 @@
1
+
2
+
3
+ import dataclasses
4
+
5
+
6
+ @dataclasses.dataclass
7
+ class ExternalDoorsMonitorConfig:
8
+
9
+ trigger_open_delay: float = 3
@@ -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