yirgacheffe 1.2.1__tar.gz → 1.3.0__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.2.1/yirgacheffe.egg-info → yirgacheffe-1.3.0}/PKG-INFO +21 -1
  2. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/README.md +20 -0
  3. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/pyproject.toml +1 -1
  4. yirgacheffe-1.3.0/tests/test_datatypes.py +67 -0
  5. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/tests/test_operators.py +16 -16
  6. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/tests/test_raster.py +3 -2
  7. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/tests/test_vectors.py +20 -19
  8. yirgacheffe-1.3.0/yirgacheffe/backends/enumeration.py +58 -0
  9. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/yirgacheffe/backends/mlx.py +57 -1
  10. yirgacheffe-1.3.0/yirgacheffe/backends/numpy.py +152 -0
  11. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/yirgacheffe/layers/__init__.py +0 -1
  12. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/yirgacheffe/layers/base.py +2 -2
  13. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/yirgacheffe/layers/constant.py +3 -4
  14. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/yirgacheffe/layers/group.py +3 -2
  15. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/yirgacheffe/layers/h3layer.py +3 -3
  16. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/yirgacheffe/layers/rasters.py +16 -7
  17. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/yirgacheffe/layers/rescaled.py +2 -1
  18. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/yirgacheffe/layers/vectors.py +26 -9
  19. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/yirgacheffe/operators.py +10 -1
  20. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0/yirgacheffe.egg-info}/PKG-INFO +21 -1
  21. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/yirgacheffe.egg-info/SOURCES.txt +1 -0
  22. yirgacheffe-1.2.1/yirgacheffe/backends/enumeration.py +0 -34
  23. yirgacheffe-1.2.1/yirgacheffe/backends/numpy.py +0 -112
  24. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/LICENSE +0 -0
  25. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/MANIFEST.in +0 -0
  26. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/setup.cfg +0 -0
  27. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/tests/test_area.py +0 -0
  28. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/tests/test_auto_windowing.py +0 -0
  29. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/tests/test_base.py +0 -0
  30. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/tests/test_group.py +0 -0
  31. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/tests/test_h3layer.py +0 -0
  32. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/tests/test_intersection.py +0 -0
  33. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/tests/test_multiband.py +0 -0
  34. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/tests/test_optimisation.py +0 -0
  35. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/tests/test_parallel_operators.py +0 -0
  36. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/tests/test_pickle.py +0 -0
  37. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/tests/test_rescaling.py +0 -0
  38. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/tests/test_rounding.py +0 -0
  39. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/tests/test_save_with_window.py +0 -0
  40. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/tests/test_sum_with_window.py +0 -0
  41. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/tests/test_uniform_area_layer.py +0 -0
  42. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/tests/test_union.py +0 -0
  43. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/tests/test_window.py +0 -0
  44. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/yirgacheffe/__init__.py +0 -0
  45. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/yirgacheffe/backends/__init__.py +0 -0
  46. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/yirgacheffe/constants.py +0 -0
  47. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/yirgacheffe/h3layer.py +0 -0
  48. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/yirgacheffe/layers/area.py +0 -0
  49. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/yirgacheffe/rounding.py +0 -0
  50. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/yirgacheffe/window.py +0 -0
  51. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/yirgacheffe.egg-info/dependency_links.txt +0 -0
  52. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/yirgacheffe.egg-info/entry_points.txt +0 -0
  53. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/yirgacheffe.egg-info/requires.txt +0 -0
  54. {yirgacheffe-1.2.1 → yirgacheffe-1.3.0}/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.2.1
3
+ Version: 1.3.0
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
@@ -35,6 +35,14 @@ Example common use-cases:
35
35
  * Do the raster layers get big and take up large amounts of memory? Yirgacheffe will let you do simple numerical operations with layers directly and then worry about the memory management behind the scenes for you.
36
36
 
37
37
 
38
+ ## Installation
39
+
40
+ Yirgacheffe is available via pypi, so can be installed with pip for example:
41
+
42
+ ```SystemShell
43
+ $ pip install yirgacheffe
44
+ ```
45
+
38
46
  ## Basic usage
39
47
 
