camera-client 0.2.4__tar.gz → 0.2.6__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: camera-client
3
- Version: 0.2.4
3
+ Version: 0.2.6
4
4
  Summary: Python SDK for camera calibration and projection transformations - handle lens distortion, coordinate transformations, and 3D ray casting with symbolic expressions.
5
5
  Author-email: Alexander Abramov <extremal.ru@gmail.com>
6
6
  License: MIT
@@ -50,6 +50,18 @@ Install from PyPI:
50
50
  pip install camera-client
51
51
  ```
52
52
 
53
+ ## CLI Usage
54
+
55
+ Download camera calibration archives from URL:
56
+
57
+ ```bash
58
+ # Download single archive
59
+ python -m camera_client get_camera_archive https://example.com/camera.npz
60
+
61
+ # Download from file with URLs (one per line, non-URL lines ignored)
62
+ python -m camera_client get_camera_archive -f urls.txt -o ./archives
63
+ ```
64
+
53
65
  ## Quick Start
54
66
 
55
67
  ```python
@@ -316,6 +328,58 @@ Get the camera position (key-point) in world space.
316
328
  **Returns:**
317
329
  - `np.ndarray`: Shape (3,) array with [x, y, z] camera position
318
330
 
331
+ ---
332
+
333
+ ### `get_ctd_points_context(ctd_points)`
334
+
335
+ Get scale context values for corrected (CTD) image points.
336
+
337
+ **Parameters:**
338
+ - `ctd_points` (np.ndarray): Shape (N, 2) array of [x, y] corrected coordinates
339
+
340
+ **Returns:**
341
+ - `dict`: Dictionary with keys:
342
+ - `wscale` (np.ndarray): Shape (N,) width scale values
343
+ - `hscale` (np.ndarray): Shape (N,) height scale values
344
+ - `vangle` (np.ndarray): Shape (N,) vertical angle values (radians)
345
+
346
+ **Note:** Out-of-bounds points will have NaN values
347
+
348
+ **Example:**
349
+ ```python
350
+ ctd_points = np.array([[640, 480], [800, 600]])
351
+ context = camera.get_ctd_points_context(ctd_points)
352
+ print(context['wscale']) # Width scale at each point
353
+ print(context['hscale']) # Height scale at each point
354
+ print(context['vangle']) # Vertical angle at each point
355
+ ```
356
+
357
+ ---
358
+
359
+ ### `get_src_points_context(src_points)`
360
+
361
+ Get scale context values for source (distorted) image points.
362
+
363
+ **Parameters:**
364
+ - `src_points` (np.ndarray): Shape (N, 2) array of [x, y] source coordinates
365
+
366
+ **Returns:**
367
+ - `dict`: Dictionary with keys:
368
+ - `wscale` (np.ndarray): Shape (N,) width scale values
369
+ - `hscale` (np.ndarray): Shape (N,) height scale values
370
+ - `vangle` (np.ndarray): Shape (N,) vertical angle values (radians)
371
+
372
+ **Note:** Internally converts source points to CTD coordinates first, then retrieves context
373
+
374
+ **Example:**
375
+ ```python
376
+ src_points = np.array([[640, 480], [800, 600]])
377
+ context = camera.get_src_points_context(src_points)
378
+ print(context['wscale']) # Width scale at each point
379
+ ```
380
+
381
+ ---
382
+
319
383
  ## Calibration File Format
320
384
 
321
385
  The calibration file is a NumPy `.npz` archive containing:
@@ -20,6 +20,18 @@ Install from PyPI:
20
20
  pip install camera-client
21
21
  ```
22
22
 
23
+ ## CLI Usage
24
+
25
+ Download camera calibration archives from URL:
26
+
27
+ ```bash
28
+ # Download single archive
29
+ python -m camera_client get_camera_archive https://example.com/camera.npz
30
+
31
+ # Download from file with URLs (one per line, non-URL lines ignored)
32
+ python -m camera_client get_camera_archive -f urls.txt -o ./archives
33
+ ```
34
+
23
35
  ## Quick Start
24
36
 
25
37
  ```python
@@ -286,6 +298,58 @@ Get the camera position (key-point) in world space.
286
298
  **Returns:**
287
299
  - `np.ndarray`: Shape (3,) array with [x, y, z] camera position
288
300
 
