yirgacheffe 1.10.2__tar.gz → 1.10.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.
- {yirgacheffe-1.10.2/yirgacheffe.egg-info → yirgacheffe-1.10.3}/PKG-INFO +1 -1
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/pyproject.toml +1 -1
- yirgacheffe-1.10.3/tests/test_aggregations.py +68 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/tests/test_constants.py +3 -3
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/tests/test_pickle.py +0 -2
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/yirgacheffe/_backends/mlx.py +17 -7
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/yirgacheffe/_operators/__init__.py +29 -19
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/yirgacheffe/layers/area.py +2 -1
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3/yirgacheffe.egg-info}/PKG-INFO +1 -1
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/yirgacheffe.egg-info/SOURCES.txt +1 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/LICENSE +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/MANIFEST.in +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/README.md +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/setup.cfg +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/tests/test_area.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/tests/test_auto_windowing.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/tests/test_datatypes.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/tests/test_group.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/tests/test_h3layer.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/tests/test_intersection.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/tests/test_multiband.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/tests/test_nodata.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/tests/test_openers.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/tests/test_operator_hashing.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/tests/test_operators.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/tests/test_optimisation.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/tests/test_parallel_operators.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/tests/test_pixel_coord.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/tests/test_projection.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/tests/test_raster.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/tests/test_reduce.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/tests/test_rescaling.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/tests/test_rounding.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/tests/test_save_with_window.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/tests/test_sum_with_window.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/tests/test_uniform_area_layer.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/tests/test_union.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/tests/test_vectors.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/tests/test_window.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/yirgacheffe/__init__.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/yirgacheffe/_backends/__init__.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/yirgacheffe/_backends/enumeration.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/yirgacheffe/_backends/numpy.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/yirgacheffe/_core.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/yirgacheffe/_operators/cse.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/yirgacheffe/constants.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/yirgacheffe/layers/__init__.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/yirgacheffe/layers/base.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/yirgacheffe/layers/constant.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/yirgacheffe/layers/group.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/yirgacheffe/layers/h3layer.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/yirgacheffe/layers/rasters.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/yirgacheffe/layers/rescaled.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/yirgacheffe/layers/vectors.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/yirgacheffe/operators.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/yirgacheffe/py.typed +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/yirgacheffe/rounding.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/yirgacheffe/window.py +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/yirgacheffe.egg-info/dependency_links.txt +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/yirgacheffe.egg-info/entry_points.txt +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/yirgacheffe.egg-info/requires.txt +0 -0
- {yirgacheffe-1.10.2 → yirgacheffe-1.10.3}/yirgacheffe.egg-info/top_level.txt +0 -0
|
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "yirgacheffe"
|
|
9
|
-
version = "1.10.
|
|
9
|
+
version = "1.10.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" }]
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
import yirgacheffe as yg
|
|
7
|
+
from yirgacheffe._backends import backend, BACKEND
|
|
8
|
+
|
|
9
|
+
def test_sum_result_is_scalar() -> None:
|
|
10
|
+
rng = np.random.default_rng(seed=42)
|
|
11
|
+
data = rng.integers(0, 128, size=(1000, 1000))
|
|
12
|
+
with yg.from_array(data, (0, 0), ("epsg:4326", (0.01, -0.01))) as layer:
|
|
13
|
+
total = layer.sum()
|
|
14
|
+
json.dumps(total)
|
|
15
|
+
|
|
16
|
+
@pytest.mark.parametrize("c,dtype,maxval", [
|
|
17
|
+
(int(2), np.int8, 120),
|
|
18
|
+
(int(2), np.uint8, 250),
|
|
19
|
+
(int(2), np.int16, 32000),
|
|
20
|
+
(int(2), np.uint16, 64000),
|
|
21
|
+
(int(2), np.int32, 66000),
|
|
22
|
+
(int(2), np.uint32, 66000),
|
|
23
|
+
])
|
|
24
|
+
@pytest.mark.parametrize("step", [1, 2, 4, 8])
|
|
25
|
+
def test_sums_of_calc_int(monkeypatch, step, c, dtype: type, maxval: int) -> None:
|
|
26
|
+
with monkeypatch.context() as m:
|
|
27
|
+
m.setattr(yg.constants, "YSTEP", step)
|
|
28
|
+
|
|
29
|
+
rng = np.random.default_rng(seed=42)
|
|
30
|
+
|
|
31
|
+
data = rng.integers(0, maxval, size=(1000, 1000), dtype=dtype)
|
|
32
|
+
typed_data = backend.promote(data)
|
|
33
|
+
|
|
34
|
+
assert np.sum(data) == backend.sum_op(typed_data)
|
|
35
|
+
|
|
36
|
+
with yg.from_array(data, (0, 0), ("epsg:4326", (0.01, -0.01))) as layer:
|
|
37
|
+
assert layer.sum() == backend.sum_op(typed_data)
|
|
38
|
+
calc = layer * c
|
|
39
|
+
actual = calc.sum()
|
|
40
|
+
expected = backend.sum_op(typed_data * c)
|
|
41
|
+
assert actual == expected
|
|
42
|
+
|
|
43
|
+
@pytest.mark.parametrize("c,dtype,maxval", [
|
|
44
|
+
(float(2.5), np.int8, 120),
|
|
45
|
+
(float(2.5), np.uint8, 250),
|
|
46
|
+
(float(2.5), np.int16, 32000),
|
|
47
|
+
(float(2.5), np.uint16, 640),
|
|
48
|
+
(float(2.5), np.int32, 660),
|
|
49
|
+
(float(2.5), np.uint32, 660),
|
|
50
|
+
])
|
|
51
|
+
@pytest.mark.parametrize("step", [1, 2, 4, 8])
|
|
52
|
+
def test_sums_of_calc_float_mlx(monkeypatch, step, c, dtype: type, maxval: int) -> None:
|
|
53
|
+
with monkeypatch.context() as m:
|
|
54
|
+
m.setattr(yg.constants, "YSTEP", step)
|
|
55
|
+
|
|
56
|
+
rng = np.random.default_rng(seed=42)
|
|
57
|
+
|
|
58
|
+
data = rng.integers(0, maxval, size=(100, 100), dtype=dtype)
|
|
59
|
+
typed_data = backend.promote(data)
|
|
60
|
+
|
|
61
|
+
with yg.from_array(data, (0, 0), ("epsg:4326", (0.01, -0.01))) as layer:
|
|
62
|
+
assert layer.sum() == backend.sum_op(typed_data)
|
|
63
|
+
calc = layer * c
|
|
64
|
+
actual = calc.sum()
|
|
65
|
+
expected = backend.sum_op(typed_data * c)
|
|
66
|
+
# MLX has a maximum float size of 32 bit for GPU and NUMPY has 64 bit on CPU
|
|
67
|
+
rel = 1e-6 if BACKEND == "MLX" else 1e-10
|
|
68
|
+
assert float(actual) == pytest.approx(float(expected), rel=rel)
|
|
@@ -23,9 +23,9 @@ def test_constant_parallel_save(monkeypatch) -> None:
|
|
|
23
23
|
area = Area(left=-1.0, right=1.0, top=1.0, bottom=-1.0)
|
|
24
24
|
scale = PixelScale(0.1, -0.1)
|
|
25
25
|
with RasterLayer.empty_raster_layer(area, scale, DataType.Float32) as result:
|
|
26
|
-
with
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
with monkeypatch.context() as m:
|
|
27
|
+
m.setattr(yg.constants, "YSTEP", 1)
|
|
28
|
+
with ConstantLayer(42.0) as c:
|
|
29
29
|
c.parallel_save(result)
|
|
30
30
|
|
|
31
31
|
expected = np.full((20, 20), 42.0)
|
|
@@ -134,8 +134,6 @@ def test_pickle_simple_calc(c) -> None:
|
|
|
134
134
|
layer = RasterLayer(gdal_dataset_of_region(area, 0.2, filename=path))
|
|
135
135
|
|
|
136
136
|
calc = layer * c
|
|
137
|
-
assert calc.sum() != 0
|
|
138
|
-
assert calc.sum() == layer.sum() * c
|
|
139
137
|
|
|
140
138
|
p = pickle.dumps(calc)
|
|
141
139
|
restore = pickle.loads(p)
|
|
@@ -14,7 +14,7 @@ float_t = mx.float32
|
|
|
14
14
|
|
|
15
15
|
promote = mx.array
|
|
16
16
|
demote_array = np.asarray
|
|
17
|
-
demote_scalar =
|
|
17
|
+
demote_scalar = lambda a: a.item()
|
|
18
18
|
|
|
19
19
|
eval_op = mx.eval
|
|
20
20
|
|
|
@@ -54,15 +54,25 @@ round_op = mx.round
|
|
|
54
54
|
ceil_op = mx.ceil
|
|
55
55
|
|
|
56
56
|
def sum_op(a):
|
|
57
|
-
#
|
|
57
|
+
# By default the type promotion rules for sum in MLX are not the same as with Numpy. E.g.,
|
|
58
|
+
#
|
|
59
|
+
# >>> mx.array(np.array([1, 2, 3], dtype=np.uint8)).sum()
|
|
60
|
+
# array(6, dtype=uint32)
|
|
61
|
+
# >>> np.array([1, 2, 3], dtype=np.uint8).sum()
|
|
62
|
+
# np.uint64(6)
|
|
63
|
+
#
|
|
64
|
+
# This has problems for Yirgacheffe as it means we get different answers depending on which
|
|
65
|
+
# backend is active. MLX is more conservative, which also can cause issues with Geospatial
|
|
66
|
+
# datasets being large and causing overflows. Thus we force promotion here to match what
|
|
67
|
+
# numpy does, even if this means things run a little slower.
|
|
58
68
|
match a.dtype:
|
|
59
|
-
case mx.int8:
|
|
60
|
-
res = mx.sum(a.astype(mx.
|
|
61
|
-
case mx.uint8:
|
|
62
|
-
res = mx.sum(a.astype(mx.
|
|
69
|
+
case mx.int8 | mx.int16 | mx.int32:
|
|
70
|
+
res = mx.sum(a.astype(mx.int64))
|
|
71
|
+
case mx.uint8 | mx.uint16 | mx.uint32:
|
|
72
|
+
res = mx.sum(a.astype(mx.uint64))
|
|
63
73
|
case _:
|
|
64
74
|
res = mx.sum(a)
|
|
65
|
-
return
|
|
75
|
+
return res
|
|
66
76
|
|
|
67
77
|
def _is_float(x):
|
|
68
78
|
if isinstance(x, float):
|
|
@@ -774,11 +774,11 @@ class LayerOperation(LayerMathMixin):
|
|
|
774
774
|
return result
|
|
775
775
|
|
|
776
776
|
def sum(self) -> float:
|
|
777
|
-
#
|
|
778
|
-
#
|
|
779
|
-
#
|
|
780
|
-
|
|
781
|
-
|
|
777
|
+
# Numpy and MLX have different behaviours that make guessing
|
|
778
|
+
# the type of the accumulator tricky, so we go by the type of the
|
|
779
|
+
# first chunk returned
|
|
780
|
+
res = None
|
|
781
|
+
|
|
782
782
|
computation_window = self.window
|
|
783
783
|
projection = self.map_projection
|
|
784
784
|
if projection is None:
|
|
@@ -799,8 +799,9 @@ class LayerOperation(LayerMathMixin):
|
|
|
799
799
|
step,
|
|
800
800
|
computation_window
|
|
801
801
|
)
|
|
802
|
-
|
|
803
|
-
|
|
802
|
+
chunk_sum = backend.sum_op(chunk)
|
|
803
|
+
res = chunk_sum if res is None else res + chunk_sum
|
|
804
|
+
return backend.demote_scalar(res) if res is not None else 0
|
|
804
805
|
|
|
805
806
|
def min(self) -> float:
|
|
806
807
|
res = float('inf')
|
|
@@ -894,7 +895,7 @@ class LayerOperation(LayerMathMixin):
|
|
|
894
895
|
f"{(destination_window.xsize, destination_window.ysize)} vs "
|
|
895
896
|
f"{(computation_window.xsize, computation_window.ysize)}"))
|
|
896
897
|
|
|
897
|
-
total =
|
|
898
|
+
total = None
|
|
898
899
|
|
|
899
900
|
cse_cache = CSECacheTable(self, computation_window)
|
|
900
901
|
|
|
@@ -916,11 +917,12 @@ class LayerOperation(LayerMathMixin):
|
|
|
916
917
|
yoffset + destination_window.yoff,
|
|
917
918
|
)
|
|
918
919
|
if and_sum:
|
|
919
|
-
|
|
920
|
+
chunk_sum = backend.sum_op(chunk)
|
|
921
|
+
total = chunk_sum if total is None else total + chunk_sum
|
|
920
922
|
if callback:
|
|
921
923
|
callback(1.0)
|
|
922
924
|
|
|
923
|
-
return total if and_sum else None
|
|
925
|
+
return backend.demote_scalar(total) if and_sum else None
|
|
924
926
|
|
|
925
927
|
def _parallel_worker(
|
|
926
928
|
self,
|
|
@@ -1030,7 +1032,7 @@ class LayerOperation(LayerMathMixin):
|
|
|
1030
1032
|
|
|
1031
1033
|
if destination_layer is not None:
|
|
1032
1034
|
try:
|
|
1033
|
-
|
|
1035
|
+
gdal_band = destination_layer._dataset.GetRasterBand(band)
|
|
1034
1036
|
except AttributeError as exc:
|
|
1035
1037
|
raise ValueError("Layer must be a raster backed layer") from exc
|
|
1036
1038
|
|
|
@@ -1052,16 +1054,23 @@ class LayerOperation(LayerMathMixin):
|
|
|
1052
1054
|
gdal.GDT_UInt32: np.dtype('uint32'),
|
|
1053
1055
|
gdal.GDT_UInt64: np.dtype('uint64'),
|
|
1054
1056
|
}
|
|
1055
|
-
np_dtype = np_type_map[
|
|
1057
|
+
np_dtype = np_type_map[gdal_band.DataType]
|
|
1056
1058
|
else:
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
+
# the aggregation path
|
|
1060
|
+
gdal_band = None
|
|
1061
|
+
match self.datatype:
|
|
1062
|
+
case DataType.Int8 | DataType.Int16 | DataType.Int32 | DataType.Int64:
|
|
1063
|
+
np_dtype = np.dtype('int64')
|
|
1064
|
+
case DataType.UInt8 | DataType.UInt16 | DataType.UInt32 | DataType.UInt64:
|
|
1065
|
+
np_dtype = np.dtype('uint64')
|
|
1066
|
+
case _:
|
|
1067
|
+
np_dtype = np.dtype('float64')
|
|
1059
1068
|
|
|
1060
1069
|
# The parallel save will cause a fork on linux, so we need to
|
|
1061
1070
|
# remove all SWIG references
|
|
1062
1071
|
self._park()
|
|
1063
1072
|
|
|
1064
|
-
total =
|
|
1073
|
+
total = None
|
|
1065
1074
|
|
|
1066
1075
|
with ExitStack() as stack:
|
|
1067
1076
|
# If we get this far, then we're going to do the multiprocessing path. In general we've had
|
|
@@ -1125,14 +1134,15 @@ class LayerOperation(LayerMathMixin):
|
|
|
1125
1134
|
continue
|
|
1126
1135
|
index, yoffset, step = res
|
|
1127
1136
|
_, sem, arr = mem_sem_cast[index]
|
|
1128
|
-
if
|
|
1129
|
-
|
|
1137
|
+
if gdal_band:
|
|
1138
|
+
gdal_band.WriteArray(
|
|
1130
1139
|
arr[0:step],
|
|
1131
1140
|
destination_window.xoff,
|
|
1132
1141
|
yoffset + destination_window.yoff,
|
|
1133
1142
|
)
|
|
1134
1143
|
if and_sum:
|
|
1135
|
-
|
|
1144
|
+
chunk_sum = np.sum(np.array(arr[0:step]).astype(np.float64))
|
|
1145
|
+
total = chunk_sum if total is None else total + chunk_sum
|
|
1136
1146
|
sem.release()
|
|
1137
1147
|
retired_blocks += 1
|
|
1138
1148
|
if callback:
|
|
@@ -1150,7 +1160,7 @@ class LayerOperation(LayerMathMixin):
|
|
|
1150
1160
|
processes.remove(candidate)
|
|
1151
1161
|
time.sleep(0.01)
|
|
1152
1162
|
|
|
1153
|
-
return total if and_sum else None
|
|
1163
|
+
return backend.demote_scalar(total) if and_sum else None
|
|
1154
1164
|
|
|
1155
1165
|
def parallel_save(
|
|
1156
1166
|
self,
|
|
@@ -9,6 +9,7 @@ from osgeo import gdal
|
|
|
9
9
|
|
|
10
10
|
from ..window import Area, Window
|
|
11
11
|
from .rasters import RasterLayer
|
|
12
|
+
from .._backends import backend
|
|
12
13
|
|
|
13
14
|
class UniformAreaLayer(RasterLayer):
|
|
14
15
|
"""If you have a pixel area map where all the row entries are identical, then you
|
|
@@ -98,4 +99,4 @@ class UniformAreaLayer(RasterLayer):
|
|
|
98
99
|
if ysize <= 0:
|
|
99
100
|
raise ValueError("Request dimensions must be positive and non-zero")
|
|
100
101
|
offset = window.yoff + yoffset
|
|
101
|
-
return self.databand[offset:offset + ysize]
|
|
102
|
+
return backend.promote(self.databand[offset:offset + ysize])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|