yirgacheffe 1.3.1__tar.gz → 1.3.3__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.3}/PKG-INFO +4 -1
  2. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/README.md +3 -0
  3. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/pyproject.toml +1 -1
  4. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/tests/test_operators.py +84 -0
  5. yirgacheffe-1.3.3/tests/test_parallel_operators.py +310 -0
  6. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/yirgacheffe/backends/enumeration.py +3 -1
  7. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/yirgacheffe/backends/mlx.py +6 -0
  8. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/yirgacheffe/backends/numpy.py +6 -0
  9. yirgacheffe-1.3.3/yirgacheffe/constants.py +2 -0
  10. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/yirgacheffe/operators.py +39 -4
  11. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3/yirgacheffe.egg-info}/PKG-INFO +4 -1
  12. yirgacheffe-1.3.1/tests/test_parallel_operators.py +0 -255
  13. yirgacheffe-1.3.1/yirgacheffe/constants.py +0 -1
  14. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/LICENSE +0 -0
  15. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/MANIFEST.in +0 -0
  16. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/setup.cfg +0 -0
  17. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/tests/test_area.py +0 -0
  18. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/tests/test_auto_windowing.py +0 -0
  19. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/tests/test_base.py +0 -0
  20. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/tests/test_datatypes.py +0 -0
  21. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/tests/test_group.py +0 -0
  22. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/tests/test_h3layer.py +0 -0
  23. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/tests/test_intersection.py +0 -0
  24. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/tests/test_multiband.py +0 -0
  25. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/tests/test_optimisation.py +0 -0
  26. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/tests/test_pickle.py +0 -0
  27. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/tests/test_raster.py +0 -0
  28. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/tests/test_rescaling.py +0 -0
  29. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/tests/test_rounding.py +0 -0
  30. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/tests/test_save_with_window.py +0 -0
  31. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/tests/test_sum_with_window.py +0 -0
  32. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/tests/test_uniform_area_layer.py +0 -0
  33. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/tests/test_union.py +0 -0
  34. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/tests/test_vectors.py +0 -0
  35. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/tests/test_window.py +0 -0
  36. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/yirgacheffe/__init__.py +0 -0
  37. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/yirgacheffe/backends/__init__.py +0 -0
  38. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/yirgacheffe/h3layer.py +0 -0
  39. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/yirgacheffe/layers/__init__.py +0 -0
  40. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/yirgacheffe/layers/area.py +0 -0
  41. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/yirgacheffe/layers/base.py +0 -0
  42. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/yirgacheffe/layers/constant.py +0 -0
  43. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/yirgacheffe/layers/group.py +0 -0
  44. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/yirgacheffe/layers/h3layer.py +0 -0
  45. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/yirgacheffe/layers/rasters.py +0 -0
  46. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/yirgacheffe/layers/rescaled.py +0 -0
  47. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/yirgacheffe/layers/vectors.py +0 -0
  48. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/yirgacheffe/rounding.py +0 -0
  49. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/yirgacheffe/window.py +0 -0
  50. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/yirgacheffe.egg-info/SOURCES.txt +0 -0
  51. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/yirgacheffe.egg-info/dependency_links.txt +0 -0
  52. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/yirgacheffe.egg-info/entry_points.txt +0 -0
  53. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/yirgacheffe.egg-info/requires.txt +0 -0
  54. {yirgacheffe-1.3.1 → yirgacheffe-1.3.3}/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.3
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
@@ -304,9 +304,11 @@ with RasterLayer.layer_from_file('test1.tif') as layer1:
304
304
  The following math operators common to numpy and other libraries are currently supported:
305
305
 
306
306
  * abs
307
+ * ceil
307
308
  * clip
308
309
  * exp
309
310
  * exp2
311
+ * floor
310
312
  * isin
311
313
  * log
312
314
  * log2
@@ -314,6 +316,7 @@ The following math operators common to numpy and other libraries are currently s
314
316
  * maximum
315
317
  * minimum
316
318
  * nan_to_num
319
+ * round
317
320
 
318
321
  Typically these can be invoked either on a layer as a method:
319
322
 
@@ -280,9 +280,11 @@ with RasterLayer.layer_from_file('test1.tif') as layer1:
280
280
  The following math operators common to numpy and other libraries are currently supported:
281
281
 
282
282
  * abs
283
+ * ceil
283
284
  * clip
284
285
  * exp
285
286
  * exp2
287
+ * floor
286
288
  * isin
287
289
  * log
288
290
  * log2
@@ -290,6 +292,7 @@ The following math operators common to numpy and other libraries are currently s
290
292
  * maximum
291
293
  * minimum
292
294
  * nan_to_num
295
+ * round
293
296
 
294
297
  Typically these can be invoked either on a layer as a method:
295
298
 
