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.
- eoml/__init__.py +74 -0
- eoml/automation/__init__.py +7 -0
- eoml/automation/configuration.py +105 -0
- eoml/automation/dag.py +233 -0
- eoml/automation/experience.py +618 -0
- eoml/automation/tasks.py +825 -0
- eoml/bin/__init__.py +6 -0
- eoml/bin/clean_checkpoint.py +146 -0
- eoml/bin/land_cover_mapping_toml.py +435 -0
- eoml/bin/mosaic_images.py +137 -0
- eoml/data/__init__.py +7 -0
- eoml/data/basic_geo_data.py +214 -0
- eoml/data/dataset_utils.py +98 -0
- eoml/data/persistence/__init__.py +7 -0
- eoml/data/persistence/generic.py +253 -0
- eoml/data/persistence/lmdb.py +379 -0
- eoml/data/persistence/serializer.py +82 -0
- eoml/raster/__init__.py +7 -0
- eoml/raster/band.py +141 -0
- eoml/raster/dataset/__init__.py +6 -0
- eoml/raster/dataset/extractor.py +604 -0
- eoml/raster/raster_reader.py +602 -0
- eoml/raster/raster_utils.py +116 -0
- eoml/torch/__init__.py +7 -0
- eoml/torch/cnn/__init__.py +7 -0
- eoml/torch/cnn/augmentation.py +150 -0
- eoml/torch/cnn/dataset_evaluator.py +68 -0
- eoml/torch/cnn/db_dataset.py +605 -0
- eoml/torch/cnn/map_dataset.py +579 -0
- eoml/torch/cnn/map_dataset_const_mem.py +135 -0
- eoml/torch/cnn/outputs_transformer.py +130 -0
- eoml/torch/cnn/torch_utils.py +404 -0
- eoml/torch/cnn/training_dataset.py +241 -0
- eoml/torch/cnn/windows_dataset.py +120 -0
- eoml/torch/dataset/__init__.py +6 -0
- eoml/torch/dataset/shade_dataset_tester.py +46 -0
- eoml/torch/dataset/shade_tree_dataset_creators.py +537 -0
- eoml/torch/model_low_use.py +507 -0
- eoml/torch/models.py +282 -0
- eoml/torch/resnet.py +437 -0
- eoml/torch/sample_statistic.py +260 -0
- eoml/torch/trainer.py +782 -0
- eoml/torch/trainer_v2.py +253 -0
- eoml-0.9.0.dist-info/METADATA +93 -0
- eoml-0.9.0.dist-info/RECORD +47 -0
- eoml-0.9.0.dist-info/WHEEL +4 -0
- eoml-0.9.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"""PyTorch datasets for training from raster images.
|
|
2
|
+
|
|
3
|
+
Provides dataset classes that extract training samples from raster images by sliding
|
|
4
|
+
windows. Includes support for data augmentation, temporal features (year), and
|
|
5
|
+
multi-band output processing.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import itertools
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
import torch
|
|
12
|
+
from libterra_gis.raster_utils import RasterImage
|
|
13
|
+
from eoml.torch.cnn.augmentation import rotate_flip_transform
|
|
14
|
+
from torch.utils.data import Dataset
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BasicTrainingDataset(Dataset):
|
|
18
|
+
"""Training dataset that extracts windows from raster images.
|
|
19
|
+
|
|
20
|
+
Loads raster images, extracts overlapping windows using stride, and stores all
|
|
21
|
+
samples in memory. Applies output function to determine sample validity.
|
|
22
|
+
Currently used for shade generation tasks.
|
|
23
|
+
|
|
24
|
+
Attributes:
|
|
25
|
+
f_transform: Data augmentation function.
|
|
26
|
+
transform_param (np.ndarray, optional): Per-sample augmentation parameters.
|
|
27
|
+
paths (list): Paths to input raster files.
|
|
28
|
+
size (int): Window size for extraction.
|
|
29
|
+
func: Function to process output windows and determine validity.
|
|
30
|
+
stride (int): Stride for window extraction.
|
|
31
|
+
n_out (int): Number of output bands.
|
|
32
|
+
samples (list): List of (input, output) sample tuples.
|
|
33
|
+
"""
|
|
34
|
+
def __init__(self, paths, size, stride, n_out, func, f_transform=None, transform_param=None):
|
|
35
|
+
"""Initialize BasicTrainingDataset.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
paths (list): List of paths to raster image files.
|
|
39
|
+
size (int): Size of windows to extract.
|
|
40
|
+
stride (int): Stride between consecutive windows.
|
|
41
|
+
n_out (int): Number of output bands (taken from last bands of raster).
|
|
42
|
+
func: Function applied to output windows. Returns None for invalid samples.
|
|
43
|
+
f_transform (callable, optional): Data augmentation function. Defaults to None.
|
|
44
|
+
transform_param (list, optional): Per-sample transform parameters. Defaults to None.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
self.f_transform = f_transform
|
|
48
|
+
|
|
49
|
+
if transform_param is not None:
|
|
50
|
+
self.transform_param = np.array(transform_param)
|
|
51
|
+
else:
|
|
52
|
+
self.transform_param = transform_param
|
|
53
|
+
|
|
54
|
+
self.paths = paths
|
|
55
|
+
self.size = size
|
|
56
|
+
self.func = func
|
|
57
|
+
self.stride = stride
|
|
58
|
+
self.n_out = n_out
|
|
59
|
+
|
|
60
|
+
self.samples = self.extract()
|
|
61
|
+
|
|
62
|
+
def extract(self):
|
|
63
|
+
|
|
64
|
+
samples = []
|
|
65
|
+
|
|
66
|
+
for path in self.paths:
|
|
67
|
+
data = RasterImage.from_file(path).data
|
|
68
|
+
|
|
69
|
+
bands, height, width = data.shape
|
|
70
|
+
|
|
71
|
+
# print(data.shape)
|
|
72
|
+
# nh = math.floor((height-self.size)/self.stride+1)
|
|
73
|
+
# nw = math.floor((width-self.size)/self.stride+1)
|
|
74
|
+
|
|
75
|
+
for i in range(0, height - self.size + 1, self.stride):
|
|
76
|
+
for j in range(0, width - self.size + 1, self.stride):
|
|
77
|
+
source_w = data[:-self.n_out, i:i + self.size, j:j + self.size]
|
|
78
|
+
output_w = data[-self.n_out:, i:i + self.size, j:j + self.size]
|
|
79
|
+
output_w = self.func(output_w)
|
|
80
|
+
|
|
81
|
+
if output_w is not None:
|
|
82
|
+
samples.append((source_w, output_w))
|
|
83
|
+
|
|
84
|
+
return samples
|
|
85
|
+
|
|
86
|
+
def __len__(self):
|
|
87
|
+
return len(self.samples)
|
|
88
|
+
|
|
89
|
+
def __getitem__(self, idx):
|
|
90
|
+
|
|
91
|
+
if hasattr(idx, '__iter__'):
|
|
92
|
+
return self._get_items(idx)
|
|
93
|
+
|
|
94
|
+
if isinstance(idx, int):
|
|
95
|
+
return self._get_one_item(idx)
|
|
96
|
+
|
|
97
|
+
if isinstance(idx, slice):
|
|
98
|
+
# Get the start, stop, and step from the slice
|
|
99
|
+
return self._get_items(range(idx.start, idx.stop, idx.step))
|
|
100
|
+
|
|
101
|
+
def _get_items(self, iterable):
|
|
102
|
+
|
|
103
|
+
labels = []
|
|
104
|
+
|
|
105
|
+
batch = len(iterable)
|
|
106
|
+
iterable = iterable.__iter__()
|
|
107
|
+
try:
|
|
108
|
+
(one_input,), target = self._get_one_item(next(iterable))
|
|
109
|
+
except StopIteration:
|
|
110
|
+
return []
|
|
111
|
+
|
|
112
|
+
# compute the shape
|
|
113
|
+
shape_out = (batch,) + one_input.shape
|
|
114
|
+
datas = torch.empty(shape_out, dtype=torch.long)
|
|
115
|
+
|
|
116
|
+
if isinstance(target, int):
|
|
117
|
+
shape_out = batch
|
|
118
|
+
labels = torch.empty(shape_out, dtype=torch.long)
|
|
119
|
+
else:
|
|
120
|
+
shape_out = (batch,) + target.shape
|
|
121
|
+
labels = torch.empty(shape_out, dtype=torch.float32)
|
|
122
|
+
|
|
123
|
+
datas[0] = one_input
|
|
124
|
+
labels[0] = target
|
|
125
|
+
|
|
126
|
+
for i, key in enumerate(iterable, 1):
|
|
127
|
+
# the nn take on parameter so we unpack the 1 tuples and make it for the batch
|
|
128
|
+
(datas[i],), labels[i] = self._get_one_item(key)
|
|
129
|
+
# the nn take on parameter so we make a 1 element tuple
|
|
130
|
+
return (datas,), labels
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _get_one_item(self, idx):
|
|
134
|
+
inputs, output = self.samples[idx]
|
|
135
|
+
|
|
136
|
+
inputs = torch.from_numpy(inputs)
|
|
137
|
+
output = torch.from_numpy(output)
|
|
138
|
+
|
|
139
|
+
if self.f_transform is not None:
|
|
140
|
+
if self.transform_param is not None:
|
|
141
|
+
inputs = self.f_transform(inputs, *self.transform_param[idx])
|
|
142
|
+
else:
|
|
143
|
+
inputs = self.f_transform(inputs)
|
|
144
|
+
|
|
145
|
+
return (inputs,), output
|
|
146
|
+
|
|
147
|
+
def add_rotation_flip(self):
|
|
148
|
+
self.f_transform = rotate_flip_transform
|
|
149
|
+
self.samples, self.transform_param = self._augmentation_setup(self.samples, [0, 90, 180, 270], [False, True])
|
|
150
|
+
|
|
151
|
+
def _augmentation_setup(self, samples, angles=None, flip=None):
|
|
152
|
+
|
|
153
|
+
if angles is None:
|
|
154
|
+
angles = [0, 90, 180, -90]
|
|
155
|
+
|
|
156
|
+
if flip is None:
|
|
157
|
+
flip = [False, True]
|
|
158
|
+
|
|
159
|
+
t_param_list = list(itertools.product(angles, flip))
|
|
160
|
+
|
|
161
|
+
t_params = []
|
|
162
|
+
samples_split = []
|
|
163
|
+
for k in samples:
|
|
164
|
+
for p in t_param_list:
|
|
165
|
+
t_params.append(p)
|
|
166
|
+
samples_split.append(k)
|
|
167
|
+
|
|
168
|
+
return samples_split, t_params
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class BasicYearTrainingDataset(BasicTrainingDataset):
|
|
173
|
+
"""Training dataset with year as additional input feature.
|
|
174
|
+
|
|
175
|
+
Extends BasicTrainingDataset to include temporal information (year) as an additional
|
|
176
|
+
input alongside image patches. Year values are normalized for neural network input.
|
|
177
|
+
|
|
178
|
+
Attributes:
|
|
179
|
+
years (list): Year values corresponding to each raster image.
|
|
180
|
+
year_normalisation (int): Value used to normalize years (year/year_normalisation).
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
def __init__(self, paths, years, size, stride, n_out, func, f_transform=None, transform_param=None, year_normalisation=2050):
|
|
184
|
+
"""Initialize BasicYearTrainingDataset.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
paths (list): List of paths to raster image files.
|
|
188
|
+
years (list): Year value for each raster file.
|
|
189
|
+
size (int): Size of windows to extract.
|
|
190
|
+
stride (int): Stride between consecutive windows.
|
|
191
|
+
n_out (int): Number of output bands.
|
|
192
|
+
func: Function applied to output windows. Returns None for invalid samples.
|
|
193
|
+
f_transform (callable, optional): Data augmentation function. Defaults to None.
|
|
194
|
+
transform_param (list, optional): Per-sample transform parameters. Defaults to None.
|
|
195
|
+
year_normalisation (int, optional): Normalization divisor for year values.
|
|
196
|
+
Defaults to 2050.
|
|
197
|
+
"""
|
|
198
|
+
self.years = years
|
|
199
|
+
self.year_normalisation = year_normalisation
|
|
200
|
+
super().__init__(paths, size, stride, n_out, func, f_transform, transform_param)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def extract(self):
|
|
204
|
+
|
|
205
|
+
samples = []
|
|
206
|
+
|
|
207
|
+
for path, year in zip(self.paths, self.years):
|
|
208
|
+
data = RasterImage.from_file(path).data
|
|
209
|
+
|
|
210
|
+
bands, height, width = data.shape
|
|
211
|
+
|
|
212
|
+
# print(data.shape)
|
|
213
|
+
# nh = math.floor((height-self.size)/self.stride+1)
|
|
214
|
+
# nw = math.floor((width-self.size)/self.stride+1)
|
|
215
|
+
|
|
216
|
+
for i in range(0, height - self.size + 1, self.stride):
|
|
217
|
+
for j in range(0, width - self.size + 1, self.stride):
|
|
218
|
+
source_w = data[:-self.n_out, i:i + self.size, j:j + self.size]
|
|
219
|
+
output_w = data[-self.n_out:, i:i + self.size, j:j + self.size]
|
|
220
|
+
output_w = self.func(output_w)
|
|
221
|
+
|
|
222
|
+
if output_w is not None:
|
|
223
|
+
samples.append((source_w, np.array([year/self.year_normalisation], dtype= np.float32), output_w))
|
|
224
|
+
|
|
225
|
+
return samples
|
|
226
|
+
|
|
227
|
+
def _get_one_item(self, idx):
|
|
228
|
+
inputs, year, output = self.samples[idx]
|
|
229
|
+
|
|
230
|
+
inputs = torch.from_numpy(inputs)
|
|
231
|
+
output = torch.from_numpy(output)
|
|
232
|
+
|
|
233
|
+
if self.f_transform is not None:
|
|
234
|
+
if self.transform_param is not None:
|
|
235
|
+
inputs = self.f_transform(inputs, *self.transform_param[idx])
|
|
236
|
+
else:
|
|
237
|
+
inputs = self.f_transform(inputs)
|
|
238
|
+
|
|
239
|
+
return inputs, year, output
|
|
240
|
+
|
|
241
|
+
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from bisect import bisect_right
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
import torch
|
|
6
|
+
from eoml.raster.raster_reader import AbstractRasterReader
|
|
7
|
+
from eoml.torch.cnn.torch_utils import conv_out_size
|
|
8
|
+
from rasterio.coords import BoundingBox
|
|
9
|
+
from rasterio.windows import Window
|
|
10
|
+
from torch.utils.data import Dataset
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class WindowsTrainingDataset(Dataset):
|
|
14
|
+
"""
|
|
15
|
+
Basic implementation of training dataset, receive a list of images, cut windows through them and store everything
|
|
16
|
+
in memories
|
|
17
|
+
|
|
18
|
+
Todo need to be finished
|
|
19
|
+
"""
|
|
20
|
+
def __init__(self,
|
|
21
|
+
input_raster_reader: AbstractRasterReader,
|
|
22
|
+
target_raster_reader: List[AbstractRasterReader],
|
|
23
|
+
size,
|
|
24
|
+
stride,
|
|
25
|
+
padding,
|
|
26
|
+
transform_output):
|
|
27
|
+
|
|
28
|
+
self.input_raster_reader = input_raster_reader
|
|
29
|
+
|
|
30
|
+
self.target_raster_readers = target_raster_reader
|
|
31
|
+
|
|
32
|
+
self.size = size
|
|
33
|
+
self.stride = stride
|
|
34
|
+
self.padding = padding
|
|
35
|
+
|
|
36
|
+
self.transform_output = transform_output
|
|
37
|
+
|
|
38
|
+
self.conv_sum = []
|
|
39
|
+
|
|
40
|
+
self.radius = math.floor(size)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def compute_stats(self):
|
|
44
|
+
|
|
45
|
+
sum = 0
|
|
46
|
+
conv_sum = []
|
|
47
|
+
for raster in self.target_raster_readers:
|
|
48
|
+
width = conv_out_size(raster.ref_raster_info().width, self.size, self.stride, self.padding)
|
|
49
|
+
height = conv_out_size(raster.ref_raster_info().height, self.size, self.stride, self.padding)
|
|
50
|
+
|
|
51
|
+
sum+= width*height
|
|
52
|
+
self.conv_sum.append(sum)
|
|
53
|
+
|
|
54
|
+
def _find_image_index(self, conv_b):
|
|
55
|
+
"""index of the image
|
|
56
|
+
bisect right return the index (on the right in case of equality to insert value to keep the list ordered)
|
|
57
|
+
"""
|
|
58
|
+
index_image = bisect_right(self.conv_sum, conv_b) - 1
|
|
59
|
+
index_in_image = conv_b - self.conv_sum[index_image]
|
|
60
|
+
|
|
61
|
+
con_per_row = conv_out_size(self.target_raster_readers[index_image].ref_raster_info().width, self.size, self.stride, self.padding)
|
|
62
|
+
|
|
63
|
+
col, row = divmod(index_in_image, con_per_row)
|
|
64
|
+
|
|
65
|
+
return index_image, col, row
|
|
66
|
+
def generate_input(self, index_image, col, row):
|
|
67
|
+
# this way would interpol the input.
|
|
68
|
+
out_reader = self.target_raster_readers[index_image]
|
|
69
|
+
target_window = Window(col - self.radius, row - self.radius, self.size, self.size)
|
|
70
|
+
|
|
71
|
+
bounding_box = out_reader.ref_raster_info().window_bounds(target_window)
|
|
72
|
+
|
|
73
|
+
out_reader.read_windows_around_coordinate(target_window)
|
|
74
|
+
|
|
75
|
+
with self.input_raster_reader:
|
|
76
|
+
input = self.input_raster_reader.read_bound(bounding_box)
|
|
77
|
+
|
|
78
|
+
with out_reader:
|
|
79
|
+
output = out_reader.read_windows_around_coordinate(target_window)
|
|
80
|
+
|
|
81
|
+
return input, output
|
|
82
|
+
|
|
83
|
+
def __len__(self):
|
|
84
|
+
return len(self.conv_sum[-1])
|
|
85
|
+
|
|
86
|
+
def __getitem__(self, idx):
|
|
87
|
+
|
|
88
|
+
if hasattr(idx, '__iter__'):
|
|
89
|
+
return self._get_items(idx)
|
|
90
|
+
|
|
91
|
+
if isinstance(idx, int):
|
|
92
|
+
return self._get_one_item(idx)
|
|
93
|
+
|
|
94
|
+
if isinstance(idx, slice):
|
|
95
|
+
# Get the start, stop, and step from the slice
|
|
96
|
+
return self._get_items(range(idx.start, idx.stop, idx.step))
|
|
97
|
+
|
|
98
|
+
def _get_items(self, iterable):
|
|
99
|
+
datas = []
|
|
100
|
+
labels = []
|
|
101
|
+
for key in iterable:
|
|
102
|
+
data, label = self._get_one_item(key)
|
|
103
|
+
datas.append(data)
|
|
104
|
+
labels.append(label)
|
|
105
|
+
return [datas, labels]
|
|
106
|
+
|
|
107
|
+
def _get_one_item(self, idx):
|
|
108
|
+
index_image, col, row = self._find_image_index(idx)
|
|
109
|
+
|
|
110
|
+
inputs, output = self.generate_input(self, index_image, col, row)
|
|
111
|
+
|
|
112
|
+
inputs = torch.from_numpy(inputs)
|
|
113
|
+
output = torch.from_numpy(output)
|
|
114
|
+
|
|
115
|
+
if self.transform_output is not None:
|
|
116
|
+
output = self.transform_output(inputs)
|
|
117
|
+
|
|
118
|
+
return inputs, output
|
|
119
|
+
|
|
120
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Evaluation utilities for shade detection datasets.
|
|
2
|
+
|
|
3
|
+
This module provides classes for testing and evaluating shade detection models
|
|
4
|
+
on validation datasets.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from eoml.torch.cnn.dataset_evaluator import DatasetEvaluator
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ShadeDatasetTestet:
|
|
11
|
+
"""Test and evaluate shade detection models on datasets.
|
|
12
|
+
|
|
13
|
+
Note: Class name appears to be a typo (Testet instead of Tester).
|
|
14
|
+
|
|
15
|
+
Attributes:
|
|
16
|
+
rasters (list): List of raster file paths.
|
|
17
|
+
datasets (list): List of dataset objects.
|
|
18
|
+
metric_list (list): List of metrics to compute.
|
|
19
|
+
model_path (str): Path to trained model file.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, rasters, datasets, metric_list, model_path):
|
|
23
|
+
"""Initialize ShadeDatasetTestet.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
rasters (list): List of raster file paths for evaluation.
|
|
27
|
+
datasets (list): List of dataset objects.
|
|
28
|
+
metric_list (list): Metrics to compute during evaluation.
|
|
29
|
+
model_path (str): Path to trained model weights.
|
|
30
|
+
"""
|
|
31
|
+
self.rasters=rasters
|
|
32
|
+
self.datasets=datasets
|
|
33
|
+
self.metric_list=metric_list
|
|
34
|
+
|
|
35
|
+
self.model_path=model_path
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def compute_metric(self):
|
|
39
|
+
"""Compute evaluation metrics on datasets.
|
|
40
|
+
|
|
41
|
+
Note: Implementation appears incomplete - references undefined variables.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
tuple: Reference and predicted values (when properly implemented).
|
|
45
|
+
"""
|
|
46
|
+
ref, pred = DatasetEvaluator(model_path).evaluate(train_dataloader, device=device)
|