40
48
  They main unit of data in Yirgacheffe is a "layer", which wraps either a raster dataset or polygon data, and then you can do work on layers without having to worry (unless you choose to) about how they align - Yirgacheffe will work out all the details around overlapping
@@ -339,6 +347,18 @@ with RasterLayer.layer_from_file('original.tif') as layer1:
339
347
  calc.save(result)
340
348
  ```
341
349
 
350
+ ### Type conversion
351
+
352
+ Similar to numpy and other Python numerical libraries, Yirgacheffe will automatically deal with simple type conversion where possible, however sometimes explicit conversion is either necessary or desired. Similar to numpy, there is an `astype` operator that lets you set the conversion:
353
+
354
+ ```python
355
+ from yirgacheffe.operations import DataType
356
+
357
+
358
+ with RasterLayer.layer_from_file('float_data.tif') as float_layer:
359
+ int_layer = float_layer.astype(DataType.Int32)
360
+ ```
361
+
342
362
  ### Apply
343
363
 
344
364
  You can specify a function that takes either data from one layer or from two layers, and returns the processed data. There's two version of this: one that lets you specify a numpy function that'll be applied to the layer data as an array, or one that is more shader like that lets you do pixel wise processing.
@@ -11,6 +11,14 @@ Example common use-cases:
11
11
  * Do the raster layers get big and take up large amounts of memory? Yirgacheffe will let you do simple numerical operations with layers directly and then worry about the memory management behind the scenes for you.
12
12
 
13
13
 
14
+ ## Installation
15
+
16
+ Yirgacheffe is available via pypi, so can be installed with pip for example:
17
+
18
+ ```SystemShell
19
+ $ pip install yirgacheffe
20
+ ```
21
+
14
22
  ## Basic usage
15
23
 
16
24
  They main unit of data in Yirgacheffe is a "layer", which wraps either a raster dataset or polygon data, and then you can do work on layers without having to worry (unless you choose to) about how they align - Yirgacheffe will work out all the details around overlapping
@@ -315,6 +323,18 @@ with RasterLayer.layer_from_file('original.tif') as layer1:
315
323
  calc.save(result)
316
324
  ```
317
325
 
326
+ ### Type conversion
327
+
328
+ Similar to numpy and other Python numerical libraries, Yirgacheffe will automatically deal with simple type conversion where possible, however sometimes explicit conversion is either necessary or desired. Similar to numpy, there is an `astype` operator that lets you set the conversion:
329
+
330
+ ```python
331
+ from yirgacheffe.operations import DataType
332
+
333
+
334
+ with RasterLayer.layer_from_file('float_data.tif') as float_layer:
335
+ int_layer = float_layer.astype(DataType.Int32)
336
+ ```
337
+
318
338
  ### Apply
319
339
 
320
340
  You can specify a function that takes either data from one layer or from two layers, and returns the processed data. There's two version of this: one that lets you specify a numpy function that'll be applied to the layer data as an array, or one that is more shader like that lets you do pixel wise processing.
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
 
7
7
  [project]
8
8
  name = "yirgacheffe"
