camera-client 0.2.10__tar.gz → 0.3.0__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 (26) hide show
  1. camera_client-0.3.0/PKG-INFO +308 -0
  2. camera_client-0.3.0/README.md +278 -0
  3. camera_client-0.3.0/camera_client/__init__.py +5 -0
  4. camera_client-0.3.0/camera_client/camera_network.py +211 -0
  5. {camera_client-0.2.10 → camera_client-0.3.0}/camera_client/client.py +24 -1
  6. camera_client-0.3.0/camera_client/error_model.py +221 -0
  7. camera_client-0.3.0/camera_client/triangulation.py +109 -0
  8. camera_client-0.3.0/camera_client.egg-info/PKG-INFO +308 -0
  9. {camera_client-0.2.10 → camera_client-0.3.0}/camera_client.egg-info/SOURCES.txt +3 -0
  10. {camera_client-0.2.10 → camera_client-0.3.0}/pyproject.toml +1 -1
  11. camera_client-0.2.10/PKG-INFO +0 -446
  12. camera_client-0.2.10/README.md +0 -416
  13. camera_client-0.2.10/camera_client/__init__.py +0 -3
  14. camera_client-0.2.10/camera_client.egg-info/PKG-INFO +0 -446
  15. {camera_client-0.2.10 → camera_client-0.3.0}/LICENSE +0 -0
  16. {camera_client-0.2.10 → camera_client-0.3.0}/MANIFEST.in +0 -0
  17. {camera_client-0.2.10 → camera_client-0.3.0}/camera_client/__main__.py +0 -0
  18. {camera_client-0.2.10 → camera_client-0.3.0}/camera_client/loading.py +0 -0
  19. {camera_client-0.2.10 → camera_client-0.3.0}/camera_client/script.py +0 -0
  20. {camera_client-0.2.10 → camera_client-0.3.0}/camera_client.egg-info/dependency_links.txt +0 -0
  21. {camera_client-0.2.10 → camera_client-0.3.0}/camera_client.egg-info/entry_points.txt +0 -0
  22. {camera_client-0.2.10 → camera_client-0.3.0}/camera_client.egg-info/requires.txt +0 -0
  23. {camera_client-0.2.10 → camera_client-0.3.0}/camera_client.egg-info/top_level.txt +0 -0
  24. {camera_client-0.2.10 → camera_client-0.3.0}/requirements.txt +0 -0
  25. {camera_client-0.2.10 → camera_client-0.3.0}/setup.cfg +0 -0
  26. {camera_client-0.2.10 → camera_client-0.3.0}/setup.py +0 -0