301
+ ---
302
+
303
+ ### `get_ctd_points_context(ctd_points)`
304
+
305
+ Get scale context values for corrected (CTD) image points.
306
+
307
+ **Parameters:**
308
+ - `ctd_points` (np.ndarray): Shape (N, 2) array of [x, y] corrected coordinates
309
+
310
+ **Returns:**
311
+ - `dict`: Dictionary with keys:
312
+ - `wscale` (np.ndarray): Shape (N,) width scale values
313
+ - `hscale` (np.ndarray): Shape (N,) height scale values
314
+ - `vangle` (np.ndarray): Shape (N,) vertical angle values (radians)
315
+
316
+ **Note:** Out-of-bounds points will have NaN values
317
+
318
+ **Example:**
319
+ ```python
320
+ ctd_points = np.array([[640, 480], [800, 600]])
321
+ context = camera.get_ctd_points_context(ctd_points)
322
+ print(context['wscale']) # Width scale at each point
323
+ print(context['hscale']) # Height scale at each point
324
+ print(context['vangle']) # Vertical angle at each point
325
+ ```
326
+
327
+ ---
328
+
329
+ ### `get_src_points_context(src_points)`
330
+
331
+ Get scale context values for source (distorted) image points.
332
+
333
+ **Parameters:**
334
+ - `src_points` (np.ndarray): Shape (N, 2) array of [x, y] source coordinates
335
+
336
+ **Returns:**
337
+ - `dict`: Dictionary with keys:
338
+ - `wscale` (np.ndarray): Shape (N,) width scale values
339
+ - `hscale` (np.ndarray): Shape (N,) height scale values
340
+ - `vangle` (np.ndarray): Shape (N,) vertical angle values (radians)
341
+
342
+ **Note:** Internally converts source points to CTD coordinates first, then retrieves context
343
+
344
+ **Example:**
345
+ ```python
346
+ src_points = np.array([[640, 480], [800, 600]])
347
+ context = camera.get_src_points_context(src_points)
348
+ print(context['wscale']) # Width scale at each point
349
+ ```
350
+
351
+ ---
352
+
289
353
  ## Calibration File Format
290
354
 
291
355
  The calibration file is a NumPy `.npz` archive containing:
@@ -0,0 +1,188 @@
1
+ """Command-line interface for camera-client package."""
2
+
3
+ import argparse
4
+ import os
5
+ import sys
6
+ from pathlib import Path
7
+ from urllib.request import urlopen, Request
8
+ from urllib.parse import urlparse
9
+
10
+
11
+ def is_url(text: str) -> bool:
12
+ """
13
+ Check if a string is a valid URL.
14
+
15
+ Args:
16
+ text: String to check
17
+
18
+ Returns:
19
+ True if the string looks like a URL, False otherwise
20
+ """
21
+ text = text.strip()
22
+ return text.startswith('http://') or text.startswith('https://')
23
+
24
+
25
+ def download_archive(url: str, output_dir: str = ".", silent: bool = False) -> bool:
26
+ """
27
+ Download camera calibration archive from URL to the specified directory.
28
+
29
+ Args:
30
+ url: URL to download the archive from
31
+ output_dir: Directory to save the downloaded file (default: current directory)
32
+ silent: If True, suppress success messages (errors still printed)
33
+
34
+ Returns:
35
+ True if download succeeded, False otherwise
36
+ """
37
+ try:
38
+ # Parse the URL to extract filename from path or Content-Disposition header
39
+ parsed_url = urlparse(url)
40
+
41
+ # Create request with headers
42
+ request = Request(url, headers={'User-Agent': 'camera-client'})
43
+
44
+ # Open URL and get response
45
+ if not silent:
46
+ print(f"Downloading from: {url}")
47
+ with urlopen(request) as response:
48
+ # Try to get filename from Content-Disposition header
49
+ content_disposition = response.headers.get('Content-Disposition', '')
50
+ filename = None
51
+
52
+ if 'filename=' in content_disposition:
53
+ # Extract filename from Content-Disposition header
54
+ parts = content_disposition.split('filename=')
55
+ if len(parts) > 1:
56
+ filename = parts[1].strip('"')
57
+
58
+ # Fallback to URL path if no Content-Disposition
59
+ if not filename:
60
+ path_parts = parsed_url.path.split('/')
61
+ filename = path_parts[-1] if path_parts[-1] else 'camera_archive.npz'
62
+
63
+ # Ensure .npz extension
64
+ if not filename.endswith('.npz'):
65
+ filename += '.npz'
66
+
67
+ # Create output path
68
+ output_path = Path(output_dir) / filename
69
+
70
+ # Download file
71
+ if not silent:
72
+ print(f"Saving to: {output_path}")
73
+ with open(output_path, 'wb') as f:
74
+ f.write(response.read())
75
+
76
+ if not silent:
77
+ print(f"Successfully downloaded: {filename}")
78
+ print(f"File size: {output_path.stat().st_size} bytes")
79
+
80
+ return True
81
+
82
+ except Exception as e:
83
+ print(f"Error downloading archive from {url}: {e}", file=sys.stderr)
84
+ return False
85
+
86
+
87
+ def download_from_file(file_path: str, output_dir: str = ".") -> None:
88
+ """
89
+ Download multiple camera calibration archives from URLs listed in a file.
90
+
91
+ Non-URL lines (comments, notes, etc.) are automatically skipped.
92
+
93
+ Args:
94
+ file_path: Path to file containing URLs (one per line, non-URL lines are ignored)
95
+ output_dir: Directory to save the downloaded files (default: current directory)
96
+ """
97
+ try:
98
+ with open(file_path, 'r') as f:
99
+ # Filter only lines that are valid URLs
100
+ urls = [line.strip() for line in f if is_url(line)]
101
+
102
+ if not urls:
103
+ print(f"No URLs found in {file_path}", file=sys.stderr)
104
+ sys.exit(1)
105
+
106
+ print(f"Found {len(urls)} URL(s) in {file_path}")
107
+ print(f"Downloading to: {output_dir}\n")
108
+
109
+ success_count = 0
110
+ failed_count = 0
111
+
112
+ for i, url in enumerate(urls, 1):
113
+ print(f"[{i}/{len(urls)}] Processing: {url}")
114
+ if download_archive(url, output_dir, silent=False):
115
+ success_count += 1
116
+ else:
117
+ failed_count += 1
118
+ print() # Empty line between downloads
119
+
120
+ # Summary
121
+ print("=" * 50)
122
+ print(f"Download complete: {success_count} succeeded, {failed_count} failed")
123
+
124
+ if failed_count > 0:
125
+ sys.exit(1)
126
+
127
+ except FileNotFoundError:
128
+ print(f"Error: File not found: {file_path}", file=sys.stderr)
129
+ sys.exit(1)
130
+ except Exception as e:
131
+ print(f"Error reading file {file_path}: {e}", file=sys.stderr)
132
+ sys.exit(1)
133
+
134
+
135
+ def main():
136
+ """Main entry point for camera-client CLI."""
137
+ parser = argparse.ArgumentParser(
138
+ prog='camera-client',
139
+ description='Camera calibration client utilities'
140
+ )
141
+
142
+ subparsers = parser.add_subparsers(dest='command', help='Available commands')
143
+
144
+ # get_camera_archive command
145
+ download_parser = subparsers.add_parser(
146
+ 'get_camera_archive',
147
+ help='Get camera calibration archive(s) from URL or file'
148
+ )
149
+ download_parser.add_argument(
150
+ 'url',
151
+ nargs='?',
152
+ help='URL to download the archive from'
153
+ )
154
+ download_parser.add_argument(
155
+ '-f', '--file',
156
+ help='File containing URLs (one per line)'
157
+ )
158
+ download_parser.add_argument(
159
+ '-o', '--output-dir',
160
+ default='.',
161
+ help='Output directory (default: current directory)'
162
+ )
163
+
164
+ args = parser.parse_args()
165
+
166
+ if args.command == 'get_camera_archive':
167
+ # Check that either url or file is provided (but not both)
168
+ if args.url and args.file:
169
+ print("Error: Cannot specify both URL and file. Use either positional URL or -f/--file option.", file=sys.stderr)
170
+ sys.exit(1)
171
+ elif not args.url and not args.file:
172
+ print("Error: Must specify either URL or file with -f/--file option.", file=sys.stderr)
173
+ download_parser.print_help()
174
+ sys.exit(1)
175
+
176
+ # Process based on input type
177
+ if args.file:
178
+ download_from_file(args.file, args.output_dir)
179
+ else:
180
+ success = download_archive(args.url, args.output_dir)
181
+ sys.exit(0 if success else 1)
182
+ else:
183
+ parser.print_help()
184
+ sys.exit(1)
185
+
186
+
187
+ if __name__ == '__main__':
188
+ main()
@@ -26,6 +26,7 @@ class CameraProjection:
26
26
  - ctd2src: Corrected to source distortion map (H x W x 2)