9
- version = "1.2.1"
9
+ version = "1.3.0"
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,67 @@
1
+
2
+ import numpy as np
3
+ import pytest
4
+ from osgeo import gdal
5
+
6
+ from yirgacheffe.operators import DataType
7
+ from yirgacheffe.backends import backend, BACKEND
8
+ from yirgacheffe.layers import RasterLayer
9
+
10
+ from helpers import gdal_dataset_with_data
11
+
12
+ @pytest.mark.parametrize("gtype", [
13
+ gdal.GDT_Int8,
14
+ gdal.GDT_Int16,
15
+ gdal.GDT_Int32,
16
+ gdal.GDT_Int64,
17
+ gdal.GDT_Byte,
18
+ gdal.GDT_UInt16,
19
+ gdal.GDT_UInt32,
20
+ gdal.GDT_UInt64,
21
+ gdal.GDT_Float32,
22
+ ])
23
+ def test_round_trip(gtype) -> None:
24
+ ytype = DataType.of_gdal(gtype)
25
+ backend_type = backend.dtype_to_backed(ytype)
26
+ assert backend.backend_to_dtype(backend_type) == ytype
27
+
28
+ @pytest.mark.parametrize("ytype", [
29
+ DataType.Int8,
30
+ DataType.Int16,
31
+ DataType.Int32,
32
+ DataType.Int64,
33
+ DataType.UInt8,
34
+ DataType.UInt16,
35
+ DataType.UInt32,
36
+ DataType.UInt64,
37
+ DataType.Float32,
38
+ DataType.Float64,
39
+ ])
40
+ def test_round_trip_from_gdal(ytype) -> None:
41
+ gtype = ytype.to_gdal()
42
+ assert DataType.of_gdal(gtype) == ytype
43
+
44
+ def test_round_trip_float64() -> None:
45
+ backend_type = backend.dtype_to_backed(DataType.Float64)
46
+ ytype = backend.backend_to_dtype(backend_type)
47
+ print(BACKEND, "sad")
48
+ match BACKEND:
49
+ case "NUMPY":
50
+ assert ytype == DataType.Float64
51
+ case "MLX":
52
+ assert ytype == DataType.Float32
53
+
54
+ def test_float_to_int() -> None:
55
+ data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.5, 6.5, 7.5, 8.5]])
56
+
57
+ layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
58
+ # Note the float 32 here is to rule out that writing the result to the
59
+ # new dataset was what caused the truncation
60
+ result = RasterLayer.empty_raster_layer_like(layer1, datatype=DataType.Float32)
61
+
62
+ comp = layer1.astype(DataType.UInt8)
63
+ comp.save(result)
64
+
65
+ expected = backend.promote(np.array([[1, 2, 3, 4], [5, 6, 7, 8]]))
66
+ actual = backend.demote_array(result.read_array(0, 0, 4, 2))
67
+ assert (expected == actual).all()
@@ -10,7 +10,7 @@ from osgeo import gdal
10
10
  from helpers import gdal_dataset_with_data
11
11
  import yirgacheffe
12
12
  from yirgacheffe.layers import RasterLayer, ConstantLayer
13
- from yirgacheffe.operators import LayerOperation
13
+ from yirgacheffe.operators import LayerOperation, DataType
14
14
  from yirgacheffe.backends import backend
15
15
 
16
16
  def test_add_byte_layers() -> None:
@@ -21,8 +21,8 @@ def test_add_byte_layers() -> None:
21
21
  layer2 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2))
22
22
  result = RasterLayer.empty_raster_layer_like(layer1)
23
23
 
24
- assert layer1.datatype == gdal.GDT_Byte
25
- assert layer2.datatype == gdal.GDT_Byte
24
+ assert layer1.datatype == DataType.Byte
25
+ assert layer2.datatype == DataType.Byte
26
26
 
27
27
  comp = layer1 + layer2
28
28
  comp.save(result)
@@ -64,8 +64,8 @@ def test_add_byte_layers_with_callback(skip, expected_steps) -> None:
64
64
  layer2 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2))
65
65
  result = RasterLayer.empty_raster_layer_like(layer1)
66
66
 
67
- assert layer1.datatype == gdal.GDT_Byte
68
- assert layer2.datatype == gdal.GDT_Byte
67
+ assert layer1.datatype == DataType.Byte
68
+ assert layer2.datatype == DataType.Byte
69
69
 
70
70
  callback_possitions = []
71
71
 
@@ -632,7 +632,7 @@ def test_save_and_sum_float32(monkeypatch) -> None:
632
632
 
633
633
  data1 = np.array(data, dtype=np.float32)
634
634
  layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
635
- assert layer1.datatype == gdal.GDT_Float32
635
+ assert layer1.datatype == DataType.Float32
636
636
 
637
637
  # Sum forces things to float64
638
638
  expected = np.sum(data1.astype(np.float64))
@@ -660,7 +660,7 @@ def test_parallel_save_and_sum_float32(monkeypatch) -> None:
660
660
  dataset1 = gdal_dataset_with_data((0.0, 0.0), 0.02, data1, filename=path1)
661
661
  dataset1.Close()
662
662
  layer1 = RasterLayer.layer_from_file(path1)