@@ -0,0 +1,308 @@
1
+ Metadata-Version: 2.4
2
+ Name: camera-client
3
+ Version: 0.3.0
4
+ Summary: Python SDK for camera calibration and projection transformations - handle lens distortion, coordinate transformations, and 3D ray casting with symbolic expressions.
5
+ Author-email: Alexander Abramov <extremal.ru@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/avabr/camera-client
8
+ Project-URL: Repository, https://github.com/avabr/camera-client
9
+ Project-URL: Issues, https://github.com/avabr/camera-client/issues
10
+ Keywords: camera,calibration,projection,3d,ray-casting,computer-vision,lens-distortion,coordinate-transformation,sympy
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.7
17
+ Classifier: Programming Language :: Python :: 3.8
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
24
+ Requires-Python: >=3.7
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: numpy>=1.20.0
28
+ Requires-Dist: sympy>=1.10.0
29
+ Dynamic: license-file
30
+
31
+ # camera-client
32
+
33
+ Python SDK for camera calibration, projection transformations, and multi-camera spatial uncertainty analysis. Transform coordinates between distorted image space, corrected image space, and real-world 3D coordinates using pre-computed calibration data. Quantify measurement uncertainty and fuse observations from multiple cameras.
34
+
35
+ ## Features
36
+
37
+ - **Vectorized operations** - Process multiple points simultaneously for high performance
38
+ - **Multiple coordinate systems** - Transform between source (distorted), corrected, and ground (3D world) coordinates
39
+ - **Lens distortion handling** - Correct for camera lens distortion using calibration lookup tables
40
+ - **Ground plane projection** - Project image coordinates to 3D world coordinates and vice versa
41
+ - **Ray casting** - Generate 3D rays from image coordinates for ray tracing and 3D reconstruction
42
+ - **Calibration error models** - Quantify pixel-level uncertainty from distortion correction and geometric calibration
43
+ - **3D spatial covariance** - Propagate pixel uncertainty to full 3x3 covariance matrices in world space
44
+ - **Multi-camera fusion** - Combine measurements from multiple cameras via information fusion
45
+ - **Triangulation** - Recover 3D positions from multi-camera observations with consistency checking
46
+ - **Sympy-based transformations** - Fast compiled symbolic expressions for mathematical transformations
47
+ - **NumPy-based** - Fast array operations with minimal dependencies
48
+
49
+ ## Installation
50
+
51
+ Install from PyPI:
52
+
53
+ ```bash
54
+ pip install camera-client
55
+ ```
56
+
57
+ ## CLI Usage
58
+
59
+ Download camera calibration archives from URL:
60
+
61
+ ```bash
62
+ # Download single archive
63
+ python -m camera_client get_camera_archive https://example.com/camera.npz
64
+
65
+ # Download from file with URLs (one per line, non-URL lines ignored)
66
+ python -m camera_client get_camera_archive -f urls.txt -o ./archives
67
+
68
+ # Download from JSON config (list of objects with "archive_url" or "camera_uuid" key)
69
+ python -m camera_client get_camera_archive -f config.json -o ./archives
70
+
71
+ # Download only specific camera from JSON config
72
+ python -m camera_client get_camera_archive -f config.json --camera_id=66 -o ./archives
73
+ ```
74
+
75
+ JSON config entries may use `"archive_url"` (direct link) or `"camera_uuid"` (requires
76
+ `CAMERA_SERVICE_ENTRYPOINT` env variable — the URL is constructed as
77
+ `<CAMERA_SERVICE_ENTRYPOINT>/processing_api/projection_npz_archive/<camera_uuid>`).
78
+
79
+ ## Quick Start (Projection)
80
+
81
+ ```python
82
+ import numpy as np
83
+ from camera_client import CameraProjection
84
+
85
+ # Load camera calibration data from NPZ archive
86
+ camera = CameraProjection.load("camera_calibration_archive.npz")
87
+
88
+ # All methods work with (N, 2) or (N, 3) shaped arrays
89
+ source_points = np.array([
90
+ [100, 200],
91
+ [300, 400],
92
+ [500, 600]
93
+ ]) # Shape: (3, 2)
94
+
95
+ # ── Forward: image → world ──
96
+
97
+ # Project to ground plane: src → gnd (at height = 0)
98
+ ground_points = camera.src_to_gnd(source_points, h=0)
99
+ print(ground_points) # (N, 3) array with [x, y, z] coordinates
100
+
101
+ # Remove lens distortion only: src → ctd
102
+ ctd_points = camera.src_to_ctd(source_points)
103
+
104
+ # ── Reverse: world → image ──
105
+
106
+ # Project 3D points back to distorted image coordinates
107
+ world_points = np.array([[10.0, 5.0, 0.0], [15.0, 8.0, 1.5]])
108
+ src_points = camera.gnd_to_src(world_points) # gnd → src
109
+ ctd_points = camera.gnd_to_ctd(world_points) # gnd → ctd
110
+
111
+ # Corrected back to distorted
112
+ src_from_ctd = camera.ctd_to_src(ctd_points) # ctd → src
113
+ ```
114
+
115
+ ## Quick Start (Measurements)
116
+
117
+ Multi-camera uncertainty analysis: error models, covariance fusion, and triangulation.
118
+
119
+ ```python
120
+ import numpy as np
121
+ from camera_client import CameraProjection, CameraNetwork, triangulation
122
+
123
+ # Load cameras and create a network
124
+ cameras = [
125
+ CameraProjection.load("camera_1.npz"),
126
+ CameraProjection.load("camera_2.npz"),
127
+ CameraProjection.load("camera_3.npz"),
128
+ ]
129
+ net = CameraNetwork(cameras)
130
+ # net.cameras — dict {camera_id: CameraProjection}
131
+ # net.covariances — dict {camera_id: CameraSpatialCovariance}
132
+ ```
133
+
134
+ ### Spatial covariance
135
+
136
+ `get_covariance` computes 3x3 spatial covariance matrices for 3D points.
137
+ The matrix encodes how pixel-level uncertainty (distortion + geometric calibration + detection)
138
+ propagates into world-space uncertainty through the camera's ray geometry.
139
+
140
+ ```python
141
+ points = np.array([
142
+ [15.0, 5.0, 0.0],
143
+ [18.0, 6.0, 1.5],
144
+ ])
145
+
146
+ # Per-camera covariance (no fusion, no visibility check)
147
+ covs_cam = net.get_covariance(points, camera_id=1177, detection_sigma=0.01)
148
+ # covs_cam[i] is a (3, 3) covariance matrix from camera 1177
149
+
150
+ # Fused covariance from all visible cameras (information fusion)
151
+ covs_fused = net.get_covariance(points, detection_sigma=0.01)
152
+ # covs_fused[i] is (3, 3) fused covariance, or None if not visible to any camera
153
+
154
+ for i, cov in enumerate(covs_fused):
155
+ if cov is not None:
156
+ stds = np.sqrt(np.linalg.eigvalsh(cov))
157
+ print(f"Point {i}: σ = {stds[0]:.3f}m, {stds[1]:.3f}m, {stds[2]:.3f}m")
158
+ ```
159
+
160
+ ### Triangulation
161
+
162
+ Recover a 3D point from pixel observations in multiple cameras.
163
+ Returns the fused position, a priori covariance (from error models),
164
+ and a posteriori covariance (inflated if cameras are inconsistent).
165
+
166
+ ```python
167
+ # Observations: {camera_id: src_point} — source (distorted) image coordinates
168
+ observations = {
169
+ 1177: np.array([946.9, 853.1]),
170
+ 1178: np.array([956.0, 765.0]),
171
+ }
172
+
173
+ result = net.triangulate(observations, detection_sigma=0.01)
174
+ p_fused, sigma_prior, sigma_posterior = result
175
+
176
+ print(f"Position: {p_fused}")
177
+ print(f"Consistent: {sigma_prior is sigma_posterior}") # True if within n_sigma
178
+
179
+ # Adjust consistency threshold (default n_sigma=3.0)
180
+ result = net.triangulate(observations, n_sigma=2.0)
181
+ ```
182
+
183
+ ### Mahalanobis distance
184
+
185
+ Check statistical consistency between two point estimates with their covariances.
186
+
187
+ ```python
188
+ d2 = triangulation.mahalanobis_distance(p1, cov1, p2, cov2)
189
+ # d2 is squared Mahalanobis distance; compare to chi-squared thresholds
190
+ # e.g. chi2(3 dof, 99%) ≈ 11.34
191
+ ```
192
+
193
+ ## Coordinate Systems
194
+
195
+ This library handles transformations between three coordinate systems:
196
+
197
+ - **src** (Source): Distorted image coordinates from the camera
198
+ - **ctd** (Corrected): Undistorted image coordinates after lens correction
199
+ - **gnd** (Ground): Real-world 3D coordinates (x, y, z)
200
+
201
+ ```
202
+ Distorted Undistorted World 3D
203
+ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
204
+ │ │ │ │ │ │
205
+ │ Source (src) │ <──> │ Corrected (ctd) │ <──> │ Ground (gnd) │
206
+ │ │ │ │ │ │
207
+ └─────────────────┘ └─────────────────┘ └─────────────────┘
208
+ Lens distortion Lens correction 3D projection
209
+ ```
210
+
211
+ ## API Reference
212
+
213
+ ### `CameraProjection`
214
+
215
+ | Method | Input | Output | Description |
216
+ |--------|-------|--------|-------------|
217
+ | `load(archive_path)` | str | `CameraProjection` | Load calibration from .npz file |
218
+ | `src_to_ctd(points)` | (N, 2) | (N, 2) | Source → corrected (undistort) |
219
+ | `ctd_to_src(points)` | (N, 2) | (N, 2) | Corrected → source (redistort) |
220
+ | `src_to_gnd(points, h)` | (N, 2), scalar/array | (N, 3) | Source → 3D ground at height h |
221
+ | `gnd_to_src(points)` | (N, 3) | (N, 2) | 3D ground → source |
222
+ | `ctd_to_gnd(points, h)` | (N, 2), scalar/array | (N, 3) | Corrected → 3D ground at height h |
223
+ | `gnd_to_ctd(points)` | (N, 3) | (N, 2) | 3D ground → corrected |
224
+ | `src_to_ray(points)` | (N, 2) | (N, 3) | Source → normalized ray directions |
225
+ | `ctd_to_ray(points)` | (N, 2) | (N, 3) | Corrected → normalized ray directions |
226
+ | `ctd_to_ray_jacobian(x, y)` | scalar, scalar | (3, 2) | Ray direction Jacobian at CTD point |
227
+ | `get_key_point()` | — | (3,) | Camera position in world space |
228
+ | `get_ctd_points_context(points)` | (N, 2) | dict | Scale context (wscale, hscale, vangle) at CTD points |
229
+ | `get_src_points_context(points)` | (N, 2) | dict | Scale context at source points (converts to CTD internally) |
230
+
231
+ All point methods expect 2D arrays. For a single point: `np.array([[x, y]])`.
232
+ Out-of-bounds points return NaN. Height `h` can be a scalar or per-point (N,) array.
233
+
234
+ ### `CameraNetwork`
235
+
236
+ | Method | Description |
237
+ |--------|-------------|
238
+ | `CameraNetwork(cameras)` | Create network from list of `CameraProjection` instances |
239
+ | `get_covariance(points, ...)` | Fused 3x3 covariance for (N, 3) points from all visible cameras |
240
+ | `get_covariance(points, camera_id=id, ...)` | Per-camera 3x3 covariance (no fusion, no visibility check) |
241
+ | `triangulate(observations, ...)` | 3D point + covariance from `{camera_id: src_point}` observations |
242
+
243
+ Common parameters: `detection_sigma` (float), `sigma_binding` (float), `use_efov` (bool), `n_sigma` (float).
244
+
245
+ ### `triangulation` module
246
+
247
+ | Function | Description |
248
+ |----------|-------------|
249
+ | `fuse_covariances(covs)` | (Σ₁⁻¹ + ... + Σₙ⁻¹)⁻¹ |
250
+ | `information_fusion(points, covs)` | Fused point + covariance |
251
+ | `mahalanobis_distance(p1, cov1, p2, cov2)` | Squared Mahalanobis distance |
252
+ | `least_squares_intersection(rays)` | Closest point to N rays (SVD) |
253
+ | `closest_point_on_ray(origin, dir, point)` | Project point onto ray |
254
+
255
+ ## Calibration File Format
256
+
257
+ The calibration file is a NumPy `.npz` archive containing:
258
+
259
+ ### Lookup Tables
260
+ - `src2ctd`: Source to corrected coordinate map (H x W x 2)
261
+ - `ctd2src`: Corrected to source coordinate map (H x W x 2)
262
+ - `map_scale_h`: Height scale values (H x W)
263
+ - `map_scale_w`: Width scale values (H x W)
264
+ - `map_scale_vang`: Vertical angle values (H x W)
265
+
266
+ ### Symbolic Expressions (stored as strings, parsed with SymPy)
267
+ - `exp_im2gnd`: Image to ground coordinate transformation
268
+ - `exp_gnd2im`: Ground to image coordinate transformation
269
+ - `exp_key_point`: Camera key-point (position) in world space
270
+ - `exp_im2ray`: Image to ray direction transformation
271
+
272
+ ### Metadata
273
+ - `format_version`: Version string of the data format
274
+ - `camera_id`: Integer identifier for the camera
275
+ - `plan_url`: URL or path to the ground plan image
276
+ - `plan_scale`: Scale factor for ground plane coordinates (pixels per meter)
277
+ - `plan_width`: Width of the ground plan in pixels
278
+ - `plan_height`: Height of the ground plan in pixels
279
+ - `im_src_url`: URL or path to the source (distorted) camera image
280
+ - `im_ctd_url`: URL or path to the corrected (undistorted) camera image
281
+ - `im_width`: Width of the camera image in pixels
282
+ - `im_height`: Height of the camera image in pixels
283
+ - `ctd_geometry`: JSON object with geometry data in CTD coordinates (efov_polygon, counting_lines)
284
+
285
+ ## Requirements
286
+
287
+ - Python >= 3.7
288
+ - NumPy >= 1.20.0
289
+ - SymPy >= 1.10.0
290
+
291
+ ## Links
292
+
293
+ - **Repository**: [https://github.com/avabr/camera-client](https://github.com/avabr/camera-client)
294
+ - **Issues**: [https://github.com/avabr/camera-client/issues](https://github.com/avabr/camera-client/issues)
295
+ - **PyPI**: [https://pypi.org/project/camera-client/](https://pypi.org/project/camera-client/)
296
+
297
+ ## License
298
+
299
+ MIT License - see [LICENSE](LICENSE) file for details.
300
+
301
+ ## Author
302
+
303
+ Alexander V. Abramov ([avabr.me@gmail.com](mailto:avabr.me@gmail.com))
304
+
305
+ ## Upload PyPi
306
+
307
+ rm dist/* && python -m build && python -m twine upload dist/*
308
+
@@ -0,0 +1,278 @@
1
+ # camera-client
2
+
3
+ Python SDK for camera calibration, projection transformations, and multi-camera spatial uncertainty analysis. Transform coordinates between distorted image space, corrected image space, and real-world 3D coordinates using pre-computed calibration data. Quantify measurement uncertainty and fuse observations from multiple cameras.
4
+
5
+ ## Features
6
+
7
+ - **Vectorized operations** - Process multiple points simultaneously for high performance
8
+ - **Multiple coordinate systems** - Transform between source (distorted), corrected, and ground (3D world) coordinates
9
+ - **Lens distortion handling** - Correct for camera lens distortion using calibration lookup tables
10
+ - **Ground plane projection** - Project image coordinates to 3D world coordinates and vice versa
11
+ - **Ray casting** - Generate 3D rays from image coordinates for ray tracing and 3D reconstruction
12
+ - **Calibration error models** - Quantify pixel-level uncertainty from distortion correction and geometric calibration
13
+ - **3D spatial covariance** - Propagate pixel uncertainty to full 3x3 covariance matrices in world space
14
+ - **Multi-camera fusion** - Combine measurements from multiple cameras via information fusion
15
+ - **Triangulation** - Recover 3D positions from multi-camera observations with consistency checking
16
+ - **Sympy-based transformations** - Fast compiled symbolic expressions for mathematical transformations
17
+ - **NumPy-based** - Fast array operations with minimal dependencies
18
+
19
+ ## Installation
20
+
21
+ Install from PyPI:
22
+
23
+ ```bash
24
+ pip install camera-client
25
+ ```
26
+
27
+ ## CLI Usage
28
+
29
+ Download camera calibration archives from URL:
30
+
31
+ ```bash
32
+ # Download single archive
33
+ python -m camera_client get_camera_archive https://example.com/camera.npz
34
+
35
+ # Download from file with URLs (one per line, non-URL lines ignored)
36
+ python -m camera_client get_camera_archive -f urls.txt -o ./archives
37
+
38
+ # Download from JSON config (list of objects with "archive_url" or "camera_uuid" key)
39
+ python -m camera_client get_camera_archive -f config.json -o ./archives
40
+
41
+ # Download only specific camera from JSON config
42
+ python -m camera_client get_camera_archive -f config.json --camera_id=66 -o ./archives
43
+ ```
44
+
45
+ JSON config entries may use `"archive_url"` (direct link) or `"camera_uuid"` (requires
46
+ `CAMERA_SERVICE_ENTRYPOINT` env variable — the URL is constructed as
47
+ `<CAMERA_SERVICE_ENTRYPOINT>/processing_api/projection_npz_archive/<camera_uuid>`).
48
+
49
+ ## Quick Start (Projection)
50
+
51
+ ```python
52
+ import numpy as np
53
+ from camera_client import CameraProjection
54
+
55
+ # Load camera calibration data from NPZ archive
56
+ camera = CameraProjection.load("camera_calibration_archive.npz")
57
+
58
+ # All methods work with (N, 2) or (N, 3) shaped arrays
59
+ source_points = np.array([
60
+ [100, 200],
61
+ [300, 400],
62
+ [500, 600]
63
+ ]) # Shape: (3, 2)
64
+
65
+ # ── Forward: image → world ──
66
+
67
+ # Project to ground plane: src → gnd (at height = 0)
68
+ ground_points = camera.src_to_gnd(source_points, h=0)
69
+ print(ground_points) # (N, 3) array with [x, y, z] coordinates
70
+
71
+ # Remove lens distortion only: src → ctd
72
+ ctd_points = camera.src_to_ctd(source_points)
73
+
74
+ # ── Reverse: world → image ──
75
+
76
+ # Project 3D points back to distorted image coordinates
77
+ world_points = np.array([[10.0, 5.0, 0.0], [15.0, 8.0, 1.5]])
78
+ src_points = camera.gnd_to_src(world_points) # gnd → src
79
+ ctd_points = camera.gnd_to_ctd(world_points) # gnd → ctd
80
+
81
+ # Corrected back to distorted
82
+ src_from_ctd = camera.ctd_to_src(ctd_points) # ctd → src
83
+ ```
84
+
85
+ ## Quick Start (Measurements)
86
+
87
+ Multi-camera uncertainty analysis: error models, covariance fusion, and triangulation.
88
+
89
+ ```python
90
+ import numpy as np
91
+ from camera_client import CameraProjection, CameraNetwork, triangulation
92
+
93
+ # Load cameras and create a network
94
+ cameras = [
95
+ CameraProjection.load("camera_1.npz"),
96
+ CameraProjection.load("camera_2.npz"),
97
+ CameraProjection.load("camera_3.npz"),
98
+ ]
99
+ net = CameraNetwork(cameras)
100
+ # net.cameras — dict {camera_id: CameraProjection}
101
+ # net.covariances — dict {camera_id: CameraSpatialCovariance}
102
+ ```
103
+
104
+ ### Spatial covariance
105
+
106
+ `get_covariance` computes 3x3 spatial covariance matrices for 3D points.
107
+ The matrix encodes how pixel-level uncertainty (distortion + geometric calibration + detection)
108
+ propagates into world-space uncertainty through the camera's ray geometry.
109
+
110
+ ```python
111
+ points = np.array([
112
+ [15.0, 5.0, 0.0],
113
+ [18.0, 6.0, 1.5],
114
+ ])
115
+
116
+ # Per-camera covariance (no fusion, no visibility check)
117
+ covs_cam = net.get_covariance(points, camera_id=1177, detection_sigma=0.01)
118
+ # covs_cam[i] is a (3, 3) covariance matrix from camera 1177
119
+
120
+ # Fused covariance from all visible cameras (information fusion)
121
+ covs_fused = net.get_covariance(points, detection_sigma=0.01)
122
+ # covs_fused[i] is (3, 3) fused covariance, or None if not visible to any camera
123
+
124
+ for i, cov in enumerate(covs_fused):
125
+ if cov is not None:
126
+ stds = np.sqrt(np.linalg.eigvalsh(cov))
127
+ print(f"Point {i}: σ = {stds[0]:.3f}m, {stds[1]:.3f}m, {stds[2]:.3f}m")
128
+ ```
129
+
130
+ ### Triangulation
131
+
132
+ Recover a 3D point from pixel observations in multiple cameras.
133
+ Returns the fused position, a priori covariance (from error models),
134
+ and a posteriori covariance (inflated if cameras are inconsistent).
135
+
136
+ ```python
137
+ # Observations: {camera_id: src_point} — source (distorted) image coordinates
138
+ observations = {
139
+ 1177: np.array([946.9, 853.1]),
140
+ 1178: np.array([956.0, 765.0]),
141
+ }
142
+
143
+ result = net.triangulate(observations, detection_sigma=0.01)
144
+ p_fused, sigma_prior, sigma_posterior = result
145
+
146
+ print(f"Position: {p_fused}")
147
+ print(f"Consistent: {sigma_prior is sigma_posterior}") # True if within n_sigma
148
+
149
+ # Adjust consistency threshold (default n_sigma=3.0)
150
+ result = net.triangulate(observations, n_sigma=2.0)
151
+ ```
152
+
153
+ ### Mahalanobis distance
154
+
155
+ Check statistical consistency between two point estimates with their covariances.
156
+
157
+ ```python
158
+ d2 = triangulation.mahalanobis_distance(p1, cov1, p2, cov2)
159
+ # d2 is squared Mahalanobis distance; compare to chi-squared thresholds
160
+ # e.g. chi2(3 dof, 99%) ≈ 11.34
161
+ ```
162
+
163
+ ## Coordinate Systems
164
+
165
+ This library handles transformations between three coordinate systems:
166
+
167
+ - **src** (Source): Distorted image coordinates from the camera
168
+ - **ctd** (Corrected): Undistorted image coordinates after lens correction
169
+ - **gnd** (Ground): Real-world 3D coordinates (x, y, z)
170
+
171
+ ```
172
+ Distorted Undistorted World 3D
173
+ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
174
+ │ │ │ │ │ │
175
+ │ Source (src) │ <──> │ Corrected (ctd) │ <──> │ Ground (gnd) │
176
+ │ │ │ │ │ │
177
+ └─────────────────┘ └─────────────────┘ └─────────────────┘
178
+ Lens distortion Lens correction 3D projection
179
+ ```
180
+
181
+ ## API Reference
182
+
183
+ ### `CameraProjection`
184
+
185
+ | Method | Input | Output | Description |
186
+ |--------|-------|--------|-------------|
187
+ | `load(archive_path)` | str | `CameraProjection` | Load calibration from .npz file |
188
+ | `src_to_ctd(points)` | (N, 2) | (N, 2) | Source → corrected (undistort) |
189
+ | `ctd_to_src(points)` | (N, 2) | (N, 2) | Corrected → source (redistort) |
190
+ | `src_to_gnd(points, h)` | (N, 2), scalar/array | (N, 3) | Source → 3D ground at height h |
191
+ | `gnd_to_src(points)` | (N, 3) | (N, 2) | 3D ground → source |
192
+ | `ctd_to_gnd(points, h)` | (N, 2), scalar/array | (N, 3) | Corrected → 3D ground at height h |
193
+ | `gnd_to_ctd(points)` | (N, 3) | (N, 2) | 3D ground → corrected |
194
+ | `src_to_ray(points)` | (N, 2) | (N, 3) | Source → normalized ray directions |
195
+ | `ctd_to_ray(points)` | (N, 2) | (N, 3) | Corrected → normalized ray directions |
196
+ | `ctd_to_ray_jacobian(x, y)` | scalar, scalar | (3, 2) | Ray direction Jacobian at CTD point |
197
+ | `get_key_point()` | — | (3,) | Camera position in world space |
198
+ | `get_ctd_points_context(points)` | (N, 2) | dict | Scale context (wscale, hscale, vangle) at CTD points |
199
+ | `get_src_points_context(points)` | (N, 2) | dict | Scale context at source points (converts to CTD internally) |
200
+
201
+ All point methods expect 2D arrays. For a single point: `np.array([[x, y]])`.
202
+ Out-of-bounds points return NaN. Height `h` can be a scalar or per-point (N,) array.
203
+
204
+ ### `CameraNetwork`
205
+
206
+ | Method | Description |
207
+ |--------|-------------|
208
+ | `CameraNetwork(cameras)` | Create network from list of `CameraProjection` instances |
209
+ | `get_covariance(points, ...)` | Fused 3x3 covariance for (N, 3) points from all visible cameras |
210
+ | `get_covariance(points, camera_id=id, ...)` | Per-camera 3x3 covariance (no fusion, no visibility check) |
211
+ | `triangulate(observations, ...)` | 3D point + covariance from `{camera_id: src_point}` observations |
212
+
213
+ Common parameters: `detection_sigma` (float), `sigma_binding` (float), `use_efov` (bool), `n_sigma` (float).
214
+
215
+ ### `triangulation` module
216
+
217
+ | Function | Description |
218
+ |----------|-------------|
219
+ | `fuse_covariances(covs)` | (Σ₁⁻¹ + ... + Σₙ⁻¹)⁻¹ |
220
+ | `information_fusion(points, covs)` | Fused point + covariance |
221
+ | `mahalanobis_distance(p1, cov1, p2, cov2)` | Squared Mahalanobis distance |
222
+ | `least_squares_intersection(rays)` | Closest point to N rays (SVD) |
223
+ | `closest_point_on_ray(origin, dir, point)` | Project point onto ray |
224
+
225
+ ## Calibration File Format
226
+
227
+ The calibration file is a NumPy `.npz` archive containing:
228
+
229
+ ### Lookup Tables
230
+ - `src2ctd`: Source to corrected coordinate map (H x W x 2)
231
+ - `ctd2src`: Corrected to source coordinate map (H x W x 2)
232
+ - `map_scale_h`: Height scale values (H x W)
233
+ - `map_scale_w`: Width scale values (H x W)
234
+ - `map_scale_vang`: Vertical angle values (H x W)
235
+
236
+ ### Symbolic Expressions (stored as strings, parsed with SymPy)
237
+ - `exp_im2gnd`: Image to ground coordinate transformation
238
+ - `exp_gnd2im`: Ground to image coordinate transformation
239
+ - `exp_key_point`: Camera key-point (position) in world space
240
+ - `exp_im2ray`: Image to ray direction transformation
241
+
242
+ ### Metadata
243
+ - `format_version`: Version string of the data format
244
+ - `camera_id`: Integer identifier for the camera
245
+ - `plan_url`: URL or path to the ground plan image
246
+ - `plan_scale`: Scale factor for ground plane coordinates (pixels per meter)
247
+ - `plan_width`: Width of the ground plan in pixels
248
+ - `plan_height`: Height of the ground plan in pixels
249
+ - `im_src_url`: URL or path to the source (distorted) camera image
250
+ - `im_ctd_url`: URL or path to the corrected (undistorted) camera image
251
+ - `im_width`: Width of the camera image in pixels
252
+ - `im_height`: Height of the camera image in pixels
253
+ - `ctd_geometry`: JSON object with geometry data in CTD coordinates (efov_polygon, counting_lines)
254
+
255
+ ## Requirements
256
+
257
+ - Python >= 3.7
258
+ - NumPy >= 1.20.0
259
+ - SymPy >= 1.10.0
260
+
261
+ ## Links
262
+
263
+ - **Repository**: [https://github.com/avabr/camera-client](https://github.com/avabr/camera-client)
264
+ - **Issues**: [https://github.com/avabr/camera-client/issues](https://github.com/avabr/camera-client/issues)
265
+ - **PyPI**: [https://pypi.org/project/camera-client/](https://pypi.org/project/camera-client/)
266
+
267
+ ## License
268
+
269
+ MIT License - see [LICENSE](LICENSE) file for details.
270
+
271
+ ## Author
272
+
273
+ Alexander V. Abramov ([avabr.me@gmail.com](mailto:avabr.me@gmail.com))
274
+
275
+ ## Upload PyPi
276
+
277
+ rm dist/* && python -m build && python -m twine upload dist/*
278
+
@@ -0,0 +1,5 @@
1
+ from camera_client.client import CameraProjection
2
+ from camera_client.camera_network import CameraNetwork
3
+ from camera_client import triangulation
4
+
5
+ __all__ = ["CameraProjection", "CameraNetwork", "triangulation"]