lab-camera-optimizer 1.0.1__tar.gz → 1.0.2__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.
Files changed (27) hide show
  1. lab_camera_optimizer-1.0.2/MANIFEST.in +4 -0
  2. {lab_camera_optimizer-1.0.1 → lab_camera_optimizer-1.0.2}/PKG-INFO +14 -5
  3. {lab_camera_optimizer-1.0.1 → lab_camera_optimizer-1.0.2}/README.md +13 -4
  4. lab_camera_optimizer-1.0.2/configs/T_zone_direction_change.yaml +200 -0
  5. lab_camera_optimizer-1.0.2/configs/example_real_world.yaml +270 -0
  6. lab_camera_optimizer-1.0.2/configs/example_simple.yaml +180 -0
  7. lab_camera_optimizer-1.0.2/configs/labo_CHU.yaml +268 -0
  8. lab_camera_optimizer-1.0.2/init_project.py +87 -0
  9. {lab_camera_optimizer-1.0.1 → lab_camera_optimizer-1.0.2}/lab_camera_optimizer.egg-info/PKG-INFO +14 -5
  10. {lab_camera_optimizer-1.0.1 → lab_camera_optimizer-1.0.2}/lab_camera_optimizer.egg-info/SOURCES.txt +8 -0
  11. {lab_camera_optimizer-1.0.1 → lab_camera_optimizer-1.0.2}/lab_camera_optimizer.egg-info/entry_points.txt +1 -0
  12. lab_camera_optimizer-1.0.2/lab_camera_optimizer.egg-info/top_level.txt +4 -0
  13. lab_camera_optimizer-1.0.2/optimize.py +510 -0
  14. lab_camera_optimizer-1.0.2/preview_room.py +472 -0
  15. {lab_camera_optimizer-1.0.1 → lab_camera_optimizer-1.0.2}/pyproject.toml +5 -1
  16. lab_camera_optimizer-1.0.1/lab_camera_optimizer.egg-info/top_level.txt +0 -1
  17. {lab_camera_optimizer-1.0.1 → lab_camera_optimizer-1.0.2}/LICENSE +0 -0
  18. {lab_camera_optimizer-1.0.1 → lab_camera_optimizer-1.0.2}/core/__init__.py +0 -0
  19. {lab_camera_optimizer-1.0.1 → lab_camera_optimizer-1.0.2}/core/candidates.py +0 -0
  20. {lab_camera_optimizer-1.0.1 → lab_camera_optimizer-1.0.2}/core/config_loader.py +0 -0
  21. {lab_camera_optimizer-1.0.1 → lab_camera_optimizer-1.0.2}/core/greedy.py +0 -0
  22. {lab_camera_optimizer-1.0.1 → lab_camera_optimizer-1.0.2}/core/room.py +0 -0
  23. {lab_camera_optimizer-1.0.1 → lab_camera_optimizer-1.0.2}/core/scoring.py +0 -0
  24. {lab_camera_optimizer-1.0.1 → lab_camera_optimizer-1.0.2}/core/visualize.py +0 -0
  25. {lab_camera_optimizer-1.0.1 → lab_camera_optimizer-1.0.2}/lab_camera_optimizer.egg-info/dependency_links.txt +0 -0
  26. {lab_camera_optimizer-1.0.1 → lab_camera_optimizer-1.0.2}/lab_camera_optimizer.egg-info/requires.txt +0 -0
  27. {lab_camera_optimizer-1.0.1 → lab_camera_optimizer-1.0.2}/setup.cfg +0 -0
@@ -0,0 +1,4 @@
1
+ include LICENSE
2
+ include README.md
3
+ recursive-include configs *.yaml
4
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lab-camera-optimizer
3
- Version: 1.0.1
3
+ Version: 1.0.2
4
4
  Summary: Automated camera placement optimiser for markerless biomechanics motion capture labs
5
5
  Author: Florian Delaplace
6
6
  License-Expression: MIT
@@ -71,14 +71,23 @@ all evaluation points, with optional bilateral coverage constraints.
71
71
  ### Option A — pip install (recommended)
72
72
 
73
73
  ```bash
74
- pip install git+https://github.com/flodelaplace/lab-camera-optimizer.git
74
+ pip install lab-camera-optimizer
75
75
  ```
76
76
 
77
- Once installed, two commands are available anywhere in your terminal:
77
+ Then initialise a working directory with the example configs:
78
78
 
79
79
  ```bash
80
- lab-camera-optimizer --config path/to/my_lab.yaml
81
- lab-camera-preview --config path/to/my_lab.yaml
80
+ mkdir my-lab && cd my-lab
81
+ lab-camera-init
82
+ ```
83
+
84
+ This copies the example YAML configs into `configs/` and creates the `outputs/`
85
+ folder in your current directory. Three commands are then available:
86
+
87
+ ```bash
88
+ lab-camera-init # copy example configs (run once)
89
+ lab-camera-preview --config configs/example_simple.yaml
90
+ lab-camera-optimizer --config configs/example_simple.yaml
82
91
  ```
83
92
 
84
93
  ### Option B — Clone and run locally
@@ -44,14 +44,23 @@ all evaluation points, with optional bilateral coverage constraints.
44
44
  ### Option A — pip install (recommended)
45
45
 
46
46
  ```bash
47
- pip install git+https://github.com/flodelaplace/lab-camera-optimizer.git
47
+ pip install lab-camera-optimizer
48
48
  ```
49
49
 
50
- Once installed, two commands are available anywhere in your terminal:
50
+ Then initialise a working directory with the example configs:
51
51
 
52
52
  ```bash
53
- lab-camera-optimizer --config path/to/my_lab.yaml
54
- lab-camera-preview --config path/to/my_lab.yaml
53
+ mkdir my-lab && cd my-lab
54
+ lab-camera-init
55
+ ```
56
+
57
+ This copies the example YAML configs into `configs/` and creates the `outputs/`
58
+ folder in your current directory. Three commands are then available:
59
+
60
+ ```bash
61
+ lab-camera-init # copy example configs (run once)
62
+ lab-camera-preview --config configs/example_simple.yaml
63
+ lab-camera-optimizer --config configs/example_simple.yaml
55
64
  ```
56
65
 
57
66
  ### Option B — Clone and run locally
@@ -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
+