663
- assert layer1.datatype == gdal.GDT_Float32
663
+ assert layer1.datatype == DataType.Float32
664
664
 
665
665
  # Sum forces things to float64
666
666
  expected = np.sum(data1.astype(np.float64))
@@ -684,7 +684,7 @@ def test_sum_float32(monkeypatch) -> None:
684
684
 
685
685
  data1 = np.array(data, dtype=np.float32)
686
686
  layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
687
- assert layer1.datatype == gdal.GDT_Float32
687
+ assert layer1.datatype == DataType.Float32
688
688
 
689
689
  # Sum forces things to float64
690
690
  expected = np.sum(data1.astype(np.float64))
@@ -703,8 +703,8 @@ def test_and_byte_layers() -> None:
703
703
  layer2 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2))
704
704
  result = RasterLayer.empty_raster_layer_like(layer1)
705
705
 
706
- assert layer1.datatype == gdal.GDT_Byte
707
- assert layer2.datatype == gdal.GDT_Byte
706
+ assert layer1.datatype == DataType.Byte
707
+ assert layer2.datatype == DataType.Byte
708
708
 
709
709
  comp = layer1 & layer2
710
710
  comp.save(result)
@@ -722,8 +722,8 @@ def test_or_byte_layers() -> None:
722
722
  layer2 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2))
723
723
  result = RasterLayer.empty_raster_layer_like(layer1)
724
724
 
725
- assert layer1.datatype == gdal.GDT_Byte
726
- assert layer2.datatype == gdal.GDT_Byte
725
+ assert layer1.datatype == DataType.Byte
726
+ assert layer2.datatype == DataType.Byte
727
727
 
728
728
  comp = layer1 | layer2
729
729
  comp.save(result)
@@ -741,8 +741,8 @@ def test_and_int_layers() -> None:
741
741
  layer2 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2))
742
742
  result = RasterLayer.empty_raster_layer_like(layer1)
743
743
 
744
- assert layer1.datatype == gdal.GDT_Int16
745
- assert layer2.datatype == gdal.GDT_Int16
744
+ assert layer1.datatype == DataType.Int16
745
+ assert layer2.datatype == DataType.Int16
746
746
 
747
747
  comp = layer1 & layer2
748
748
  comp.save(result)
@@ -760,8 +760,8 @@ def test_or_int_layers() -> None:
760
760
  layer2 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2))
761
761
  result = RasterLayer.empty_raster_layer_like(layer1)
762
762
 
763
- assert layer1.datatype == gdal.GDT_Int16
764
- assert layer2.datatype == gdal.GDT_Int16
763
+ assert layer1.datatype == DataType.Int16
764
+ assert layer2.datatype == DataType.Int16
765
765
 
766
766
  comp = layer1 | layer2
767
767
  comp.save(result)
@@ -9,6 +9,7 @@ from helpers import gdal_dataset_of_region, gdal_multiband_dataset_with_data
9
9
  from yirgacheffe.window import Area, PixelScale, Window
10
10
  from yirgacheffe.layers import RasterLayer, InvalidRasterBand
11
11
  from yirgacheffe.rounding import round_up_pixels
12
+ from yirgacheffe.operators import DataType
12
13
 
13
14
 
14
15
  # There is a lot of "del" in this file, due to a combination of gdal having no way
@@ -128,12 +129,12 @@ def test_empty_layer_from_raster_with_new_smaller_area():
128
129
 
129
130
  def test_empty_layer_from_raster_new_datatype():
130
131
  source = RasterLayer(gdal_dataset_of_region(Area(-10, 10, 10, -10), 0.02))
131
- assert source.datatype == gdal.GDT_Byte
132
+ assert source.datatype == DataType.Byte
132
133
  empty = RasterLayer.empty_raster_layer_like(source, datatype=gdal.GDT_Float64)
133
134
  assert empty.pixel_scale == source.pixel_scale
134
135
  assert empty.projection == source.projection
135
136
  assert empty.window == source.window
136
- assert empty.datatype == gdal.GDT_Float64
137
+ assert empty.datatype == DataType.Float64
137
138
 
