yirgacheffe 1.3.1__tar.gz → 1.3.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 yirgacheffe might be problematic. Click here for more details.

Files changed (54) hide show
  1. {yirgacheffe-1.3.1/yirgacheffe.egg-info → yirgacheffe-1.3.2}/PKG-INFO +1 -1
  2. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/pyproject.toml +1 -1
  3. yirgacheffe-1.3.2/tests/test_parallel_operators.py +310 -0
  4. yirgacheffe-1.3.2/yirgacheffe/constants.py +2 -0
  5. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/yirgacheffe/operators.py +15 -4
  6. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2/yirgacheffe.egg-info}/PKG-INFO +1 -1
  7. yirgacheffe-1.3.1/tests/test_parallel_operators.py +0 -255
  8. yirgacheffe-1.3.1/yirgacheffe/constants.py +0 -1
  9. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/LICENSE +0 -0
  10. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/MANIFEST.in +0 -0
  11. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/README.md +0 -0
  12. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/setup.cfg +0 -0
  13. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/tests/test_area.py +0 -0
  14. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/tests/test_auto_windowing.py +0 -0
  15. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/tests/test_base.py +0 -0
  16. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/tests/test_datatypes.py +0 -0
  17. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/tests/test_group.py +0 -0
  18. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/tests/test_h3layer.py +0 -0
  19. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/tests/test_intersection.py +0 -0
  20. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/tests/test_multiband.py +0 -0
  21. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/tests/test_operators.py +0 -0
  22. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/tests/test_optimisation.py +0 -0
  23. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/tests/test_pickle.py +0 -0
  24. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/tests/test_raster.py +0 -0
  25. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/tests/test_rescaling.py +0 -0
  26. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/tests/test_rounding.py +0 -0
  27. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/tests/test_save_with_window.py +0 -0
  28. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/tests/test_sum_with_window.py +0 -0
  29. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/tests/test_uniform_area_layer.py +0 -0
  30. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/tests/test_union.py +0 -0
  31. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/tests/test_vectors.py +0 -0
  32. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/tests/test_window.py +0 -0
  33. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/yirgacheffe/__init__.py +0 -0
  34. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/yirgacheffe/backends/__init__.py +0 -0
  35. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/yirgacheffe/backends/enumeration.py +0 -0
  36. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/yirgacheffe/backends/mlx.py +0 -0
  37. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/yirgacheffe/backends/numpy.py +0 -0
  38. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/yirgacheffe/h3layer.py +0 -0
  39. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/yirgacheffe/layers/__init__.py +0 -0
  40. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/yirgacheffe/layers/area.py +0 -0
  41. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/yirgacheffe/layers/base.py +0 -0
  42. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/yirgacheffe/layers/constant.py +0 -0
  43. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/yirgacheffe/layers/group.py +0 -0
  44. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/yirgacheffe/layers/h3layer.py +0 -0
  45. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/yirgacheffe/layers/rasters.py +0 -0
  46. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/yirgacheffe/layers/rescaled.py +0 -0
  47. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/yirgacheffe/layers/vectors.py +0 -0
  48. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/yirgacheffe/rounding.py +0 -0
  49. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/yirgacheffe/window.py +0 -0
  50. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/yirgacheffe.egg-info/SOURCES.txt +0 -0
  51. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/yirgacheffe.egg-info/dependency_links.txt +0 -0
  52. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/yirgacheffe.egg-info/entry_points.txt +0 -0
  53. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/yirgacheffe.egg-info/requires.txt +0 -0
  54. {yirgacheffe-1.3.1 → yirgacheffe-1.3.2}/yirgacheffe.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yirgacheffe
3
- Version: 1.3.1
3
+ Version: 1.3.2
4
4
  Summary: Abstraction of gdal datasets for doing basic math operations
5
5
  Author-email: Michael Dales <mwd24@cam.ac.uk>
6
6
  License-Expression: ISC
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
 
7
7
  [project]
8
8
  name = "yirgacheffe"
9
- version = "1.3.1"
9
+ version = "1.3.2"
10
10
  description = "Abstraction of gdal datasets for doing basic math operations"
11
11
  readme = "README.md"
12
12
  authors = [{ name = "Michael Dales", email = "mwd24@cam.ac.uk" }]