@@ -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.3"
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" }]
@@ -1332,3 +1332,87 @@ def test_simple_conv2d_over_calculated_result(skip) -> None:
1332
1332
 
1333
1333
  # Torch and MLX give slightly different rounding
1334
1334
  assert np.isclose(expected, actual).all()
1335
+
1336
+ def test_floor_method() -> None:
1337
+ data1 = np.array([[1.0, 1.2, 1.5, 1.7], [-1.0, -1.2, -1.5, -1.7]])
1338
+ layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
1339
+ result = RasterLayer.empty_raster_layer_like(layer1)
1340
+
1341
+ comp = layer1.floor()
1342
+ comp.save(result)
1343
+
1344
+ expected = backend.floor_op(backend.promote(data1))
1345
+ backend.eval_op(expected)
1346
+
1347
+ actual = result.read_array(0, 0, 4, 2)
1348
+ assert (expected == actual).all()
1349
+
1350
+ def test_floor_module() -> None:
1351
+ data1 = np.array([[1.0, 1.2, 1.5, 1.7], [-1.0, -1.2, -1.5, -1.7]])
1352
+ layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
1353
+ result = RasterLayer.empty_raster_layer_like(layer1)
1354
+
1355
+ comp = LayerOperation.floor(layer1)
1356
+ comp.save(result)
1357
+
1358
+ expected = backend.floor_op(backend.promote(data1))
1359
+ backend.eval_op(expected)
1360
+
1361
+ actual = result.read_array(0, 0, 4, 2)
1362
+ assert (expected == actual).all()
1363
+
1364
+ def test_round_method() -> None:
1365
+ data1 = np.array([[1.0, 1.2, 1.5, 1.7], [-1.0, -1.2, -1.5, -1.7]])
1366
+ layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
1367
+ result = RasterLayer.empty_raster_layer_like(layer1)
1368
+
1369
+ comp = layer1.round()
1370
+ comp.save(result)
1371
+
1372
+ expected = backend.round_op(backend.promote(data1))
1373
+ backend.eval_op(expected)
1374
+
1375
+ actual = result.read_array(0, 0, 4, 2)
1376
+ assert (expected == actual).all()
1377
+
1378
+ def test_round_module() -> None:
1379
+ data1 = np.array([[1.0, 1.2, 1.5, 1.7], [-1.0, -1.2, -1.5, -1.7]])
1380
+ layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
1381
+ result = RasterLayer.empty_raster_layer_like(layer1)
1382
+
1383
+ comp = LayerOperation.round(layer1)
1384
+ comp.save(result)
1385
+
1386
+ expected = backend.round_op(backend.promote(data1))
1387
+ backend.eval_op(expected)
1388
+
1389
+ actual = result.read_array(0, 0, 4, 2)
1390
+ assert (expected == actual).all()
1391
+
1392
+ def test_ceil_method() -> None:
1393
+ data1 = np.array([[1.0, 1.2, 1.5, 1.7], [-1.0, -1.2, -1.5, -1.7]])
1394
+ layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
1395
+ result = RasterLayer.empty_raster_layer_like(layer1)
1396
+
1397
+ comp = layer1.ceil()
1398
+ comp.save(result)
1399
+
1400
+ expected = backend.ceil_op(backend.promote(data1))
1401
+ backend.eval_op(expected)
1402
+
1403
+ actual = result.read_array(0, 0, 4, 2)
1404
+ assert (expected == actual).all()
1405
+
1406
+ def test_ceil_module() -> None:
1407
+ data1 = np.array([[1.0, 1.2, 1.5, 1.7], [-1.0, -1.2, -1.5, -1.7]])
1408
+ layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
1409
+ result = RasterLayer.empty_raster_layer_like(layer1)
1410
+
1411
+ comp = LayerOperation.ceil(layer1)
1412
+ comp.save(result)
1413
+
1414
+ expected = backend.ceil_op(backend.promote(data1))
1415
+ backend.eval_op(expected)
1416
+
1417
+ actual = result.read_array(0, 0, 4, 2)
1418
+ assert (expected == actual).all()
@@ -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()
@@ -35,7 +35,9 @@ class operators(Enum):
35
35
  CONV2D = 30
36
36
  ABS = 31
37
37
  ASTYPE = 32
38
-
38
+ FLOOR = 33
39
+ ROUND = 34
40
+ CEIL = 35
39
41
 
40
42
  class dtype(Enum):
41
43
  Float32 = gdal.GDT_Float32
@@ -45,6 +45,9 @@ allclose = mx.allclose
45
45
  remainder_op = mx.remainder
46
46
  floordiv_op = mx.array.__floordiv__
47
47
  abs_op = mx.abs
48
+ floor_op = mx.floor
49
+ round_op = mx.round
50
+ ceil_op = mx.ceil
48
51
 