138
139
  def test_empty_layer_from_raster_with_window():
139
140
  source = RasterLayer(gdal_dataset_of_region(Area(-10, 10, 10, -10), 0.02))
@@ -8,6 +8,7 @@ from helpers import make_vectors_with_mutlile_ids, make_vectors_with_id, make_ve
8
8
  from yirgacheffe import WGS_84_PROJECTION
9
9
  from yirgacheffe.layers import RasterLayer, RasteredVectorLayer, VectorLayer, VectorRangeLayer, DynamicVectorRangeLayer
10
10
  from yirgacheffe.window import Area, PixelScale, Window
11
+ from yirgacheffe.operators import DataType
11
12
 
12
13
  def test_basic_vector_layer_no_filter_match() -> None:
13
14
  with tempfile.TemporaryDirectory() as tempdir:
@@ -139,7 +140,7 @@ def test_vector_layers_with_default_burn_value(klass) -> None:
139
140
  assert layer.area == Area(-10.0, 10.0, 10.0, -10.0)
140
141
  assert layer.geo_transform == (-10.0, 1.0, 0.0, 10.0, 0.0, -1.0)
141
142
  assert layer.window == Window(0, 0, 20, 20)
142
- assert layer.datatype == gdal.GDT_Byte
143
+ assert layer.datatype == DataType.Byte
143
144
 
144
145
  # The default burn value is 1, so check that if we sum the area
145
146
  # we get half and half
@@ -256,13 +257,13 @@ def test_vector_layers_with_field_value(klass) -> None:
256
257
  @pytest.mark.parametrize(
257
258
  "value,expected",
258
259
  [
259
- (1, gdal.GDT_Byte),
260
- (42, gdal.GDT_Byte),
261
- (-1, gdal.GDT_Int16),
262
- (1024, gdal.GDT_UInt16),
263
- (1024*1024, gdal.GDT_UInt32),
264
- (-1024*1024, gdal.GDT_Int32),
265
- (1.0, gdal.GDT_Float64),
260
+ (1, DataType.Byte),
261
+ (42, DataType.Byte),
262
+ (-1, DataType.Int16),
263
+ (1024, DataType.UInt16),
264
+ (1024*1024, DataType.UInt32),
265
+ (-1024*1024, DataType.Int32),
266
+ (1.0, DataType.Float64),
266
267
  ]
267
268
  )
268
269
  def test_vector_layers_with_guessed_type_burn_value(value, expected) -> None:
@@ -294,14 +295,14 @@ def test_vector_layers_with_guessed_type_burn_value(value, expected) -> None:
294
295
  @pytest.mark.parametrize(
295
296
  "value,datatype",
296
297
  [
297
- (1, gdal.GDT_Byte),
298
- (42, gdal.GDT_Byte),
299
- (1, gdal.GDT_Int16),
300
- (42, gdal.GDT_Int16),
301
- (1024, gdal.GDT_Int16),
302
- (1.0, gdal.GDT_Float32),
303
- (0.5, gdal.GDT_Float32),
304
- (1.0, gdal.GDT_Float64),
298
+ (1, DataType.Byte),
299
+ (42, DataType.Byte),
300
+ (1, DataType.Int16),
301
+ (42, DataType.Int16),
302
+ (1024, DataType.Int16),
303
+ (1.0, DataType.Float32),
304
+ (0.5, DataType.Float32),
305
+ (1.0, DataType.Float64),
305
306
  ]
306
307
  )
307
308
  def test_vector_layers_with_different_type_burn_value(value, datatype) -> None:
@@ -334,8 +335,8 @@ def test_vector_layers_with_different_type_burn_value(value, datatype) -> None:
334
335
  @pytest.mark.parametrize(
335
336
  "value,expected",
336
337
  [
337
- (1, gdal.GDT_Int64),
338
- (1.0, gdal.GDT_Float64),
338
+ (1, DataType.Int64),
339
+ (1.0, DataType.Float64),
339
340
  ]
340
341
  )
341
342
  def test_vector_layers_with_guess_field_type_burn_value(value, expected) -> None:
@@ -448,7 +449,7 @@ def test_vector_layers_with_empty_features(klass) -> None:
448
449
  assert layer.area == Area(-10.0, 10.0, 10.0, -10.0)
449
450
  assert layer.geo_transform == (-10.0, 1.0, 0.0, 10.0, 0.0, -1.0)
450
451
  assert layer.window == Window(0, 0, 20, 20)
451
- assert layer.datatype == gdal.GDT_Byte
452
+ assert layer.datatype == DataType.Byte
452
453
 
453
454
  # The default burn value is 1, so check that if we sum the area
454
455
  # we get half and half
@@ -0,0 +1,58 @@
1
+ from enum import Enum
2
+
3
+ from osgeo import gdal
4
+
5
+ class operators(Enum):
6
+ ADD = 1
7
+ SUB = 2
8
+ MUL = 3
9
+ TRUEDIV = 4
10
+ POW = 5
11
+ EQ = 6
12
+ NE = 7
13
+ LT = 8
14
+ LE = 9
15
+ GT = 10
16
+ GE = 11
17
+ AND = 12
18
+ OR = 13
19
+ LOG = 14
20
+ LOG2 = 15
21
+ LOG10 = 16
22
+ EXP = 17
23
+ EXP2 = 18
24
+ CLIP = 19
25
+ WHERE = 20
26
+ MIN = 21
27
+ MAX = 22
28
+ SUM = 23
29
+ MINIMUM = 24
30
+ MAXIMUM = 25
31
+ NAN_TO_NUM = 26
32
+ ISIN = 27
33
+ REMAINDER = 28
34
+ FLOORDIV = 29
35
+ CONV2D = 30
36
+ ABS = 31
37
+ ASTYPE = 32
38
+
39
+
40
+ class dtype(Enum):
41
+ Float32 = gdal.GDT_Float32
42
+ Float64 = gdal.GDT_Float64
43
+ Byte = gdal.GDT_Byte
44
+ Int8 = gdal.GDT_Int8
45
+ Int16 = gdal.GDT_Int16
46
+ Int32 = gdal.GDT_Int32
47
+ Int64 = gdal.GDT_Int64
48
+ UInt8 = gdal.GDT_Byte
49
+ UInt16 = gdal.GDT_UInt16
50
+ UInt32 = gdal.GDT_UInt32
51
+ UInt64 = gdal.GDT_UInt64
52
+
53
+ def to_gdal(self):
54
+ return self.value
55
+
56
+ @classmethod
57
+ def of_gdal(cls, val):
58
+ return cls(val)
@@ -4,6 +4,7 @@ import mlx.core as mx # pylint: disable=E0001,E0611,E0401
4
4
  import mlx.nn
5
5
 
6
6
  from .enumeration import operators as op
7
+ from .enumeration import dtype
7
8
 
8
9
  array_t = mx.array
9
10
  float_t = mx.float32
@@ -121,9 +122,63 @@ def conv2d_op(data, weights):
121
122
  preped_data = mx.array(np.reshape(unshifted_data_shape, conv_data_shape))
122
123
 
123
124
  shifted_res = conv(preped_data)[0]
124
- res = np.reshape(shifted_res, [1] + list(shifted_res.shape)[:-1])
125
+ res = mx.reshape(shifted_res, [1] + list(shifted_res.shape)[:-1])
125
126
  return res[0]
126
127
 