27
27
  - x_gnd, y_gnd, z_gnd: String expressions for ctd -> ground
28
28
  - x_im, y_im: String expressions for ground -> ctd
29
+ - map_scale_h, map_scale_w, map_scale_vang: Scale context maps (H x W)
29
30
  """
30
31
  data = cam_archive_data
31
32
 
@@ -47,6 +48,11 @@ class CameraProjection:
47
48
  self.src2ctd_points_map = data["src2ctd"]
48
49
  self.ctd2src_points_map = data["ctd2src"]
49
50
 
51
+ # Store scale context maps
52
+ self.map_scale_h = data["map_scale_h"]
53
+ self.map_scale_w = data["map_scale_w"]
54
+ self.map_scale_vang = data["map_scale_vang"]
55
+
50
56
  self.im_size = self.src2ctd_points_map.shape[:2]
51
57
 
52
58
  # Compile transformation expressions for ctd -> gnd
@@ -294,6 +300,81 @@ class CameraProjection:
294
300
  ctd_points = self.src_to_ctd(points)
295
301
  return self.ctd_to_ray(ctd_points)
296
302
 
303
+ def get_ctd_points_context(self, ctd_points):
304
+ """
305
+ Get scale context values for corrected (CTD) image points.
306
+
307
+ Args:
308
+ ctd_points: (N, 2) array of corrected points [[x1, y1], [x2, y2], ...]
309
+
310
+ Returns:
311
+ Dictionary with keys:
312
+ - wscale: (N,) array of width scale values
313
+ - hscale: (N,) array of height scale values
314
+ - vangle: (N,) array of vertical angle values
315
+ Out-of-bounds points will have NaN values.
316
+ """
317
+ ctd_points = np.asarray(ctd_points, dtype=float)
318
+ if ctd_points.ndim != 2 or ctd_points.shape[1] != 2:
319
+ raise ValueError(f"Expected (N, 2) array, got shape {ctd_points.shape}")
320
+
321
+ N = len(ctd_points)
322
+ wscale = np.full(N, np.nan, dtype=float)
323
+ hscale = np.full(N, np.nan, dtype=float)
324
+ vangle = np.full(N, np.nan, dtype=float)
325
+
326
+ # Check for NaN input points
327
+ valid_input = ~np.isnan(ctd_points).any(axis=1)
328
+
329
+ if not valid_input.any():
330
+ return {"wscale": wscale, "hscale": hscale, "vangle": vangle}
331
+
332
+ # Round to integer coordinates
333
+ p_int = np.round(ctd_points[valid_input]).astype(int)
334
+
335
+ # Vectorized bounds checking
336
+ in_bounds = (
337
+ (p_int[:, 0] >= 0)
338
+ & (p_int[:, 0] < self.map_scale_w.shape[1])
339
+ & (p_int[:, 1] >= 0)
340
+ & (p_int[:, 1] < self.map_scale_w.shape[0])
341
+ )
342
+
343
+ # Create mask for points that are both valid input and in bounds
344
+ valid_indices = np.where(valid_input)[0]
345
+ final_valid_indices = valid_indices[in_bounds]
346
+ valid_p_int = p_int[in_bounds]
347
+
348
+ # Vectorized lookup using advanced indexing (y, x indexing)
349
+ wscale[final_valid_indices] = self.map_scale_w[
350
+ valid_p_int[:, 1], valid_p_int[:, 0]
351
+ ]
352
+ hscale[final_valid_indices] = self.map_scale_h[
353
+ valid_p_int[:, 1], valid_p_int[:, 0]
354
+ ]
355
+ vangle[final_valid_indices] = self.map_scale_vang[
356
+ valid_p_int[:, 1], valid_p_int[:, 0]
357
+ ]
358
+
359
+ return {"wscale": wscale, "hscale": hscale, "vangle": vangle}
360
+
361
+ def get_src_points_context(self, src_points):
362
+ """
363
+ Get scale context values for source (distorted) image points.
364
+
365
+ Args:
366
+ src_points: (N, 2) array of source points [[x1, y1], [x2, y2], ...]
367
+
368
+ Returns:
369
+ Dictionary with keys:
370
+ - wscale: (N,) array of width scale values
371
+ - hscale: (N,) array of height scale values
372
+ - vangle: (N,) array of vertical angle values
373
+ Out-of-bounds points will have NaN values.
374
+ """
375
+ ctd_points = self.src_to_ctd(src_points)
376
+ return self.get_ctd_points_context(ctd_points)
377
+
297
378
  @classmethod
298
379
  def load(cls, archive_path):
299
380
  """
