geebeam 0.3.2__tar.gz → 0.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.
- {geebeam-0.3.2 → geebeam-0.3.3}/PKG-INFO +32 -29
- {geebeam-0.3.2 → geebeam-0.3.3}/README.md +30 -28
- {geebeam-0.3.2 → geebeam-0.3.3}/_version.py +2 -2
- {geebeam-0.3.2 → geebeam-0.3.3}/pyproject.toml +6 -0
- geebeam-0.3.3/src/geebeam/__init__.py +13 -0
- geebeam-0.3.2/src/geebeam/ee_utils.py → geebeam-0.3.3/src/geebeam/_ee_utils.py +3 -3
- geebeam-0.3.3/src/geebeam/_pipeline.py +334 -0
- geebeam-0.3.2/src/geebeam/tf_utils.py → geebeam-0.3.3/src/geebeam/_tf_utils.py +25 -23
- geebeam-0.3.2/src/geebeam/tfds_writer.py → geebeam-0.3.3/src/geebeam/_tfds_writer.py +47 -73
- geebeam-0.3.2/src/geebeam/tfrecord_writer.py → geebeam-0.3.3/src/geebeam/_tfrecord_writer.py +30 -26
- geebeam-0.3.2/src/geebeam/tiff_writer.py → geebeam-0.3.3/src/geebeam/_tiff_writer.py +24 -36
- geebeam-0.3.2/src/geebeam/transforms.py → geebeam-0.3.3/src/geebeam/_transforms.py +13 -13
- geebeam-0.3.3/src/geebeam/_wds_writer.py +122 -0
- {geebeam-0.3.2 → geebeam-0.3.3}/src/geebeam/climate_indices.py +11 -0
- geebeam-0.3.3/src/geebeam/sampler.py +261 -0
- geebeam-0.3.3/tests/conftest.py +7 -0
- geebeam-0.3.3/tests/test_ee_utils.py +102 -0
- geebeam-0.3.3/tests/test_integration.py +182 -0
- geebeam-0.3.3/tests/test_pipeline.py +94 -0
- geebeam-0.3.3/tests/test_sampler.py +155 -0
- {geebeam-0.3.2 → geebeam-0.3.3}/tests/test_tf_utils.py +2 -2
- geebeam-0.3.3/tests/test_tiff_writer.py +54 -0
- geebeam-0.3.3/tests/test_transforms.py +120 -0
- geebeam-0.3.3/tests/test_wds_writer.py +56 -0
- geebeam-0.3.2/src/geebeam/__init__.py +0 -10
- geebeam-0.3.2/src/geebeam/pipeline.py +0 -200
- geebeam-0.3.2/src/geebeam/sampler.py +0 -97
- geebeam-0.3.2/tests/test_ee_utils.py +0 -46
- geebeam-0.3.2/tests/test_pipeline.py +0 -30
- geebeam-0.3.2/tests/test_sampler.py +0 -48
- geebeam-0.3.2/tests/test_transforms.py +0 -57
- {geebeam-0.3.2 → geebeam-0.3.3}/.gitignore +0 -0
- {geebeam-0.3.2 → geebeam-0.3.3}/LICENSE +0 -0
- {geebeam-0.3.2 → geebeam-0.3.3}/tests/test_climate_indices.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: geebeam
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: Earth Engine + Apache Beam
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -12,6 +12,7 @@ Requires-Dist: earthengine-api>=1.7.10
|
|
|
12
12
|
Requires-Dist: geopandas>=1.0.1
|
|
13
13
|
Requires-Dist: grpcio>=1.62.3
|
|
14
14
|
Requires-Dist: rasterio>=1.3.10
|
|
15
|
+
Requires-Dist: webdataset>=1.0.2
|
|
15
16
|
Provides-Extra: all
|
|
16
17
|
Requires-Dist: importlib-resources>=6.5.2; extra == 'all'
|
|
17
18
|
Requires-Dist: pytest; extra == 'all'
|
|
@@ -75,49 +76,51 @@ PROJECT_ID = google.auth.default()[1]
|
|
|
75
76
|
# Initialize ee client, replace with your GCP project ID
|
|
76
77
|
ee.Initialize(project=PROJECT_ID)
|
|
77
78
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
79
|
+
### Build image for download
|
|
80
|
+
# Load a raw Landsat 5 ImageCollection for a single year.
|
|
81
|
+
ls5_collection = ee.ImageCollection('LANDSAT/LT05/C02/T1').filterDate(
|
|
82
|
+
'2010-01-01', '2010-12-31'
|
|
83
|
+
)
|
|
84
|
+
# Create a (mostly) cloud-free Landsat composite
|
|
85
|
+
ls5_composite = ee.Algorithms.Landsat.simpleComposite(
|
|
86
|
+
ls5_collection,
|
|
87
|
+
asFloat=True,
|
|
88
|
+
cloudScoreRange=5)
|
|
86
89
|
|
|
87
90
|
# Building and triggering the pipeline is done with a single command:
|
|
88
|
-
geebeam.
|
|
89
|
-
image_list = [
|
|
90
|
-
|
|
91
|
-
patch_size=128, # Pixel dimensions in each direction
|
|
92
|
-
scale=500, # Final export resolution in meters
|
|
91
|
+
geebeam.sample_and_run_pipeline(
|
|
92
|
+
image_list = [ls5_composite], # Important: has to be a list of images
|
|
93
|
+
sampling_region=ee.Geometry.Rectangle(-55.0, -12.0, -50.0, -16.0), # In central-west Brazil
|
|
93
94
|
n_sample=10, # Number of tiles to sample
|
|
95
|
+
patch_size=128, # Number of pixels in each direction
|
|
96
|
+
scale=30, # Final export resolution in meters
|
|
97
|
+
crs='EPSG:4326', # CRS for final output
|
|
98
|
+
project=PROJECT_ID, # GCP Project ID
|
|
99
|
+
output_path='./test_data/', # Output path, local or on GCP
|
|
94
100
|
validation_ratio=0.2, # Fraction to select as validation data
|
|
95
|
-
output_path='./test_tf_data/',
|
|
96
|
-
sampling_region=ee.Geometry.Rectangle(-63.0, -9.0, -56.0, -4.0)
|
|
97
101
|
)
|
|
98
102
|
```
|
|
99
103
|
|
|
100
|
-
Now let's add another dataset: MapBiomas
|
|
104
|
+
Now let's add another dataset: MapBiomas land-cover from same year.
|
|
105
|
+
For more info, and legend, see: [MapBiomas Brasil](https://brasil.mapbiomas.org/en/codigos-de-legenda/)
|
|
101
106
|
```python
|
|
102
|
-
# MB Land-use/land-cover
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
.lt(10)
|
|
107
|
-
.reduceResolution('mean', maxPixels=500)
|
|
107
|
+
# MB Land-use/land-cover
|
|
108
|
+
mb_lulc = (
|
|
109
|
+
ee.Image('projects/mapbiomas-public/assets/brazil/lulc/collection10_1/mapbiomas_brazil_collection10_1_coverage_v1')
|
|
110
|
+
.select('classification_2010')
|
|
108
111
|
)
|
|
109
112
|
|
|
110
113
|
# Exporting both together is as simple as this:
|
|
111
|
-
geebeam.
|
|
112
|
-
image_list = [
|
|
114
|
+
geebeam.sample_and_run_pipeline(
|
|
115
|
+
image_list = [ls5_composite, mb_lulc],
|
|
113
116
|
project=PROJECT_ID,
|
|
117
|
+
crs='EPSG:4326',
|
|
114
118
|
patch_size=128,
|
|
115
|
-
scale=
|
|
119
|
+
scale=30,
|
|
116
120
|
n_sample=10,
|
|
117
121
|
validation_ratio=0.2,
|
|
118
|
-
output_path='./
|
|
119
|
-
sampling_region=ee.Geometry.Rectangle(-
|
|
120
|
-
num_workers=1
|
|
122
|
+
output_path='./test_data_w_mb/',
|
|
123
|
+
sampling_region=ee.Geometry.Rectangle(-55.0, -12.0, -50.0, -16.0)
|
|
121
124
|
)
|
|
122
125
|
```
|
|
123
126
|
|
|
@@ -37,49 +37,51 @@ PROJECT_ID = google.auth.default()[1]
|
|
|
37
37
|
# Initialize ee client, replace with your GCP project ID
|
|
38
38
|
ee.Initialize(project=PROJECT_ID)
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
40
|
+
### Build image for download
|
|
41
|
+
# Load a raw Landsat 5 ImageCollection for a single year.
|
|
42
|
+
ls5_collection = ee.ImageCollection('LANDSAT/LT05/C02/T1').filterDate(
|
|
43
|
+
'2010-01-01', '2010-12-31'
|
|
44
|
+
)
|
|
45
|
+
# Create a (mostly) cloud-free Landsat composite
|
|
46
|
+
ls5_composite = ee.Algorithms.Landsat.simpleComposite(
|
|
47
|
+
ls5_collection,
|
|
48
|
+
asFloat=True,
|
|
49
|
+
cloudScoreRange=5)
|
|
48
50
|
|
|
49
51
|
# Building and triggering the pipeline is done with a single command:
|
|
50
|
-
geebeam.
|
|
51
|
-
image_list = [
|
|
52
|
-
|
|
53
|
-
patch_size=128, # Pixel dimensions in each direction
|
|
54
|
-
scale=500, # Final export resolution in meters
|
|
52
|
+
geebeam.sample_and_run_pipeline(
|
|
53
|
+
image_list = [ls5_composite], # Important: has to be a list of images
|
|
54
|
+
sampling_region=ee.Geometry.Rectangle(-55.0, -12.0, -50.0, -16.0), # In central-west Brazil
|
|
55
55
|
n_sample=10, # Number of tiles to sample
|
|
56
|
+
patch_size=128, # Number of pixels in each direction
|
|
57
|
+
scale=30, # Final export resolution in meters
|
|
58
|
+
crs='EPSG:4326', # CRS for final output
|
|
59
|
+
project=PROJECT_ID, # GCP Project ID
|
|
60
|
+
output_path='./test_data/', # Output path, local or on GCP
|
|
56
61
|
validation_ratio=0.2, # Fraction to select as validation data
|
|
57
|
-
output_path='./test_tf_data/',
|
|
58
|
-
sampling_region=ee.Geometry.Rectangle(-63.0, -9.0, -56.0, -4.0)
|
|
59
62
|
)
|
|
60
63
|
```
|
|
61
64
|
|
|
62
|
-
Now let's add another dataset: MapBiomas
|
|
65
|
+
Now let's add another dataset: MapBiomas land-cover from same year.
|
|
66
|
+
For more info, and legend, see: [MapBiomas Brasil](https://brasil.mapbiomas.org/en/codigos-de-legenda/)
|
|
63
67
|
```python
|
|
64
|
-
# MB Land-use/land-cover
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
.lt(10)
|
|
69
|
-
.reduceResolution('mean', maxPixels=500)
|
|
68
|
+
# MB Land-use/land-cover
|
|
69
|
+
mb_lulc = (
|
|
70
|
+
ee.Image('projects/mapbiomas-public/assets/brazil/lulc/collection10_1/mapbiomas_brazil_collection10_1_coverage_v1')
|
|
71
|
+
.select('classification_2010')
|
|
70
72
|
)
|
|
71
73
|
|
|
72
74
|
# Exporting both together is as simple as this:
|
|
73
|
-
geebeam.
|
|
74
|
-
image_list = [
|
|
75
|
+
geebeam.sample_and_run_pipeline(
|
|
76
|
+
image_list = [ls5_composite, mb_lulc],
|
|
75
77
|
project=PROJECT_ID,
|
|
78
|
+
crs='EPSG:4326',
|
|
76
79
|
patch_size=128,
|
|
77
|
-
scale=
|
|
80
|
+
scale=30,
|
|
78
81
|
n_sample=10,
|
|
79
82
|
validation_ratio=0.2,
|
|
80
|
-
output_path='./
|
|
81
|
-
sampling_region=ee.Geometry.Rectangle(-
|
|
82
|
-
num_workers=1
|
|
83
|
+
output_path='./test_data_w_mb/',
|
|
84
|
+
sampling_region=ee.Geometry.Rectangle(-55.0, -12.0, -50.0, -16.0)
|
|
83
85
|
)
|
|
84
86
|
```
|
|
85
87
|
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.3.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 3,
|
|
21
|
+
__version__ = version = '0.3.3'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 3, 3)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -17,6 +17,7 @@ dependencies = [
|
|
|
17
17
|
"geopandas>=1.0.1",
|
|
18
18
|
"grpcio>=1.62.3",
|
|
19
19
|
"rasterio>=1.3.10",
|
|
20
|
+
"webdataset>=1.0.2",
|
|
20
21
|
]
|
|
21
22
|
|
|
22
23
|
[project.optional-dependencies]
|
|
@@ -57,3 +58,8 @@ include = [
|
|
|
57
58
|
"pyproject.toml",
|
|
58
59
|
"setup.py"
|
|
59
60
|
]
|
|
61
|
+
|
|
62
|
+
[tool.pytest.ini_options]
|
|
63
|
+
markers = [
|
|
64
|
+
"integration: marks tests as integration tests (slower)",
|
|
65
|
+
]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Beam and Earth Engine helpers for running data pipelines"""
|
|
2
|
+
|
|
3
|
+
from . import climate_indices, sampler
|
|
4
|
+
|
|
5
|
+
from ._pipeline import run_pipeline, sample_and_run_pipeline, grid_and_run_pipeline
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"climate_indices",
|
|
9
|
+
"sampler",
|
|
10
|
+
"run_pipeline",
|
|
11
|
+
"sample_and_run_pipeline",
|
|
12
|
+
"grid_and_run_pipeline"
|
|
13
|
+
]
|
|
@@ -7,11 +7,11 @@ import io
|
|
|
7
7
|
import ee
|
|
8
8
|
import numpy as np
|
|
9
9
|
|
|
10
|
-
def
|
|
10
|
+
def _deserialize(obj_json):
|
|
11
11
|
"""Deserialize Earth Engine JSON DAG"""
|
|
12
12
|
return ee.deserializer.fromJSON(obj_json)
|
|
13
13
|
|
|
14
|
-
def
|
|
14
|
+
def _serialize(obj_ee):
|
|
15
15
|
"""Serialize Earth Engine object to JSON for Dataflow workers"""
|
|
16
16
|
return ee.serializer.toJSON(obj_ee)
|
|
17
17
|
|
|
@@ -39,7 +39,7 @@ def build_prepped_image(input_list, split_processing=False):
|
|
|
39
39
|
return prepped_im, band_groups, band_names_flat.getInfo()
|
|
40
40
|
|
|
41
41
|
def list_to_im(input_list):
|
|
42
|
-
prepped_im, _ = build_prepped_image(input_list)
|
|
42
|
+
prepped_im, *_ = build_prepped_image(input_list)
|
|
43
43
|
return prepped_im
|
|
44
44
|
|
|
45
45
|
def get_pixels(im, point, patch_size, scale_x, scale_y, crs_code):
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
"""Prepare and run Beam pipeline to download image 'chips' from Earth Engine"""
|
|
2
|
+
|
|
3
|
+
import warnings
|
|
4
|
+
import ee
|
|
5
|
+
import geopandas as gpd
|
|
6
|
+
import pandas as pd
|
|
7
|
+
from apache_beam.options.pipeline_options import PipelineOptions
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
from geebeam import _ee_utils, sampler, _transforms
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _check_if_localrunner(pipeline_options):
|
|
14
|
+
"""Fixes gRPC timeout issue for local runners."""
|
|
15
|
+
runner = pipeline_options.get_all_options()['runner']
|
|
16
|
+
if runner is None or runner in ['DirectRunner', 'PrismRunner']:
|
|
17
|
+
return True
|
|
18
|
+
else:
|
|
19
|
+
return False
|
|
20
|
+
|
|
21
|
+
def _type_inference(val):
|
|
22
|
+
if isinstance(val, int):
|
|
23
|
+
return 'int'
|
|
24
|
+
elif isinstance(val, float):
|
|
25
|
+
return 'float'
|
|
26
|
+
elif isinstance(val, list) or isinstance(val, np.ndarray):
|
|
27
|
+
return {'arraylike': np.array(val).shape}
|
|
28
|
+
elif isinstance(val, str):
|
|
29
|
+
return 'str'
|
|
30
|
+
else:
|
|
31
|
+
raise ValueError
|
|
32
|
+
|
|
33
|
+
def _build_md_feature_dict(record, extra_metadata):
|
|
34
|
+
md_feature_dict = {
|
|
35
|
+
'id': 'int',
|
|
36
|
+
'x': 'float',
|
|
37
|
+
'y': 'float',
|
|
38
|
+
'split':'str'
|
|
39
|
+
}
|
|
40
|
+
if extra_metadata is not None:
|
|
41
|
+
for key in extra_metadata.keys():
|
|
42
|
+
# Basic type inference for extra metadata
|
|
43
|
+
try:
|
|
44
|
+
md_type = _type_inference(extra_metadata[key])
|
|
45
|
+
except ValueError:
|
|
46
|
+
raise ValueError(f'Could not determine data type of extra_metadata feature {key}')
|
|
47
|
+
md_feature_dict[key] = md_type
|
|
48
|
+
|
|
49
|
+
for key in record.keys():
|
|
50
|
+
if key not in ['id','x','y','split']:
|
|
51
|
+
try:
|
|
52
|
+
md_type = _type_inference(record[key])
|
|
53
|
+
except ValueError:
|
|
54
|
+
raise ValueError(f'Could not determine data type of column feature {key}')
|
|
55
|
+
md_feature_dict[key] = md_type
|
|
56
|
+
return md_feature_dict
|
|
57
|
+
|
|
58
|
+
def _prepare_run_metadata(config):
|
|
59
|
+
ee.Initialize(project=config['project_id'])
|
|
60
|
+
|
|
61
|
+
proj = ee.Projection(config['crs']).atScale(config['scale'])
|
|
62
|
+
proj_dict = proj.getInfo()
|
|
63
|
+
|
|
64
|
+
scale_x = proj_dict['transform'][0]
|
|
65
|
+
scale_y = -proj_dict['transform'][4]
|
|
66
|
+
|
|
67
|
+
return scale_x, scale_y
|
|
68
|
+
|
|
69
|
+
def run_pipeline(
|
|
70
|
+
image_list: list[ee.Image],
|
|
71
|
+
output_path: str,
|
|
72
|
+
project: str,
|
|
73
|
+
patch_size: int,
|
|
74
|
+
scale: float,
|
|
75
|
+
sampling_points: pd.DataFrame | gpd.GeoDataFrame | ee.FeatureCollection,
|
|
76
|
+
output_type: str = 'tiff',
|
|
77
|
+
crs: str = 'EPSG:4326',
|
|
78
|
+
split_processing: bool = False,
|
|
79
|
+
extra_metadata: dict = {},
|
|
80
|
+
beam_options: dict[str] | list[str] | None = None,
|
|
81
|
+
dataset_version: str = '1.0.0',
|
|
82
|
+
dataset_name: str = 'geebeam_dataset'
|
|
83
|
+
) -> None:
|
|
84
|
+
"""Run a Beam pipeline to download image chips from Earth Engine.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
image_list: A list of image identifiers to process.
|
|
88
|
+
sampling_points: Locations to sample from, specifying upper-left of box.
|
|
89
|
+
output_path: The path where output will be saved.
|
|
90
|
+
output_type: 'tiff' (tiffs with parquet for metadata),
|
|
91
|
+
'webdataset' (tiffs with jsons, in sharded tars),
|
|
92
|
+
'tfrecord' (raw tfrecords), or 'tfds' (tensorflow-dataset).
|
|
93
|
+
project: The Google Cloud project ID.
|
|
94
|
+
patch_size: The size of the patches to be processed.
|
|
95
|
+
scale: The scale factor for image processing.
|
|
96
|
+
crs: The coordinate reference system. Defaults to 'EPSG:4326'.
|
|
97
|
+
split_processing: Flag to indicate if processing should be split. Defaults to False.
|
|
98
|
+
extra_metadata: Additional metadata to include. Defaults to an empty dictionary.
|
|
99
|
+
beam_options_dict: Options for the Beam pipeline. Defaults to an empty dictionary.
|
|
100
|
+
"""
|
|
101
|
+
import logging
|
|
102
|
+
|
|
103
|
+
logging.getLogger().setLevel(logging.INFO)
|
|
104
|
+
|
|
105
|
+
# Set up configuration dict to pass along
|
|
106
|
+
config = {
|
|
107
|
+
'project_id': project,
|
|
108
|
+
'patch_size': patch_size,
|
|
109
|
+
'scale': scale,
|
|
110
|
+
'crs': crs
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
# Parses from command line and/or retrieves from dict. Note that dict takes precedent.
|
|
114
|
+
if isinstance(beam_options, dict):
|
|
115
|
+
pipeline_options = PipelineOptions(
|
|
116
|
+
**beam_options,
|
|
117
|
+
project=config['project_id'],
|
|
118
|
+
save_main_session=True,
|
|
119
|
+
)
|
|
120
|
+
elif isinstance(beam_options, list):
|
|
121
|
+
warnings.warn('Creating PipelineOptions from beam_options list.'
|
|
122
|
+
' Ignores command-line beam options.')
|
|
123
|
+
pipeline_options = PipelineOptions(
|
|
124
|
+
beam_options,
|
|
125
|
+
project=config['project_id'],
|
|
126
|
+
save_main_session=True,
|
|
127
|
+
)
|
|
128
|
+
else:
|
|
129
|
+
pipeline_options = PipelineOptions(
|
|
130
|
+
project=config['project_id'],
|
|
131
|
+
save_main_session=True,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Get sample points
|
|
135
|
+
input_records, splits = sampler._process_sampling_points(sampling_points, target_crs=config['crs'])
|
|
136
|
+
|
|
137
|
+
# Get types of extra non-image data in extra_medtada or input_records
|
|
138
|
+
md_feature_dict = _build_md_feature_dict(input_records[0], extra_metadata)
|
|
139
|
+
|
|
140
|
+
# Pre-run info:
|
|
141
|
+
scale_x, scale_y = _prepare_run_metadata(config)
|
|
142
|
+
|
|
143
|
+
# Check if a local runner. If so, add longer job timeout to fix grpcio timeout issue
|
|
144
|
+
is_local = _check_if_localrunner(pipeline_options)
|
|
145
|
+
if is_local:
|
|
146
|
+
logging.warning('Running on local runner. Setting beam job_server_timeout'
|
|
147
|
+
' to 9999999 seconds to avoid grpcio timeout errors.')
|
|
148
|
+
pipeline_options_dict = pipeline_options.get_all_options(drop_default=True)
|
|
149
|
+
pipeline_options_dict['job_server_timeout'] = 9999999
|
|
150
|
+
pipeline_options = PipelineOptions.from_dictionary(pipeline_options_dict)
|
|
151
|
+
|
|
152
|
+
# Prepare and serialize inputs
|
|
153
|
+
# band_groups is a list of lists containing bands to export
|
|
154
|
+
# if split_processing = False, will contain one list with all bands
|
|
155
|
+
# if split_processing = True, contains separate band_lists for each image in image_list
|
|
156
|
+
prepped_image, band_groups, all_bands = _ee_utils.build_prepped_image(image_list, split_processing=split_processing)
|
|
157
|
+
serialized_image = _ee_utils._serialize(prepped_image)
|
|
158
|
+
|
|
159
|
+
# Execute pipeline based on output type:
|
|
160
|
+
if output_type == 'tfrecord':
|
|
161
|
+
try:
|
|
162
|
+
from geebeam import _tfrecord_writer
|
|
163
|
+
except ImportError:
|
|
164
|
+
raise ImportError(
|
|
165
|
+
"Missing dependencies for tfrecord writer. "
|
|
166
|
+
"Install them with `pip install geebeam[tensorflow]`"
|
|
167
|
+
)
|
|
168
|
+
# Write sidecar schema before pipeline execution
|
|
169
|
+
extra_keys = list(extra_metadata.keys())
|
|
170
|
+
_transforms._write_sidecar_schema(output_path, all_bands, extra_keys,
|
|
171
|
+
is_gcs=output_path.startswith('gs://'))
|
|
172
|
+
_tfrecord_writer.run_tfrecord_export(
|
|
173
|
+
input_records=input_records,
|
|
174
|
+
splits=splits,
|
|
175
|
+
output_path=output_path,
|
|
176
|
+
config=config,
|
|
177
|
+
serialized_image=serialized_image,
|
|
178
|
+
band_groups=band_groups,
|
|
179
|
+
scale_x=scale_x,
|
|
180
|
+
scale_y=scale_y,
|
|
181
|
+
extra_metadata=extra_metadata,
|
|
182
|
+
md_feature_dict=md_feature_dict,
|
|
183
|
+
pipeline_options=pipeline_options
|
|
184
|
+
)
|
|
185
|
+
elif output_type == 'tfds':
|
|
186
|
+
try:
|
|
187
|
+
from geebeam import _tfds_writer
|
|
188
|
+
except ImportError:
|
|
189
|
+
raise ImportError(
|
|
190
|
+
"Missing dependencies for TFDS writer. "
|
|
191
|
+
"Install them with `pip install geebeam[tensorflow]`"
|
|
192
|
+
)
|
|
193
|
+
_tfds_writer.run_tfds_export(
|
|
194
|
+
input_records=input_records,
|
|
195
|
+
splits=splits,
|
|
196
|
+
output_path=output_path,
|
|
197
|
+
config=config,
|
|
198
|
+
serialized_image=serialized_image,
|
|
199
|
+
band_groups=band_groups,
|
|
200
|
+
all_bands=all_bands,
|
|
201
|
+
scale_x=scale_x,
|
|
202
|
+
scale_y=scale_y,
|
|
203
|
+
extra_metadata=extra_metadata,
|
|
204
|
+
md_feature_dict=md_feature_dict,
|
|
205
|
+
pipeline_options=pipeline_options,
|
|
206
|
+
dataset_name=dataset_name,
|
|
207
|
+
dataset_version=dataset_version
|
|
208
|
+
)
|
|
209
|
+
elif output_type == 'tif' or output_type == 'tiff':
|
|
210
|
+
from geebeam import _tiff_writer
|
|
211
|
+
_tiff_writer.run_tiff_export(
|
|
212
|
+
input_records=input_records,
|
|
213
|
+
splits=splits,
|
|
214
|
+
output_path=output_path,
|
|
215
|
+
config=config,
|
|
216
|
+
serialized_image=serialized_image,
|
|
217
|
+
band_groups=band_groups,
|
|
218
|
+
scale_x=scale_x,
|
|
219
|
+
scale_y=scale_y,
|
|
220
|
+
extra_metadata=extra_metadata,
|
|
221
|
+
md_feature_dict=md_feature_dict,
|
|
222
|
+
pipeline_options=pipeline_options
|
|
223
|
+
)
|
|
224
|
+
elif output_type == 'webdataset':
|
|
225
|
+
from geebeam import _wds_writer
|
|
226
|
+
_wds_writer.run_webdataset_export(
|
|
227
|
+
input_records=input_records,
|
|
228
|
+
splits=splits,
|
|
229
|
+
output_path=output_path,
|
|
230
|
+
config=config,
|
|
231
|
+
serialized_image=serialized_image,
|
|
232
|
+
band_groups=band_groups,
|
|
233
|
+
scale_x=scale_x,
|
|
234
|
+
scale_y=scale_y,
|
|
235
|
+
extra_metadata=extra_metadata,
|
|
236
|
+
pipeline_options=pipeline_options
|
|
237
|
+
)
|
|
238
|
+
else:
|
|
239
|
+
raise ValueError(f"output_type {output_type} not implemented. Options are ['tfds', 'tfrecord', 'tiff', 'webdataset']")
|
|
240
|
+
|
|
241
|
+
def sample_and_run_pipeline(
|
|
242
|
+
sampling_region: str | gpd.GeoDataFrame | ee.Geometry,
|
|
243
|
+
n_sample: int,
|
|
244
|
+
validation_ratio: float = 0,
|
|
245
|
+
random_seed: int = 0,
|
|
246
|
+
crs: str = 'EPSG:4326',
|
|
247
|
+
*args,
|
|
248
|
+
**kwargs
|
|
249
|
+
) -> None:
|
|
250
|
+
"""Sample random points and then run a Beam pipeline to download image chips from Earth Engine.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
image_list: A list of image identifiers to process.
|
|
254
|
+
sampling_region: Region to sample from, polygon or group of polygons.
|
|
255
|
+
n_sample: Number of points to sample.
|
|
256
|
+
validation_ratio: Fraction of points to mark as validation.
|
|
257
|
+
random_seed: Seed for random sampling
|
|
258
|
+
output_path: The path where output will be saved.
|
|
259
|
+
output_type: 'tiff' (tiffs with parquet for metadata),
|
|
260
|
+
'webdataset' (tiffs with jsons, in sharded tars),
|
|
261
|
+
'tfrecord' (raw tfrecords), or 'tfds' (tensorflow-dataset).
|
|
262
|
+
project: The Google Cloud project ID.
|
|
263
|
+
patch_size: The size of the patches to be processed.
|
|
264
|
+
scale: The scale factor for image processing.
|
|
265
|
+
crs: The coordinate reference system. Defaults to 'EPSG:4326'.
|
|
266
|
+
split_processing: Flag to indicate if processing should be split. Defaults to False.
|
|
267
|
+
extra_metadata: Additional metadata to include. Defaults to an empty dictionary.
|
|
268
|
+
beam_options_dict: Options for the Beam pipeline. Defaults to an empty dictionary.
|
|
269
|
+
"""
|
|
270
|
+
|
|
271
|
+
sample_points = sampler.sample_region_random(
|
|
272
|
+
roi=sampling_region,
|
|
273
|
+
n_sample=n_sample,
|
|
274
|
+
random_seed=random_seed,
|
|
275
|
+
crs=crs,
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
if validation_ratio > 0:
|
|
279
|
+
sample_points = sampler.split_sets(
|
|
280
|
+
sample_points, split_names=['train','validation'], split_ratios=[1-validation_ratio,validation_ratio],
|
|
281
|
+
random_seed=random_seed
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
return run_pipeline(sampling_points=sample_points, crs=crs, *args, **kwargs)
|
|
285
|
+
|
|
286
|
+
def grid_and_run_pipeline(
|
|
287
|
+
sampling_region: str | gpd.GeoDataFrame | ee.Geometry,
|
|
288
|
+
validation_ratio: float,
|
|
289
|
+
scale: float,
|
|
290
|
+
stride: int,
|
|
291
|
+
buffer_distance: float = 0,
|
|
292
|
+
crs: str = 'EPSG:4326',
|
|
293
|
+
random_seed: int = 0,
|
|
294
|
+
*args,
|
|
295
|
+
**kwargs
|
|
296
|
+
) -> None:
|
|
297
|
+
"""Sample points from regular grid and then run a Beam pipeline to download image chips from Earth Engine.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
image_list: A list of image identifiers to process.
|
|
301
|
+
sampling_region: Region to sample from, polygon or group of polygons.
|
|
302
|
+
validation_ratio: Fraction of points to mark as validation.
|
|
303
|
+
stride: Number of pixels between consecutive samples. If want full coverage without overlaps,
|
|
304
|
+
stride should be equal to patch_size. If less than patch_size, will generate overlaps.
|
|
305
|
+
If greater, will be gaps between sampled patches.
|
|
306
|
+
random_seed: Seed for random sampling
|
|
307
|
+
output_path: The path where output will be saved.
|
|
308
|
+
output_type: 'tiff' (tiffs with parquet for metadata),
|
|
309
|
+
'webdataset' (tiffs with jsons, in sharded tars),
|
|
310
|
+
'tfrecord' (raw tfrecords), or 'tfds' (tensorflow-dataset).
|
|
311
|
+
project: The Google Cloud project ID.
|
|
312
|
+
patch_size: The size of the patches to be processed.
|
|
313
|
+
scale: The scale factor for image processing.
|
|
314
|
+
crs: The coordinate reference system. Defaults to 'EPSG:4326'.
|
|
315
|
+
split_processing: Flag to indicate if processing should be split. Defaults to False.
|
|
316
|
+
extra_metadata: Additional metadata to include. Defaults to an empty dictionary.
|
|
317
|
+
beam_options_dict: Options for the Beam pipeline. Defaults to an empty dictionary.
|
|
318
|
+
"""
|
|
319
|
+
|
|
320
|
+
sample_points = sampler.sample_region_grid(
|
|
321
|
+
roi=sampling_region,
|
|
322
|
+
crs='EPSG:4326',
|
|
323
|
+
stride=stride,
|
|
324
|
+
scale=scale,
|
|
325
|
+
buffer_distance=buffer_distance,
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
if validation_ratio > 0:
|
|
329
|
+
sample_points = sampler.split_sets(
|
|
330
|
+
sample_points, split_names=['train','validation'], split_ratios=[1-validation_ratio,validation_ratio],
|
|
331
|
+
random_seed=random_seed
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
return run_pipeline(sampling_points=sample_points, crs=crs, scale=scale, *args, **kwargs)
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import tensorflow as tf
|
|
4
4
|
import apache_beam as beam
|
|
5
|
+
import numpy as np
|
|
5
6
|
|
|
6
|
-
from geebeam import
|
|
7
|
+
from geebeam import _transforms
|
|
7
8
|
|
|
8
9
|
def _bytes_feature(value):
|
|
9
10
|
"""Returns a bytes_list from a string / byte."""
|
|
@@ -17,28 +18,39 @@ def _int64_feature(value):
|
|
|
17
18
|
"""Returns an int64_list from a bool / enum / int / uint."""
|
|
18
19
|
return tf.train.Feature(int64_list=tf.train.Int64List(value=[value]))
|
|
19
20
|
|
|
20
|
-
def
|
|
21
|
+
def _dict_to_example(record):
|
|
21
22
|
""""Convert structured numpy array to tf.Example proto."""
|
|
22
23
|
# First add metadata
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
# Add metadata features
|
|
25
|
+
md_dict = {}
|
|
26
|
+
for md_key, md_val in record['metadata'].items():
|
|
27
|
+
if isinstance(md_val, str):
|
|
28
|
+
md_dict[f'md_{md_key}'] = _bytes_feature(md_val.encode('utf-8'))
|
|
29
|
+
elif np.isscalar(md_val):
|
|
30
|
+
if isinstance(md_val, int):
|
|
31
|
+
md_dict[f'md_{md_key}'] = _int64_feature(record['metadata'][md_key])
|
|
32
|
+
else:
|
|
33
|
+
md_dict[f'md_{md_key}'] = _float_feature(record['metadata'][md_key])
|
|
34
|
+
else:
|
|
35
|
+
md_dict[f'md_{md_key}'] = tf.train.Feature(float_list=
|
|
31
36
|
tf.train.FloatList(
|
|
32
|
-
value =
|
|
37
|
+
value = _transforms._convert_to_iterable(record['metadata'][md_key])
|
|
33
38
|
)
|
|
34
39
|
)
|
|
35
40
|
|
|
36
41
|
# Build image feature with named bands
|
|
37
42
|
array_dict = {}
|
|
38
|
-
for
|
|
43
|
+
for band_name, band_data in record['array'].items():
|
|
44
|
+
array_dict[f'{band_name}'] = band_data.flatten().astype('float32')
|
|
45
|
+
|
|
46
|
+
# Build image feature with named bands
|
|
47
|
+
array_dict = {}
|
|
48
|
+
for im_feat, im_data in record['array'].items():
|
|
39
49
|
array_dict['im_'+im_feat] = tf.train.Feature(
|
|
40
50
|
float_list = tf.train.FloatList(
|
|
41
|
-
value =
|
|
51
|
+
value = im_data.flatten()
|
|
52
|
+
)
|
|
53
|
+
)
|
|
42
54
|
|
|
43
55
|
# Combine
|
|
44
56
|
feature = {**md_dict, **array_dict}
|
|
@@ -47,16 +59,6 @@ def dict_to_example(element):
|
|
|
47
59
|
return tf.train.Example(
|
|
48
60
|
features = tf.train.Features(feature = feature)).SerializeToString()
|
|
49
61
|
|
|
50
|
-
def array_to_example(structured_array):
|
|
51
|
-
""""Convert structured numpy array to tf.Example proto."""
|
|
52
|
-
feature = {}
|
|
53
|
-
for f in structured_array.dtype.names:
|
|
54
|
-
feature[f] = tf.train.Feature(
|
|
55
|
-
float_list = tf.train.FloatList(
|
|
56
|
-
value = structured_array[f].flatten()))
|
|
57
|
-
return tf.train.Example(
|
|
58
|
-
features = tf.train.Features(feature = feature))
|
|
59
|
-
|
|
60
62
|
class WriteTFExample(beam.PTransform):
|
|
61
63
|
"""Write example"""
|
|
62
64
|
def __init__(self, output_dir, file_name_suffix='.tfrecord.gz'):
|