kuva-metadata 1.1.7__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,134 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ *.ipynb
12
+ build/
13
+ develop-eggs/
14
+ dist/
15
+ downloads/
16
+ eggs/
17
+ .eggs/
18
+ lib/
19
+ lib64/
20
+ parts/
21
+ sdist/
22
+ var/
23
+ wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .coverage
43
+ .coverage.*
44
+ .cache
45
+ nosetests.xml
46
+ coverage.xml
47
+ *.cover
48
+ .hypothesis/
49
+ .pytest_cache/
50
+
51
+ # Translations
52
+ *.mo
53
+ *.pot
54
+
55
+ # Django stuff:
56
+ *.log
57
+ local_settings.py
58
+ db.sqlite3
59
+
60
+ # Flask stuff:
61
+ instance/
62
+ .webassets-cache
63
+
64
+ # Torch stuff
65
+ lightning_logs/
66
+
67
+ # Scrapy stuff:
68
+ .scrapy
69
+
70
+ # Sphinx documentation
71
+ docs/_build/
72
+
73
+ # PyBuilder
74
+ target/
75
+
76
+ # Jupyter Notebook
77
+ .ipynb_checkpoints
78
+
79
+ # VSCode
80
+ .vscode
81
+
82
+ # pyenv
83
+ .python-version
84
+
85
+ # celery beat schedule file
86
+ celerybeat-schedule
87
+
88
+ # SageMath parsed files
89
+ *.sage.py
90
+
91
+ # Environments
92
+ .env
93
+ .venv
94
+ env/
95
+ venv/
96
+ ENV/
97
+ env.bak/
98
+ venv.bak/
99
+
100
+ # Spyder project settings
101
+ .spyderproject
102
+ .spyproject
103
+
104
+ # Rope project settings
105
+ .ropeproject
106
+
107
+ # mkdocs documentation
108
+ /site
109
+
110
+ # mypy
111
+ .mypy_cache/
112
+
113
+ # Direnv stuff
114
+ .direnv/
115
+ .envrc
116
+
117
+ # Poetry
118
+ # poetry.lock
119
+
120
+ # Supervisord
121
+ supervisord.log
122
+ supervisord.pid
123
+
124
+ # Debug folders
125
+ _debug/
126
+
127
+ # Kuva data
128
+ *.tif
129
+ *.tiff
130
+ *.npy
131
+ hyperfield*.json
132
+
133
+ # Do not ignore Kuva files in the test_data directory
134
+ !kuva-reader/tests/test_data/**
@@ -0,0 +1,83 @@
1
+ Metadata-Version: 2.4
2
+ Name: kuva-metadata
3
+ Version: 1.1.7
4
+ Summary: Definitions of Kuva Space product metadata
5
+ Author-email: Guillem Ballesteros <guillem@kuvaspace.com>, Lennert Antson <lennert.antson@kuvaspace.com>, Arthur Vandenhoeke <arthur.vandenhoeke@kuvaspace.com>, Olli Eloranta <olli.eloranta@kuvaspace.com>
6
+ License-Expression: MIT
7
+ Requires-Python: <=3.13,>=3.10
8
+ Requires-Dist: kuva-geometry<2.0.0,>=1.0.1
9
+ Requires-Dist: networkx<4.0.0,>=3.4.2
10
+ Requires-Dist: numpy-quaternion>=2022.4.4
11
+ Requires-Dist: numpy>=1.26.4
12
+ Requires-Dist: pint<1.0.0,>=0.22
13
+ Requires-Dist: pydantic<3.0.0,>=2.9.2
14
+ Requires-Dist: rasterio<2,>=1.4.3
15
+ Requires-Dist: shapely<3.0.0,>=2.0.6
16
+ Requires-Dist: sympy<2.0.0,>=1.13.3
17
+ Description-Content-Type: text/markdown
18
+
19
+ <div align="center">
20
+ <picture>
21
+ <source media="(prefers-color-scheme: dark)" srcset="https://github.com/user-attachments/assets/1d8b44f1-1999-4cfb-8744-32871056c253">
22
+ <img alt="kuva-space-logo" src="https://github.com/user-attachments/assets/d8f47cc8-1491-4d0c-a8cf-318ea7e0afdc" width="50%">
23
+ </picture>
24
+ </div>
25
+
26
+ # Kuva Metadata
27
+
28
+ Images taken from a satellite are complicated beasts with lot of metadata associated
29
+ to them. This repository contains the metadata definitions for the Hyperfield products.
30
+ This metadata along with the acquired GeoTIFF images form the Kuva Space products in its
31
+ various processing levels.
32
+
33
+ With the metadata and images, we may process products to the
34
+ next processing levels, or do more precise processing than just with a GeoTIFF.
35
+
36
+ # Installation
37
+
38
+ ```bash
39
+ pip install kuva-metadata
40
+ ```
41
+
42
+ This package is also included when installing the `kuva-reader`.
43
+
44
+ ### Requirements
45
+
46
+ `Python 3.10` to `3.13`, preferably within a virtual environment
47
+
48
+ # Processing levels
49
+
50
+ Currently there are metadata definitions for the following processing levels of Kuva products:
51
+
52
+ - **L0**: Radiometrically corrected frames as TOA radiance
53
+ - **L1AB**: Band-aligned product formed from multiple L0 products
54
+ - **L1C**: Georeferences and orthorectified L1 product
55
+ - **L2A**: Atmospherically corrected product as BOA reflectance
56
+
57
+ # Architecture
58
+
59
+ All the metadata are defined as Pydantic models, this has several advantages:
60
+
61
+ - A very rich set of validations that are applied before data object construction
62
+ - The ability to easily (de)serialize (from)to JSON
63
+
64
+ # Contributing
65
+
66
+ Please follow the guidelines in [CONTRIBUTING.md](https://github.com/KuvaSpace/kuva-data-processing/blob/main/CONTRIBUTING.md).
67
+
68
+ Also, please follow our [Code of Conduct](https://github.com/KuvaSpace/kuva-data-processing/blob/main/CODE_OF_CONDUCT.md)
69
+ while discussing in the issues and pull requests.
70
+
71
+ # Contact information
72
+
73
+ For questions or support, please open an issue. If you have been given a support contact,
74
+ feel free to send them an email explaining your issue.
75
+
76
+ # License
77
+
78
+ The `kuva-reader` project software is under the [MIT license](https://github.com/KuvaSpace/kuva-data-processing/blob/main/LICENSE.md).
79
+
80
+
81
+ # Status of unit tests
82
+
83
+ [![Unit tests for kuva-metadata](https://github.com/KuvaSpace/kuva-data-processing/actions/workflows/test-kuva-metadata.yml/badge.svg)](https://github.com/KuvaSpace/kuva-data-processing/actions/workflows/test-kuva-metadata.yml)
@@ -0,0 +1,65 @@
1
+ <div align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://github.com/user-attachments/assets/1d8b44f1-1999-4cfb-8744-32871056c253">
4
+ <img alt="kuva-space-logo" src="https://github.com/user-attachments/assets/d8f47cc8-1491-4d0c-a8cf-318ea7e0afdc" width="50%">
5
+ </picture>
6
+ </div>
7
+
8
+ # Kuva Metadata
9
+
10
+ Images taken from a satellite are complicated beasts with lot of metadata associated
11
+ to them. This repository contains the metadata definitions for the Hyperfield products.
12
+ This metadata along with the acquired GeoTIFF images form the Kuva Space products in its
13
+ various processing levels.
14
+
15
+ With the metadata and images, we may process products to the
16
+ next processing levels, or do more precise processing than just with a GeoTIFF.
17
+
18
+ # Installation
19
+
20
+ ```bash
21
+ pip install kuva-metadata
22
+ ```
23
+
24
+ This package is also included when installing the `kuva-reader`.
25
+
26
+ ### Requirements
27
+
28
+ `Python 3.10` to `3.13`, preferably within a virtual environment
29
+
30
+ # Processing levels
31
+
32
+ Currently there are metadata definitions for the following processing levels of Kuva products:
33
+
34
+ - **L0**: Radiometrically corrected frames as TOA radiance
35
+ - **L1AB**: Band-aligned product formed from multiple L0 products
36
+ - **L1C**: Georeferences and orthorectified L1 product
37
+ - **L2A**: Atmospherically corrected product as BOA reflectance
38
+
39
+ # Architecture
40
+
41
+ All the metadata are defined as Pydantic models, this has several advantages:
42
+
43
+ - A very rich set of validations that are applied before data object construction
44
+ - The ability to easily (de)serialize (from)to JSON
45
+
46
+ # Contributing
47
+
48
+ Please follow the guidelines in [CONTRIBUTING.md](https://github.com/KuvaSpace/kuva-data-processing/blob/main/CONTRIBUTING.md).
49
+
50
+ Also, please follow our [Code of Conduct](https://github.com/KuvaSpace/kuva-data-processing/blob/main/CODE_OF_CONDUCT.md)
51
+ while discussing in the issues and pull requests.
52
+
53
+ # Contact information
54
+
55
+ For questions or support, please open an issue. If you have been given a support contact,
56
+ feel free to send them an email explaining your issue.
57
+
58
+ # License
59
+
60
+ The `kuva-reader` project software is under the [MIT license](https://github.com/KuvaSpace/kuva-data-processing/blob/main/LICENSE.md).
61
+
62
+
63
+ # Status of unit tests
64
+
65
+ [![Unit tests for kuva-metadata](https://github.com/KuvaSpace/kuva-data-processing/actions/workflows/test-kuva-metadata.yml/badge.svg)](https://github.com/KuvaSpace/kuva-data-processing/actions/workflows/test-kuva-metadata.yml)
@@ -0,0 +1,25 @@
1
+ """
2
+ This library defines the metadata format used by the sidecar files that accompany
3
+ the images produced by Hyperfield satellites.
4
+
5
+ The core library used is pydantic which among other nice things allows us to
6
+ (de)serialize (from)to JSON .
7
+
8
+ If you are interested on reading from the Hyperfield metadata database into such
9
+ objects then you want to look at the `hyperfield-db` project.
10
+ """
11
+
12
+ __version__ = "1.1.2"
13
+
14
+ from .sections_common import MetadataBase
15
+ from .sections_l0 import MetadataLevel0
16
+ from .sections_l1 import MetadataLevel1AB, MetadataLevel1C
17
+ from .sections_l2 import MetadataLevel2A
18
+
19
+ __all__ = [
20
+ "MetadataBase",
21
+ "MetadataLevel0",
22
+ "MetadataLevel1AB",
23
+ "MetadataLevel1C",
24
+ "MetadataLevel2A",
25
+ ]
@@ -0,0 +1,36 @@
1
+ """Custom classes to store in Pydantic models"""
2
+
3
+ import numpy as np
4
+ from pydantic import BaseModel, ConfigDict
5
+ from rasterio.crs import CRS
6
+ from shapely import Point, Polygon, get_coordinates
7
+
8
+
9
+ class CRSGeometry(BaseModel):
10
+ """
11
+ Store a `shapely.geometry` together with the relevant CRS.
12
+
13
+ Attributes
14
+ ----------
15
+ geom: Polygon
16
+ Shapely polygon with the geometry
17
+ crs_epsg: rasterio.crs.CRS
18
+ CRS over which the polygon is represented.
19
+ """
20
+
21
+ geom: Polygon | Point
22
+ crs_epsg: CRS
23
+ model_config = ConfigDict(
24
+ validate_assignment=True,
25
+ arbitrary_types_allowed=True,
26
+ )
27
+
28
+ @property
29
+ def numpy(self) -> np.ndarray:
30
+ """Turn the geometry into a numpy array of polygon coordinates.
31
+
32
+ Notes
33
+ -----
34
+ This loses the CRS information so make sure to keep track of it elsewhere
35
+ """
36
+ return get_coordinates(self.geom, include_z=True).squeeze()
@@ -0,0 +1,189 @@
1
+ """Geometry utilities to find the footprint associated with an in orbit camera"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ import numpy as np
8
+ import quaternion
9
+ import shapely
10
+ from kuva_geometry import ellipsoid, geometry
11
+
12
+ if TYPE_CHECKING:
13
+ from .sections_l0 import Camera, Frame
14
+
15
+
16
+ def get_sensor_corner_rays(
17
+ camera: Camera,
18
+ position: np.ndarray,
19
+ orientation: quaternion.quaternion,
20
+ use_negative_sensor_plane: bool = False,
21
+ ):
22
+ """Get the rays emanating from a camera associated with the corners of the sensor
23
+
24
+ Parameters
25
+ ----------
26
+ camera
27
+ Camera intrinsic parameters
28
+ position
29
+ The position of the satellite in ECEF coordinates expressed in meters (3 values)
30
+ orientation
31
+ Quaternion describing the orientation of the satellite wrt ECEF
32
+ use_negative_sensor_plane, optional
33
+ Whether to use the negative sensor plane to calculate the rays, by default False
34
+
35
+ Returns
36
+ -------
37
+ The vectors that join the back nodal point to the sensor corners.
38
+ """
39
+ ray_origin, rays = geometry.get_sensor_corner_rays(
40
+ np.array(position),
41
+ orientation,
42
+ camera.focal_distance.to("meters").magnitude,
43
+ camera.width.to("meters").magnitude,
44
+ camera.height.to("meters").magnitude,
45
+ use_negative_sensor_plane,
46
+ )
47
+
48
+ return ray_origin, rays
49
+
50
+
51
+ def get_sensor_rays(
52
+ sensor_coords: np.ndarray,
53
+ camera: Camera,
54
+ position: np.ndarray,
55
+ orientation: quaternion.quaternion,
56
+ ) -> tuple[np.ndarray, np.ndarray]:
57
+ """
58
+ Produce an array with the ray vectors for a collections of sensor coords.
59
+
60
+ Sensor coords are measured from the center of the sensor and are valid within
61
+ [-0.5, 0.5]^2.
62
+
63
+ Parameters
64
+ ----------
65
+ sensor_coords
66
+ A rank-2 tensor of dimension (n_sensor_coords, 2)
67
+ camera
68
+ Camera intrinsic parameters
69
+ position
70
+ The position of the satellite in ECEF coordinates expressed in meters (3 values)
71
+ orientation
72
+ Quaternion describing the orientation of the satellite wrt ECEF
73
+
74
+ Returns
75
+ -------
76
+ The position from which the rays emanate, i.e. the camera position and rank 2 tensor
77
+ of dimensions (n_sensor_coords, 3) describing the direction vector of the rays.
78
+ """
79
+ rays = geometry.get_sensor_rays(
80
+ sensor_coords,
81
+ position,
82
+ orientation,
83
+ camera.focal_distance.to("meters").magnitude,
84
+ camera.width.to("meters").magnitude,
85
+ camera.height.to("meters").magnitude,
86
+ use_negative_sensor_plane=True,
87
+ )
88
+
89
+ return position, rays
90
+
91
+
92
+ def frame_ray_Earth_intersections(
93
+ sensor_coords: np.ndarray,
94
+ frame: Frame,
95
+ camera: Camera,
96
+ mode: str = "shapely",
97
+ ) -> list[shapely.Point] | np.ndarray:
98
+ """
99
+ Return the intersecton of camera ray with the WGS84 ellipsoid.
100
+
101
+ Parameters
102
+ ----------
103
+ sensor_coords
104
+ The coordinates of the ray we are interested on. Within sensor coords range
105
+ between +- 0.5 on both axis.
106
+ frame
107
+ Information about how the frame was taken. Namely camera orientation and
108
+ position.
109
+ camera
110
+ The camera parameters
111
+ mode, optional
112
+ Whether to return a list of shapely points or a numpy array, by default "shapely"
113
+
114
+ Returns
115
+ -------
116
+ Intersection of camera rays on the Earth
117
+ """
118
+ sat_pos, sat_ecef_orientation = frame.position.numpy, frame.sat_ecef_orientation
119
+
120
+ # Make sure the order is correct, i.e. begin from the right-hand side!
121
+ # The process is the same as for how we calculate homographies.
122
+ orientation = sat_ecef_orientation * camera.sensor_wrt_sat_axis_quaternion
123
+
124
+ _, rays = get_sensor_rays(sensor_coords, camera, sat_pos, orientation)
125
+
126
+ rays = rays / np.sqrt((rays**2).sum(axis=1))[:, None]
127
+
128
+ intersections = ellipsoid.ray_Earth_intersection_new(sat_pos, rays)
129
+
130
+ match mode:
131
+ case "shapely":
132
+ intersection_points = [
133
+ shapely.Point(
134
+ intersections[idx][0], intersections[idx][1], intersections[idx][2]
135
+ )
136
+ for idx in range(intersections.shape[0])
137
+ ]
138
+ case "array":
139
+ intersection_points = intersections
140
+ case _:
141
+ e_ = "The valid modes are 'shapely' and 'array'."
142
+ raise ValueError(e_)
143
+
144
+ return intersection_points
145
+
146
+
147
+ def frame_footprint(
148
+ frame: Frame,
149
+ camera: Camera,
150
+ use_negative_sensor_plane: bool = False,
151
+ ) -> shapely.Polygon:
152
+ """Find the footprint on the ground associated with the corners rays of a camera
153
+
154
+ Parameters
155
+ ----------
156
+ frame
157
+ Information about how the frame was taken. Namely camera orientation and
158
+ position.
159
+ camera
160
+ The camera parameters
161
+ use_negative_sensor_plane, optional
162
+ Whether to use the negative sensor plane to calculate the rays, by default False
163
+
164
+ Returns
165
+ -------
166
+ The footprint on the ground as a polygon
167
+ """
168
+ sat_pos, sat_ecef_orientation = frame.position.numpy, frame.sat_ecef_orientation
169
+
170
+ orientation = sat_ecef_orientation * camera.sensor_wrt_sat_axis_quaternion
171
+
172
+ _, sensor_corners_ray = get_sensor_corner_rays(
173
+ camera, sat_pos, orientation, use_negative_sensor_plane
174
+ )
175
+
176
+ sensor_corners_ray = (
177
+ sensor_corners_ray / np.sqrt((sensor_corners_ray**2).sum(axis=1))[:, None]
178
+ )
179
+
180
+ footprint = [
181
+ ellipsoid.ray_Earth_intersection(sat_pos, sensor_corners_ray[idx])
182
+ for idx in range(sensor_corners_ray.shape[0])
183
+ ]
184
+
185
+ # Polygons are ordered X,Y despite the CRS being lat, long i.e. x,y. If you
186
+ # leave the points in the order they come in the 4326 CRS things will break.
187
+ footprint = shapely.Polygon(footprint)
188
+
189
+ return footprint
@@ -0,0 +1,13 @@
1
+ """Additional type aliases"""
2
+
3
+ import numpy as np
4
+ from pint import Quantity
5
+ from quaternion import quaternion
6
+ from shapely import Polygon
7
+
8
+ # All this types with lists would be better expressed with tuples but pydantic
9
+ # reads the json as lists so we have a type to match.
10
+ Quantity_ = Quantity | list[float | str]
11
+ quaternion_ = quaternion | list[float]
12
+ array_3x3_ = np.ndarray | list[list[float]]
13
+ Polygon_ = Polygon | str
File without changes