49
52
  def sum_op(a):
50
53
  # There are weird issues around how MLX overflows int8, so just promote the data ahead of summing
@@ -211,4 +214,7 @@ operator_map = {
211
214
  op.CONV2D: conv2d_op,
212
215
  op.ABS: mx.abs,
213
216
  op.ASTYPE: astype_op,
217
+ op.FLOOR: mx.floor,
218
+ op.ROUND: mx.round,
219
+ op.CEIL: mx.ceil,
214
220
  }
@@ -48,6 +48,9 @@ allclose = np.allclose
48
48
  remainder_op = np.ndarray.__mod__
49
49
  floordiv_op = np.ndarray.__floordiv__
50
50
  abs_op = np.abs
51
+ floor_op = np.floor
52
+ round_op = np.round
53
+ ceil_op = np.ceil
51
54
 
52
55
  def conv2d_op(data, weights):
53
56
  # torch wants to process dimensions of channels of width of height
@@ -149,4 +152,7 @@ operator_map = {
149
152
  op.CONV2D: conv2d_op,
150
153
  op.ABS: np.abs,
151
154
  op.ASTYPE: astype_op,
155
+ op.FLOOR: np.floor,
156
+ op.ROUND: np.round,
157
+ op.CEIL: np.ceil,
152
158
  }
@@ -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
@@ -120,6 +121,27 @@ class LayerMathMixin:
120
121
  window_op=WindowOperation.NONE,
121
122
  )
122
123
 
124
+ def floor(self):
125
+ return LayerOperation(
126
+ self,
127
+ op.FLOOR,
128
+ window_op=WindowOperation.NONE,
129
+ )
130
+
131
+ def round(self):
132
+ return LayerOperation(
133
+ self,
134
+ op.ROUND,
135
+ window_op=WindowOperation.NONE,
136
+ )
137
+
138
+ def ceil(self):
139
+ return LayerOperation(
140
+ self,
141
+ op.CEIL,
142
+ window_op=WindowOperation.NONE,
143
+ )
144
+
123
145
  def log(self):
124
146
  return LayerOperation(
125
147
  self,
@@ -578,6 +600,20 @@ class LayerOperation(LayerMathMixin):
578
600
  def _parallel_save(self, destination_layer, and_sum=False, callback=None, parallelism=None, band=1):
579
601
  assert (destination_layer is not None) or and_sum
580
602
  computation_window = self.window
603
+
604
+ worker_count = parallelism or multiprocessing.cpu_count()
605
+ work_blocks = len(range(0, computation_window.ysize, self.ystep))
606
+ adjusted_blocks = math.ceil(work_blocks / constants.MINIMUM_CHUNKS_PER_THREAD)
607
+ worker_count = min(adjusted_blocks, worker_count)
608
+
609
+ if worker_count == 1:
610
+ if destination_layer:
611
+ return self.save(destination_layer, and_sum, callback, band)
612
+ elif and_sum:
613
+ return self.sum()
614
+ else:
615
+ assert False
616
+
581
617
  if destination_layer is not None:
582
618
  try:
583
619
  band = destination_layer._dataset.GetRasterBand(band)
@@ -615,10 +651,6 @@ class LayerOperation(LayerMathMixin):
615
651
  with multiprocessing.Manager() as manager:
616
652
  with SharedMemoryManager() as smm:
617
653
 
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
654
  mem_sem_cast = []
623
655
  for i in range(worker_count):
624
656
  shared_buf = smm.SharedMemory(size=np_dtype.itemsize * self.ystep * computation_window.xsize)
@@ -754,3 +786,6 @@ exp2 = LayerOperation.exp2
754
786
  nan_to_num = LayerOperation.nan_to_num
755
787
  isin = LayerOperation.isin
756
788
  abs = LayerOperation.abs # pylint: disable=W0622
789
+ floor = LayerOperation.floor
790
+ round = LayerOperation.round # pylint: disable=W0622
791
+ ceil = LayerOperation.ceil
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yirgacheffe
3
- Version: 1.3.1
3
+ Version: 1.3.3
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
@@ -304,9 +304,11 @@ with RasterLayer.layer_from_file('test1.tif') as layer1:
304
304
  The following math operators common to numpy and other libraries are currently supported:
305
305
 
306
306
  * abs
307
+ * ceil
307
308
  * clip
308
309
  * exp
309
310
  * exp2
311
+ * floor
310
312
  * isin
311
313
  * log
312
314
  * log2
@@ -314,6 +316,7 @@ The following math operators common to numpy and other libraries are currently s
314
316
  * maximum
315
317
  * minimum
316
318
  * nan_to_num
319
+ * round
317
320
 
318
321
  Typically these can be invoked either on a layer as a method:
319
322
 
@@ -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