eoml 0.9.0__py3-none-any.whl

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.
Files changed (47) hide show
  1. eoml/__init__.py +74 -0
  2. eoml/automation/__init__.py +7 -0
  3. eoml/automation/configuration.py +105 -0
  4. eoml/automation/dag.py +233 -0
  5. eoml/automation/experience.py +618 -0
  6. eoml/automation/tasks.py +825 -0
  7. eoml/bin/__init__.py +6 -0
  8. eoml/bin/clean_checkpoint.py +146 -0
  9. eoml/bin/land_cover_mapping_toml.py +435 -0
  10. eoml/bin/mosaic_images.py +137 -0
  11. eoml/data/__init__.py +7 -0
  12. eoml/data/basic_geo_data.py +214 -0
  13. eoml/data/dataset_utils.py +98 -0
  14. eoml/data/persistence/__init__.py +7 -0
  15. eoml/data/persistence/generic.py +253 -0
  16. eoml/data/persistence/lmdb.py +379 -0
  17. eoml/data/persistence/serializer.py +82 -0
  18. eoml/raster/__init__.py +7 -0
  19. eoml/raster/band.py +141 -0
  20. eoml/raster/dataset/__init__.py +6 -0
  21. eoml/raster/dataset/extractor.py +604 -0
  22. eoml/raster/raster_reader.py +602 -0
  23. eoml/raster/raster_utils.py +116 -0
  24. eoml/torch/__init__.py +7 -0
  25. eoml/torch/cnn/__init__.py +7 -0
  26. eoml/torch/cnn/augmentation.py +150 -0
  27. eoml/torch/cnn/dataset_evaluator.py +68 -0
  28. eoml/torch/cnn/db_dataset.py +605 -0
  29. eoml/torch/cnn/map_dataset.py +579 -0
  30. eoml/torch/cnn/map_dataset_const_mem.py +135 -0
  31. eoml/torch/cnn/outputs_transformer.py +130 -0
  32. eoml/torch/cnn/torch_utils.py +404 -0
  33. eoml/torch/cnn/training_dataset.py +241 -0
  34. eoml/torch/cnn/windows_dataset.py +120 -0
  35. eoml/torch/dataset/__init__.py +6 -0
  36. eoml/torch/dataset/shade_dataset_tester.py +46 -0
  37. eoml/torch/dataset/shade_tree_dataset_creators.py +537 -0
  38. eoml/torch/model_low_use.py +507 -0
  39. eoml/torch/models.py +282 -0
  40. eoml/torch/resnet.py +437 -0
  41. eoml/torch/sample_statistic.py +260 -0
  42. eoml/torch/trainer.py +782 -0
  43. eoml/torch/trainer_v2.py +253 -0
  44. eoml-0.9.0.dist-info/METADATA +93 -0
  45. eoml-0.9.0.dist-info/RECORD +47 -0
  46. eoml-0.9.0.dist-info/WHEEL +4 -0
  47. eoml-0.9.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,116 @@
1
+ import json
2
+
3
+ import numpy as np
4
+ import rasterio
5
+ from rasterio.transform import TransformMethodsMixin
6
+ from rasterio.windows import WindowMethodsMixin
7
+
8
+
9
+ class RasterInfo(WindowMethodsMixin, TransformMethodsMixin):
10
+
11
+ def __init__(self, transform, height, width, crs, bounds):
12
+ self.transform = transform
13
+ self.height = height
14
+ self.width = width
15
+ self.crs = crs
16
+ self.bounds = bounds
17
+
18
+ @classmethod
19
+ def from_file(cls, path):
20
+ with rasterio.open(path) as src:
21
+ return cls(src.transform, src.height, src.width, src.crs, src.bounds)
22
+
23
+ def read_gdal_stats(path):
24
+ with open(path) as file:
25
+ # returns JSON object as VN
26
+
27
+ # a dictionary
28
+ data = json.load(file)
29
+
30
+ bands = data["bands"]
31
+ stats = np.zeros((len(bands),2))
32
+
33
+
34
+ for b in data["bands"]:
35
+ stats[b["band"]-1]= np.array([b["mean"], b["stdDev"]])
36
+ return stats
37
+
38
+
39
+ def normalize_sigma(data, means, std_devs, n, truncate=False, transform_no_data=None):
40
+ """
41
+ Normalize in place, the values between mean +- n*sigma are compressed between 0 and 1
42
+ :param data: to normalize in place
43
+ :param means: of the original data
44
+ :param std_devs: of the original data
45
+ :param n: number of sigma to map betweren 0 and 1
46
+ :param truncate: weather to truncat value smaller or bigger than 0 or 1 to 0 or 1
47
+ :return: The array changed in place
48
+ """
49
+ for b in range(len(data)):
50
+ data[b] = (1 + (data[b] - means[b]) / (n * std_devs[b])) / 2
51
+
52
+ if transform_no_data is not None:
53
+ np.nan_to_num(data, copy=False, nan=transform_no_data, posinf=None, neginf=None)
54
+
55
+ if truncate:
56
+ bigger = data > 1
57
+ data[bigger] = 1
58
+ smaller = data < 0
59
+ data[smaller] = 0
60
+
61
+
62
+
63
+ class NaNToNumber:
64
+ def __init__(self, number):
65
+ """
66
+ """
67
+ self.number = number
68
+ def __call__(self, data):
69
+ np.nan_to_num(data, copy=False, nan=self.number, posinf=None, neginf=None)
70
+ return data
71
+
72
+ class SigmaNormalizer:
73
+ def __init__(self, means, std_devs, n, truncate=False, transform_no_data=None):
74
+ """
75
+ Normalize in place, the values between mean +- n*sigma are compressed between 0 and 1
76
+ Object version of function. Usefull for multi threading usinf the spawn methode
77
+ :param means: of the original data
78
+ :param std_devs: of the original data
79
+ :param n: number of sigma to map betweren 0 and 1
80
+ :param truncate: weather to truncat value smaller or bigger than 0 or 1 to 0 or 1
81
+ :return: The array changed in place
82
+ """
83
+ self.means = means
84
+ self.std_devs = std_devs
85
+ self.n = n
86
+ self.truncate = truncate
87
+ self.transform_no_data = transform_no_data
88
+
89
+
90
+ def __call__(self, data):
91
+ normalize_sigma(data, self.means, self.std_devs, self.n, self.truncate, self.transform_no_data)
92
+
93
+
94
+ class CastSigmaNormalizer:
95
+ def __init__(self, means, std_devs, n, truncate=False, transform_no_data=None, dtype=None):
96
+ """
97
+ Normalize in place, the values between mean +- n*sigma are compressed between 0 and 1
98
+ Object version of function. Usefull for multi threading usinf the spawn methode
99
+ :param means: of the original data
100
+ :param std_devs: of the original data
101
+ :param n: number of sigma to map betweren 0 and 1
102
+ :param truncate: weather to truncat value smaller or bigger than 0 or 1 to 0 or 1
103
+ :return: The array changed in place
104
+ """
105
+ self.means = means
106
+ self.std_devs = std_devs
107
+ self.n = n
108
+ self.truncate = truncate
109
+ self.transform_no_data = transform_no_data
110
+ self.dtype = dtype
111
+
112
+
113
+ def __call__(self, data):
114
+ if self.dtype is not None:
115
+ data.astype(self.dtype, copy=False)
116
+ normalize_sigma(data, self.means, self.std_devs, self.n, self.truncate, self.transform_no_data)
eoml/torch/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ """
2
+ PyTorch Module for EOML.
3
+
4
+ This module provides PyTorch-based machine learning utilities for Earth
5
+ observation applications, including neural network architectures, training
6
+ utilities, dataset loaders, and model evaluation tools.
7
+ """
@@ -0,0 +1,7 @@
1
+ """
2
+ CNN Submodule for PyTorch.
3
+
4
+ This submodule provides convolutional neural network utilities including
5
+ dataset loaders, augmentation strategies, training datasets, and mapping
6
+ utilities for applying trained models to large raster datasets.
7
+ """
@@ -0,0 +1,150 @@
1
+ """Data augmentation transformations for PyTorch image datasets.
2
+
3
+ This module provides transformation classes and functions for augmenting image data
4
+ during training. Includes rotation, flipping, cropping, scaling, shearing, and
5
+ blurring operations using torchvision's functional API.
6
+ """
7
+
8
+ import logging
9
+ import math
10
+ import random
11
+
12
+ import torchvision.transforms.functional as TF
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ def rotate_crop_flip_transform(img, size=13, angle=180, vflip=False):
18
+ """Apply rotation, crop, and optional flip to an image.
19
+
20
+ Args:
21
+ img: Input image tensor.
22
+ size: Size to crop to after rotation. Defaults to 13.
23
+ angle: Rotation angle in degrees. Defaults to 180.
24
+ vflip: Whether to apply vertical flip. Defaults to False.
25
+
26
+ Returns:
27
+ Transformed image tensor.
28
+ """
29
+ img = TF.rotate(img, angle=angle)
30
+ img = TF.center_crop(img, size)
31
+ if vflip:
32
+ img = TF.vflip(img)
33
+
34
+ return img
35
+
36
+
37
+ # Due to memory bug in dataset, the dataset return numpy type that we cast to int Todo need to be fixed in dataset
38
+ def rotate_flip_transform(img, angle=180, vflip=False):
39
+ """Apply rotation and optional flip to an image.
40
+
41
+ Note:
42
+ Due to dataset memory bug, parameters are cast from numpy types.
43
+
44
+ Todo:
45
+ Fix dataset to avoid numpy type issue.
46
+
47
+ Args:
48
+ img: Input image tensor.
49
+ angle: Rotation angle in degrees. Defaults to 180.
50
+ vflip: Whether to apply vertical flip. Defaults to False.
51
+
52
+ Returns:
53
+ Transformed image tensor.
54
+ """
55
+ img = TF.rotate(img, angle=int(angle))
56
+ if bool(vflip):
57
+ img = TF.vflip(img)
58
+
59
+ return img
60
+
61
+
62
+ class CropTransform:
63
+ """Crop images to a specified size.
64
+
65
+ Useful for working with databases containing samples larger than needed,
66
+ allowing on-the-fly cropping to the desired size.
67
+
68
+ Attributes:
69
+ width: Target width/size for square crop.
70
+ """
71
+ def __init__(self, width):
72
+ self.width = width
73
+
74
+
75
+ def __call__(self, inputs):
76
+
77
+ inputs = TF.center_crop(inputs, self.width)
78
+ return inputs
79
+
80
+ def __repr__(self):
81
+ return f'CropTransform(width: {self.width})'
82
+
83
+
84
+ class RandomTransform:
85
+ """Randomly apply augmentation transformations to images.
86
+
87
+ Applies random combinations of rotation, flip, scale, shear, and blur,
88
+ then crops to the specified size. Useful for data augmentation during training.
89
+
90
+ Attributes:
91
+ width: Target width for final crop.
92
+ p_rot: Probability of applying rotation. Defaults to 0.50.
93
+ p_flip: Probability of applying vertical flip. Defaults to 0.50.
94
+ p_scale: Probability of applying scaling. Defaults to 0.2.
95
+ p_shear: Probability of applying shear. Defaults to 0.2.
96
+ p_blur: Probability of applying Gaussian blur. Defaults to 0.2.
97
+ """
98
+
99
+ def __init__(self, width, p_rot=0.50, p_flip=0.50, p_scale=0.2, p_shear= 0.2, p_blur= 0.2):
100
+ self.width = width
101
+ self.p_rot = p_rot
102
+ self.p_flip = p_flip
103
+ self.p_scale = p_scale
104
+ self.p_shear = p_shear
105
+ self.p_blur = p_blur
106
+
107
+ def __repr__(self):
108
+ return f'RandomTransform(width: {self.width}, ' \
109
+ f'p_rot: {self.p_rot} , ' \
110
+ f'p_flip: {self.p_flip} , ' \
111
+ f'p_scale: {self.p_scale} , ' \
112
+ f'p_shear: {self.p_shear} , ' \
113
+ f'p_blur: {self.p_blur})' \
114
+
115
+ # size need to be multiplied by to avoid dark pixel
116
+ # 1.4145
117
+ def __call__(self, inputs):
118
+ c, i_h, i_w = inputs.shape
119
+
120
+ rotation_angle = random.randint(-180, 180) if self.p_rot > random.uniform(0, 1) else 0
121
+ shear = random.randint(-15, 15) if self.p_shear > random.uniform(0, 1) else 0
122
+ scale = random.randint(2, 4) if self.p_scale > random.uniform(0, 1) else 1
123
+ flip = True if self.p_flip > random.uniform(0, 1) else False
124
+
125
+ blur = True if self.p_blur > random.uniform(0, 1) else False
126
+
127
+ # distance to the border to avoid black border du to rotation
128
+ safe_width = math.ceil(1.4143* self.width)
129
+
130
+ if i_h < safe_width:
131
+ logger.warning("Transformation: the width of the input is not big enough and may be truncated.")
132
+
133
+
134
+ #input = TF.rotate(input, rotation_angle, interpolation=TF.InterpolationMode.BILINEAR)
135
+ # affine
136
+ inputs = TF.affine(inputs, angle=rotation_angle, translate=[0,0], scale=scale, shear=shear,
137
+ interpolation=TF.InterpolationMode.NEAREST)
138
+
139
+ inputs = TF.center_crop(inputs, self.width)
140
+
141
+ # flip
142
+ if flip:
143
+ inputs = TF.vflip(inputs)
144
+
145
+ # gaussian
146
+ if blur:
147
+ inputs = TF.gaussian_blur(inputs, kernel_size=[3, 3], sigma=[0.45, 0.45])
148
+
149
+ return inputs
150
+
@@ -0,0 +1,68 @@
1
+ """Model evaluation utilities for PyTorch datasets.
2
+
3
+ This module provides classes for evaluating trained neural network models on
4
+ datasets, collecting predictions and reference values for analysis.
5
+ """
6
+
7
+ import numpy as np
8
+ import torch
9
+ from tqdm import tqdm
10
+
11
+
12
+ class DatasetEvaluator:
13
+ """Evaluate a neural network model on a dataset.
14
+
15
+ Runs inference on a complete dataset and collects predictions along with
16
+ reference labels for evaluation metrics.
17
+
18
+ Todo:
19
+ Implement aggressive/optimized version.
20
+
21
+ Attributes:
22
+ model: PyTorch model or path to JIT-compiled model.
23
+ """
24
+ def __init__(self, model):
25
+
26
+ if isinstance(model, str):
27
+ self.model = torch.jit.load(model)
28
+ else:
29
+ self.model = model
30
+
31
+ def evaluate(self, loader, device="cpu"):
32
+ """Evaluate model on dataset and collect predictions.
33
+
34
+ Args:
35
+ loader: PyTorch DataLoader providing test samples.
36
+ device: Device to run inference on ('cpu' or 'cuda'). Defaults to "cpu".
37
+
38
+ Returns:
39
+ tuple: (reference_labels, predictions) as numpy arrays.
40
+ """
41
+
42
+ # Make sure gradient tracking is off, and do a pass over the data
43
+ self.model.train(False)
44
+
45
+ results=[]
46
+ reference=[]
47
+
48
+ with torch.inference_mode():
49
+
50
+ with tqdm(total=len(loader),desc="Batch") as pbar:
51
+ for i, data in enumerate(loader):
52
+ # Every data instance is an input + label pair
53
+
54
+ inputs, labels = data
55
+ if device is not None:
56
+ if isinstance(inputs, (list, tuple)):
57
+ inputs = map(lambda x: x.to(device, non_blocking=True), inputs)
58
+ else:
59
+ inputs = inputs.to(device, non_blocking=True)
60
+
61
+ # Make predictions for this batch
62
+ outputs = self.model(*inputs)
63
+
64
+ results.extend(outputs.cpu())
65
+ reference.extend(labels)
66
+
67
+ return np.array(reference), np.array(results)
68
+