geebeam 0.2.0__tar.gz → 0.2.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: geebeam
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Earth Engine + Apache Beam
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -72,7 +72,7 @@ geebeam.run_pipeline(
72
72
  scale=500, # Final export resolution in meters
73
73
  n_sample=10, # Number of tiles to sample
74
74
  validation_ratio=0.2, # Fraction to select as validation data
75
- output_path='./test/',
75
+ output_path='./test_tf_data/',
76
76
  sampling_region=ee.Geometry.Rectangle(-63.0, -9.0, -56.0, -4.0)
77
77
  )
78
78
  ```
@@ -95,7 +95,7 @@ geebeam.run_pipeline(
95
95
  scale=500,
96
96
  n_sample=10,
97
97
  validation_ratio=0.2,
98
- output_path='./test/',
98
+ output_path='./test_tf_data/',
99
99
  sampling_region=ee.Geometry.Rectangle(-63.0, -9.0, -56.0, -4.0),
100
100
  num_workers=1
101
101
  )
@@ -54,7 +54,7 @@ geebeam.run_pipeline(
54
54
  scale=500, # Final export resolution in meters
55
55
  n_sample=10, # Number of tiles to sample
56
56
  validation_ratio=0.2, # Fraction to select as validation data
57
- output_path='./test/',
57
+ output_path='./test_tf_data/',
58
58
  sampling_region=ee.Geometry.Rectangle(-63.0, -9.0, -56.0, -4.0)
59
59
  )
60
60
  ```
@@ -77,7 +77,7 @@ geebeam.run_pipeline(
77
77
  scale=500,
78
78
  n_sample=10,
79
79
  validation_ratio=0.2,
80
- output_path='./test/',
80
+ output_path='./test_tf_data/',
81
81
  sampling_region=ee.Geometry.Rectangle(-63.0, -9.0, -56.0, -4.0),
82
82
  num_workers=1
83
83
  )
@@ -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.2.0'
22
- __version_tuple__ = version_tuple = (0, 2, 0)
21
+ __version__ = version = '0.2.2'
22
+ __version_tuple__ = version_tuple = (0, 2, 2)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -1,5 +1,9 @@
1
1
  """Prepare and run Beam pipeline to download image 'chips' from Earth Engine"""
2
+
3
+ import warnings
2
4
  import ee
5
+ import geopandas as gpd
6
+ import pandas as pd
3
7
  import numpy as np
4
8
  import apache_beam as beam
5
9
  from apache_beam.options.pipeline_options import PipelineOptions
@@ -11,9 +15,9 @@ from google.protobuf.json_format import MessageToJson
11
15
  from geebeam import ee_utils, sampler, transforms
12
16
 
13
17
 
14
- def _check_if_localrunner(beam_options):
18
+ def _check_if_localrunner(pipeline_options):
15
19
  """Fixes gRPC timeout issue for local runners."""
16
- runner = beam_options.get_all_options()['runner']
20
+ runner = pipeline_options.get_all_options()['runner']
17
21
  if runner is None or runner in ['DirectRunner', 'PrismRunner']:
18
22
  return True
19
23
  else:
@@ -32,29 +36,45 @@ def _prepare_run_metadata(config):
32
36
  return scale_x, scale_y
33
37
 
34
38
  def run_pipeline(
35
- image_list,
36
- sampling_region,
37
- output_path,
39
+ image_list: list[ee.Image],
40
+ output_path: str,
38
41
  project: str,
39
42
  patch_size: int,
40
43
  scale: float,
41
44
  n_sample: int,
42
- validation_ratio: float=0.2,
43
- crs='EPSG:4326',
44
- random_seed=None,
45
- split_processing=False,
46
- extra_metadata={},
47
- beam_options_dict={}
48
- ):
45
+ sampling_region: str | gpd.GeoDataFrame | ee.Geometry | None = None,
46
+ sampling_points: pd.DataFrame | gpd.GeoDataFrame | ee.Geometry | None = None,
47
+ validation_ratio: float = 0.2,
48
+ crs: str = 'EPSG:4326',
49
+ random_seed: int = None,
50
+ split_processing: bool = False,
51
+ extra_metadata: dict = {},
52
+ beam_options: dict[str] | list[str] | None = None
53
+ ) -> None:
54
+ """Run a Beam pipeline to download image chips from Earth Engine.
55
+
56
+ Args:
57
+ image_list: A list of image identifiers to process.
58
+ sampling_region: The region for sampling images.
59
+ sampling_points: Locations to sample from, specifying upper-left of box.
60
+ output_path: The path where output will be saved.
61
+ project: The Google Cloud project ID.
62
+ patch_size: The size of the patches to be processed.
63
+ scale: The scale factor for image processing.
64
+ n_sample: The number of samples to take.
65
+ validation_ratio: The ratio of data to use for validation. Defaults to 0.2.
66
+ crs: The coordinate reference system. Defaults to 'EPSG:4326'.
67
+ random_seed: Seed for random number generation. Defaults to None.
68
+ split_processing: Flag to indicate if processing should be split. Defaults to False.
69
+ extra_metadata: Additional metadata to include. Defaults to an empty dictionary.
70
+ beam_options_dict: Options for the Beam pipeline. Defaults to an empty dictionary.
71
+ """
49
72
  import logging
