lab-camera-optimizer 1.0.2__tar.gz → 1.0.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {lab_camera_optimizer-1.0.2/lab_camera_optimizer.egg-info → lab_camera_optimizer-1.0.3}/PKG-INFO +1 -1
- {lab_camera_optimizer-1.0.2 → lab_camera_optimizer-1.0.3}/init_project.py +23 -12
- lab_camera_optimizer-1.0.3/lab_camera_optimizer/__init__.py +3 -0
- lab_camera_optimizer-1.0.3/lab_camera_optimizer/configs/T_zone_direction_change.yaml +200 -0
- lab_camera_optimizer-1.0.3/lab_camera_optimizer/configs/example_real_world.yaml +270 -0
- lab_camera_optimizer-1.0.3/lab_camera_optimizer/configs/example_simple.yaml +180 -0
- lab_camera_optimizer-1.0.3/lab_camera_optimizer/configs/labo_CHU.yaml +268 -0
- {lab_camera_optimizer-1.0.2 → lab_camera_optimizer-1.0.3/lab_camera_optimizer.egg-info}/PKG-INFO +1 -1
- {lab_camera_optimizer-1.0.2 → lab_camera_optimizer-1.0.3}/lab_camera_optimizer.egg-info/SOURCES.txt +6 -1
- {lab_camera_optimizer-1.0.2 → lab_camera_optimizer-1.0.3}/lab_camera_optimizer.egg-info/top_level.txt +1 -0
- {lab_camera_optimizer-1.0.2 → lab_camera_optimizer-1.0.3}/pyproject.toml +3 -3
- {lab_camera_optimizer-1.0.2 → lab_camera_optimizer-1.0.3}/LICENSE +0 -0
- {lab_camera_optimizer-1.0.2 → lab_camera_optimizer-1.0.3}/MANIFEST.in +0 -0
- {lab_camera_optimizer-1.0.2 → lab_camera_optimizer-1.0.3}/README.md +0 -0
- {lab_camera_optimizer-1.0.2 → lab_camera_optimizer-1.0.3}/configs/T_zone_direction_change.yaml +0 -0
- {lab_camera_optimizer-1.0.2 → lab_camera_optimizer-1.0.3}/configs/example_real_world.yaml +0 -0
- {lab_camera_optimizer-1.0.2 → lab_camera_optimizer-1.0.3}/configs/example_simple.yaml +0 -0
- {lab_camera_optimizer-1.0.2 → lab_camera_optimizer-1.0.3}/configs/labo_CHU.yaml +0 -0
- {lab_camera_optimizer-1.0.2 → lab_camera_optimizer-1.0.3}/core/__init__.py +0 -0
- {lab_camera_optimizer-1.0.2 → lab_camera_optimizer-1.0.3}/core/candidates.py +0 -0
- {lab_camera_optimizer-1.0.2 → lab_camera_optimizer-1.0.3}/core/config_loader.py +0 -0
- {lab_camera_optimizer-1.0.2 → lab_camera_optimizer-1.0.3}/core/greedy.py +0 -0
- {lab_camera_optimizer-1.0.2 → lab_camera_optimizer-1.0.3}/core/room.py +0 -0
- {lab_camera_optimizer-1.0.2 → lab_camera_optimizer-1.0.3}/core/scoring.py +0 -0
- {lab_camera_optimizer-1.0.2 → lab_camera_optimizer-1.0.3}/core/visualize.py +0 -0
- {lab_camera_optimizer-1.0.2 → lab_camera_optimizer-1.0.3}/lab_camera_optimizer.egg-info/dependency_links.txt +0 -0
- {lab_camera_optimizer-1.0.2 → lab_camera_optimizer-1.0.3}/lab_camera_optimizer.egg-info/entry_points.txt +0 -0
- {lab_camera_optimizer-1.0.2 → lab_camera_optimizer-1.0.3}/lab_camera_optimizer.egg-info/requires.txt +0 -0
- {lab_camera_optimizer-1.0.2 → lab_camera_optimizer-1.0.3}/optimize.py +0 -0
- {lab_camera_optimizer-1.0.2 → lab_camera_optimizer-1.0.3}/preview_room.py +0 -0
- {lab_camera_optimizer-1.0.2 → lab_camera_optimizer-1.0.3}/setup.cfg +0 -0
|
@@ -13,20 +13,31 @@ import sys
|
|
|
13
13
|
|
|
14
14
|
def _find_bundled_configs():
|
|
15
15
|
"""
|
|
16
|
-
Locate the bundled configs/ directory
|
|
17
|
-
|
|
18
|
-
-
|
|
16
|
+
Locate the bundled configs/ directory.
|
|
17
|
+
After pip install, configs live inside the lab_camera_optimizer package:
|
|
18
|
+
site-packages/lab_camera_optimizer/configs/
|
|
19
|
+
When run locally from the repo, they are at:
|
|
20
|
+
./configs/ or ./lab_camera_optimizer/configs/
|
|
19
21
|
"""
|
|
20
|
-
|
|
22
|
+
import importlib.util
|
|
23
|
+
|
|
24
|
+
# 1. Try via the installed lab_camera_optimizer package (pip install path)
|
|
25
|
+
spec = importlib.util.find_spec("lab_camera_optimizer")
|
|
26
|
+
if spec and spec.origin:
|
|
27
|
+
pkg_dir = os.path.dirname(spec.origin)
|
|
28
|
+
candidate = os.path.join(pkg_dir, "configs")
|
|
29
|
+
if os.path.isdir(candidate):
|
|
30
|
+
return candidate
|
|
31
|
+
|
|
32
|
+
# 2. Local repo: configs/ next to this file
|
|
21
33
|
here = os.path.dirname(os.path.abspath(__file__))
|
|
22
|
-
candidate
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return candidate
|
|
34
|
+
for candidate in [
|
|
35
|
+
os.path.join(here, "configs"),
|
|
36
|
+
os.path.join(here, "lab_camera_optimizer", "configs"),
|
|
37
|
+
os.path.join(os.path.dirname(here), "configs"),
|
|
38
|
+
]:
|
|
39
|
+
if os.path.isdir(candidate):
|
|
40
|
+
return candidate
|
|
30
41
|
|
|
31
42
|
return None
|
|
32
43
|
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# =============================================================
|
|
2
|
+
# Lab Camera Optimizer — T-shaped capture zone
|
|
3
|
+
# Use case: approach run + 90° direction change at the END
|
|
4
|
+
#
|
|
5
|
+
# Room : 12m × 7m rectangle with a small structural pillar
|
|
6
|
+
# Cameras: 10 wall-mounted cameras at 2.0m or 2.2m height
|
|
7
|
+
#
|
|
8
|
+
# T-shape (RELATIVE coordinates, junction at the end of the run):
|
|
9
|
+
#
|
|
10
|
+
# [8,-1.5]──[10,-1.5]
|
|
11
|
+
# │ left │ ← turn left priority 1.8
|
|
12
|
+
# │ turn │
|
|
13
|
+
# [0,0]──────────────────[8, 0]──[10, 0]
|
|
14
|
+
# │ approach (8m×1m) │ inter │ priority 1.0 / 2.5
|
|
15
|
+
# [0,1]──────────────────[8, 1]──[10, 1]
|
|
16
|
+
# │ right │ ← turn right priority 1.8
|
|
17
|
+
# │ turn │
|
|
18
|
+
# [8, 2.5]──[10,2.5]
|
|
19
|
+
#
|
|
20
|
+
# Approach arm : X=0→8, Y=0→1 (8m long × 1m wide)
|
|
21
|
+
# Turn arm : X=8→10, Y=-1.5→2.5 (2m wide × 4m total)
|
|
22
|
+
# Intersection : X=8→10, Y=0→1 (2m × 1m — end of run)
|
|
23
|
+
#
|
|
24
|
+
# The whole T translates via x_offsets / y_offsets.
|
|
25
|
+
# =============================================================
|
|
26
|
+
|
|
27
|
+
# ─────────────────────────────────────────────────────────────
|
|
28
|
+
# 1. ROOM
|
|
29
|
+
# ─────────────────────────────────────────────────────────────
|
|
30
|
+
room:
|
|
31
|
+
corners:
|
|
32
|
+
- [0, 0]
|
|
33
|
+
- [12, 0]
|
|
34
|
+
- [12, 7]
|
|
35
|
+
- [0, 7]
|
|
36
|
+
height: 3.0
|
|
37
|
+
|
|
38
|
+
# ─────────────────────────────────────────────────────────────
|
|
39
|
+
# 2. OBSTACLES
|
|
40
|
+
# ─────────────────────────────────────────────────────────────
|
|
41
|
+
obstacles:
|
|
42
|
+
- type: rect
|
|
43
|
+
bounds: [5.8, 0.0, 6.1, 0.3]
|
|
44
|
+
height: 3.0
|
|
45
|
+
label: "Pillar"
|
|
46
|
+
can_mount_camera: false
|
|
47
|
+
|
|
48
|
+
# ─────────────────────────────────────────────────────────────
|
|
49
|
+
# 3. SUBJECT
|
|
50
|
+
# ─────────────────────────────────────────────────────────────
|
|
51
|
+
subject:
|
|
52
|
+
height: 1.9
|
|
53
|
+
foot_z: 0.0
|
|
54
|
+
|
|
55
|
+
# ─────────────────────────────────────────────────────────────
|
|
56
|
+
# 4. CAMERA SETS
|
|
57
|
+
# ─────────────────────────────────────────────────────────────
|
|
58
|
+
camera_sets:
|
|
59
|
+
|
|
60
|
+
- id: "cam_A"
|
|
61
|
+
name: "Wall camera"
|
|
62
|
+
mounting: "wall"
|
|
63
|
+
optional: false
|
|
64
|
+
fov_h_landscape: 110.0
|
|
65
|
+
fov_v_landscape: 70.0
|
|
66
|
+
fov_h_portrait: 70.0
|
|
67
|
+
fov_v_portrait: 110.0
|
|
68
|
+
max_range: 12.0
|
|
69
|
+
min_range: 0.5
|
|
70
|
+
height_options: [2.0, 2.2]
|
|
71
|
+
max_count: 10
|
|
72
|
+
min_spacing: 1.2
|
|
73
|
+
score_weight: 1.0
|
|
74
|
+
color: "#1f77b4"
|
|
75
|
+
|
|
76
|
+
- id: "cam_B"
|
|
77
|
+
name: "Tripod camera"
|
|
78
|
+
mounting: "tripod"
|
|
79
|
+
optional: true
|
|
80
|
+
fov_h_landscape: 80.0
|
|
81
|
+
fov_v_landscape: 58.0
|
|
82
|
+
fov_h_portrait: 58.0
|
|
83
|
+
fov_v_portrait: 80.0
|
|
84
|
+
max_range: 8.0
|
|
85
|
+
min_range: 0.5
|
|
86
|
+
height_options: [1.5]
|
|
87
|
+
max_count: 0 # disabled — set > 0 to re-enable
|
|
88
|
+
min_spacing: 1.5
|
|
89
|
+
walk_axis_margin: 0.7
|
|
90
|
+
score_weight: 0.6
|
|
91
|
+
color: "#d62728"
|
|
92
|
+
|
|
93
|
+
# ─────────────────────────────────────────────────────────────
|
|
94
|
+
# 5. CAPTURE ZONES
|
|
95
|
+
# ─────────────────────────────────────────────────────────────
|
|
96
|
+
#
|
|
97
|
+
# All coordinates are RELATIVE.
|
|
98
|
+
# The entire T moves together via the shared x_offsets / y_offsets.
|
|
99
|
+
#
|
|
100
|
+
# Visual (relative coords):
|
|
101
|
+
#
|
|
102
|
+
# [8,-1.5]──[10,-1.5]
|
|
103
|
+
# │ LEFT │
|
|
104
|
+
# │ TURN │ priority 1.8
|
|
105
|
+
# [0,0]──────────────────[8, 0 ]──[10, 0]
|
|
106
|
+
# │ ║ INTER ║
|
|
107
|
+
# │ APPROACH (8m×1m) ║ 2.5 ║ priority 1.0
|
|
108
|
+
# │ ║ ║
|
|
109
|
+
# [0,1]──────────────────[8, 1 ]──[10, 1]
|
|
110
|
+
# │ RIGHT │
|
|
111
|
+
# │ TURN │ priority 1.8
|
|
112
|
+
# [8, 2.5]──[10,2.5]
|
|
113
|
+
#
|
|
114
|
+
capture_zones:
|
|
115
|
+
|
|
116
|
+
# ── Level 1 : approach corridor (8m × 1m) ────────────────────────────
|
|
117
|
+
# The full run-up. Covered but not the priority focus.
|
|
118
|
+
- id: "approach"
|
|
119
|
+
type: "polygon"
|
|
120
|
+
priority: 1.0
|
|
121
|
+
grid_step: 0.30
|
|
122
|
+
vertices:
|
|
123
|
+
- [1.0, 0.0] # start bottom-left
|
|
124
|
+
- [7.0, 0.0] # end bottom-right (junction with turn arm)
|
|
125
|
+
- [7.0, 1.0] # end top-right
|
|
126
|
+
- [1.0, 1.0] # start top-left
|
|
127
|
+
placement:
|
|
128
|
+
# 3 X positions × 2 Y positions = 6 combos
|
|
129
|
+
# x_offsets : left end of approach at X = 0, 0.5, 1.0
|
|
130
|
+
# y_offsets : bottom of approach at Y = 2.5, 3.0
|
|
131
|
+
x_offsets: [0.0, 0.5, 1.0]
|
|
132
|
+
y_offsets: [2.5, 3.0]
|
|
133
|
+
|
|
134
|
+
# ── Level 2 : turn arm — left side (toward +Y, 2m × 1.5m) ───────────
|
|
135
|
+
# The portion of the perpendicular corridor ABOVE the approach axis.
|
|
136
|
+
# Subject turns left here.
|
|
137
|
+
- id: "turn_left"
|
|
138
|
+
type: "polygon"
|
|
139
|
+
priority: 1.8
|
|
140
|
+
grid_step: 0.25
|
|
141
|
+
vertices:
|
|
142
|
+
- [8.0, -1.5] # far end top-left
|
|
143
|
+
- [10.0, -1.5] # far end top-right
|
|
144
|
+
- [10.0, 0.0] # meets approach top (intersection boundary)
|
|
145
|
+
- [8.0, 0.0] # meets approach top (intersection boundary)
|
|
146
|
+
placement:
|
|
147
|
+
x_offsets: [0.0, 0.5, 1.0]
|
|
148
|
+
y_offsets: [2.5, 3.0]
|
|
149
|
+
|
|
150
|
+
# ── Level 2 : turn arm — right side (toward -Y, 2m × 1.5m) ──────────
|
|
151
|
+
# The portion of the perpendicular corridor BELOW the approach axis.
|
|
152
|
+
# Subject turns right here.
|
|
153
|
+
- id: "turn_right"
|
|
154
|
+
type: "polygon"
|
|
155
|
+
priority: 1.8
|
|
156
|
+
grid_step: 0.25
|
|
157
|
+
vertices:
|
|
158
|
+
- [8.0, 1.0] # meets approach bottom (intersection boundary)
|
|
159
|
+
- [10.0, 1.0] # meets approach bottom (intersection boundary)
|
|
160
|
+
- [10.0, 2.5] # far end bottom-right
|
|
161
|
+
- [8.0, 2.5] # far end bottom-left
|
|
162
|
+
placement:
|
|
163
|
+
x_offsets: [0.0, 0.5, 1.0]
|
|
164
|
+
y_offsets: [2.5, 3.0]
|
|
165
|
+
|
|
166
|
+
# ── Level 3 : intersection (2m × 1m) — maximum priority ──────────────
|
|
167
|
+
# Where the approach meets the turn arm. The direction change happens
|
|
168
|
+
# here — maximise camera coverage at all costs.
|
|
169
|
+
- id: "intersection"
|
|
170
|
+
type: "polygon"
|
|
171
|
+
priority: 2.5
|
|
172
|
+
grid_step: 0.15
|
|
173
|
+
vertices:
|
|
174
|
+
- [7.0, 0.0] # bottom-left (end of approach / top of right turn)
|
|
175
|
+
- [10.0, 0.0] # bottom-right
|
|
176
|
+
- [10.0, 1.0] # top-right
|
|
177
|
+
- [7.0, 1.0] # top-left
|
|
178
|
+
placement:
|
|
179
|
+
x_offsets: [0.0, 0.5, 1.0]
|
|
180
|
+
y_offsets: [2.5, 3.0]
|
|
181
|
+
|
|
182
|
+
# ─────────────────────────────────────────────────────────────
|
|
183
|
+
# 6. OPTIMISATION PARAMETERS
|
|
184
|
+
# ─────────────────────────────────────────────────────────────
|
|
185
|
+
optimization:
|
|
186
|
+
target_coverage: 5
|
|
187
|
+
bilateral_weight: 0.9
|
|
188
|
+
vertical_coverage_threshold: 0.9
|
|
189
|
+
restarts_per_combo: 15
|
|
190
|
+
wall_step: 0.35
|
|
191
|
+
angle_steps: 24
|
|
192
|
+
tripod_grid_step: 0.80
|
|
193
|
+
distance_quality_factor: 0.005
|
|
194
|
+
# algo options:
|
|
195
|
+
# greedy → pure greedy (fast, original behaviour)
|
|
196
|
+
# greedy_1opt → greedy init + 1-opt local search (better quality, recommended)
|
|
197
|
+
algo: "greedy_1opt"
|
|
198
|
+
early_stop: 5 # stop if no improvement after 5 consecutive restarts (0 = auto)
|
|
199
|
+
graph_mode: "all"
|
|
200
|
+
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# =============================================================
|
|
2
|
+
# Lab Camera Optimizer — Real-world example configuration
|
|
3
|
+
# L-shaped room with obstacles, two camera sets, corridor zones.
|
|
4
|
+
# Based on an actual biomechanics lab (13m × 4.7m, L-shaped).
|
|
5
|
+
# Use this as a starting point for complex lab geometries.
|
|
6
|
+
# =============================================================
|
|
7
|
+
# Units : metres for all distances, degrees for all angles.
|
|
8
|
+
# Angles follow the standard mathematical convention:
|
|
9
|
+
# 0° = East (+X), 90° = North (+Y), 180°/-180° = West (-X)
|
|
10
|
+
# =============================================================
|
|
11
|
+
|
|
12
|
+
# ─────────────────────────────────────────────────────────────
|
|
13
|
+
# 1. ROOM GEOMETRY
|
|
14
|
+
# ─────────────────────────────────────────────────────────────
|
|
15
|
+
room:
|
|
16
|
+
# List of (X, Y) vertices of the room polygon, in order (CW or CCW).
|
|
17
|
+
# The room is assumed to be a flat floor plan.
|
|
18
|
+
corners:
|
|
19
|
+
- [0, 0 ]
|
|
20
|
+
- [13, 0 ]
|
|
21
|
+
- [13, 3.0]
|
|
22
|
+
- [8, 3.0]
|
|
23
|
+
- [8, 4.7]
|
|
24
|
+
- [0, 4.7]
|
|
25
|
+
|
|
26
|
+
# Floor-to-ceiling height (metres)
|
|
27
|
+
height: 3.0
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# ─────────────────────────────────────────────────────────────
|
|
31
|
+
# 2. OBSTACLES
|
|
32
|
+
# ─────────────────────────────────────────────────────────────
|
|
33
|
+
# Two types supported:
|
|
34
|
+
#
|
|
35
|
+
# type: rect → axis-aligned rectangle
|
|
36
|
+
# bounds: [x_min, y_min, x_max, y_max]
|
|
37
|
+
#
|
|
38
|
+
# type: polygon → arbitrary polygon
|
|
39
|
+
# vertices: [[x0,y0],[x1,y1],...] (in order)
|
|
40
|
+
#
|
|
41
|
+
# height: floor-to-ceiling height of the obstacle (metres).
|
|
42
|
+
# = room.height → fully blocks line-of-sight (wall/pillar)
|
|
43
|
+
# < room.height → partial height (object, furniture)
|
|
44
|
+
# does NOT block line-of-sight above it
|
|
45
|
+
#
|
|
46
|
+
# can_mount_camera: true → wall cameras can be placed on this surface
|
|
47
|
+
# false (default) → no camera on this obstacle
|
|
48
|
+
|
|
49
|
+
obstacles:
|
|
50
|
+
- type: rect
|
|
51
|
+
bounds: [1.70, 0.00, 2.04, 0.34]
|
|
52
|
+
height: 3.0
|
|
53
|
+
label: "Stub 1"
|
|
54
|
+
can_mount_camera: true
|
|
55
|
+
|
|
56
|
+
- type: rect
|
|
57
|
+
bounds: [5.39, 0.00, 5.59, 0.42]
|
|
58
|
+
height: 3.0
|
|
59
|
+
label: "Stub 2"
|
|
60
|
+
can_mount_camera: true
|
|
61
|
+
|
|
62
|
+
- type: rect
|
|
63
|
+
bounds: [8.94, 0.00, 9.14, 0.42]
|
|
64
|
+
height: 3.0
|
|
65
|
+
label: "Stub 3"
|
|
66
|
+
can_mount_camera: true
|
|
67
|
+
|
|
68
|
+
- type: rect
|
|
69
|
+
bounds: [5.65, 3.85, 5.85, 4.25]
|
|
70
|
+
height: 3.0
|
|
71
|
+
label: "Stub 4"
|
|
72
|
+
can_mount_camera: true
|
|
73
|
+
|
|
74
|
+
- type: rect
|
|
75
|
+
bounds: [5.65, 2.85, 6.35, 3.55]
|
|
76
|
+
height: 1.4
|
|
77
|
+
label: "Object 1"
|
|
78
|
+
can_mount_camera: false # partial-height object, no camera mount
|
|
79
|
+
|
|
80
|
+
- type: polygon
|
|
81
|
+
vertices:
|
|
82
|
+
- [1.47, 4.70]
|
|
83
|
+
- [1.47, 4.38]
|
|
84
|
+
- [1.71, 4.38]
|
|
85
|
+
- [1.71, 3.88]
|
|
86
|
+
- [2.04, 3.88]
|
|
87
|
+
- [2.04, 4.27]
|
|
88
|
+
- [3.02, 4.27]
|
|
89
|
+
- [3.02, 4.70]
|
|
90
|
+
height: 3.0
|
|
91
|
+
label: "Wall N"
|
|
92
|
+
can_mount_camera: true
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# ─────────────────────────────────────────────────────────────
|
|
96
|
+
# 3. SUBJECT (person being recorded)
|
|
97
|
+
# ─────────────────────────────────────────────────────────────
|
|
98
|
+
subject:
|
|
99
|
+
height: 1.9 # total height feet-to-head (metres)
|
|
100
|
+
foot_z: 0.0 # height of feet above floor (metres, usually 0)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# ─────────────────────────────────────────────────────────────
|
|
104
|
+
# 4. CAMERA SETS
|
|
105
|
+
# ─────────────────────────────────────────────────────────────
|
|
106
|
+
# Define as many camera sets as needed.
|
|
107
|
+
# Each set is an independent group of cameras with its own FOV,
|
|
108
|
+
# mounting type, placement constraints and score weight.
|
|
109
|
+
#
|
|
110
|
+
# mounting:
|
|
111
|
+
# wall → cameras placed on room walls and floor-to-ceiling obstacles
|
|
112
|
+
# tripod → cameras placed freely inside the room on a tripod stand
|
|
113
|
+
# ceiling → (future) cameras hung from the ceiling
|
|
114
|
+
#
|
|
115
|
+
# optional: true → this entire set is skipped if max_count is 0
|
|
116
|
+
|
|
117
|
+
camera_sets:
|
|
118
|
+
|
|
119
|
+
- id: "cam_A"
|
|
120
|
+
name: "ZED2i"
|
|
121
|
+
mounting: "wall"
|
|
122
|
+
optional: false
|
|
123
|
+
|
|
124
|
+
# Field of view (degrees) — landscape and portrait orientations
|
|
125
|
+
fov_h_landscape: 110.0
|
|
126
|
+
fov_v_landscape: 70.0
|
|
127
|
+
fov_h_portrait: 70.0
|
|
128
|
+
fov_v_portrait: 110.0
|
|
129
|
+
|
|
130
|
+
# Detection range (metres)
|
|
131
|
+
max_range: 15.0
|
|
132
|
+
min_range: 0.5 # blind zone closer than this
|
|
133
|
+
|
|
134
|
+
# Camera heights to evaluate (metres).
|
|
135
|
+
# Each candidate position is tested at ALL heights listed here.
|
|
136
|
+
# A single optimised configuration can mix different heights.
|
|
137
|
+
height_options: [2.0, 2.2]
|
|
138
|
+
|
|
139
|
+
# Placement constraints
|
|
140
|
+
max_count: 10 # maximum number of cameras to place
|
|
141
|
+
min_spacing: 1.5 # minimum distance between two cameras of this set (m)
|
|
142
|
+
|
|
143
|
+
# Score contribution
|
|
144
|
+
score_weight: 1.0 # relative weight vs other camera sets
|
|
145
|
+
|
|
146
|
+
# Visualisation
|
|
147
|
+
color: "#1f77b4" # hex colour for graphs
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
- id: "cam_B"
|
|
151
|
+
name: "iPad"
|
|
152
|
+
mounting: "tripod"
|
|
153
|
+
optional: true # set max_count: 0 to fully disable
|
|
154
|
+
|
|
155
|
+
fov_h_landscape: 80.0
|
|
156
|
+
fov_v_landscape: 58.0
|
|
157
|
+
fov_h_portrait: 58.0
|
|
158
|
+
fov_v_portrait: 80.0
|
|
159
|
+
|
|
160
|
+
max_range: 10.0
|
|
161
|
+
min_range: 0.5
|
|
162
|
+
|
|
163
|
+
height_options: [1.5]
|
|
164
|
+
|
|
165
|
+
max_count: 2
|
|
166
|
+
min_spacing: 1.5
|
|
167
|
+
|
|
168
|
+
# Tripod-specific: minimum distance from the capture axis (metres).
|
|
169
|
+
# Prevents the tripod from blocking the walking path.
|
|
170
|
+
walk_axis_margin: 0.7
|
|
171
|
+
|
|
172
|
+
score_weight: 0.6
|
|
173
|
+
|
|
174
|
+
color: "#d62728"
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
# ─────────────────────────────────────────────────────────────
|
|
178
|
+
# 5. CAPTURE ZONES
|
|
179
|
+
# ─────────────────────────────────────────────────────────────
|
|
180
|
+
# Zones define WHERE the cameras must provide coverage and HOW MUCH
|
|
181
|
+
# each zone matters (priority weight in the global score).
|
|
182
|
+
#
|
|
183
|
+
# Zone types:
|
|
184
|
+
# corridor → rectangular strip, explored at several X/Y positions
|
|
185
|
+
# sub_zone → rectangular strip contained within a parent corridor
|
|
186
|
+
# point → circular zone (e.g. a chair test)
|
|
187
|
+
#
|
|
188
|
+
# The optimiser tests all combinations of (x_start, y_position, sub_offset)
|
|
189
|
+
# and keeps the globally best placement.
|
|
190
|
+
|
|
191
|
+
capture_zones:
|
|
192
|
+
|
|
193
|
+
# ── Primary corridor (total walking path) ─────────────────
|
|
194
|
+
- id: "full_corridor"
|
|
195
|
+
type: "corridor"
|
|
196
|
+
priority: 0.5 # lower weight: coverage outside analysis is a bonus
|
|
197
|
+
|
|
198
|
+
length: 10.0 # length of the corridor (metres, along main axis)
|
|
199
|
+
width: 1.0 # transverse width of the evaluation strip (metres)
|
|
200
|
+
|
|
201
|
+
# Positions to explore during optimisation
|
|
202
|
+
placement:
|
|
203
|
+
# X start positions of the corridor to test (metres from room origin)
|
|
204
|
+
x_start_options: [1.0, 2.0]
|
|
205
|
+
# Transverse (Y) axis positions of the corridor centre to test
|
|
206
|
+
y_options: [1.0, 1.4, 1.8]
|
|
207
|
+
|
|
208
|
+
# ── Analysis zone (priority sub-zone inside the corridor) ──
|
|
209
|
+
- id: "analysis_zone"
|
|
210
|
+
type: "sub_zone"
|
|
211
|
+
priority: 1.0 # main zone: highest weight on coverage score
|
|
212
|
+
|
|
213
|
+
length: 6.0 # length of the priority sub-zone (metres)
|
|
214
|
+
contained_in: "full_corridor"
|
|
215
|
+
|
|
216
|
+
# Offsets (metres) from the corridor start where the sub-zone can begin.
|
|
217
|
+
# e.g. offset 1.0 → sub-zone starts 1m after corridor start (1m run-up)
|
|
218
|
+
# offset 3.0 → sub-zone starts 3m in (3m run-up, 1m deceleration)
|
|
219
|
+
offset_options: [1.0, 2.0, 3.0]
|
|
220
|
+
|
|
221
|
+
# ── STS point (sit-to-stand chair test — secondary priority zone) ──
|
|
222
|
+
- id: "sts_point"
|
|
223
|
+
type: "point"
|
|
224
|
+
priority: 2.0 # highest weight: this spot must be very well covered
|
|
225
|
+
|
|
226
|
+
radius: 0.6 # coverage circle radius (metres)
|
|
227
|
+
contained_in: "analysis_zone"
|
|
228
|
+
|
|
229
|
+
# If true, the optimiser searches automatically for the best position
|
|
230
|
+
# of this point inside the parent zone (maximises camera coverage score).
|
|
231
|
+
auto_optimize: true
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
# ─────────────────────────────────────────────────────────────
|
|
235
|
+
# 6. OPTIMISATION PARAMETERS
|
|
236
|
+
# ─────────────────────────────────────────────────────────────
|
|
237
|
+
optimization:
|
|
238
|
+
|
|
239
|
+
# Number of cameras required per evaluation point to consider it "well covered"
|
|
240
|
+
target_coverage: 6
|
|
241
|
+
|
|
242
|
+
# Bilateral constraint weight [0.0 – 1.0]
|
|
243
|
+
# 0.0 = disabled (pure coverage maximisation)
|
|
244
|
+
# 1.0 = fully enforced (score 0 if only one side covered)
|
|
245
|
+
# Recommended: 0.6 – 0.8 for biomechanics labs
|
|
246
|
+
bilateral_weight: 0.9
|
|
247
|
+
|
|
248
|
+
# Fraction of the subject body (feet→head) that must be visible
|
|
249
|
+
# for a camera to "count" at an evaluation point.
|
|
250
|
+
vertical_coverage_threshold: 0.9
|
|
251
|
+
|
|
252
|
+
# Greedy optimisation restarts per combination of zone positions
|
|
253
|
+
restarts_per_combo: 10
|
|
254
|
+
|
|
255
|
+
# Candidate generation resolution
|
|
256
|
+
wall_step: 0.35 # spacing between camera positions along walls (m)
|
|
257
|
+
angle_steps: 24 # number of yaw angles tested per wall position
|
|
258
|
+
tripod_grid_step: 0.70 # spacing of the interior tripod grid (m)
|
|
259
|
+
distance_quality_factor: 0.001
|
|
260
|
+
|
|
261
|
+
# Distance quality decay: score multiplier = 1 / (1 + k * d²)
|
|
262
|
+
# Higher k → strong penalty for distant cameras
|
|
263
|
+
# k=0.01: 0.96 @ 2m | 0.80 @ 5m | 0.50 @ 10m
|
|
264
|
+
# Graph generation mode:
|
|
265
|
+
# all → save a graph for every optimisation attempt (verbose)
|
|
266
|
+
# records_only → save only when a new global record is set
|
|
267
|
+
# best_per_combo → save only the best result of each zone combination
|
|
268
|
+
algo: "greedy_1opt" # greedy | greedy_1opt
|
|
269
|
+
graph_mode: "all"
|
|
270
|
+
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# =============================================================
|
|
2
|
+
# Lab Camera Optimizer — Minimal example configuration
|
|
3
|
+
# Simple rectangular room, one wall-mounted camera set, no obstacles.
|
|
4
|
+
# A good starting point for any new lab.
|
|
5
|
+
# =============================================================
|
|
6
|
+
# Units : metres for distances, degrees for angles.
|
|
7
|
+
# =============================================================
|
|
8
|
+
|
|
9
|
+
# ─────────────────────────────────────────────────────────────
|
|
10
|
+
# 1. ROOM
|
|
11
|
+
# ─────────────────────────────────────────────────────────────
|
|
12
|
+
room:
|
|
13
|
+
# Simple 10m × 6m rectangle
|
|
14
|
+
corners:
|
|
15
|
+
- [0, 0]
|
|
16
|
+
- [10, 0]
|
|
17
|
+
- [10, 6]
|
|
18
|
+
- [0, 6]
|
|
19
|
+
height: 3.0 # floor-to-ceiling (metres)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# ─────────────────────────────────────────────────────────────
|
|
23
|
+
# 2. OBSTACLES
|
|
24
|
+
# ─────────────────────────────────────────────────────────────
|
|
25
|
+
# No obstacles in this minimal example.
|
|
26
|
+
# To add one, uncomment and edit the block below:
|
|
27
|
+
#
|
|
28
|
+
# obstacles:
|
|
29
|
+
# - type: rect
|
|
30
|
+
# bounds: [2.0, 0.0, 2.2, 0.4] # [x_min, y_min, x_max, y_max]
|
|
31
|
+
# height: 3.0
|
|
32
|
+
# label: "Pillar"
|
|
33
|
+
# can_mount_camera: false
|
|
34
|
+
obstacles: []
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# ─────────────────────────────────────────────────────────────
|
|
38
|
+
# 3. SUBJECT
|
|
39
|
+
# ─────────────────────────────────────────────────────────────
|
|
40
|
+
subject:
|
|
41
|
+
height: 1.9 # subject height feet-to-head (metres)
|
|
42
|
+
foot_z: 0.0 # height of feet above floor (almost always 0)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# ─────────────────────────────────────────────────────────────
|
|
46
|
+
# 4. CAMERA SETS
|
|
47
|
+
# ─────────────────────────────────────────────────────────────
|
|
48
|
+
camera_sets:
|
|
49
|
+
|
|
50
|
+
- id: "cam_A"
|
|
51
|
+
name: "Wall camera"
|
|
52
|
+
mounting: "wall"
|
|
53
|
+
optional: false
|
|
54
|
+
|
|
55
|
+
# Field of view — landscape and portrait orientations (degrees)
|
|
56
|
+
fov_h_landscape: 110.0
|
|
57
|
+
fov_v_landscape: 70.0
|
|
58
|
+
fov_h_portrait: 70.0
|
|
59
|
+
fov_v_portrait: 110.0
|
|
60
|
+
|
|
61
|
+
# Detection range
|
|
62
|
+
max_range: 12.0 # metres
|
|
63
|
+
min_range: 0.5 # blind zone closer than this
|
|
64
|
+
|
|
65
|
+
# Heights to test (metres). Mix different heights in one run.
|
|
66
|
+
height_options: [2.0]
|
|
67
|
+
|
|
68
|
+
max_count: 8 # maximum cameras to place
|
|
69
|
+
min_spacing: 1.5 # minimum distance between two cameras of this set
|
|
70
|
+
|
|
71
|
+
score_weight: 1.0
|
|
72
|
+
color: "#1f77b4"
|
|
73
|
+
|
|
74
|
+
# Optional tripod camera set — set max_count: 0 to disable entirely
|
|
75
|
+
- id: "cam_B"
|
|
76
|
+
name: "Tripod camera"
|
|
77
|
+
mounting: "tripod"
|
|
78
|
+
optional: true
|
|
79
|
+
|
|
80
|
+
fov_h_landscape: 80.0
|
|
81
|
+
fov_v_landscape: 58.0
|
|
82
|
+
fov_h_portrait: 58.0
|
|
83
|
+
fov_v_portrait: 80.0
|
|
84
|
+
|
|
85
|
+
max_range: 8.0
|
|
86
|
+
min_range: 0.5
|
|
87
|
+
|
|
88
|
+
height_options: [1.5]
|
|
89
|
+
|
|
90
|
+
max_count: 0 # set to 0 = disabled, set to 2 = enable 2 tripod cameras
|
|
91
|
+
min_spacing: 1.5
|
|
92
|
+
walk_axis_margin: 0.7 # tripod must stay at least this far from the capture axis
|
|
93
|
+
|
|
94
|
+
score_weight: 0.6
|
|
95
|
+
color: "#d62728"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# ─────────────────────────────────────────────────────────────
|
|
99
|
+
# 5. CAPTURE ZONES
|
|
100
|
+
# ─────────────────────────────────────────────────────────────
|
|
101
|
+
# Zone types:
|
|
102
|
+
#
|
|
103
|
+
# corridor → rectangular strip swept at multiple X/Y positions
|
|
104
|
+
# sub_zone → rectangle contained inside a corridor
|
|
105
|
+
# point → circle (e.g. chair test)
|
|
106
|
+
# polygon → arbitrary shape (L, T, U, cross…) — fixed position in the room
|
|
107
|
+
# vertices: list of (X,Y) corners in order
|
|
108
|
+
# grid_step: evaluation point density inside (metres)
|
|
109
|
+
#
|
|
110
|
+
capture_zones:
|
|
111
|
+
|
|
112
|
+
# Full walking corridor (low priority — bonus coverage)
|
|
113
|
+
- id: "full_corridor"
|
|
114
|
+
type: "corridor"
|
|
115
|
+
priority: 0.5
|
|
116
|
+
length: 8.0 # total corridor length (metres)
|
|
117
|
+
width: 1.0 # transverse evaluation strip width
|
|
118
|
+
placement:
|
|
119
|
+
x_start_options: [1.0] # where the corridor can start along X
|
|
120
|
+
y_options: [3.0] # Y axis position (centre of room = 3.0m)
|
|
121
|
+
|
|
122
|
+
# Priority analysis zone inside the corridor
|
|
123
|
+
- id: "analysis_zone"
|
|
124
|
+
type: "sub_zone"
|
|
125
|
+
priority: 1.0
|
|
126
|
+
length: 4.0 # length of the priority sub-zone
|
|
127
|
+
contained_in: "full_corridor"
|
|
128
|
+
offset_options: [2.0] # starts 2m after corridor start → 2m run-up
|
|
129
|
+
|
|
130
|
+
# Key point (e.g. chair test, STS) — highest priority
|
|
131
|
+
- id: "key_point"
|
|
132
|
+
type: "point"
|
|
133
|
+
priority: 2.0
|
|
134
|
+
radius: 0.5
|
|
135
|
+
contained_in: "analysis_zone"
|
|
136
|
+
auto_optimize: true # optimiser finds the best position automatically
|
|
137
|
+
|
|
138
|
+
# ── Example polygon zone (commented out — uncomment to use) ──────────
|
|
139
|
+
# Vertices are in RELATIVE coordinates (origin = 0,0 = bottom-left corner
|
|
140
|
+
# of the shape). The placement.x_offsets and y_offsets define how to
|
|
141
|
+
# translate the whole shape across the room — like a corridor sweep but
|
|
142
|
+
# for any arbitrary shape (L, T, U, cross…).
|
|
143
|
+
#
|
|
144
|
+
# - id: "L_shaped_zone"
|
|
145
|
+
# type: "polygon"
|
|
146
|
+
# priority: 1.5
|
|
147
|
+
# grid_step: 0.20 # evaluation point spacing inside the polygon (metres)
|
|
148
|
+
# vertices: # relative (X, Y) corners — origin is bottom-left of shape
|
|
149
|
+
# - [0.0, 0.0] # bottom-left
|
|
150
|
+
# - [6.0, 0.0] # bottom-right of horizontal arm
|
|
151
|
+
# - [6.0, 1.5] # top-right of horizontal arm
|
|
152
|
+
# - [2.0, 1.5] # inner corner of the L
|
|
153
|
+
# - [2.0, 4.0] # top of vertical arm
|
|
154
|
+
# - [0.0, 4.0] # top-left
|
|
155
|
+
# placement:
|
|
156
|
+
# x_offsets: [0.5, 1.0, 1.5] # translate shape by these X values
|
|
157
|
+
# y_offsets: [1.0, 1.5] # translate shape by these Y values
|
|
158
|
+
# # → 3 × 2 = 6 polygon placements tested in addition to corridor sweeps
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
# ─────────────────────────────────────────────────────────────
|
|
162
|
+
# 6. OPTIMISATION PARAMETERS
|
|
163
|
+
# ─────────────────────────────────────────────────────────────
|
|
164
|
+
optimization:
|
|
165
|
+
target_coverage: 3 # target number of cameras per evaluation point
|
|
166
|
+
bilateral_weight: 0.9 # 0.0 = disabled, 1.0 = fully enforced
|
|
167
|
+
vertical_coverage_threshold: 0.9
|
|
168
|
+
restarts_per_combo: 10 # lower = faster (increase for better results)
|
|
169
|
+
wall_step: 0.40 # camera spacing along walls (m) — increase for speed
|
|
170
|
+
angle_steps: 20 # yaw angles tested per position
|
|
171
|
+
tripod_grid_step: 0.80
|
|
172
|
+
distance_quality_factor: 0.01
|
|
173
|
+
|
|
174
|
+
# Graph generation mode:
|
|
175
|
+
# all → save a graph for every optimisation attempt (verbose)
|
|
176
|
+
# records_only → save only when a new global record is set
|
|
177
|
+
# best_per_combo → save only the best result of each zone combination
|
|
178
|
+
algo: "greedy_1opt" # greedy | greedy_1opt
|
|
179
|
+
graph_mode: "all"
|
|
180
|
+
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# =============================================================
|
|
2
|
+
# Lab Camera Optimizer — Configuration file
|
|
3
|
+
# Project : CHU Biomechanics Lab — L-shaped room
|
|
4
|
+
# =============================================================
|
|
5
|
+
# Units : metres for all distances, degrees for all angles.
|
|
6
|
+
# Angles follow the standard mathematical convention:
|
|
7
|
+
# 0° = East (+X), 90° = North (+Y), 180°/-180° = West (-X)
|
|
8
|
+
# =============================================================
|
|
9
|
+
|
|
10
|
+
# ─────────────────────────────────────────────────────────────
|
|
11
|
+
# 1. ROOM GEOMETRY
|
|
12
|
+
# ─────────────────────────────────────────────────────────────
|
|
13
|
+
room:
|
|
14
|
+
# List of (X, Y) vertices of the room polygon, in order (CW or CCW).
|
|
15
|
+
# The room is assumed to be a flat floor plan.
|
|
16
|
+
corners:
|
|
17
|
+
- [0, 0 ]
|
|
18
|
+
- [13, 0 ]
|
|
19
|
+
- [13, 3.0]
|
|
20
|
+
- [8, 3.0]
|
|
21
|
+
- [8, 4.7]
|
|
22
|
+
- [0, 4.7]
|
|
23
|
+
|
|
24
|
+
# Floor-to-ceiling height (metres)
|
|
25
|
+
height: 3.0
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# ─────────────────────────────────────────────────────────────
|
|
29
|
+
# 2. OBSTACLES
|
|
30
|
+
# ─────────────────────────────────────────────────────────────
|
|
31
|
+
# Two types supported:
|
|
32
|
+
#
|
|
33
|
+
# type: rect → axis-aligned rectangle
|
|
34
|
+
# bounds: [x_min, y_min, x_max, y_max]
|
|
35
|
+
#
|
|
36
|
+
# type: polygon → arbitrary polygon
|
|
37
|
+
# vertices: [[x0,y0],[x1,y1],...] (in order)
|
|
38
|
+
#
|
|
39
|
+
# height: floor-to-ceiling height of the obstacle (metres).
|
|
40
|
+
# = room.height → fully blocks line-of-sight (wall/pillar)
|
|
41
|
+
# < room.height → partial height (object, furniture)
|
|
42
|
+
# does NOT block line-of-sight above it
|
|
43
|
+
#
|
|
44
|
+
# can_mount_camera: true → wall cameras can be placed on this surface
|
|
45
|
+
# false (default) → no camera on this obstacle
|
|
46
|
+
|
|
47
|
+
obstacles:
|
|
48
|
+
- type: rect
|
|
49
|
+
bounds: [1.70, 0.00, 2.04, 0.34]
|
|
50
|
+
height: 3.0
|
|
51
|
+
label: "Stub 1"
|
|
52
|
+
can_mount_camera: true
|
|
53
|
+
|
|
54
|
+
- type: rect
|
|
55
|
+
bounds: [5.39, 0.00, 5.59, 0.42]
|
|
56
|
+
height: 3.0
|
|
57
|
+
label: "Stub 2"
|
|
58
|
+
can_mount_camera: true
|
|
59
|
+
|
|
60
|
+
- type: rect
|
|
61
|
+
bounds: [8.94, 0.00, 9.14, 0.42]
|
|
62
|
+
height: 3.0
|
|
63
|
+
label: "Stub 3"
|
|
64
|
+
can_mount_camera: true
|
|
65
|
+
|
|
66
|
+
- type: rect
|
|
67
|
+
bounds: [5.65, 3.85, 5.85, 4.25]
|
|
68
|
+
height: 3.0
|
|
69
|
+
label: "Stub 4"
|
|
70
|
+
can_mount_camera: true
|
|
71
|
+
|
|
72
|
+
- type: rect
|
|
73
|
+
bounds: [5.65, 2.85, 6.35, 3.55]
|
|
74
|
+
height: 1.4
|
|
75
|
+
label: "Object 1"
|
|
76
|
+
can_mount_camera: false # partial-height object, no camera mount
|
|
77
|
+
|
|
78
|
+
- type: polygon
|
|
79
|
+
vertices:
|
|
80
|
+
- [1.47, 4.70]
|
|
81
|
+
- [1.47, 4.38]
|
|
82
|
+
- [1.71, 4.38]
|
|
83
|
+
- [1.71, 3.88]
|
|
84
|
+
- [2.04, 3.88]
|
|
85
|
+
- [2.04, 4.27]
|
|
86
|
+
- [3.02, 4.27]
|
|
87
|
+
- [3.02, 4.70]
|
|
88
|
+
height: 3.0
|
|
89
|
+
label: "Wall N"
|
|
90
|
+
can_mount_camera: true
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# ─────────────────────────────────────────────────────────────
|
|
94
|
+
# 3. SUBJECT (person being recorded)
|
|
95
|
+
# ─────────────────────────────────────────────────────────────
|
|
96
|
+
subject:
|
|
97
|
+
height: 1.9 # total height feet-to-head (metres)
|
|
98
|
+
foot_z: 0.0 # height of feet above floor (metres, usually 0)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# ─────────────────────────────────────────────────────────────
|
|
102
|
+
# 4. CAMERA SETS
|
|
103
|
+
# ─────────────────────────────────────────────────────────────
|
|
104
|
+
# Define as many camera sets as needed.
|
|
105
|
+
# Each set is an independent group of cameras with its own FOV,
|
|
106
|
+
# mounting type, placement constraints and score weight.
|
|
107
|
+
#
|
|
108
|
+
# mounting:
|
|
109
|
+
# wall → cameras placed on room walls and floor-to-ceiling obstacles
|
|
110
|
+
# tripod → cameras placed freely inside the room on a tripod stand
|
|
111
|
+
# ceiling → (future) cameras hung from the ceiling
|
|
112
|
+
#
|
|
113
|
+
# optional: true → this entire set is skipped if max_count is 0
|
|
114
|
+
|
|
115
|
+
camera_sets:
|
|
116
|
+
|
|
117
|
+
- id: "cam_A"
|
|
118
|
+
name: "ZED2i"
|
|
119
|
+
mounting: "wall"
|
|
120
|
+
optional: false
|
|
121
|
+
|
|
122
|
+
# Field of view (degrees) — landscape and portrait orientations
|
|
123
|
+
fov_h_landscape: 110.0
|
|
124
|
+
fov_v_landscape: 70.0
|
|
125
|
+
fov_h_portrait: 70.0
|
|
126
|
+
fov_v_portrait: 110.0
|
|
127
|
+
|
|
128
|
+
# Detection range (metres)
|
|
129
|
+
max_range: 15.0
|
|
130
|
+
min_range: 0.5 # blind zone closer than this
|
|
131
|
+
|
|
132
|
+
# Camera heights to evaluate (metres).
|
|
133
|
+
# Each candidate position is tested at ALL heights listed here.
|
|
134
|
+
# A single optimised configuration can mix different heights.
|
|
135
|
+
height_options: [2.0, 2.2]
|
|
136
|
+
|
|
137
|
+
# Placement constraints
|
|
138
|
+
max_count: 10 # maximum number of cameras to place
|
|
139
|
+
min_spacing: 1.5 # minimum distance between two cameras of this set (m)
|
|
140
|
+
|
|
141
|
+
# Score contribution
|
|
142
|
+
score_weight: 1.0 # relative weight vs other camera sets
|
|
143
|
+
|
|
144
|
+
# Visualisation
|
|
145
|
+
color: "#1f77b4" # hex colour for graphs
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
- id: "cam_B"
|
|
149
|
+
name: "iPad"
|
|
150
|
+
mounting: "tripod"
|
|
151
|
+
optional: true # set max_count: 0 to fully disable
|
|
152
|
+
|
|
153
|
+
fov_h_landscape: 80.0
|
|
154
|
+
fov_v_landscape: 58.0
|
|
155
|
+
fov_h_portrait: 58.0
|
|
156
|
+
fov_v_portrait: 80.0
|
|
157
|
+
|
|
158
|
+
max_range: 10.0
|
|
159
|
+
min_range: 0.5
|
|
160
|
+
|
|
161
|
+
height_options: [1.5]
|
|
162
|
+
|
|
163
|
+
max_count: 2
|
|
164
|
+
min_spacing: 1.5
|
|
165
|
+
|
|
166
|
+
# Tripod-specific: minimum distance from the capture axis (metres).
|
|
167
|
+
# Prevents the tripod from blocking the walking path.
|
|
168
|
+
walk_axis_margin: 0.7
|
|
169
|
+
|
|
170
|
+
score_weight: 0.6
|
|
171
|
+
|
|
172
|
+
color: "#d62728"
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# ─────────────────────────────────────────────────────────────
|
|
176
|
+
# 5. CAPTURE ZONES
|
|
177
|
+
# ─────────────────────────────────────────────────────────────
|
|
178
|
+
# Zones define WHERE the cameras must provide coverage and HOW MUCH
|
|
179
|
+
# each zone matters (priority weight in the global score).
|
|
180
|
+
#
|
|
181
|
+
# Zone types:
|
|
182
|
+
# corridor → rectangular strip, explored at several X/Y positions
|
|
183
|
+
# sub_zone → rectangular strip contained within a parent corridor
|
|
184
|
+
# point → circular zone (e.g. a chair test)
|
|
185
|
+
#
|
|
186
|
+
# The optimiser tests all combinations of (x_start, y_position, sub_offset)
|
|
187
|
+
# and keeps the globally best placement.
|
|
188
|
+
|
|
189
|
+
capture_zones:
|
|
190
|
+
|
|
191
|
+
# ── Primary corridor (total walking path) ─────────────────
|
|
192
|
+
- id: "full_corridor"
|
|
193
|
+
type: "corridor"
|
|
194
|
+
priority: 0.5 # lower weight: coverage outside analysis is a bonus
|
|
195
|
+
|
|
196
|
+
length: 10.0 # length of the corridor (metres, along main axis)
|
|
197
|
+
width: 1.0 # transverse width of the evaluation strip (metres)
|
|
198
|
+
|
|
199
|
+
# Positions to explore during optimisation
|
|
200
|
+
placement:
|
|
201
|
+
# X start positions of the corridor to test (metres from room origin)
|
|
202
|
+
x_start_options: [1.0, 2.0]
|
|
203
|
+
# Transverse (Y) axis positions of the corridor centre to test
|
|
204
|
+
y_options: [1.0, 1.4, 1.8]
|
|
205
|
+
|
|
206
|
+
# ── Analysis zone (priority sub-zone inside the corridor) ──
|
|
207
|
+
- id: "analysis_zone"
|
|
208
|
+
type: "sub_zone"
|
|
209
|
+
priority: 1.0 # main zone: highest weight on coverage score
|
|
210
|
+
|
|
211
|
+
length: 6.0 # length of the priority sub-zone (metres)
|
|
212
|
+
contained_in: "full_corridor"
|
|
213
|
+
|
|
214
|
+
# Offsets (metres) from the corridor start where the sub-zone can begin.
|
|
215
|
+
# e.g. offset 1.0 → sub-zone starts 1m after corridor start (1m run-up)
|
|
216
|
+
# offset 3.0 → sub-zone starts 3m in (3m run-up, 1m deceleration)
|
|
217
|
+
offset_options: [1.0, 2.0, 3.0]
|
|
218
|
+
|
|
219
|
+
# ── STS point (sit-to-stand chair test — secondary priority zone) ──
|
|
220
|
+
- id: "sts_point"
|
|
221
|
+
type: "point"
|
|
222
|
+
priority: 2.0 # highest weight: this spot must be very well covered
|
|
223
|
+
|
|
224
|
+
radius: 0.6 # coverage circle radius (metres)
|
|
225
|
+
contained_in: "analysis_zone"
|
|
226
|
+
|
|
227
|
+
# If true, the optimiser searches automatically for the best position
|
|
228
|
+
# of this point inside the parent zone (maximises camera coverage score).
|
|
229
|
+
auto_optimize: true
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
# ─────────────────────────────────────────────────────────────
|
|
233
|
+
# 6. OPTIMISATION PARAMETERS
|
|
234
|
+
# ─────────────────────────────────────────────────────────────
|
|
235
|
+
optimization:
|
|
236
|
+
|
|
237
|
+
# Number of cameras required per evaluation point to consider it "well covered"
|
|
238
|
+
target_coverage: 6
|
|
239
|
+
|
|
240
|
+
# Bilateral constraint weight [0.0 – 1.0]
|
|
241
|
+
# 0.0 = disabled (pure coverage maximisation)
|
|
242
|
+
# 1.0 = fully enforced (score 0 if only one side covered)
|
|
243
|
+
# Recommended: 0.6 – 0.8 for biomechanics labs
|
|
244
|
+
bilateral_weight: 0.9
|
|
245
|
+
|
|
246
|
+
# Fraction of the subject body (feet→head) that must be visible
|
|
247
|
+
# for a camera to "count" at an evaluation point.
|
|
248
|
+
vertical_coverage_threshold: 0.9
|
|
249
|
+
|
|
250
|
+
# Greedy optimisation restarts per combination of zone positions
|
|
251
|
+
restarts_per_combo: 10
|
|
252
|
+
|
|
253
|
+
# Candidate generation resolution
|
|
254
|
+
wall_step: 0.35 # spacing between camera positions along walls (m)
|
|
255
|
+
angle_steps: 24 # number of yaw angles tested per wall position
|
|
256
|
+
tripod_grid_step: 0.70 # spacing of the interior tripod grid (m)
|
|
257
|
+
distance_quality_factor: 0.001
|
|
258
|
+
|
|
259
|
+
# Distance quality decay: score multiplier = 1 / (1 + k * d²)
|
|
260
|
+
# Higher k → strong penalty for distant cameras
|
|
261
|
+
# k=0.01: 0.96 @ 2m | 0.80 @ 5m | 0.50 @ 10m
|
|
262
|
+
# Graph generation mode:
|
|
263
|
+
# all → save a graph for every optimisation attempt (verbose)
|
|
264
|
+
# records_only → save only when a new global record is set
|
|
265
|
+
# best_per_combo → save only the best result of each zone combination
|
|
266
|
+
algo: "greedy_1opt" # greedy | greedy_1opt
|
|
267
|
+
graph_mode: "all"
|
|
268
|
+
|
{lab_camera_optimizer-1.0.2 → lab_camera_optimizer-1.0.3}/lab_camera_optimizer.egg-info/SOURCES.txt
RENAMED
|
@@ -16,9 +16,14 @@ core/greedy.py
|
|
|
16
16
|
core/room.py
|
|
17
17
|
core/scoring.py
|
|
18
18
|
core/visualize.py
|
|
19
|
+
lab_camera_optimizer/__init__.py
|
|
19
20
|
lab_camera_optimizer.egg-info/PKG-INFO
|
|
20
21
|
lab_camera_optimizer.egg-info/SOURCES.txt
|
|
21
22
|
lab_camera_optimizer.egg-info/dependency_links.txt
|
|
22
23
|
lab_camera_optimizer.egg-info/entry_points.txt
|
|
23
24
|
lab_camera_optimizer.egg-info/requires.txt
|
|
24
|
-
lab_camera_optimizer.egg-info/top_level.txt
|
|
25
|
+
lab_camera_optimizer.egg-info/top_level.txt
|
|
26
|
+
lab_camera_optimizer/configs/T_zone_direction_change.yaml
|
|
27
|
+
lab_camera_optimizer/configs/example_real_world.yaml
|
|
28
|
+
lab_camera_optimizer/configs/example_simple.yaml
|
|
29
|
+
lab_camera_optimizer/configs/labo_CHU.yaml
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "lab-camera-optimizer"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.3"
|
|
8
8
|
description = "Automated camera placement optimiser for markerless biomechanics motion capture labs"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -46,10 +46,10 @@ lab-camera-init = "init_project:main"
|
|
|
46
46
|
|
|
47
47
|
[tool.setuptools.packages.find]
|
|
48
48
|
where = ["."]
|
|
49
|
-
include = ["core*"]
|
|
49
|
+
include = ["core*", "lab_camera_optimizer*"]
|
|
50
50
|
|
|
51
51
|
[tool.setuptools.package-data]
|
|
52
|
-
"
|
|
52
|
+
"lab_camera_optimizer" = ["configs/*.yaml"]
|
|
53
53
|
|
|
54
54
|
# Include top-level scripts and configs in the source distribution
|
|
55
55
|
[tool.setuptools]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lab_camera_optimizer-1.0.2 → lab_camera_optimizer-1.0.3}/configs/T_zone_direction_change.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lab_camera_optimizer-1.0.2 → lab_camera_optimizer-1.0.3}/lab_camera_optimizer.egg-info/requires.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|