@@ -0,0 +1,310 @@
1
+ import os
2
+ import tempfile
3
+
4
+ import numpy as np
5
+ import pytest
6
+ import torch
7
+
8
+ import yirgacheffe
9
+ from helpers import gdal_dataset_with_data
10
+ from yirgacheffe.layers import RasterLayer, ConstantLayer
11
+ from yirgacheffe.operators import LayerOperation
12
+
13
+ # These tests are marked skip for MLX, because there seems to be a problem with
14
+ # calling mx.eval in the tests for parallel save on Linux (which is what we use
15
+ # for github actions for instance). They do pass on macOS - but that could also
16
+ # just be down to Python versions. To add further complication, if I just run
17
+ # this file along on linux with MLX it passes fine 🤦
18
+ #
19
+ # It seems that under the hood MLX is doing some threading of its own and my
20
+ # guess is that that's interacting with the Python threading here.
21
+
22
+ def test_add_byte_layers_with_one_thread_uses_regular_save(monkeypatch) -> None:
23
+ with monkeypatch.context() as m:
24
+ m.setattr(yirgacheffe.constants, "YSTEP", 4)
25
+ m.setattr(LayerOperation, "save", None)
26
+ with tempfile.TemporaryDirectory() as tempdir:
27
+ path1 = os.path.join(tempdir, "test1.tif")
28
+ data1 = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])
29
+ dataset1 = gdal_dataset_with_data((0.0, 0.0), 0.02, data1, filename=path1)
30
+ dataset1.Close()
31
+
32
+ path2 = os.path.join(tempdir, "test2.tif")
33
+ data2 = np.array([[10, 20, 30, 40], [50, 60, 70, 80]])
34
+ dataset2 = gdal_dataset_with_data((0.0, 0.0), 0.02, data2, filename=path2)
35
+ dataset2.Close()
36
+
37
+ layer1 = RasterLayer.layer_from_file(path1)
38
+ layer2 = RasterLayer.layer_from_file(path2)
39
+ result = RasterLayer.empty_raster_layer_like(layer1)
40
+
41
+ comp = layer1 + layer2
42
+ with pytest.raises(TypeError):
43
+ comp.parallel_save(result)
44
+
45
+
46
+ @pytest.mark.skipif(yirgacheffe.backends.BACKEND != "NUMPY", reason="Only applies for numpy")
47
+ def test_add_byte_layers(monkeypatch) -> None:
48
+ with monkeypatch.context() as m:
49
+ m.setattr(yirgacheffe.constants, "YSTEP", 1)
50
+ m.setattr(LayerOperation, "save", None)
51
+ with tempfile.TemporaryDirectory() as tempdir:
52
+ path1 = os.path.join(tempdir, "test1.tif")
53
+ data1 = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])
54
+ dataset1 = gdal_dataset_with_data((0.0, 0.0), 0.02, data1, filename=path1)
55
+ dataset1.Close()
56
+
57
+ path2 = os.path.join(tempdir, "test2.tif")
58
+ data2 = np.array([[10, 20, 30, 40], [50, 60, 70, 80]])
59
+ dataset2 = gdal_dataset_with_data((0.0, 0.0), 0.02, data2, filename=path2)
60
+ dataset2.Close()
61
+
62
+ layer1 = RasterLayer.layer_from_file(path1)
63
+ layer2 = RasterLayer.layer_from_file(path2)
64
+ result = RasterLayer.empty_raster_layer_like(layer1)
65
+
66
+ comp = layer1 + layer2
67
+ comp.parallel_save(result)
68
+
69
+ expected = data1 + data2
70
+ actual = result.read_array(0, 0, 4, 2)
71
+
72
+ assert (expected == actual).all()
73
+
74
+ @pytest.mark.skipif(yirgacheffe.backends.BACKEND != "NUMPY", reason="Only applies for numpy")
75
+ def test_add_byte_layers_and_sum(monkeypatch) -> None:
76
+ with monkeypatch.context() as m:
77
+ m.setattr(yirgacheffe.constants, "YSTEP", 1)
78
+ m.setattr(LayerOperation, "save", None)
79
+ with tempfile.TemporaryDirectory() as tempdir:
80
+ path1 = os.path.join(tempdir, "test1.tif")
81
+ data1 = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])
82
+ dataset1 = gdal_dataset_with_data((0.0, 0.0), 0.02, data1, filename=path1)
83
+ dataset1.Close()
84
+
85
+ path2 = os.path.join(tempdir, "test2.tif")
86
+ data2 = np.array([[10, 20, 30, 40], [50, 60, 70, 80]])
87
+ dataset2 = gdal_dataset_with_data((0.0, 0.0), 0.02, data2, filename=path2)
88
+ dataset2.Close()
89
+
90
+ layer1 = RasterLayer.layer_from_file(path1)
91
+ layer2 = RasterLayer.layer_from_file(path2)
92
+ result = RasterLayer.empty_raster_layer_like(layer1)
93
+
94
+ comp = layer1 + layer2
95
+ sum_total = comp.parallel_save(result, and_sum=True)
96
+
97
+ expected = data1 + data2
98
+ actual = result.read_array(0, 0, 4, 2)
99
+
100
+ assert (expected == actual).all()
101
+ assert sum_total == expected.sum()
102
+
103
+ @pytest.mark.skipif(yirgacheffe.backends.BACKEND != "NUMPY", reason="Only applies for numpy")
104
+ def test_parallel_sum(monkeypatch) -> None:
105
+ with monkeypatch.context() as m:
106
+ m.setattr(yirgacheffe.constants, "YSTEP", 1)
107
+ m.setattr(LayerOperation, "save", None)
108
+ with tempfile.TemporaryDirectory() as tempdir:
109
+ path1 = os.path.join(tempdir, "test1.tif")
110
+ data1 = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])
111
+ dataset1 = gdal_dataset_with_data((0.0, 0.0), 0.02, data1, filename=path1)
112
+ dataset1.Close()
113
+
114
+ path2 = os.path.join(tempdir, "test2.tif")
115
+ data2 = np.array([[10, 20, 30, 40], [50, 60, 70, 80]])
116
+ dataset2 = gdal_dataset_with_data((0.0, 0.0), 0.02, data2, filename=path2)
117
+ dataset2.Close()
118
+
119
+ layer1 = RasterLayer.layer_from_file(path1)
120
+ layer2 = RasterLayer.layer_from_file(path2)
121
+
122
+ comp = layer1 + layer2
123
+ sum_total = comp.parallel_sum()
124
+
125
+ expected = data1 + data2
126
+ assert sum_total == expected.sum()
127
+
128
+ @pytest.mark.skipif(yirgacheffe.backends.BACKEND != "NUMPY", reason="Only applies for numpy")
129
+ @pytest.mark.parametrize("skip,expected_steps", [
130
+ (1, [0.0, 0.25, 0.5, 0.75, 1.0]),
131
+ (2, [0.0, 0.5, 1.0]),
132
+ ])
133
+ def test_parallel_with_different_skip(monkeypatch, skip, expected_steps) -> None:
134
+ with monkeypatch.context() as m:
135
+ m.setattr(yirgacheffe.constants, "YSTEP", 1)
136
+ m.setattr(LayerOperation, "save", None)
137
+ with tempfile.TemporaryDirectory() as tempdir:
138
+ path1 = os.path.join(tempdir, "test1.tif")
139
+ data1 = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [1, 2, 3, 4], [5, 6, 7, 8]])
140
+ dataset1 = gdal_dataset_with_data((0.0, 0.0), 0.02, data1, filename=path1)
141
+ dataset1.Close()
142
+
143
+ path2 = os.path.join(tempdir, "test2.tif")
144
+ data2 = np.array([[10, 20, 30, 40], [50, 60, 70, 80], [10, 20, 30, 40], [50, 60, 70, 80]])
145
+ dataset2 = gdal_dataset_with_data((0.0, 0.0), 0.02, data2, filename=path2)
146
+ dataset2.Close()
147
+
148
+ layer1 = RasterLayer.layer_from_file(path1)
149
+ layer2 = RasterLayer.layer_from_file(path2)
150
+ result = RasterLayer.empty_raster_layer_like(layer1)
151
+
152
+ callback_possitions = []
153
+
154
+ comp = layer1 + layer2
155
+ comp.ystep = skip
156
+ comp.parallel_save(result, callback=lambda x: callback_possitions.append(x))
157
+
158
+ expected = data1 + data2
159
+ actual = result.read_array(0, 0, 4, 4)
160
+
161
+ assert (expected == actual).all()
162
+
163
+ assert callback_possitions == expected_steps
164
+
165
+ @pytest.mark.skipif(yirgacheffe.backends.BACKEND != "NUMPY", reason="Only applies for numpy")
166
+ def test_parallel_equality(monkeypatch) -> None:
167
+ with monkeypatch.context() as m:
168
+ m.setattr(yirgacheffe.constants, "YSTEP", 1)
169
+ m.setattr(LayerOperation, "save", None)
170
+ with tempfile.TemporaryDirectory() as tempdir:
171
+ path1 = os.path.join(tempdir, "test1.tif")
172
+ data1 = np.array([[1, 2, 3, 4], [4, 3, 2, 1]])
173
+ dataset1 = gdal_dataset_with_data((0.0, 0.0), 0.02, data1, filename=path1)
174
+ dataset1.Close()
175
+ with RasterLayer.layer_from_file(path1) as layer1:
176
+ with RasterLayer.empty_raster_layer_like(layer1) as result:
177
+ comp = layer1 == 2
178
+ comp.parallel_save(result)
179
+
180
+ expected = np.array([[0, 1, 0, 0], [0, 0, 1, 0]])
181
+ actual = result.read_array(0, 0, 4, 2)
182
+
183
+ assert (expected == actual).all()
184
+
185
+ @pytest.mark.skipif(yirgacheffe.backends.BACKEND != "NUMPY", reason="Only applies for numpy")
186
+ def test_parallel_equality_to_file(monkeypatch) -> None:
187
+ with monkeypatch.context() as m:
188
+ m.setattr(yirgacheffe.constants, "YSTEP", 1)
189
+ m.setattr(LayerOperation, "save", None)
190
+ with tempfile.TemporaryDirectory() as tempdir:
191
+ path1 = os.path.join(tempdir, "test1.tif")
192
+ path2 = os.path.join(tempdir, "result.tif")
193
+ data1 = np.array([[1, 2, 3, 4], [4, 3, 2, 1]])
194
+ dataset1 = gdal_dataset_with_data((0.0, 0.0), 0.02, data1, filename=path1)
195
+ dataset1.Close()
196
+ with RasterLayer.layer_from_file(path1) as layer1:
197
+ comp = layer1 == 2
198
+ with RasterLayer.empty_raster_layer_like(layer1, filename=path2) as result:
199
+ comp.parallel_save(result)
200
+ with RasterLayer.layer_from_file(path2) as actual_result:
201
+ expected = np.array([[0, 1, 0, 0], [0, 0, 1, 0]])
202
+ actual = actual_result.read_array(0, 0, 4, 2)
203
+ assert (expected == actual).all()
204
+
205
+ @pytest.mark.skipif(yirgacheffe.backends.BACKEND != "NUMPY", reason="Only applies for numpy")
206
+ def test_parallel_unary_numpy_apply_with_function(monkeypatch) -> None:
207
+ with monkeypatch.context() as m:
208
+ m.setattr(yirgacheffe.constants, "YSTEP", 1)
209
+ m.setattr(LayerOperation, "save", None)
210
+ with tempfile.TemporaryDirectory() as tempdir:
211
+ path1 = os.path.join(tempdir, "test1.tif")
212
+ data1 = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])
213
+ dataset1 = gdal_dataset_with_data((0.0, 0.0), 0.02, data1, filename=path1)
214
+ dataset1.Close()
215
+ layer1 = RasterLayer.layer_from_file(path1)
216
+
217
+ result = RasterLayer.empty_raster_layer_like(layer1)
218
+
219
+ def simple_add(chunk):
220
+ return chunk + 1.0
221
+
222
+ comp = layer1.numpy_apply(simple_add)
223
+ comp.ystep = 1
224
+ comp.parallel_save(result)
225
+
226
+ expected = data1 + 1.0
227
+ actual = result.read_array(0, 0, 4, 2)
228
+
229
+ assert (expected == actual).all()
230
+
231
+ @pytest.mark.skipif(yirgacheffe.backends.BACKEND != "NUMPY", reason="Only applies for numpy")
232
+ def test_parallel_unary_numpy_apply_with_lambda(monkeypatch) -> None:
233
+ with monkeypatch.context() as m:
234
+ m.setattr(yirgacheffe.constants, "YSTEP", 1)
235
+ m.setattr(LayerOperation, "save", None)
236
+ with tempfile.TemporaryDirectory() as tempdir:
237
+ path1 = os.path.join(tempdir, "test1.tif")
238
+ data1 = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])
239
+ dataset1 = gdal_dataset_with_data((0.0, 0.0), 0.02, data1, filename=path1)
240
+ dataset1.Close()
241
+ layer1 = RasterLayer.layer_from_file(path1)
242
+
243
+ result = RasterLayer.empty_raster_layer_like(layer1)
244
+
245
+ comp = layer1.numpy_apply(lambda a: a + 1.0)
246
+ comp.ystep = 1
247
+ comp.parallel_save(result)
248
+
249
+ expected = data1 + 1.0
250
+ actual = result.read_array(0, 0, 4, 2)
251
+
252
+ assert (expected == actual).all()
253
+
254
+ @pytest.mark.skipif(yirgacheffe.backends.BACKEND != "NUMPY", reason="Only applies for numpy")
255
+ def test_parallel_where_simple(monkeypatch) -> None:
256
+ with monkeypatch.context() as m:
257
+ m.setattr(yirgacheffe.constants, "YSTEP", 1)
258
+ m.setattr(LayerOperation, "save", None)
259
+ with tempfile.TemporaryDirectory() as tempdir:
260
+ path1 = os.path.join(tempdir, "test1.tif")
261
+ data1 = np.array([[0, 1, 0, 2], [0, 0, 1, 1]])
262
+ dataset1 = gdal_dataset_with_data((0.0, 0.0), 0.02, data1, filename=path1)
263
+ dataset1.Close()
264
+ layer1 = RasterLayer.layer_from_file(path1)
265
+
266
+ result = RasterLayer.empty_raster_layer_like(layer1)
267
+
268
+ comp = LayerOperation.where(layer1 > 0, 1, 2)
269
+ comp.ystep = 1
270
+ comp.parallel_save(result)
271
+
272
+ expected = np.where(data1 > 0, 1, 2)
273
+ actual = result.read_array(0, 0, 4, 2)
274
+ assert (expected == actual).all()
275
+
276
+ @pytest.mark.skipif(yirgacheffe.backends.BACKEND != "NUMPY", reason="Only applies for numpy")
277
+ def test_parallel_conv2d() -> None:
278
+ with tempfile.TemporaryDirectory() as tempdir:
279
+
280
+ data1 = np.array([
281
+ [0, 0, 0, 0, 0],
282
+ [0, 1, 1, 1, 0],
283
+ [0, 1, 1, 1, 0],
284
+ [0, 1, 1, 1, 0],
285
+ [0, 0, 0, 0, 0],
286
+ ]).astype(np.float32)
287
+ weights = np.array([
288
+ [0.0, 0.1, 0.0],
289
+ [0.1, 0.6, 0.1],
290
+ [0.0, 0.1, 0.0],
291
+ ])
292
+
293
+ conv = torch.nn.Conv2d(1, 1, 3, padding=1, bias=False)
294
+ conv.weight = torch.nn.Parameter(torch.from_numpy(np.array([[weights.astype(np.float32)]])))
295
+ tensorres = conv(torch.from_numpy(np.array([[data1]])))
296
+ expected = tensorres.detach().numpy()[0][0]
297
+
298
+ path1 = os.path.join(tempdir, "test1.tif")
299
+ dataset1 = gdal_dataset_with_data((0.0, 0.0), 0.02, data1, filename=path1)
300
+ dataset1.Close()
301
+
302
+ with RasterLayer.layer_from_file(path1) as layer1:
303
+
304
+ calc = layer1.conv2d(weights)
305
+ with RasterLayer.empty_raster_layer_like(layer1) as res:
306
+ calc.save(res)
307
+ actual = res.read_array(0, 0, 5, 5)
308
+
309
+ # Torch and MLX give slightly different rounding
310
+ assert np.isclose(expected, actual).all()
@@ -0,0 +1,2 @@
1
+ YSTEP = 512
2
+ MINIMUM_CHUNKS_PER_THREAD = 1
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ import math
2
3
  import multiprocessing
