panorama-projection-toolkit 0.1.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.
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2026 Nicolai Skutsch
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,387 @@
1
+ Metadata-Version: 2.4
2
+ Name: panorama-projection-toolkit
3
+ Version: 0.1.0
4
+ Summary: GPU-accelerated Python toolkit for panorama projections
5
+ Author: Nicolai Skutsch
6
+ License-Expression: MIT
7
+ Project-URL: Repository, https://github.com/n-skutsch/panorama-projection-toolkit
8
+ Project-URL: Issues, https://github.com/n-skutsch/panorama-projection-toolkit/issues
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Development Status :: 4 - Beta
12
+ Requires-Python: >=3.9
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: numpy
16
+ Requires-Dist: opencv-python
17
+ Requires-Dist: scipy
18
+ Provides-Extra: gpu
19
+ Requires-Dist: cupy; extra == "gpu"
20
+ Dynamic: license-file
21
+
22
+ # Panorama Projection Toolkit
23
+
24
+ GPU-accelerated Python toolkit for projecting and reprojecting perspective views, cubemaps, and equirectangular panoramas. The toolkit provides fast, fully vectorized implementations of the following transformations:
25
+ - equirectangular panorama → perspective view
26
+ - perspective view → equirectangular panorama
27
+ - perspective view → perspective view
28
+ - cubemaps → equirectangular panorama
29
+
30
+ It supports both CPU execution through NumPy/SciPy and GPU acceleration through CuPy/CUDA with the same API.
31
+
32
+
33
+ ## Installation
34
+
35
+ ### Requirements
36
+
37
+ - Python 3.9+
38
+ - NumPy
39
+ - SciPy
40
+ - OpenCV
41
+ - Optional: CuPy + CUDA
42
+
43
+ ### Installation from pip
44
+
45
+ ```bash
46
+ pip install panorama_projection_toolkit
47
+ ```
48
+
49
+ ### Manual installation
50
+
51
+ Clone the repository:
52
+
53
+ ```bash
54
+ git clone <repository-url>
55
+ cd panorama_projection_toolkit
56
+ ```
57
+
58
+ Build the package:
59
+
60
+ ```bash
61
+ python -m build
62
+ ```
63
+
64
+ Install the wheel:
65
+
66
+ ```bash
67
+ pip install dist/panorama_projection_toolkit-VERSION-py3-none-any.whl
68
+ ```
69
+
70
+ ### Optional: GPU Acceleration
71
+
72
+ The toolkit automatically uses CuPy if it is installed and CUDA is available. Otherwise, it falls back to NumPy/SciPy automatically.
73
+
74
+ 1. Verify CUDA compatibility: https://developer.nvidia.com/cuda/gpus
75
+ 2. Download and install CUDA Toolkit: https://developer.nvidia.com/cuda/toolkit
76
+ 3. Install the matching CuPy build for your CUDA version: https://docs.cupy.dev/en/stable/install.html
77
+
78
+
79
+ ## Supported Formats
80
+
81
+ | Format | Read | Write | Typical data type |
82
+ | ---------- | ---- | ----- | ----------------- |
83
+ | JPG / JPEG | ✔ | ✔ | uint8 |
84
+ | PNG | ✔ | ✔ | uint8 |
85
+ | EXR | ✔ | ✔ | float32 |
86
+
87
+ Internally, most computations use `float32`. The output image is restored to the original input data type whenever possible. OpenCV is used internally, so color images use BGR channel ordering.
88
+
89
+ OpenEXR support requires the following environment variable to be set before importing OpenCV:
90
+
91
+ ```python
92
+ import os
93
+ os.environ['OPENCV_IO_ENABLE_OPENEXR'] = '1'
94
+ ```
95
+
96
+ This is already handled automatically by the package.
97
+
98
+
99
+ ## Coordinate System and Panorama Convention
100
+
101
+ Equirectangular panoramas are interpreted as:
102
+ - horizontal axis → longitude θ ∈ [-π, π]
103
+ - vertical axis → latitude φ ∈ [-π/2, π/2]
104
+
105
+ The panorama origin corresponds to:
106
+ - center → north-facing direction
107
+ - top → zenith
108
+ - bottom → nadir
109
+
110
+ The toolkit assumes a right-handed ENU coordinate system:
111
+ - X → East
112
+ - Y → North
113
+ - Z → Up
114
+
115
+ Euler angles are interpreted as pitch, roll, and yaw with rotations around:
116
+ - pitch → x-axis
117
+ - roll → y-axis
118
+ - yaw → z-axis
119
+
120
+ A yaw angle of 0° is equivalent to facing north. All angles are specified in degrees.
121
+
122
+
123
+ ## Sampling Methods
124
+
125
+ | Sampling Method | Application |
126
+ | ---------------------- | ------------------------------- |
127
+ | Nearest-neighbor | 6-view cubemap stitching |
128
+ | Bilinear interpolation | All other projection operations |
129
+
130
+
131
+ ## Public API
132
+
133
+ ```python
134
+ from panorama_projection_toolkit import (
135
+ load_image,
136
+ save_image,
137
+ pano_to_view,
138
+ view_to_pano,
139
+ view_to_view,
140
+ cubemaps_to_pano
141
+ )
142
+ ```
143
+
144
+ ### load_image
145
+
146
+ ```python
147
+ load_image(file_path)
148
+ ```
149
+
150
+ Loads an image from the specified file path. EXR files are typically loaded as floating-point arrays (e.g., `float32`), whereas all other file types are typically loaded as uint8 arrays. The image is returned as a NumPy array of shape `(height, width)` for single-channel images and `(height, width, channels)` for multi-channel images. For color images, the color channels are in BGR order. Non-EXR grayscale images are also loaded as 3-channel BGR images. In order to load images from EXR files, the environment variable `OPENCV_IO_ENABLE_OPENEXR` must be set to `1` before importing `cv2`.
151
+
152
+ Parameters:
153
+
154
+ | Parameter | Description |
155
+ | --------- | -------------------------------------------- |
156
+ | file_path | The file path at which the image is located. |
157
+
158
+ Example:
159
+
160
+ ```python
161
+ image = load_image('image.png')
162
+ ```
163
+
164
+ ### save_image
165
+
166
+ ```python
167
+ save_image(file_path, image)
168
+ ```
169
+
170
+ Saves an image to the specified file path. The image is expected to be a NumPy array of shape `(height, width)` for single-channel images and `(height, width, channels)` with color channels in BGR order for multi-channel images. If the file extension is either JPG/JPEG or PNG and the image is not an `uint8` array, it is converted to an `uint8` array before saving it. If the file extension is EXR and the image is not a `float32` array, it is converted to a `float32` array before saving it. These conversions may lead to unexpected results. The pixel values of `float` images are expected to fall within the range of `[0, 1]`. The pixel values of `uint8` images are expected to fall within the range of `[0, 255]`. In order to save images to EXR files, the environment variable `OPENCV_IO_ENABLE_OPENEXR` must be set to `1` before importing cv2.
171
+
172
+ Parameters:
173
+
174
+ | Parameter | Description |
175
+ | --------- | --------------------------------------------------------- |
176
+ | file_path | The file path to which the image should be saved. |
177
+ | image | The image to save of shape `(height, width[, channels])`. |
178
+
179
+ Example:
180
+
181
+ ```python
182
+ save_image('output.png', image)
183
+ ```
184
+
185
+ ### pano_to_view
186
+
187
+ ```python
188
+ pano_to_view(pano, fov, orientation, view_size)
189
+ ```
190
+
191
+ Creates a view image from an equirectangular panorama. The panorama is assumed to be an array of shape `(height, width[, channels])`. The FOV and orientation angles are assumed to be in DEG. The FOV is assumed to be the same in vertical and horizontal direction. The orientation is assumed to be in `(pitch, roll, yaw)` order. The order of the orientation angles, the coordinate conversions, and the spherical coordinate calculation are designed to work for right-handed ENU coordinate systems. Results may differ for other coordinate systems. Due to inaccuracies in the sampling process, it is not an exact inverse of the `view_to_pano` function.
192
+
193
+ Parameters:
194
+
195
+ | Parameter | Description |
196
+ | ----------- | -------------------------------------------------------------- |
197
+ | pano | The reference panorama of shape `(height, width[, channels])`. |
198
+ | fov | The FOV [°] of the new view. |
199
+ | orientation | The orientation angles [°] `(pitch, roll, yaw)` of new view. |
200
+ | view_size | The size of the output image of shape `(height, width)`. |
201
+
202
+
203
+ Example:
204
+
205
+ ```python
206
+ import numpy as np
207
+
208
+ pano = load_image('pano.png')
209
+
210
+ view = pano_to_view(
211
+ pano=pano,
212
+ fov=90,
213
+ orientation=np.array([0, 0, 0], dtype=np.float32),
214
+ view_size=(1024, 1024)
215
+ )
216
+
217
+ save_image('view.png', view)
218
+ ```
219
+
220
+ ### view_to_pano
221
+
222
+ ```python
223
+ view_to_pano(view, fov, orientation, pano_size)
224
+ ```
225
+
226
+ Projects a perspective view onto an equirectangular panorama. The panorama will be black except for the areas onto which the view is projected. The view is assumed to be an array of shape `(height, width[, channels])`. The FOV and orientation angles are assumed to be in DEG. The FOV is assumed to be the same in vertical and horizontal direction. The orientation is assumed to be in `(pitch, roll, yaw)` order. The order of the orientation angles, the coordinate conversions, and the spherical coordinate calculation are designed to work for right-handed ENU coordinate systems. Results may differ for other coordinate systems. Due to inaccuracies in the sampling process, it is not an exact inverse of the `pano_to_view` function.
227
+
228
+ Parameters:
229
+
230
+ | Parameter | Description |
231
+ | ----------- | ------------------------------------------------------------------ |
232
+ | view | The reference view of shape `(height, width[, channels])`. |
233
+ | fov | The FOV [°] of the reference view. |
234
+ | orientation | The orientation angles [°] `(pitch, roll, yaw)` of reference view. |
235
+ | pano_size | The size of the output image of shape `(height, width)`. |
236
+
237
+ Example:
238
+
239
+ ```python
240
+ import numpy as np
241
+
242
+ view = load_image('view.png')
243
+
244
+ pano = view_to_pano(
245
+ view=view,
246
+ fov=90,
247
+ orientation=np.array([0, 0, 0], dtype=np.float32),
248
+ pano_size=(2048, 4096)
249
+ )
250
+
251
+ save_image('pano.png', pano)
252
+ ```
253
+
254
+ ### view_to_view
255
+
256
+ ```python
257
+ view_to_view(old_view, old_fov, old_orientation, new_fov, new_orientation, view_size)
258
+ ```
259
+
260
+ Projects one perspective view into another. The new view will be black except for the areas onto which the view is projected. The view is assumed to be an array of shape `(height, width[, channels])`. The FOV and orientation angles are assumed to be in DEG. The FOV is assumed to be the same in vertical and horizontal direction. The orientation is assumed to be in `(pitch, roll, yaw)` order. The order of the orientation angles, the coordinate conversions, and the spherical coordinate calculation are designed to work for right-handed ENU coordinate systems. Results may differ for other coordinate systems. Due to inaccuracies in the sampling process, the function is not an exact inverse of itself.
261
+
262
+ Parameters:
263
+
264
+ | Parameter | Description |
265
+ | --------------- | ---------------------------------------------------------------------- |
266
+ | old_view | The reference view of shape `(height, width[, channels])`. |
267
+ | old_fov | The FOV [°] of the reference view. |
268
+ | old_orientation | The orientation angles [°] `(pitch, roll, yaw)` of the reference view. |
269
+ | new_fov | The FOV [°] of the new view. |
270
+ | new_orientation | The orientation angles [°] `(pitch, roll, yaw)` of the new view. |
271
+ | view_size | The size of the output image of shape `(height, width)`. |
272
+
273
+ Example:
274
+
275
+ ```python
276
+ import numpy as np
277
+
278
+ old_view = load_image('old_view.png')
279
+
280
+ new_view = view_to_view(
281
+ old_view=old_view,
282
+ old_fov=90,
283
+ old_orientation=np.array([0, 0, 0], dtype=np.float32),
284
+ new_fov=70,
285
+ new_orientation=np.array([0, 0, 45], dtype=np.float32),
286
+ view_size=(1024, 1024)
287
+ )
288
+
289
+ save_image('new_view.png', new_view)
290
+ ```
291
+
292
+ ### cubemaps_to_pano
293
+
294
+ ```python
295
+ cubemaps_to_pano(cubemaps, pano_size)
296
+ ```
297
+
298
+ Projects either 6 non-overlapping cubemaps or 18 overlapping cubemaps onto an equirectangular panorama. The cubemaps are assumed to be arrays of shape (height, width[, channels]) representing square images with a 90° horizontal and vertical FOV. For 6 cubemaps, nearest neighbor sampling is used and the views are stitched directly without applying alpha blending. For 18 cubemaps, bilinear sampling is used and the views are alpha-blended.
299
+
300
+ The 6-view layout uses orthogonal directions spaced 90° apart:
301
+ ```
302
+ [NORTH, SOUTH, EAST, WEST, UP, DOWN]
303
+ ```
304
+
305
+ The 18-view layout additionally includes intermediate views spaced 45° apart:
306
+ ```
307
+ [NORTH, SOUTH, EAST, WEST, UP, DOWN,
308
+ NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST,
309
+ UPNORTH, UPSOUTH, UPEAST, UPWEST,
310
+ DOWNNORTH, DOWNSOUTH, DOWNEAST, DOWNWEST]
311
+ ```
312
+
313
+ Parameters:
314
+
315
+ | Parameter | Description |
316
+ | --------- | --------------------------------------------------------------------------- |
317
+ | cubemaps | The list of either 6 or 18 cubemaps of shape `(height, width[, channels])`. |
318
+ | pano_size | The size of the output image of shape `(height, width)`. |
319
+
320
+
321
+ Example:
322
+
323
+ ```python
324
+ cubemaps = [
325
+ load_image('cubemap_N.png'),
326
+ load_image('cubemap_S.png'),
327
+ load_image('cubemap_E.png'),
328
+ load_image('cubemap_W.png'),
329
+ load_image('cubemap_U.png'),
330
+ load_image('cubemap_D.png')
331
+ ]
332
+
333
+ pano = cubemaps_to_pano(
334
+ cubemaps=cubemaps,
335
+ pano_size=(2048, 4096)
336
+ )
337
+
338
+ save_image('pano.png', pano)
339
+ ```
340
+
341
+
342
+ ## Example Workflow
343
+
344
+ ```python
345
+ import numpy as np
346
+
347
+ from panorama_projection_toolkit import (
348
+ load_image,
349
+ save_image,
350
+ pano_to_view
351
+ )
352
+
353
+ pano = load_image('pano.png')
354
+
355
+ view = pano_to_view(
356
+ pano=pano,
357
+ fov=90,
358
+ orientation=np.array([0, 0, 45], dtype=np.float32),
359
+ view_size=(1024, 1024)
360
+ )
361
+
362
+ save_image('view.png', view)
363
+ ```
364
+
365
+
366
+ ## Project Structure
367
+
368
+ ```text
369
+ .
370
+ ├── src/
371
+ │ └── panorama_projection_toolkit/
372
+ │ ├── __init__.py
373
+ │ ├── io.py
374
+ │ ├── projections.py
375
+ │ ├── transformations.py
376
+ │ └── utils.py
377
+ └── tests/
378
+ └── test_projections.py
379
+ ```
380
+
381
+ ## License
382
+
383
+ This project is licensed under the MIT License.
384
+
385
+ The MIT License is a permissive open-source license that allows anyone to use, modify, distribute, and sell this software, provided that the original copyright notice and license text are included in all copies or substantial portions of the software.
386
+
387
+ See the LICENSE file for the full license text.