128
+
129
+ def dtype_to_backed(dt):
130
+ match dt:
131
+ case dtype.Float32:
132
+ return mx.float32
133
+ case dtype.Float64:
134
+ return mx.float32
135
+ case dtype.Byte:
136
+ return mx.uint8
137
+ case dtype.Int8:
138
+ return mx.int8
139
+ case dtype.Int16:
140
+ return mx.int16
141
+ case dtype.Int32:
142
+ return mx.int32
143
+ case dtype.Int64:
144
+ return mx.int64
145
+ case dtype.UInt8:
146
+ return mx.uint8
147
+ case dtype.UInt16:
148
+ return mx.uint16
149
+ case dtype.UInt32:
150
+ return mx.uint32
151
+ case dtype.UInt64:
152
+ return mx.uint64
153
+ case _:
154
+ raise ValueError
155
+
156
+ def backend_to_dtype(val):
157
+ match val:
158
+ case mx.float32:
159
+ return dtype.Float32
160
+ case mx.int8:
161
+ return dtype.Int8
162
+ case mx.int16:
163
+ return dtype.Int16
164
+ case mx.int32:
165
+ return dtype.Int32
166
+ case mx.int64:
167
+ return dtype.Int64
168
+ case mx.uint8:
169
+ return dtype.Byte
170
+ case mx.uint16:
171
+ return dtype.UInt16
172
+ case mx.uint32:
173
+ return dtype.UInt32
174
+ case mx.uint64:
175
+ return dtype.UInt64
176
+ case _:
177
+ raise ValueError
178
+
179
+ def astype_op(data, datatype):
180
+ return data.astype(dtype_to_backed(datatype))
181
+
127
182
  operator_map = {
128
183
  op.ADD: mx.array.__add__,
129
184
  op.SUB: mx.array.__sub__,
@@ -155,4 +210,5 @@ operator_map = {
155
210
  op.FLOORDIV: mx.array.__floordiv__,
156
211
  op.CONV2D: conv2d_op,
157
212
  op.ABS: mx.abs,
213
+ op.ASTYPE: astype_op,
158
214
  }
@@ -0,0 +1,152 @@
1
+
2
+ import numpy as np
3
+ import torch
4
+
5
+ from .enumeration import operators as op
6
+ from .enumeration import dtype
7
+
8
+ array_t = np.ndarray
9
+ float_t = np.float64
10
+
11
+ promote = lambda a: a
12
+ demote_array = lambda a: a
13
+ demote_scalar = lambda a: a
14
+ eval_op = lambda a: a
15
+
16
+ add_op = np.ndarray.__add__
17
+ sub_op = np.ndarray.__sub__
18
+ mul_op = np.ndarray.__mul__
19
+ truediv_op = np.ndarray.__truediv__
20
+ pow_op = np.ndarray.__pow__
21
+ eq_op = np.ndarray.__eq__
22
+ ne_op = np.ndarray.__ne__
23
+ lt_op = np.ndarray.__lt__
24
+ le_op = np.ndarray.__le__
25
+ gt_op = np.ndarray.__gt__
26
+ ge_op = np.ndarray.__ge__
27
+ and_op = np.ndarray.__and__
28
+ or_op = np.ndarray.__or__
29
+ nan_to_num = np.nan_to_num
30
+ isin = np.isin
31
+ log = np.log
32
+ log2 = np.log2
33
+ log10 = np.log10
34
+ exp = np.exp
35
+ exp2 = np.exp2
36
+ clip = np.clip
37
+ where = np.where
38
+ min_op = np.min
39
+ max_op = np.max
40
+ maximum = np.maximum
41
+ minimum = np.minimum
42
+ zeros = np.zeros
43
+ pad = np.pad
44
+ sum_op = lambda a: np.sum(a.astype(np.float64))
45
+ isscalar = np.isscalar
46
+ full = np.full
47
+ allclose = np.allclose
48
+ remainder_op = np.ndarray.__mod__
49
+ floordiv_op = np.ndarray.__floordiv__
50
+ abs_op = np.abs
51
+
52
+ def conv2d_op(data, weights):
53
+ # torch wants to process dimensions of channels of width of height
54
+ # Which is why both the data and weights get nested into two arrays here,
55
+ # and then we have to unpack it from that nesting.
56
+
57
+ preped_weights = np.array([[weights]])
58
+ conv = torch.nn.Conv2d(1, 1, weights.shape, bias=False)
59
+ conv.weight = torch.nn.Parameter(torch.from_numpy(preped_weights))
60
+ preped_data = torch.from_numpy(np.array([[data]]))
61
+
62
+ res = conv(preped_data)
63
+ return res.detach().numpy()[0][0]
64
+
65
+ def dtype_to_backed(dt):
66
+ match dt:
67
+ case dtype.Float32:
68
+ return np.float32
69
+ case dtype.Float64:
70
+ return np.float64
71
+ case dtype.Byte:
72
+ return np.uint8
73
+ case dtype.Int8:
74
+ return np.int8
75
+ case dtype.Int16:
76
+ return np.int16
77
+ case dtype.Int32:
78
+ return np.int32
79
+ case dtype.Int64:
80
+ return np.int64
81
+ case dtype.UInt8:
82
+ return np.uint8
83
+ case dtype.UInt16:
84
+ return np.uint16
85
+ case dtype.UInt32:
86
+ return np.uint32
87
+ case dtype.UInt64:
88
+ return np.uint64
89
+ case _:
90
+ raise ValueError
91
+
92
+ def backend_to_dtype(val):
93
+ match val:
94
+ case np.float32:
95
+ return dtype.Float32
96
+ case np.float64:
97
+ return dtype.Float64
98
+ case np.int8:
99
+ return dtype.Int8
100
+ case np.int16:
101
+ return dtype.Int16
102
+ case np.int32:
103
+ return dtype.Int32
104
+ case np.int64:
105
+ return dtype.Int64
106
+ case np.uint8:
107
+ return dtype.Byte
108
+ case np.uint16:
109
+ return dtype.UInt16
110
+ case np.uint32:
111
+ return dtype.UInt32
112
+ case np.uint64:
113
+ return dtype.UInt64
114
+ case _:
115
+ raise ValueError
116
+
117
+ def astype_op(data, datatype):
118
+ return data.astype(dtype_to_backed(datatype))
119
+
120
+ operator_map = {
121
+ op.ADD: np.ndarray.__add__,
122
+ op.SUB: np.ndarray.__sub__,
123
+ op.MUL: np.ndarray.__mul__,
124
+ op.TRUEDIV: np.ndarray.__truediv__,
125
+ op.POW: np.ndarray.__pow__,
126
+ op.EQ: np.ndarray.__eq__,
127
+ op.NE: np.ndarray.__ne__,
128
+ op.LT: np.ndarray.__lt__,
129
+ op.LE: np.ndarray.__le__,
130
+ op.GT: np.ndarray.__gt__,
131
+ op.GE: np.ndarray.__ge__,
132
+ op.AND: np.ndarray.__and__,
133
+ op.OR: np.ndarray.__or__,
134
+ op.LOG: np.log,
135
+ op.LOG2: np.log2,
136
+ op.LOG10: np.log10,
137
+ op.EXP: np.exp,
138
+ op.EXP2: np.exp2,
139
+ op.CLIP: np.clip,
140
+ op.WHERE: np.where,
141
+ op.MIN: np.min,
142
+ op.MAX: np.max,
143
+ op.MINIMUM: np.minimum,
144
+ op.MAXIMUM: np.maximum,
145
+ op.NAN_TO_NUM: np.nan_to_num,
146
+ op.ISIN: np.isin,
147
+ op.REMAINDER: np.ndarray.__mod__,
148
+ op.FLOORDIV: np.ndarray.__floordiv__,
149
+ op.CONV2D: conv2d_op,
150
+ op.ABS: np.abs,
151
+ op.ASTYPE: astype_op,
152
+ }
@@ -13,7 +13,6 @@ try:
13
13
  except ModuleNotFoundError:
14
14
  pass
15
15
 
16
-
17
16
  class Layer(RasterLayer):
18
17
  """A place holder for now, at some point I want to replace Layer with RasterLayer."""
19
18
 
@@ -1,7 +1,7 @@
1
1
 
2
2
  from typing import Any, List, Optional, Tuple
3
3
 
4
- from ..operators import LayerMathMixin
4
+ from ..operators import DataType, LayerMathMixin
5
5
  from ..rounding import almost_equal, are_pixel_scales_equal_enough, round_up_pixels, round_down_pixels
6
6
  from ..window import Area, PixelScale, Window
7
7
 
@@ -35,7 +35,7 @@ class YirgacheffeLayer(LayerMathMixin):
35
35
  self.close()
36
36
 
37
37
  @property
38
- def datatype(self) -> int:
38
+ def datatype(self) -> DataType:
39
39
  raise NotImplementedError("Must be overridden by subclass")
40
40
 
41
41
  @property