3
4
  import sys
4
5
  import time
@@ -578,6 +579,20 @@ class LayerOperation(LayerMathMixin):
578
579
  def _parallel_save(self, destination_layer, and_sum=False, callback=None, parallelism=None, band=1):
579
580
  assert (destination_layer is not None) or and_sum
580
581
  computation_window = self.window
582
+
583
+ worker_count = parallelism or multiprocessing.cpu_count()
584
+ work_blocks = len(range(0, computation_window.ysize, self.ystep))
585
+ adjusted_blocks = math.ceil(work_blocks / constants.MINIMUM_CHUNKS_PER_THREAD)
586
+ worker_count = min(adjusted_blocks, worker_count)
587
+
588
+ if worker_count == 1:
589
+ if destination_layer:
590
+ return self.save(destination_layer, and_sum, callback, band)
591
+ elif and_sum:
592
+ return self.sum()
593
+ else:
594
+ assert False
595
+
581
596
  if destination_layer is not None:
582
597
  try:
583
598
  band = destination_layer._dataset.GetRasterBand(band)
@@ -615,10 +630,6 @@ class LayerOperation(LayerMathMixin):
615
630
  with multiprocessing.Manager() as manager:
616
631
  with SharedMemoryManager() as smm:
617
632
 
618
- worker_count = parallelism or multiprocessing.cpu_count()
619
- work_blocks = len(range(0, computation_window.ysize, self.ystep))
620
- worker_count = min(work_blocks, worker_count)
621
-
622
633
  mem_sem_cast = []
