kuva-reader 1.1.1__tar.gz → 1.1.2__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.

Potentially problematic release.


This version of kuva-reader might be problematic. Click here for more details.

@@ -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/**
@@ -1,22 +1,16 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: kuva-reader
3
- Version: 1.1.1
3
+ Version: 1.1.2
4
4
  Summary: Manipulate the Kuva Space image and metadata formats
5
- License: MIT
6
- Author: Guillem Ballesteros
7
- Author-email: guillem@kuvaspace.com
8
- Requires-Python: >=3.10,<=3.13
9
- Classifier: License :: OSI Approved :: MIT License
10
- Classifier: Programming Language :: Python :: 3
11
- Classifier: Programming Language :: Python :: 3.10
12
- Classifier: Programming Language :: Python :: 3.11
13
- Classifier: Programming Language :: Python :: 3.12
14
- Requires-Dist: kuva-geometry (>=1.0.1,<2.0.0)
15
- Requires-Dist: kuva-metadata (>=1.1.1,<2.0.0)
16
- Requires-Dist: numpy (>=1.26.4,<2.0.0)
17
- Requires-Dist: numpy-quaternion (>=2022.4.4,<2023.0.0)
18
- Requires-Dist: pint (>=0.22,<0.23)
19
- Requires-Dist: rasterio (>=1.4.1,<2.0.0)
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: kuva-metadata<2.0.0,>=1.1.1
10
+ Requires-Dist: numpy-quaternion>=2023.4.4
11
+ Requires-Dist: numpy>=1.26.4
12
+ Requires-Dist: pint<1.0.0,>=0.22
13
+ Requires-Dist: rasterio<2,>=1.4.3
20
14
  Description-Content-Type: text/markdown
21
15
 
22
16
  <div align="center">
@@ -128,4 +122,3 @@ The `kuva-reader` project software is under the [MIT license](https://github.com
128
122
  # Status of unit tests
129
123
 
130
124
  [![Unit tests for kuva-reader](https://github.com/KuvaSpace/kuva-data-processing/actions/workflows/test-kuva-reader.yml/badge.svg)](https://github.com/KuvaSpace/kuva-data-processing/actions/workflows/test-kuva-reader.yml)
131
-
@@ -22,7 +22,7 @@ Dependencies
22
22
  visualization.
23
23
  """
24
24
 
25
- __version__ = "0.1.0"
25
+ __version__ = "1.1.2"
26
26
 
27
27
  from .reader.image import (
28
28
  image_footprint,
@@ -61,7 +61,7 @@ class Level0Product(ProductBase[MetadataLevel0]):
61
61
  ) -> None:
62
62
  super().__init__(image_path, metadata, target_ureg)
63
63
 
64
- self.images = {
64
+ self._images = {
65
65
  camera: cast(
66
66
  rio.DatasetReader,
67
67
  rio.open(
@@ -97,6 +97,12 @@ class Level0Product(ProductBase[MetadataLevel0]):
97
97
  """Return the datarray for the chosen camera."""
98
98
  return self.images[camera]
99
99
 
100
+ @property
101
+ def images(self) -> dict[str, rio.DatasetReader]:
102
+ if self._images is None:
103
+ raise RuntimeError("Images has been released.")
104
+ return self._images
105
+
100
106
  def keys(self) -> list[str]:
101
107
  """Easy access to the camera keys."""
102
108
  return list(self.images.keys())
@@ -230,11 +236,14 @@ class Level0Product(ProductBase[MetadataLevel0]):
230
236
  """Explicitely closes the Rasterio DatasetReaders and releases the memory of
231
237
  the `images` variable.
232
238
  """
233
- for k in self.images.keys():
234
- self.images[k].close()
235
-
236
- del self.images
237
- self.images = None
239
+ if self._images is not None:
240
+ for k in self._images.keys():
241
+ self._images[k].close()
242
+
243
+ del self._images
244
+ # We know that images are not None as long as somebody doesn't call
245
+ # this function beforehand....
246
+ self._images = None
238
247
 
239
248
 
240
249
  def generate_level_0_metafile():
@@ -50,7 +50,7 @@ class Level1ABProduct(ProductBase[MetadataLevel1AB]):
50
50
  ) -> None:
51
51
  super().__init__(image_path, metadata, target_ureg)
52
52
 
53
- self.image = cast(
53
+ self._image = cast(
54
54
  rio.DatasetReader,
55
55
  rio.open(self.image_path / "L1B.tif"),
56
56
  )
@@ -73,6 +73,12 @@ class Level1ABProduct(ProductBase[MetadataLevel1AB]):
73
73
  else:
74
74
  return f"{self.__class__.__name__} loaded from '{self.image_path}'"
75
75
 
76
+ @property
77
+ def image(self) -> rio.DatasetReader:
78
+ if self._image is None:
79
+ raise RuntimeError("Images has been released.")
80
+ return self._image
81
+
76
82
  def footprint(self, crs="") -> Polygon:
77
83
  """The product footprint as a Shapely polygon."""
78
84
  return image_footprint(self.image, crs)
@@ -130,8 +136,10 @@ class Level1ABProduct(ProductBase[MetadataLevel1AB]):
130
136
  """Explicitely closes the Rasterio DatasetReader and releases the memory of
131
137
  the `image` variable.
132
138
  """
133
- del self.image
134
- self.image = None
139
+ if self._image is not None:
140
+ self._image.close()
141
+ del self._image
142
+ self._image = None
135
143
 
136
144
 
137
145
  class Level1CProduct(ProductBase[MetadataLevel1C]):
@@ -170,7 +178,7 @@ class Level1CProduct(ProductBase[MetadataLevel1C]):
170
178
  ) -> None:
171
179
  super().__init__(image_path, metadata, target_ureg)
172
180
 
173
- self.image = cast(
181
+ self._image = cast(
174
182
  rio.DatasetReader,
175
183
  rio.open(self.image_path / "L1C.tif"),
176
184
  )
@@ -193,6 +201,12 @@ class Level1CProduct(ProductBase[MetadataLevel1C]):
193
201
  else:
194
202
  return f"{self.__class__.__name__} loaded from '{self.image_path}'"
195
203
 
204
+ @property
205
+ def image(self) -> rio.DatasetReader:
206
+ if self._image is None:
207
+ raise RuntimeError("Images has been released.")
208
+ return self._image
209
+
196
210
  def footprint(self, crs="") -> Polygon:
197
211
  """The product footprint as a Shapely polygon."""
198
212
  return image_footprint(self.image, crs)
@@ -231,9 +245,10 @@ class Level1CProduct(ProductBase[MetadataLevel1C]):
231
245
  """Explicitely closes the Rasterio DatasetReader and releases the memory of
232
246
  the `image` variable.
233
247
  """
234
- self.image.close()
235
- del self.image
236
- self.image = None
248
+ if self._image is not None:
249
+ self._image.close()
250
+ del self._image
251
+ self._image = None
237
252
 
238
253
 
239
254
  def generate_level_1_metafile():
@@ -35,7 +35,7 @@ class ProductBase(Generic[TMetadata], metaclass=ABCMeta):
35
35
  def __init__(
36
36
  self,
37
37
  image_path: Path,
38
- metadata: MetadataBase | None = None,
38
+ metadata: TMetadata | None = None,
39
39
  target_ureg: UnitRegistry | None = None,
40
40
  ):
41
41
  self.image_path = Path(image_path)
@@ -1,35 +1,43 @@
1
1
  [build-system]
2
- requires = [ "poetry-core>=1.0.0",]
3
- build-backend = "poetry.core.masonry.api"
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
4
 
5
- [tool.poetry]
5
+ [project]
6
6
  name = "kuva-reader"
7
- version = "1.1.1"
7
+ version = "1.1.2"
8
8
  description = "Manipulate the Kuva Space image and metadata formats"
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>"]
9
+ authors = [
10
+ { name = "Guillem Ballesteros", email = "guillem@kuvaspace.com" },
11
+ { name = "Lennert Antson", email = "lennert.antson@kuvaspace.com" },
12
+ { name = "Arthur Vandenhoeke", email = "arthur.vandenhoeke@kuvaspace.com" },
13
+ { name = "Olli Eloranta", email = "olli.eloranta@kuvaspace.com" },
14
+ ]
10
15
  readme = "README.md"
11
16
  license = "MIT"
17
+ requires-python = ">=3.10, <=3.13"
18
+ dependencies = [
19
+ "numpy>=1.26.4",
20
+ "numpy-quaternion>=2023.4.4",
21
+ "pint >=0.22, <1.0.0",
22
+ "rasterio>=1.4.3,<2",
23
+ "kuva-geometry>=1.0.1,<2.0.0",
24
+ "kuva-metadata>=1.1.1,<2.0.0"
25
+ ]
26
+ scripts.make-l0-meta = "kuva_reader.reader.level0:generate_level_0_metafile"
27
+ scripts.make-l1-meta = "kuva_reader.reader.level1:generate_level_1_metafile"
28
+
29
+ [dependency-groups]
30
+ dev = [
31
+ "pyright>=1.1.403",
32
+ "pytest>=7.4.2,<8",
33
+ "ruff>=0.12.4",
34
+ ]
12
35
 
13
36
  [tool.ruff]
14
37
  target-version = "py310"
15
38
  line-length = 88
16
39
  dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
17
40
 
18
- [tool.mypy]
19
- ignore_missing_imports = true
20
-
21
- [tool.poetry.scripts]
22
- make-l0-meta = "kuva_reader.reader.level0:generate_level_0_metafile"
23
- make-l1-meta = "kuva_reader.reader.level1:generate_level_1_metafile"
24
-
25
- [tool.poetry.dependencies]
26
- python = ">=3.10,<=3.13"
27
- numpy = "^1.26.4"
28
- numpy-quaternion = "^2022.4.4"
29
- pint = "^0.22"
30
- rasterio = "^1.4.1"
31
- kuva-geometry = "^1.0.1"
32
- kuva-metadata = "^1.1.1"
33
41
 
34
42
  [tool.ruff.lint]
35
43
  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",]
@@ -44,7 +52,7 @@ convention = "numpy"
44
52
  [tool.ruff.lint.per-file-ignores]
45
53
  "__init__.py" = [ "F401", "D104", "E402",]
46
54
 
47
- [tool.poetry.group.dev.dependencies]
48
- mypy = "^1.2.0"
49
- pytest = "^7.4.2"
50
- ruff = "^0.1.1"
55
+ # Uncomment during development
56
+ # [tool.uv.sources]
57
+ # kuva-geometry = { path = "../kuva-geometry" }
58
+ # kuva-metadata = { path = "../kuva-metadata" }
File without changes
@@ -0,0 +1,74 @@
1
+ """Tests for reading of actual Hyperfield products.
2
+
3
+ NOTE: To limit data size, the test data used in the kuva-reader tests have been cropped,
4
+ and most of the bands have been removed. The original images are from a normal
5
+ acquisition."""
6
+
7
+ from pathlib import Path
8
+
9
+ import pytest
10
+
11
+ from kuva_reader import Level1CProduct, Level2AProduct, read_product
12
+
13
+ TEST_DATA_ROOT = Path(__file__).parent / "test_data"
14
+ L1C_PATH = TEST_DATA_ROOT / "hyperfield1a_L1C_20250310T142413"
15
+ L2A_PATH = TEST_DATA_ROOT / "hyperfield1a_L2A_20250310T142413"
16
+
17
+
18
+ @pytest.fixture
19
+ def l1c_product() -> Level1CProduct:
20
+ """Fetch test L1C product.
21
+
22
+ NOTE: This is a cropped version of a Hyperfield-1A L1C cube with few bands (5)
23
+ """
24
+ return Level1CProduct(L1C_PATH)
25
+
26
+
27
+ @pytest.fixture
28
+ def l2a_product() -> Level2AProduct:
29
+ """Fetch test L2A product.
30
+
31
+ NOTE: This is a cropped version of a Hyperfield-1A L2A cube with few bands (5)
32
+ """
33
+ return Level2AProduct(L2A_PATH)
34
+
35
+
36
+ def test_product_reader():
37
+ """Read the correct products with product reader function"""
38
+ with pytest.raises(ValueError):
39
+ read_product(L2A_PATH.parent)
40
+
41
+ product = read_product(L2A_PATH)
42
+ assert product.__class__ == Level2AProduct
43
+
44
+
45
+ def test_read_l1c(l1c_product: Level1CProduct):
46
+ """Product reading was successful based on image, metadata and tags"""
47
+ # Check that image was loaded with correct number of bands
48
+ assert l1c_product.image.read().shape[0] == 5
49
+ # Check that metadata exists and has same shape as image
50
+ assert len(l1c_product.metadata.image.bands) == 5
51
+ # Check that tags exist
52
+ assert l1c_product.data_tags.get("AREA_OR_POINT") is not None
53
+
54
+
55
+ def test_read_l2a(l2a_product: Level2AProduct):
56
+ """Product reading was successful based on image, metadata and tags"""
57
+ # Check that image was loaded with correct number of bands
58
+ assert l2a_product.image.read().shape[0] == 5
59
+ # Check that metadata exists and has same shape as image
60
+ assert len(l2a_product.metadata.image.bands) == 5
61
+ # Check that tags exist
62
+ assert l2a_product.data_tags.get("AREA_OR_POINT") is not None
63
+
64
+
65
+ def test_read_bad_pixel_mask_l1c(l1c_product: Level1CProduct):
66
+ """Bad pixel mask is correctly loaded and is same shape as product"""
67
+ bad_pixel_mask = l1c_product.get_bad_pixel_mask().read()
68
+ assert bad_pixel_mask.shape[1:] == l1c_product.image.shape
69
+
70
+
71
+ def test_read_bad_pixel_mask_l2a(l2a_product: Level2AProduct):
72
+ """Bad pixel mask is correctly loaded and is same shape as product"""
73
+ bad_pixel_mask = l2a_product.get_bad_pixel_mask().read()
74
+ assert bad_pixel_mask.shape[1:] == l2a_product.image.shape