@@ -0,0 +1,7 @@
1
+ """Entry point script for camera-client console command."""
2
+
3
+ from camera_client.__main__ import main
4
+
5
+
6
+ if __name__ == '__main__':
7
+ main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: camera-client
3
- Version: 0.2.4
3
+ Version: 0.2.6
4
4
  Summary: Python SDK for camera calibration and projection transformations - handle lens distortion, coordinate transformations, and 3D ray casting with symbolic expressions.
5
5
  Author-email: Alexander Abramov <extremal.ru@gmail.com>
6
6
  License: MIT
@@ -50,6 +50,18 @@ Install from PyPI:
50
50
  pip install camera-client
51
51
  ```
52
52
 
53
+ ## CLI Usage
54
+
55
+ Download camera calibration archives from URL:
56
+
57
+ ```bash
58
+ # Download single archive
59
+ python -m camera_client get_camera_archive https://example.com/camera.npz
60
+
61
+ # Download from file with URLs (one per line, non-URL lines ignored)
62
+ python -m camera_client get_camera_archive -f urls.txt -o ./archives
63
+ ```
64
+
53
65
  ## Quick Start
54
66
 
55
67
  ```python
@@ -316,6 +328,58 @@ Get the camera position (key-point) in world space.
316
328
  **Returns:**
317
329
  - `np.ndarray`: Shape (3,) array with [x, y, z] camera position
318
330
 
331
+ ---
332
+
333
+ ### `get_ctd_points_context(ctd_points)`
334
+
335
+ Get scale context values for corrected (CTD) image points.
336
+
337
+ **Parameters:**
338
+ - `ctd_points` (np.ndarray): Shape (N, 2) array of [x, y] corrected coordinates
339
+
340
+ **Returns:**
341
+ - `dict`: Dictionary with keys:
342
+ - `wscale` (np.ndarray): Shape (N,) width scale values
343
+ - `hscale` (np.ndarray): Shape (N,) height scale values
344
+ - `vangle` (np.ndarray): Shape (N,) vertical angle values (radians)
345
+
346
+ **Note:** Out-of-bounds points will have NaN values
347
+
348
+ **Example:**
349
+ ```python
350
+ ctd_points = np.array([[640, 480], [800, 600]])
351
+ context = camera.get_ctd_points_context(ctd_points)
352
+ print(context['wscale']) # Width scale at each point
353
+ print(context['hscale']) # Height scale at each point
354
+ print(context['vangle']) # Vertical angle at each point
355
+ ```
356
+
357
+ ---
358
+
359
+ ### `get_src_points_context(src_points)`
360
+
361
+ Get scale context values for source (distorted) image points.
362
+
363
+ **Parameters:**
364
+ - `src_points` (np.ndarray): Shape (N, 2) array of [x, y] source coordinates
365
+
366
+ **Returns:**
367
+ - `dict`: Dictionary with keys:
368
+ - `wscale` (np.ndarray): Shape (N,) width scale values
369
+ - `hscale` (np.ndarray): Shape (N,) height scale values
370
+ - `vangle` (np.ndarray): Shape (N,) vertical angle values (radians)
371
+
372
+ **Note:** Internally converts source points to CTD coordinates first, then retrieves context
373
+
374
+ **Example:**
375
+ ```python
376
+ src_points = np.array([[640, 480], [800, 600]])
377
+ context = camera.get_src_points_context(src_points)
378
+ print(context['wscale']) # Width scale at each point
379
+ ```
380
+
381
+ ---
382
+
319
383
  ## Calibration File Format
320
384
 
321
385
  The calibration file is a NumPy `.npz` archive containing:
@@ -5,8 +5,10 @@ pyproject.toml
5
5
  requirements.txt
6
6
  setup.py
7
7
  camera_client/__init__.py
8
+ camera_client/__main__.py
8
9
  camera_client/client.py
9
10
  camera_client/loading.py
11
+ camera_client/script.py
10
12
  camera_client.egg-info/PKG-INFO
11
13
  camera_client.egg-info/SOURCES.txt
12
14
  camera_client.egg-info/dependency_links.txt
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "camera-client"
7
- version = "0.2.4"
7
+ version = "0.2.6"
8
8
  description = "Python SDK for camera calibration and projection transformations - handle lens distortion, coordinate transformations, and 3D ray casting with symbolic expressions."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.7"
File without changes
File without changes
File without changes
File without changes