50
73
 
51
74
  logging.getLogger().setLevel(logging.INFO)
52
75
 
53
76
  rng = np.random.default_rng(random_seed)
54
77
 
55
- # Parses from command line and/or retrieves from dict. Note that dict takes precedent.
56
- beam_options = PipelineOptions(beam_options_dict)
57
-
58
78
  # Set up configuration dict to pass along
59
79
  config = {
60
80
  'project_id': project,
@@ -65,9 +85,33 @@ def run_pipeline(
65
85
  'crs': crs
66
86
  }
67
87
 
68
- # Randomly sample points
69
- roi = sampler.get_roi(sampling_region)
70
- sampled_points = sampler.sample_random_points(roi, config['n_sample'], rng)
88
+ # Parses from command line and/or retrieves from dict. Note that dict takes precedent.
89
+ if isinstance(beam_options, dict):
90
+ pipeline_options = PipelineOptions(
91
+ **beam_options,
92
+ project=config['project_id'],
93
+ save_main_session=True,
94
+ )
95
+ elif isinstance(beam_options, list):
96
+ warnings.warn('Creating PipelineOptions from beam_options list.'
97
+ ' Ignores command-line beam options.')
98
+ pipeline_options = PipelineOptions(
99
+ beam_options,
100
+ project=config['project_id'],
101
+ save_main_session=True,
102
+ )
103
+ else:
104
+ pipeline_options = PipelineOptions(
105
+ project=config['project_id'],
106
+ save_main_session=True,
107
+ )
108
+
109
+ # Get sample points
110
+ if sampling_points is not None:
111
+ sampled_points = sampler.process_sampling_points(sampling_points, target_crs=config['crs'])
112
+ else:
113
+ roi = sampler.get_roi(sampling_region, image_list, target_crs=config['crs'])
114
+ sampled_points = sampler.sample_random_points(roi, config['n_sample'], rng)
71
115
 
72
116
  # Split into training and validation
73
117
  input_records = sampler.split_train_validation(
@@ -78,14 +122,9 @@ def run_pipeline(
78
122
  scale_x, scale_y = _prepare_run_metadata(config)
79
123
 
80
124
  # Set up pipeline
81
- beam_options = PipelineOptions(beam_options,
82
- project=config['project_id'],
83
- save_main_session=True,
84
- use_public_ips=False
85
- )
86
125
 
87
126
  # Check if a local runner
88
- is_local = _check_if_localrunner(beam_options)
127
+ is_local = _check_if_localrunner(pipeline_options)
89
128
 
90
129
  # Prepare and serialize inputs
91
130
  # band_groups is a list of lists containing bands to export
@@ -95,7 +134,7 @@ def run_pipeline(
95
134
  serialized_image = ee_utils.serialize(prepped_image)
96
135
 
97
136
  # Execute pipeline
98
- with beam.Pipeline(options=beam_options) as pipeline:
137
+ with beam.Pipeline(options=pipeline_options) as pipeline:
99
138
 
100
139
  points = pipeline | 'Create points' >> beam.Create(input_records)
101
140
 
@@ -0,0 +1,97 @@
1
+ """
2
+ Sample points from region of interest
3
+ """
4
+
5
+ import warnings
6
+
7
+ import json
8
+ import numpy as np
9
+ import pandas as pd
10
+ import geopandas as gpd
11
+ import ee
12
+
13
+
14
+ def get_roi(
15
+ sampling_region: str | ee.Geometry | gpd.GeoDataFrame,
16
+ image_list: list[ee.Image],
17
+ target_crs: str
18
+ ) -> gpd.GeoDataFrame:
19
+
20
+ if sampling_region is not None:
21
+ if isinstance(sampling_region, gpd.GeoDataFrame):
22
+ roi_df = sampling_region
23
+ elif isinstance(sampling_region, str):
24
+ roi_df = gpd.read_file(sampling_region)
25
+ elif isinstance(sampling_region, ee.Geometry):
26
+ # Looking for a better way to do this, this is silly
27
+ roi_df = gpd.read_file(json.dumps(sampling_region.getInfo()))
28
+ else:
29
+ raise ValueError("'sampling_region' must be one of"
30
+ "[str, ee.Geometry, gpd.GeoDataFrame]")
31
+ if roi_df.crs.to_string() != target_crs:
32
+ roi_df = roi_df.to_crs(target_crs)
33
+ return roi_df
34
+ else:
35
+ warnings.warn('Neither sampling_region nor sampling_points specified.\n'
36
+ 'Defaulting to footprint of the first image in image_list.')
37
+ return image_list[0].geometry()
38
+
39
+
40
+ def sample_random_points(roi: gpd.GeoDataFrame, n_sample: int, rng: np.random.Generator) -> pd.DataFrame:
41
+ """Get random points within region of interest."""
42
+ sampled_points = roi.sample_points(n_sample, rng=rng).geometry.explode().get_coordinates()
43
+ sampled_points.index = np.arange(sampled_points.shape[0])
44
+ x = sampled_points.values[:,0]
45
+ y = sampled_points.values[:,1]
46
+ out_df = pd.DataFrame({
47
+ 'y': y,
48
+ 'x': x,
49
+ }
50
+ )
51
+ return out_df
52
+
53
+ def split_train_validation(points_df: pd.DataFrame, validation_ratio: float, rng: np.random.Generator, shuffle: bool = True):
54
+ # Shuffle order
55
+ points_df['split'] = 'train'
56
+ if shuffle:
57
+ points_df = points_df.sample(frac=1, random_state=rng).reset_index(drop=True)
58
+ points_df['id'] = points_df.index
59
+
60
+ # Split
61
+ num_train = round(points_df.shape[0]*(1-validation_ratio))
62
+ points_df.loc[num_train:, 'split'] = 'val'
63
+
64
+ return points_df
65
+
66
+ def process_sampling_points(
67
+ sampling_points: pd.DataFrame | ee.FeatureCollection | gpd.GeoDataFrame,
68
+ target_crs: str
69
+ ) -> pd.DataFrame:
70
+
71
+ if isinstance(sampling_points, pd.DataFrame):
72
+ if 'x' not in sampling_points.columns or 'y' not in sampling_points.columns:
73
+ raise ValueError('If provided as pd.DataFrame, sampling_points must have columns '
74
+ '`x` and `y` with coordinates for sampling in target crs.')
75
+ points_df = sampling_points
76
+ elif isinstance(sampling_points, gpd.GeoDataFrame):
77
+ if sampling_points.crs != target_crs:
78
+ raise ValueError('sampling_points projection does not match target_crs.')
79
+ sampling_points['x'] = sampling_points.geometry.x
80
+ sampling_points['y'] = sampling_points.geometry.y
81
+ points_df = sampling_points
82
+ elif isinstance(sampling_points, ee.FeatureCollection):
83
+ fc_crs = sampling_points.first().geometry().projection().getInfo()
84
+ if fc_crs != target_crs:
85
+ raise ValueError('sampling_points projection does not match target_crs.')
86
+ points_df = ee.data.computeFeatures({
87
+ 'expression': sampling_points,
88
+ 'fileFormat': 'GEOPANDAS_GEODATAFRAME'
89
+ }
90
+ ).set_crs(fc_crs)
91
+ points_df['x'] = points_df.geometry.x
92
+ points_df['y'] = points_df.geometry.y
93
+ else:
94
+ raise ValueError("'sampling_points' must be one of"
95
+ "[pd.DataFrame, gpd.GeoDataFrame, ee.FeatureCollection]")
96
+
97
+ return points_df
@@ -46,11 +46,11 @@ def dict_to_example(element):
46
46
  # First add metadata
47
47
  md_dict = {
48
48
  'md_id': _int64_feature(element['metadata']['id']),
49
- 'md_lat': _float_feature(element['metadata']['lat']),
50
- 'md_lon': _float_feature(element['metadata']['lon']),
49
+ 'md_y': _float_feature(element['metadata']['y']),
50
+ 'md_x': _float_feature(element['metadata']['x']),
51
51
  }
52
52
  for md_key in element['metadata'].keys():
53
- if md_key not in ['id','lat','lon', 'split']:
53
+ if md_key not in ['id','y','x', 'split']:
54
54
  md_dict['md_' + md_key] = tf.train.Feature(float_list=
55
55
  tf.train.FloatList(
56
56
  value = convert_to_iterable(element['metadata'][md_key])
@@ -133,10 +133,10 @@ class EEComputePatch(beam.DoFn):
133
133
  'affineTransform': {
134
134
  'scaleX': self.scale_x,
135
135
  'shearX': 0,
136
- 'translateX': point['lon'],
136
+ 'translateX': point['x'],
137
137
  'shearY': 0,
138
138
  'scaleY': self.scale_y,
139
- 'translateY': point['lat']
139
+ 'translateY': point['y']
140
140
  },
141
141
  'crsCode': 'EPSG:4326',
142
142
  },
@@ -19,16 +19,15 @@ def test_sample_random_points():
19
19
 
20
20
  assert isinstance(result, pd.DataFrame)
21
21
  assert result.shape == (2, 2)
22
- assert 'lat' in result.columns
23
- assert 'lon' in result.columns
24
- # x is lon, y is lat in get_coordinates() typically
25
- assert result.iloc[0]['lon'] == 10.0
26
- assert result.iloc[0]['lat'] == 50.0
22
+ assert 'y' in result.columns
23
+ assert 'x' in result.columns
24
+ assert result.iloc[0]['x'] == 10.0
25
+ assert result.iloc[0]['y'] == 50.0
27
26
 
28
27
  def test_split_train_validation():
29
28
  df = pd.DataFrame({
30
- 'lat': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
31
- 'lon': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
29
+ 'y': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
30
+ 'x': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
32
31
  })
33
32
  rng = np.random.default_rng(42)
34
33
 
@@ -13,8 +13,8 @@ def test_dict_to_example():
13
13
  element = {
14
14
  'metadata': {
15
15
  'id': 1,
16
- 'lat': 10.0,
17
- 'lon': 20.0,
16
+ 'y': 10.0,
17
+ 'x': 20.0,
18
18
  'extra': 0.5
19
19
  },
20
20
  'array': {
@@ -30,8 +30,8 @@ def test_dict_to_example():
30
30
 
31
31
  features = example.features.feature
32
32
  assert features['md_id'].int64_list.value[0] == 1
33
- assert features['md_lat'].float_list.value[0] == 10.0
34
- assert features['md_lon'].float_list.value[0] == 20.0
33
+ assert features['md_y'].float_list.value[0] == 10.0
34
+ assert features['md_x'].float_list.value[0] == 20.0
35
35
  assert features['md_extra'].float_list.value[0] == 0.5
36
36
  assert list(features['im_band1'].float_list.value) == [1.0, 2.0, 3.0, 4.0]
37
37
 
@@ -75,7 +75,7 @@ def test_ee_compute_patch(mock_from_json, mock_compute_pixels, mock_ee_init):
75
75
  patch_fn = EEComputePatch(config, serialized_image, scale_x, scale_y, band_groups)
76
76
  patch_fn.setup() # Initialize EE
77
77
 
78
- point = {'id': 1, 'lat': 10.0, 'lon': 20.0}
78
+ point = {'id': 1, 'y': 10.0, 'x': 20.0}
79
79
  results = list(patch_fn.process(point))
80
80
  print(results)
81
81
 
@@ -1,52 +0,0 @@
1
- """
2
- Sample points from region of interest
3
- """
4
-
5
- import json
6
- import numpy as np
7
- import pandas as pd
8
- import geopandas as gpd
9
- import ee
10
-
11
-
12
- def get_roi(
13
- sampling_region: str | ee.Geometry | gpd.GeoDataFrame
14
- ) -> gpd.GeoDataFrame:
15
-
16
- if isinstance(sampling_region, gpd.GeoDataFrame):
17
- roi_df = sampling_region
18
- elif isinstance(sampling_region, str):
19
- roi_df = gpd.read_file(sampling_region)
20
- elif isinstance(sampling_region, ee.Geometry):
21
- # Looking for a better way to do this, this is silly
22
- roi_df = gpd.read_file(json.dumps(sampling_region.getInfo()))
23
- else:
24
- raise ValueError("'sampling_region' must be one of"
25
- "[str, ee.Geometry, gpd.GeoDataFrame]")
26
- return roi_df
27
-
28
- def sample_random_points(roi: gpd.GeoDataFrame, n_sample: int, rng: np.random.Generator)->pd.DataFrame:
29
- """Get random points within region of interest."""
30
- sampled_points = roi.sample_points(n_sample, rng=rng).geometry.explode().get_coordinates()
31
- sampled_points.index = np.arange(sampled_points.shape[0])
32
- lon = sampled_points.values[:,0]
33
- lat = sampled_points.values[:,1]
34
- out_df = pd.DataFrame({
35
- 'lat': lat,
36
- 'lon': lon,
37
- }
38
- )
39
- return out_df
40
-
41
- def split_train_validation(points_df: pd.DataFrame, validation_ratio: float, rng: np.random.Generator, shuffle: bool = True):
42
- # Shuffle order
43
- points_df['split'] = 'train'
44
- if shuffle:
45
- points_df = points_df.sample(frac=1, random_state=rng).reset_index(drop=True)
46
- points_df['id'] = points_df.index
47
-
48
- # Split
49
- num_train = round(points_df.shape[0]*(1-validation_ratio))
50
- points_df.loc[num_train:, 'split'] = 'val'
51
-
52
- return points_df
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes