kuva-reader 1.0.2__tar.gz → 1.0.4__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.
- {kuva_reader-1.0.2 → kuva_reader-1.0.4}/PKG-INFO +18 -4
- {kuva_reader-1.0.2 → kuva_reader-1.0.4}/README.md +17 -3
- {kuva_reader-1.0.2 → kuva_reader-1.0.4}/kuva_reader/__init__.py +2 -0
- {kuva_reader-1.0.2 → kuva_reader-1.0.4}/kuva_reader/reader/image.py +27 -0
- {kuva_reader-1.0.2 → kuva_reader-1.0.4}/kuva_reader/reader/level0.py +10 -4
- {kuva_reader-1.0.2 → kuva_reader-1.0.4}/kuva_reader/reader/level1.py +27 -1
- {kuva_reader-1.0.2 → kuva_reader-1.0.4}/kuva_reader/reader/level2.py +8 -1
- {kuva_reader-1.0.2 → kuva_reader-1.0.4}/kuva_reader/reader/read.py +1 -1
- {kuva_reader-1.0.2 → kuva_reader-1.0.4}/pyproject.toml +1 -11
- {kuva_reader-1.0.2 → kuva_reader-1.0.4}/kuva_reader/py.typed +0 -0
- {kuva_reader-1.0.2 → kuva_reader-1.0.4}/kuva_reader/reader/__init__.py +0 -0
- {kuva_reader-1.0.2 → kuva_reader-1.0.4}/kuva_reader/reader/product_base.py +0 -0
- {kuva_reader-1.0.2 → kuva_reader-1.0.4}/kuva_reader/reader/py.typed +0 -0
- {kuva_reader-1.0.2 → kuva_reader-1.0.4}/kuva_reader/reader/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: kuva-reader
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.4
|
|
4
4
|
Summary: Manipulate the Kuva Space image and metadata formats
|
|
5
5
|
License: MIT
|
|
6
6
|
Author: Guillem Ballesteros
|
|
@@ -61,8 +61,8 @@ The loaded product is stored in a `rioxarray` object, which contains extensive G
|
|
|
61
61
|
```python
|
|
62
62
|
from kuva_reader import read_product
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
print(
|
|
64
|
+
product = read_product("my_data_folder/hyperfield1a_L2A_20250105T092548")
|
|
65
|
+
print(product) # Will show some main information such as image shape and CRS
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
This assumes a mostly untouched folder after distributing. Otherwise, you may need to
|
|
@@ -75,7 +75,21 @@ l2a_product = Level2AProduct("your/l2a/folder")
|
|
|
75
75
|
```
|
|
76
76
|
|
|
77
77
|
The actual raster image is stored and can be analysed in `product.image`, while metadata
|
|
78
|
-
information of the product is in `product.metadata`.
|
|
78
|
+
information of the product is in `product.metadata`.
|
|
79
|
+
|
|
80
|
+
## Other tips
|
|
81
|
+
|
|
82
|
+
The product object attributes and methods allow the retrieval of other interesting information as well:
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from kuva_reader import read_product
|
|
86
|
+
|
|
87
|
+
product = read_product("your/product/folder")
|
|
88
|
+
product.footprint(crs="EPSG:4326") # Footprint with option to transform CRS
|
|
89
|
+
product.image.shape # The image attribute contains all the image data
|
|
90
|
+
product.wavelengths # Wavelengths corresponding to image bands
|
|
91
|
+
product.crs # CRS
|
|
92
|
+
```
|
|
79
93
|
|
|
80
94
|
## Processing levels
|
|
81
95
|
|
|
@@ -38,8 +38,8 @@ The loaded product is stored in a `rioxarray` object, which contains extensive G
|
|
|
38
38
|
```python
|
|
39
39
|
from kuva_reader import read_product
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
print(
|
|
41
|
+
product = read_product("my_data_folder/hyperfield1a_L2A_20250105T092548")
|
|
42
|
+
print(product) # Will show some main information such as image shape and CRS
|
|
43
43
|
```
|
|
44
44
|
|
|
45
45
|
This assumes a mostly untouched folder after distributing. Otherwise, you may need to
|
|
@@ -52,7 +52,21 @@ l2a_product = Level2AProduct("your/l2a/folder")
|
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
The actual raster image is stored and can be analysed in `product.image`, while metadata
|
|
55
|
-
information of the product is in `product.metadata`.
|
|
55
|
+
information of the product is in `product.metadata`.
|
|
56
|
+
|
|
57
|
+
## Other tips
|
|
58
|
+
|
|
59
|
+
The product object attributes and methods allow the retrieval of other interesting information as well:
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from kuva_reader import read_product
|
|
63
|
+
|
|
64
|
+
product = read_product("your/product/folder")
|
|
65
|
+
product.footprint(crs="EPSG:4326") # Footprint with option to transform CRS
|
|
66
|
+
product.image.shape # The image attribute contains all the image data
|
|
67
|
+
product.wavelengths # Wavelengths corresponding to image bands
|
|
68
|
+
product.crs # CRS
|
|
69
|
+
```
|
|
56
70
|
|
|
57
71
|
## Processing levels
|
|
58
72
|
|
|
@@ -28,6 +28,7 @@ from .reader.image import (
|
|
|
28
28
|
image_to_dtype_range,
|
|
29
29
|
image_to_original_range,
|
|
30
30
|
image_to_uint16_range,
|
|
31
|
+
image_footprint,
|
|
31
32
|
)
|
|
32
33
|
from .reader.level0 import Level0Product
|
|
33
34
|
from .reader.level1 import Level1ABProduct, Level1CProduct
|
|
@@ -42,5 +43,6 @@ __all__ = [
|
|
|
42
43
|
"image_to_dtype_range",
|
|
43
44
|
"image_to_original_range",
|
|
44
45
|
"image_to_uint16_range",
|
|
46
|
+
"image_footprint",
|
|
45
47
|
"read_product",
|
|
46
48
|
]
|
|
@@ -4,11 +4,38 @@ from typing import cast, overload
|
|
|
4
4
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
import xarray
|
|
7
|
+
from shapely.geometry import box
|
|
8
|
+
from pyproj import Transformer
|
|
9
|
+
from shapely import Polygon
|
|
7
10
|
|
|
8
11
|
# Helper type for image processing purposes. The same operations work both for EO
|
|
9
12
|
# DataArrays and Numpy arrays.
|
|
10
13
|
ImageArray_ = np.ndarray | xarray.DataArray
|
|
11
14
|
|
|
15
|
+
def image_footprint(image: xarray.DataArray, crs: str = "") -> Polygon:
|
|
16
|
+
"""Return a product footprint as a shapely polygon
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
image
|
|
21
|
+
The product image
|
|
22
|
+
crs, optional
|
|
23
|
+
CRS to convert to, by default "", keeping the image's CRS
|
|
24
|
+
|
|
25
|
+
Returns
|
|
26
|
+
-------
|
|
27
|
+
A shapely polygon footprint
|
|
28
|
+
"""
|
|
29
|
+
if crs:
|
|
30
|
+
transformer = Transformer.from_crs(image.rio.crs, crs, always_xy=True)
|
|
31
|
+
bounds = image.rio.bounds()
|
|
32
|
+
minx, miny = transformer.transform(bounds[0], bounds[1])
|
|
33
|
+
maxx, maxy = transformer.transform(bounds[2], bounds[3])
|
|
34
|
+
footprint = box(minx, miny, maxx, maxy)
|
|
35
|
+
else:
|
|
36
|
+
footprint = box(*image.rio.bounds())
|
|
37
|
+
return footprint
|
|
38
|
+
|
|
12
39
|
|
|
13
40
|
@overload
|
|
14
41
|
def image_to_dtype_range(
|
|
@@ -6,8 +6,9 @@ import rioxarray as rx
|
|
|
6
6
|
import xarray
|
|
7
7
|
from kuva_metadata import MetadataLevel0
|
|
8
8
|
from pint import UnitRegistry
|
|
9
|
+
from shapely import Polygon
|
|
9
10
|
|
|
10
|
-
from kuva_reader import image_to_dtype_range, image_to_original_range
|
|
11
|
+
from kuva_reader import image_to_dtype_range, image_to_original_range, image_footprint
|
|
11
12
|
|
|
12
13
|
from .product_base import ProductBase
|
|
13
14
|
|
|
@@ -80,6 +81,7 @@ class Level0Product(ProductBase[MetadataLevel0]):
|
|
|
80
81
|
)
|
|
81
82
|
for camera, cube in self.metadata.image.data_cubes.items() # type: ignore
|
|
82
83
|
}
|
|
84
|
+
self.crs = self.images[list(self.images.keys())[0]].rio.crs
|
|
83
85
|
|
|
84
86
|
# Read tags for images and denormalize / renormalize if needed
|
|
85
87
|
self.data_tags = {camera: img.attrs for camera, img in self.images.items()}
|
|
@@ -106,9 +108,9 @@ class Level0Product(ProductBase[MetadataLevel0]):
|
|
|
106
108
|
if self.images is not None and len(self.images):
|
|
107
109
|
return (
|
|
108
110
|
f"{self.__class__.__name__}"
|
|
109
|
-
f"with
|
|
110
|
-
f"and
|
|
111
|
-
f"Loaded from: '{self.image_path}'."
|
|
111
|
+
f"with VIS shape {self.images['vis'].shape} "
|
|
112
|
+
f"and NIR shape {self.images['nir'].shape} "
|
|
113
|
+
f"(CRS '{self.crs}'). Loaded from: '{self.image_path}'."
|
|
112
114
|
)
|
|
113
115
|
else:
|
|
114
116
|
return f"{self.__class__.__name__} loaded from '{self.image_path}'."
|
|
@@ -121,6 +123,10 @@ class Level0Product(ProductBase[MetadataLevel0]):
|
|
|
121
123
|
"""Easy access to the camera keys."""
|
|
122
124
|
return list(self.images.keys())
|
|
123
125
|
|
|
126
|
+
def footprint(self, crs="") -> Polygon:
|
|
127
|
+
"""The product footprint as a Shapely polygon."""
|
|
128
|
+
return image_footprint(self.images["vis"], crs)
|
|
129
|
+
|
|
124
130
|
def _get_data_from_sidecar(
|
|
125
131
|
self, sidecar_path: Path, target_ureg: UnitRegistry | None = None
|
|
126
132
|
) -> MetadataLevel0:
|
|
@@ -3,8 +3,10 @@ from typing import cast
|
|
|
3
3
|
|
|
4
4
|
import rioxarray as rx
|
|
5
5
|
import xarray
|
|
6
|
+
from kuva_reader import image_footprint
|
|
6
7
|
from kuva_metadata import MetadataLevel1AB, MetadataLevel1C
|
|
7
8
|
from pint import UnitRegistry
|
|
9
|
+
from shapely import Polygon
|
|
8
10
|
from xarray import Dataset
|
|
9
11
|
|
|
10
12
|
from .product_base import ProductBase
|
|
@@ -56,6 +58,25 @@ class Level1ABProduct(ProductBase[MetadataLevel1AB]):
|
|
|
56
58
|
rx.open_rasterio(self.image_path / "L1B.tif"),
|
|
57
59
|
)
|
|
58
60
|
self.data_tags = self.image.attrs
|
|
61
|
+
self.wavelengths = [
|
|
62
|
+
b.wavelength.to("nm").magnitude for b in self.metadata.image.bands
|
|
63
|
+
]
|
|
64
|
+
self.crs = self.image.rio.crs
|
|
65
|
+
|
|
66
|
+
def __repr__(self):
|
|
67
|
+
"""Pretty printing of the object with the most important info"""
|
|
68
|
+
if self.image is not None:
|
|
69
|
+
return (
|
|
70
|
+
f"{self.__class__.__name__} with shape {self.image.shape} "
|
|
71
|
+
f"and wavelengths {self.wavelengths} (CRS: '{self.crs}'). "
|
|
72
|
+
f"Loaded from: '{self.image_path}'."
|
|
73
|
+
)
|
|
74
|
+
else:
|
|
75
|
+
return f"{self.__class__.__name__} loaded from '{self.image_path}'"
|
|
76
|
+
|
|
77
|
+
def footprint(self, crs="") -> Polygon:
|
|
78
|
+
"""The product footprint as a Shapely polygon."""
|
|
79
|
+
return image_footprint(self.image, crs)
|
|
59
80
|
|
|
60
81
|
def _get_data_from_sidecar(
|
|
61
82
|
self, sidecar_path: Path, target_ureg: UnitRegistry | None = None
|
|
@@ -163,18 +184,23 @@ class Level1CProduct(ProductBase[MetadataLevel1C]):
|
|
|
163
184
|
self.wavelengths = [
|
|
164
185
|
b.wavelength.to("nm").magnitude for b in self.metadata.image.bands
|
|
165
186
|
]
|
|
187
|
+
self.crs = self.image.rio.crs
|
|
166
188
|
|
|
167
189
|
def __repr__(self):
|
|
168
190
|
"""Pretty printing of the object with the most important info"""
|
|
169
191
|
if self.image is not None:
|
|
170
192
|
return (
|
|
171
193
|
f"{self.__class__.__name__} with shape {self.image.shape} "
|
|
172
|
-
f"and wavelengths {self.wavelengths} (CRS: '{self.
|
|
194
|
+
f"and wavelengths {self.wavelengths} (CRS: '{self.crs}'). "
|
|
173
195
|
f"Loaded from: '{self.image_path}'."
|
|
174
196
|
)
|
|
175
197
|
else:
|
|
176
198
|
return f"{self.__class__.__name__} loaded from '{self.image_path}'"
|
|
177
199
|
|
|
200
|
+
def footprint(self, crs="") -> Polygon:
|
|
201
|
+
"""The product footprint as a Shapely polygon."""
|
|
202
|
+
return image_footprint(self.image, crs)
|
|
203
|
+
|
|
178
204
|
def _get_data_from_sidecar(
|
|
179
205
|
self, sidecar_path: Path, target_ureg: UnitRegistry | None = None
|
|
180
206
|
) -> MetadataLevel1C:
|
|
@@ -2,8 +2,10 @@ from pathlib import Path
|
|
|
2
2
|
from typing import cast
|
|
3
3
|
|
|
4
4
|
import rioxarray as rx
|
|
5
|
+
from kuva_reader import image_footprint
|
|
5
6
|
from kuva_metadata import MetadataLevel2A
|
|
6
7
|
from pint import UnitRegistry
|
|
8
|
+
from shapely import Polygon
|
|
7
9
|
from xarray import Dataset
|
|
8
10
|
|
|
9
11
|
from .product_base import ProductBase
|
|
@@ -55,18 +57,23 @@ class Level2AProduct(ProductBase[MetadataLevel2A]):
|
|
|
55
57
|
self.wavelengths = [
|
|
56
58
|
b.wavelength.to("nm").magnitude for b in self.metadata.image.bands
|
|
57
59
|
]
|
|
60
|
+
self.crs = self.image.rio.crs
|
|
58
61
|
|
|
59
62
|
def __repr__(self):
|
|
60
63
|
"""Pretty printing of the object with the most important info"""
|
|
61
64
|
if self.image is not None:
|
|
62
65
|
return (
|
|
63
66
|
f"{self.__class__.__name__} with shape {self.image.shape} "
|
|
64
|
-
f"and wavelengths {self.wavelengths} (CRS: '{self.
|
|
67
|
+
f"and wavelengths {self.wavelengths} (CRS: '{self.crs}'). "
|
|
65
68
|
f"Loaded from: '{self.image_path}'."
|
|
66
69
|
)
|
|
67
70
|
else:
|
|
68
71
|
return f"{self.__class__.__name__} loaded from '{self.image_path}'"
|
|
69
72
|
|
|
73
|
+
def footprint(self, crs="") -> Polygon:
|
|
74
|
+
"""The product footprint as a Shapely polygon."""
|
|
75
|
+
return image_footprint(self.image, crs)
|
|
76
|
+
|
|
70
77
|
def _get_data_from_sidecar(
|
|
71
78
|
self, sidecar_path: Path, target_ureg: UnitRegistry | None = None
|
|
72
79
|
) -> MetadataLevel2A:
|
|
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
|
|
|
4
4
|
|
|
5
5
|
[tool.poetry]
|
|
6
6
|
name = "kuva-reader"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.4"
|
|
8
8
|
description = "Manipulate the Kuva Space image and metadata formats"
|
|
9
9
|
authors = ["Guillem Ballesteros <guillem@kuvaspace.com>" , "Lennert Antson <lennert.antson@kuvaspace.com>", "Arthur Vandenhoeke <arthur.vandenhoeke@kuvaspace.com>", "Olli Eloranta <olli.eloranta@kuvaspace.com>"]
|
|
10
10
|
readme = "README.md"
|
|
@@ -33,16 +33,6 @@ rioxarray = "^0.12.4"
|
|
|
33
33
|
kuva-geometry = "*"
|
|
34
34
|
kuva-metadata = "*"
|
|
35
35
|
|
|
36
|
-
# Temporarily can replace pypi version with relative dep if doing local development
|
|
37
|
-
# [tool.poetry.dependencies.kuva-geometry]
|
|
38
|
-
# path = "../kuva-geometry"
|
|
39
|
-
# develop = true
|
|
40
|
-
|
|
41
|
-
# [tool.poetry.dependencies.kuva-metadata]
|
|
42
|
-
# path = "../kuva-metadata"
|
|
43
|
-
# develop = true
|
|
44
|
-
|
|
45
|
-
|
|
46
36
|
[tool.ruff.lint]
|
|
47
37
|
select = [ "E", "F", "A", "DTZ", "NPY", "I", "ISC", "B003", "B004", "B015", "PTH", "D100", "D101", "D102", "D103", "D104", "D105", "D200", "W191", "W291", "W293", "N801", "N804", "N805", "T100", "S105", "S106", "S108", "S604", "S602", "S609", "UP003", "UP005", "UP006", "UP007", "UP008", "UP032", "UP035", "RUF001", "RUF200", "RUF013", "C901", "COM818", "RSE102", "EM101",]
|
|
48
38
|
exclude = [ ".direnv", ".eggs", ".git", ".mypy_cache", ".nox", ".pytype", ".ruff_cache", ".tox", ".venv", "__pypackages__", "_build", "build", "dist", "venv", "__pycache__",]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|