623
634
  for i in range(worker_count):
624
635
  shared_buf = smm.SharedMemory(size=np_dtype.itemsize * self.ystep * computation_window.xsize)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yirgacheffe
3
- Version: 1.3.1
3
+ Version: 1.3.2
4
4
  Summary: Abstraction of gdal datasets for doing basic math operations
5
5
  Author-email: Michael Dales <mwd24@cam.ac.uk>
6
6
  License-Expression: ISC
@@ -1,255 +0,0 @@
1
- import os
2
- import tempfile
3
-
4
- import numpy as np
5
- import pytest
6
- import torch
7
-
8
- import yirgacheffe
9
- from helpers import gdal_dataset_with_data
10
- from yirgacheffe.layers import RasterLayer, ConstantLayer
11
- from yirgacheffe.operators import LayerOperation
12
-
13
- # These tests are marked skip for MLX, because there seems to be a problem with
14
- # calling mx.eval in the tests for parallel save on Linux (which is what we use
15
- # for github actions for instance). They do pass on macOS - but that could also
16
- # just be down to Python versions. To add further complication, if I just run
17
- # this file along on linux with MLX it passes fine 🤦
18
- #
19
- # It seems that under the hood MLX is doing some threading of its own and my
20
- # guess is that that's interacting with the Python threading here.
21
-
22
-
23
- @pytest.mark.skipif(yirgacheffe.backends.BACKEND != "NUMPY", reason="Only applies for numpy")
24
- def test_add_byte_layers() -> None:
25
- with tempfile.TemporaryDirectory() as tempdir:
26
- path1 = os.path.join(tempdir, "test1.tif")
27
- data1 = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])
28
- dataset1 = gdal_dataset_with_data((0.0, 0.0), 0.02, data1, filename=path1)
29
- dataset1.Close()
30
-
31
- path2 = os.path.join(tempdir, "test2.tif")
32
- data2 = np.array([[10, 20, 30, 40], [50, 60, 70, 80]])
33
- dataset2 = gdal_dataset_with_data((0.0, 0.0), 0.02, data2, filename=path2)
34
- dataset2.Close()
35
-
36
- layer1 = RasterLayer.layer_from_file(path1)
37
- layer2 = RasterLayer.layer_from_file(path2)
38
- result = RasterLayer.empty_raster_layer_like(layer1)
39
-
40
- comp = layer1 + layer2
41
- comp.parallel_save(result)
42
-
43
- expected = data1 + data2
44
- actual = result.read_array(0, 0, 4, 2)
45
-
46
- assert (expected == actual).all()
47
-
48
- @pytest.mark.skipif(yirgacheffe.backends.BACKEND != "NUMPY", reason="Only applies for numpy")
49
- def test_add_byte_layers_and_sum() -> None:
50
- with tempfile.TemporaryDirectory() as tempdir:
51
- path1 = os.path.join(tempdir, "test1.tif")
52
- data1 = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])
53
- dataset1 = gdal_dataset_with_data((0.0, 0.0), 0.02, data1, filename=path1)
54
- dataset1.Close()
55
-
56
- path2 = os.path.join(tempdir, "test2.tif")
57
- data2 = np.array([[10, 20, 30, 40], [50, 60, 70, 80]])
58
- dataset2 = gdal_dataset_with_data((0.0, 0.0), 0.02, data2, filename=path2)
59
- dataset2.Close()
60
-
61
- layer1 = RasterLayer.layer_from_file(path1)
62
- layer2 = RasterLayer.layer_from_file(path2)
63
- result = RasterLayer.empty_raster_layer_like(layer1)
64
-
65
- comp = layer1 + layer2
66
- sum_total = comp.parallel_save(result, and_sum=True)
67
-
68
- expected = data1 + data2
69
- actual = result.read_array(0, 0, 4, 2)
70
-
71
- assert (expected == actual).all()
72
- assert sum_total == expected.sum()
73
-
74
- @pytest.mark.skipif(yirgacheffe.backends.BACKEND != "NUMPY", reason="Only applies for numpy")
75
- def test_parallel_sum() -> None:
76
- with tempfile.TemporaryDirectory() as tempdir:
77
- path1 = os.path.join(tempdir, "test1.tif")
78
- data1 = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])
79
- dataset1 = gdal_dataset_with_data((0.0, 0.0), 0.02, data1, filename=path1)
80
- dataset1.Close()
81
-
82
- path2 = os.path.join(tempdir, "test2.tif")
83
- data2 = np.array([[10, 20, 30, 40], [50, 60, 70, 80]])
84
- dataset2 = gdal_dataset_with_data((0.0, 0.0), 0.02, data2, filename=path2)
85
- dataset2.Close()
86
-
87
- layer1 = RasterLayer.layer_from_file(path1)
88
- layer2 = RasterLayer.layer_from_file(path2)
89
-
90
- comp = layer1 + layer2
91
- sum_total = comp.parallel_sum()
92
-
93
- expected = data1 + data2
94
- assert sum_total == expected.sum()
95
-
96
- @pytest.mark.skipif(yirgacheffe.backends.BACKEND != "NUMPY", reason="Only applies for numpy")
97
- @pytest.mark.parametrize("skip,expected_steps", [
98
- (1, [0.0, 0.5, 1.0]),
99
- (2, [0.0, 1.0]),
100
- ])
101
- def test_parallel_with_different_skip(skip, expected_steps) -> None:
102
- with tempfile.TemporaryDirectory() as tempdir:
103
- path1 = os.path.join(tempdir, "test1.tif")
104
- data1 = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])
105
- dataset1 = gdal_dataset_with_data((0.0, 0.0), 0.02, data1, filename=path1)
106
- dataset1.Close()
107
-
108
- path2 = os.path.join(tempdir, "test2.tif")
109
- data2 = np.array([[10, 20, 30, 40], [50, 60, 70, 80]])
110
- dataset2 = gdal_dataset_with_data((0.0, 0.0), 0.02, data2, filename=path2)
111
- dataset2.Close()
112
-
113
- layer1 = RasterLayer.layer_from_file(path1)
114
- layer2 = RasterLayer.layer_from_file(path2)
115
- result = RasterLayer.empty_raster_layer_like(layer1)
116
-
117
- callback_possitions = []
118
-
119
- comp = layer1 + layer2
120
- comp.ystep = skip
121
- comp.parallel_save(result, callback=lambda x: callback_possitions.append(x))
122
-
123
- expected = data1 + data2
124
- actual = result.read_array(0, 0, 4, 2)
125
-
126
- assert (expected == actual).all()
127
-
128
- assert callback_possitions == expected_steps
129
-
130
- @pytest.mark.skipif(yirgacheffe.backends.BACKEND != "NUMPY", reason="Only applies for numpy")
131
- def test_parallel_equality() -> None:
132
- with tempfile.TemporaryDirectory() as tempdir:
133
- path1 = os.path.join(tempdir, "test1.tif")
134
- data1 = np.array([[1, 2, 3, 4], [4, 3, 2, 1]])
135
- dataset1 = gdal_dataset_with_data((0.0, 0.0), 0.02, data1, filename=path1)
136
- dataset1.Close()
137
- with RasterLayer.layer_from_file(path1) as layer1:
138
- with RasterLayer.empty_raster_layer_like(layer1) as result:
139
- comp = layer1 == 2
140
- comp.parallel_save(result)
141
-
142
- expected = np.array([[0, 1, 0, 0], [0, 0, 1, 0]])
143
- actual = result.read_array(0, 0, 4, 2)
144
-
145
- assert (expected == actual).all()
146
-
147
- @pytest.mark.skipif(yirgacheffe.backends.BACKEND != "NUMPY", reason="Only applies for numpy")
148
- def test_parallel_equality_to_file() -> None:
149
- with tempfile.TemporaryDirectory() as tempdir:
150
- path1 = os.path.join(tempdir, "test1.tif")
151
- path2 = os.path.join(tempdir, "result.tif")
152
- data1 = np.array([[1, 2, 3, 4], [4, 3, 2, 1]])
153
- dataset1 = gdal_dataset_with_data((0.0, 0.0), 0.02, data1, filename=path1)
154
- dataset1.Close()
155
- with RasterLayer.layer_from_file(path1) as layer1:
156
- comp = layer1 == 2
157
- with RasterLayer.empty_raster_layer_like(layer1, filename=path2) as result:
158
- comp.parallel_save(result)
159
- with RasterLayer.layer_from_file(path2) as actual_result:
160
- expected = np.array([[0, 1, 0, 0], [0, 0, 1, 0]])
161
- actual = actual_result.read_array(0, 0, 4, 2)
162
- assert (expected == actual).all()
163
-
164
- @pytest.mark.skipif(yirgacheffe.backends.BACKEND != "NUMPY", reason="Only applies for numpy")
165
- def test_parallel_unary_numpy_apply_with_function() -> None:
166
- with tempfile.TemporaryDirectory() as tempdir:
167
- path1 = os.path.join(tempdir, "test1.tif")
168
- data1 = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])
169
- dataset1 = gdal_dataset_with_data((0.0, 0.0), 0.02, data1, filename=path1)
170
- dataset1.Close()
171
- layer1 = RasterLayer.layer_from_file(path1)
172
-
173
- result = RasterLayer.empty_raster_layer_like(layer1)
174
-
175
- def simple_add(chunk):
176
- return chunk + 1.0
177
-
178
- comp = layer1.numpy_apply(simple_add)
179
- comp.ystep = 1
180
- comp.parallel_save(result)
181
-
182
- expected = data1 + 1.0
183
- actual = result.read_array(0, 0, 4, 2)
184
-
185
- assert (expected == actual).all()
186
-
187
- @pytest.mark.skipif(yirgacheffe.backends.BACKEND != "NUMPY", reason="Only applies for numpy")
188
- def test_parallel_unary_numpy_apply_with_lambda() -> None:
189
- with tempfile.TemporaryDirectory() as tempdir:
190
- path1 = os.path.join(tempdir, "test1.tif")
191
- data1 = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])
192
- dataset1 = gdal_dataset_with_data((0.0, 0.0), 0.02, data1, filename=path1)
193
- dataset1.Close()
194
- layer1 = RasterLayer.layer_from_file(path1)
195
-
196
- result = RasterLayer.empty_raster_layer_like(layer1)
197
-
198
- comp = layer1.numpy_apply(lambda a: a + 1.0)
199
- comp.ystep = 1
200
- comp.parallel_save(result)
201
-
202
- expected = data1 + 1.0
203
- actual = result.read_array(0, 0, 4, 2)
204
-
205
- assert (expected == actual).all()
206
-
207
- @pytest.mark.skipif(yirgacheffe.backends.BACKEND != "NUMPY", reason="Only applies for numpy")
208
- def test_parallel_where_simple() -> None:
209
- with tempfile.TemporaryDirectory() as tempdir:
210
- path1 = os.path.join(tempdir, "test1.tif")
211
- data1 = np.array([[0, 1, 0, 2], [0, 0, 1, 1]])
212
- dataset1 = gdal_dataset_with_data((0.0, 0.0), 0.02, data1, filename=path1)
213
- dataset1.Close()
214
- layer1 = RasterLayer.layer_from_file(path1)
215
-
216
- result = RasterLayer.empty_raster_layer_like(layer1)
217
-
218
- comp = LayerOperation.where(layer1 > 0, 1, 2)
219
- comp.ystep = 1
220
- comp.parallel_save(result)
221
-
222
- expected = np.where(data1 > 0, 1, 2)
223
- actual = result.read_array(0, 0, 4, 2)
224
- assert (expected == actual).all()
225
-
226
- @pytest.mark.skipif(yirgacheffe.backends.BACKEND != "NUMPY", reason="Only applies for numpy")
227
- def test_parallel_conv2d() -> None:
228
- data1 = np.array([
229
- [0, 0, 0, 0, 0],
230
- [0, 1, 1, 1, 0],
231
- [0, 1, 1, 1, 0],
232
- [0, 1, 1, 1, 0],
233
- [0, 0, 0, 0, 0],
234
- ]).astype(np.float32)
235
- weights = np.array([
236
- [0.0, 0.1, 0.0],
237
- [0.1, 0.6, 0.1],
238
- [0.0, 0.1, 0.0],
239
- ])
240
-
241
- conv = torch.nn.Conv2d(1, 1, 3, padding=1, bias=False)
242
- conv.weight = torch.nn.Parameter(torch.from_numpy(np.array([[weights.astype(np.float32)]])))
243
- tensorres = conv(torch.from_numpy(np.array([[data1]])))
244
- expected = tensorres.detach().numpy()[0][0]
245
-
246
- with RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1)) as layer1:
247
-
248
- calc = layer1.conv2d(weights)
249
- calc.ystep = 1
250
- with RasterLayer.empty_raster_layer_like(layer1) as res:
251
- calc.save(res)
252
- actual = res.read_array(0, 0, 5, 5)
253
-
254
- # Torch and MLX give slightly different rounding
255
- assert np.isclose(expected, actual).all()
@@ -1 +0,0 @@
1
- YSTEP = 512
File without changes
File without changes
File without changes
File without changes