openforis-whisp 0.0.1__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.
@@ -0,0 +1,695 @@
1
+ import ee
2
+
3
+ # ee.Authenticate()
4
+ # ee.Initialize()
5
+
6
+ from datetime import datetime
7
+
8
+ from openforis_whisp.parameters.config_runtime import (
9
+ geometry_area_column,
10
+ ) # ideally make relative import statement
11
+
12
+ import inspect
13
+
14
+
15
+ import logging
16
+
17
+ # Configure logging
18
+ logging.basicConfig(
19
+ level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
20
+ )
21
+
22
+
23
+ def get_logger(name):
24
+ return logging.getLogger(name)
25
+
26
+
27
+ # Add datasets below
28
+
29
+ # tree cover datasets
30
+
31
+
32
+ # ESA_TC_2020
33
+ def esa_worldcover_trees_prep():
34
+ esa_worldcover_2020_raw = ee.Image("ESA/WorldCover/v100/2020")
35
+ esa_worldcover_trees_2020 = esa_worldcover_2020_raw.eq(95).Or(
36
+ esa_worldcover_2020_raw.eq(10)
37
+ ) # get trees and mnangroves
38
+ return esa_worldcover_trees_2020.rename("ESA_TC_2020")
39
+
40
+
41
+ # EUFO_2020
42
+ def jrc_gfc_2020_prep():
43
+ jrc_gfc2020_raw = ee.ImageCollection("JRC/GFC2020/V2")
44
+ return jrc_gfc2020_raw.mosaic().rename("EUFO_2020")
45
+
46
+
47
+ # JAXA_FNF_2020
48
+ def jaxa_forest_prep():
49
+ jaxa_forest_non_forest_raw = ee.ImageCollection("JAXA/ALOS/PALSAR/YEARLY/FNF4")
50
+ jaxa_forest_non_forest_2020 = (
51
+ jaxa_forest_non_forest_raw.filterDate("2020-01-01", "2020-12-31")
52
+ .select("fnf")
53
+ .mosaic()
54
+ )
55
+ return jaxa_forest_non_forest_2020.lte(2).rename("JAXA_FNF_2020")
56
+
57
+
58
+ # GFC_TC_2020
59
+ def glad_gfc_10pc_prep():
60
+ gfc = ee.Image("UMD/hansen/global_forest_change_2023_v1_11")
61
+ gfc_treecover2000 = gfc.select(["treecover2000"])
62
+ gfc_loss2001_2020 = gfc.select(["lossyear"]).lte(20)
63
+ gfc_treecover2020 = gfc_treecover2000.where(gfc_loss2001_2020.eq(1), 0)
64
+ return gfc_treecover2020.gt(10).rename("GFC_TC_2020")
65
+
66
+
67
+ # GLAD_Primary
68
+ def glad_pht_prep():
69
+ primary_ht_forests2001_raw = ee.ImageCollection(
70
+ "UMD/GLAD/PRIMARY_HUMID_TROPICAL_FORESTS/v1"
71
+ )
72
+ primary_ht_forests2001 = (
73
+ primary_ht_forests2001_raw.select("Primary_HT_forests").mosaic().selfMask()
74
+ )
75
+ gfc = ee.Image("UMD/hansen/global_forest_change_2023_v1_11")
76
+ gfc_loss2001_2020 = gfc.select(["lossyear"]).lte(20)
77
+ return primary_ht_forests2001.where(gfc_loss2001_2020.eq(1), 0).rename(
78
+ "GLAD_Primary"
79
+ )
80
+
81
+
82
+ # TMF_undist (undistrubed forest in 2020)
83
+ def jrc_tmf_undisturbed_prep():
84
+ TMF_undist_2020 = (
85
+ ee.ImageCollection("projects/JRC/TMF/v1_2023/AnnualChanges")
86
+ .select("Dec2020")
87
+ .mosaic()
88
+ .eq(1)
89
+ ) # update from https://github.com/forestdatapartnership/whisp/issues/42
90
+ return TMF_undist_2020.rename("TMF_undist")
91
+
92
+
93
+ # Forest Persistence FDaP
94
+ def fdap_forest_prep():
95
+ fdap_forest_raw = ee.Image(
96
+ "projects/forestdatapartnership/assets/community_forests/ForestPersistence_2020"
97
+ )
98
+ fdap_forest = fdap_forest_raw.gt(0.75)
99
+ return fdap_forest.rename("Forest_FDaP")
100
+
101
+
102
+ ############plantation data
103
+
104
+
105
+ # TMF_plant (plantations in 2020)
106
+ def jrc_tmf_plantation_prep():
107
+ transition = ee.ImageCollection(
108
+ "projects/JRC/TMF/v1_2023/TransitionMap_Subtypes"
109
+ ).mosaic()
110
+ deforestation_year = ee.ImageCollection(
111
+ "projects/JRC/TMF/v1_2023/DeforestationYear"
112
+ ).mosaic()
113
+ plantation = (transition.gte(81)).And(transition.lte(86))
114
+ plantation_2020 = plantation.where(
115
+ deforestation_year.gte(2021), 0
116
+ ) # update from https://github.com/forestdatapartnership/whisp/issues/42
117
+ return plantation_2020.rename("TMF_plant")
118
+
119
+
120
+ # # Oil_palm_Descals
121
+ # NB updated to Descals et al 2024 paper (as opposed to Descals et al 2021 paper)
122
+ def creaf_descals_palm_prep():
123
+ # Load the Global Oil Palm Year of Plantation image and mosaic it
124
+ img = (
125
+ ee.ImageCollection(
126
+ "projects/ee-globaloilpalm/assets/shared/GlobalOilPalm_YoP_2021"
127
+ )
128
+ .mosaic()
129
+ .select("minNBR_date")
130
+ )
131
+
132
+ # Calculate the year of plantation and select all below and including 2020
133
+ oil_palm_plantation_year = img.divide(365).add(1970).floor().lte(2020)
134
+
135
+ # Create a mask for plantations in the year 2020 or earlier
136
+ plantation_2020 = oil_palm_plantation_year.lte(2020).selfMask()
137
+ return plantation_2020.rename("Oil_palm_Descals")
138
+
139
+ # Calculate the year of plantation
140
+ oil_palm_plantation_year = img.divide(365).add(1970).floor().lte(2020)
141
+
142
+ # Create a mask for plantations in the year 2020 or earlier
143
+ plantation_2020 = oil_palm_plantation_year.lte(2020).selfMask()
144
+ return plantation_2020.rename("Oil_palm_Descals")
145
+
146
+
147
+ # Cocoa_ETH
148
+ def eth_kalischek_cocoa_prep():
149
+ return ee.Image("projects/ee-nk-cocoa/assets/cocoa_map_threshold_065").rename(
150
+ "Cocoa_ETH"
151
+ )
152
+
153
+
154
+ # Oil Palm FDaP
155
+ def fdap_palm_prep():
156
+ fdap_palm2020_model_raw = ee.ImageCollection(
157
+ "projects/forestdatapartnership/assets/palm/model_2024a"
158
+ )
159
+ fdap_palm = (
160
+ fdap_palm2020_model_raw.filterDate("2020-01-01", "2020-12-31")
161
+ .mosaic()
162
+ .gt(0.83) # Threshold for Oil Palm
163
+ )
164
+ return fdap_palm.rename("Oil_palm_FDaP")
165
+
166
+
167
+ # Rubber FDaP
168
+ def fdap_rubber_prep():
169
+ fdap_rubber2020_model_raw = ee.ImageCollection(
170
+ "projects/forestdatapartnership/assets/rubber/model_2024a"
171
+ )
172
+ fdap_rubber = (
173
+ fdap_rubber2020_model_raw.filterDate("2020-01-01", "2020-12-31")
174
+ .mosaic()
175
+ .gt(0.93) # Threshold for Rubber
176
+ )
177
+ return fdap_rubber.rename("Rubber_FDaP")
178
+
179
+
180
+ # Cocoa FDaP
181
+ def fdap_cocoa_prep():
182
+ fdap_cocoa2020_model_raw = ee.ImageCollection(
183
+ "projects/forestdatapartnership/assets/cocoa/model_2024a"
184
+ )
185
+ fdap_cocoa = (
186
+ fdap_cocoa2020_model_raw.filterDate("2020-01-01", "2020-12-31")
187
+ .mosaic()
188
+ .gt(0.5) # Threshold for Cocoa
189
+ )
190
+ return fdap_cocoa.rename("Cocoa_FDaP")
191
+
192
+
193
+ # Cocoa_bnetd
194
+ def civ_ocs2020_prep():
195
+ return (
196
+ ee.Image("BNETD/land_cover/v1/2020")
197
+ .select("classification")
198
+ .eq(9)
199
+ .rename("Cocoa_bnetd")
200
+ ) # cocoa from national land cover map for Côte d'Ivoire
201
+
202
+
203
+ # Rubber_RBGE - from Royal Botanical Gardens of Edinburgh (RBGE) NB for 2021
204
+ def rbge_rubber_prep():
205
+ return (
206
+ ee.Image(
207
+ "users/wangyxtina/MapRubberPaper/rRubber10m202122_perc1585DifESAdist5pxPF"
208
+ )
209
+ .unmask()
210
+ .rename("Rubber_RBGE")
211
+ )
212
+
213
+
214
+ #### disturbances by year
215
+
216
+ # RADD_year_2019 to RADD_year_< current year >
217
+ def radd_year_prep():
218
+ from datetime import datetime
219
+
220
+ radd = ee.ImageCollection("projects/radar-wur/raddalert/v1")
221
+
222
+ radd_date = (
223
+ radd.filterMetadata("layer", "contains", "alert").select("Date").mosaic()
224
+ )
225
+ # date of avaialbility
226
+ start_year = 19 ## (starts 2019 in Africa, then 2020 for S America and Asia: https://data.globalforestwatch.org/datasets/gfw::deforestation-alerts-radd/about
227
+
228
+ current_year = (
229
+ datetime.now().year
230
+ % 100
231
+ # NB the % 100 part gets last two digits needed
232
+ )
233
+
234
+ img_stack = None
235
+ # Generate an image based on GFC with one band of forest tree loss per year from 2001 to <current year>
236
+ for year in range(start_year, current_year + 1):
237
+ # gfc_loss_year = gfc.select(['lossyear']).eq(i).And(gfc.select(['treecover2000']).gt(10)) # use any definition of loss
238
+ start = year * 1000
239
+ end = year * 1000 + 365
240
+ radd_year = (
241
+ radd_date.updateMask(radd_date.gte(start))
242
+ .updateMask(radd_date.lte(end))
243
+ .gt(0)
244
+ .rename("RADD_year_" + "20" + str(year))
245
+ )
246
+
247
+ if img_stack is None:
248
+ img_stack = radd_year
249
+ else:
250
+ img_stack = img_stack.addBands(radd_year)
251
+ return img_stack
252
+
253
+
254
+ # TMF_def_2000 to TMF_def_2023
255
+ def tmf_def_per_year_prep():
256
+ # Load the TMF Deforestation annual product
257
+ tmf_def = ee.ImageCollection("projects/JRC/TMF/v1_2023/DeforestationYear").mosaic()
258
+ img_stack = None
259
+ # Generate an image based on GFC with one band of forest tree loss per year from 2001 to 2022
260
+ for i in range(0, 23 + 1):
261
+ tmf_def_year = tmf_def.eq(2000 + i).rename("TMF_def_" + str(2000 + i))
262
+ if img_stack is None:
263
+ img_stack = tmf_def_year
264
+ else:
265
+ img_stack = img_stack.addBands(tmf_def_year)
266
+ return img_stack
267
+
268
+
269
+ # TMF_deg_2000 to TMF_deg_2023
270
+ def tmf_deg_per_year_prep():
271
+ # Load the TMF Degradation annual product
272
+ tmf_def = ee.ImageCollection("projects/JRC/TMF/v1_2023/DegradationYear").mosaic()
273
+ img_stack = None
274
+ # Generate an image based on GFC with one band of forest tree loss per year from 2001 to 2022
275
+ for i in range(0, 23 + 1):
276
+ tmf_def_year = tmf_def.eq(2000 + i).rename("TMF_deg_" + str(2000 + i))
277
+ if img_stack is None:
278
+ img_stack = tmf_def_year
279
+ else:
280
+ img_stack = img_stack.addBands(tmf_def_year)
281
+ return img_stack
282
+
283
+
284
+ # GFC_loss_year_2001 to GFC_loss_year_2023 (correct for version 11)
285
+ def glad_gfc_loss_per_year_prep():
286
+ # Load the Global Forest Change dataset
287
+ gfc = ee.Image("UMD/hansen/global_forest_change_2023_v1_11")
288
+ img_stack = None
289
+ # Generate an image based on GFC with one band of forest tree loss per year from 2001 to 2022
290
+ for i in range(1, 23 + 1):
291
+ gfc_loss_year = (
292
+ gfc.select(["lossyear"]).eq(i).And(gfc.select(["treecover2000"]).gt(10))
293
+ )
294
+ gfc_loss_year = gfc_loss_year.rename("GFC_loss_year_" + str(2000 + i))
295
+ if img_stack is None:
296
+ img_stack = gfc_loss_year
297
+ else:
298
+ img_stack = img_stack.addBands(gfc_loss_year)
299
+ return img_stack
300
+
301
+
302
+ # MODIS_fire_2000 to MODIS_fire_< current year >
303
+ def modis_fire_prep():
304
+ modis_fire = ee.ImageCollection("MODIS/061/MCD64A1")
305
+ start_year = 2000
306
+
307
+ # Determine the last available year by checking the latest image in the collection
308
+ last_image = modis_fire.sort("system:time_start", False).first()
309
+ last_date = ee.Date(last_image.get("system:time_start"))
310
+ end_year = last_date.get("year").getInfo()
311
+
312
+ img_stack = None
313
+
314
+ for year in range(start_year, end_year + 1):
315
+ date_st = f"{year}-01-01"
316
+ date_ed = f"{year}-12-31"
317
+ modis_year = (
318
+ modis_fire.filterDate(date_st, date_ed)
319
+ .mosaic()
320
+ .select(["BurnDate"])
321
+ .gte(0)
322
+ .rename(f"MODIS_fire_{year}")
323
+ )
324
+ img_stack = modis_year if img_stack is None else img_stack.addBands(modis_year)
325
+
326
+ return img_stack
327
+
328
+
329
+ # ESA_fire_2000 to ESA_fire_2020
330
+ def esa_fire_prep():
331
+ esa_fire = ee.ImageCollection("ESA/CCI/FireCCI/5_1")
332
+ start_year = 2001
333
+
334
+ # Determine the last available year by checking the latest image in the collection
335
+ last_image = esa_fire.sort("system:time_start", False).first()
336
+ last_date = ee.Date(last_image.get("system:time_start"))
337
+ end_year = last_date.get("year").getInfo()
338
+
339
+ img_stack = None
340
+
341
+ for year in range(start_year, end_year + 1):
342
+ date_st = f"{year}-01-01"
343
+ date_ed = f"{year}-12-31"
344
+ esa_year = (
345
+ esa_fire.filterDate(date_st, date_ed)
346
+ .mosaic()
347
+ .select(["BurnDate"])
348
+ .gte(0)
349
+ .rename(f"ESA_fire_{year}")
350
+ )
351
+ img_stack = esa_year if img_stack is None else img_stack.addBands(esa_year)
352
+
353
+ return img_stack
354
+
355
+
356
+ # # DIST_alert_2024 to DIST_alert_< current year >
357
+ # # Notes:
358
+ # # 1) so far only available for 2024 onwards in GEE
359
+ # # TO DO - see if gee asset for pre 2020-2024 is available from GLAD team, else download from nasa and put in Whisp assets
360
+ # # 2) masked alerts (as dist alerts are for all vegetation) to JRC EUFO 2020 layer, as close to EUDR definition
361
+ # # TO DO - ask opinions on if others (such as treecover data from GLAD team) should be used instead
362
+
363
+
364
+ # def glad_dist_year_prep():
365
+
366
+ # # Load the vegetation disturbance collections
367
+
368
+ # # Vegetation disturbance status (0-8, class flag, 8-bit)
369
+ # VEGDISTSTATUS = ee.ImageCollection(
370
+ # "projects/glad/HLSDIST/current/VEG-DIST-STATUS"
371
+ # ).mosaic()
372
+ # # Initial vegetation disturbance date (>0: days since 2020-12-31, 16-bit)
373
+ # VEGDISTDATE = ee.ImageCollection(
374
+ # "projects/glad/HLSDIST/current/VEG-DIST-DATE"
375
+ # ).mosaic()
376
+
377
+ # # NB relies on initial date of disturbance - consider if last date needed? : VEGLASTDATE = ee.ImageCollection("projects/glad/HLSDIST/current/VEG-LAST-DATE").mosaic(); # Last assessed observation date (≥1, days, 16-bit)
378
+
379
+ # # Key for high-confidence alerts (values 3, 6, 7, 8)
380
+ # high_conf_values = [3, 6, 7, 8]
381
+ # # where:
382
+ # # 3 = <50% loss, high confidence, ongoing
383
+ # # 6 = ≥50% loss, high confidence, ongoing
384
+ # # 7 = <50% loss, high confidence, finished
385
+ # # 8 = ≥50% loss, high confidence, finished
386
+ # # Note could use <50% loss (i.e. only 6 and 7) for if want to be more strict
387
+
388
+ # # Create high-confidence mask
389
+ # dist_high_conf = VEGDISTSTATUS.remap(
390
+ # high_conf_values, [1] * len(high_conf_values), 0
391
+ # )
392
+
393
+ # # Determine start year and current year dynamically
394
+ # start_year = 2024 # Set the first year of interest
395
+ # current_year = datetime.now().year
396
+
397
+ # # Calculate days since December 31, 2020 for start and end dates (server-side)
398
+ # start_of_2020 = ee.Date("2020-12-31").millis().divide(86400000).int()
399
+
400
+ # # Create a list to hold the yearly images
401
+ # yearly_images = []
402
+
403
+ # for year in range(start_year, current_year + 1):
404
+ # start_of_year = (
405
+ # ee.Date(f"{year}-01-01")
406
+ # .millis()
407
+ # .divide(86400000)
408
+ # .int()
409
+ # .subtract(start_of_2020)
410
+ # )
411
+ # start_of_next_year = (
412
+ # ee.Date(f"{year + 1}-01-01")
413
+ # .millis()
414
+ # .divide(86400000)
415
+ # .int()
416
+ # .subtract(start_of_2020)
417
+ # )
418
+
419
+ # # Filter VEG-DIST-DATE for the selected year
420
+ # dist_year = VEGDISTDATE.gte(start_of_year).And(
421
+ # VEGDISTDATE.lt(start_of_next_year)
422
+ # )
423
+
424
+ # # Apply high-confidence mask and rename the band
425
+ # high_conf_year = dist_year.updateMask(dist_high_conf).rename(
426
+ # f"DIST_year_{year}"
427
+ # )
428
+
429
+ # # Append the year's data to the list
430
+ # yearly_images.append(high_conf_year)
431
+
432
+ # # Combine all yearly images into a single image
433
+ # img_stack = ee.Image.cat(yearly_images)
434
+
435
+ # # Rename the bands correctly
436
+ # band_names = [f"DIST_year_{year}" for year in range(start_year, current_year + 1)]
437
+ # img_stack = img_stack.select(img_stack.bandNames(), band_names)
438
+
439
+ # return img_stack.updateMask(
440
+ # jrc_gfc_2020_prep()
441
+ # ) # mask yearly dist alerts to forest cover in 2020
442
+
443
+
444
+ #### disturbances combined (split into before and after 2020)
445
+
446
+ # RADD_after_2020
447
+ def radd_after_2020_prep():
448
+ from datetime import datetime
449
+
450
+ radd = ee.ImageCollection("projects/radar-wur/raddalert/v1")
451
+
452
+ radd_date = (
453
+ radd.filterMetadata("layer", "contains", "alert").select("Date").mosaic()
454
+ )
455
+ # date of avaialbility
456
+ start_year = 21 ## (starts 2019 in Africa, then 2020 for S America and Asia: https://data.globalforestwatch.org/datasets/gfw::deforestation-alerts-radd/about)
457
+
458
+ current_year = (
459
+ datetime.now().year % 100
460
+ ) # NB the % 100 part gets last two digits needed
461
+ start = start_year * 1000
462
+ end = current_year * 1000 + 365
463
+ return (
464
+ radd_date.updateMask(radd_date.gte(start))
465
+ .updateMask(radd_date.lte(end))
466
+ .gt(0)
467
+ .rename("RADD_after_2020")
468
+ )
469
+
470
+
471
+ # RADD_before_2020
472
+ def radd_before_2020_prep():
473
+ from datetime import datetime
474
+
475
+ radd = ee.ImageCollection("projects/radar-wur/raddalert/v1")
476
+
477
+ radd_date = (
478
+ radd.filterMetadata("layer", "contains", "alert").select("Date").mosaic()
479
+ )
480
+ # date of avaialbility
481
+ start_year = 19 ## (starts 2019 in Africa, then 2020 for S America and Asia: https://data.globalforestwatch.org/datasets/gfw::deforestation-alerts-radd/about)
482
+
483
+ # current_year = datetime.now().year % 100 # NB the % 100 part gets last two digits needed
484
+
485
+ start = start_year * 1000
486
+ end = 20 * 1000 + 365
487
+ return (
488
+ radd_date.updateMask(radd_date.gte(start))
489
+ .updateMask(radd_date.lte(end))
490
+ .gt(0)
491
+ .rename("RADD_before_2020")
492
+ )
493
+
494
+
495
+ # # DIST_after_2020
496
+ # # alerts only for after 2020 currently so need to use date
497
+ # def glad_dist_after_2020_prep():
498
+
499
+ # # Load the vegetation disturbance collections
500
+ # VEGDISTSTATUS = ee.ImageCollection(
501
+ # "projects/glad/HLSDIST/current/VEG-DIST-STATUS"
502
+ # ).mosaic()
503
+
504
+ # # Key for high-confidence alerts (values 3, 6, 7, 8)
505
+ # high_conf_values = [3, 6, 7, 8]
506
+
507
+ # # Create high-confidence mask
508
+ # dist_high_conf = VEGDISTSTATUS.remap(
509
+ # high_conf_values, [1] * len(high_conf_values), 0
510
+ # )
511
+
512
+ # return dist_high_conf.updateMask(jrc_gfc_2020_prep()).rename(
513
+ # "DIST_after_2020"
514
+ # ) # Mask alerts to forest and rename band
515
+
516
+
517
+ # TMF_deg_before_2020
518
+ def tmf_deg_before_2020_prep():
519
+ tmf_deg = ee.ImageCollection("projects/JRC/TMF/v1_2023/DegradationYear").mosaic()
520
+ return (tmf_deg.lte(2020)).And(tmf_deg.gte(2000)).rename("TMF_deg_before_2020")
521
+
522
+
523
+ # TMF_deg_after_2020
524
+ def tmf_deg_after_2020_prep():
525
+ tmf_deg = ee.ImageCollection("projects/JRC/TMF/v1_2023/DegradationYear").mosaic()
526
+ return tmf_deg.gt(2020).rename("TMF_deg_after_2020")
527
+
528
+
529
+ # tmf_def_before_2020
530
+ def tmf_def_before_2020_prep():
531
+ tmf_def = ee.ImageCollection("projects/JRC/TMF/v1_2023/DeforestationYear").mosaic()
532
+ return (tmf_def.lte(2020)).And(tmf_def.gte(2000)).rename("TMF_def_before_2020")
533
+
534
+
535
+ # tmf_def_after_2020
536
+ def tmf_def_after_2020_prep():
537
+ tmf_def = ee.ImageCollection("projects/JRC/TMF/v1_2023/DeforestationYear").mosaic()
538
+ return tmf_def.gt(2020).rename("TMF_def_after_2020")
539
+
540
+
541
+ # GFC_loss_before_2020 (loss within 10 percent cover; includes 2020; correct for version 11)
542
+ def glad_gfc_loss_before_2020_prep():
543
+ # Load the Global Forest Change dataset
544
+ gfc = ee.Image("UMD/hansen/global_forest_change_2023_v1_11")
545
+ gfc_loss = (
546
+ gfc.select(["lossyear"]).lte(20).And(gfc.select(["treecover2000"]).gt(10))
547
+ )
548
+ return gfc_loss.rename("GFC_loss_before_2020")
549
+
550
+
551
+ # GFC_loss_after_2020 (loss within 10 percent cover; correct for version 11)
552
+ def glad_gfc_loss_after_2020_prep():
553
+ # Load the Global Forest Change dataset
554
+ gfc = ee.Image("UMD/hansen/global_forest_change_2023_v1_11")
555
+ gfc_loss = gfc.select(["lossyear"]).gt(20).And(gfc.select(["treecover2000"]).gt(10))
556
+ return gfc_loss.rename("GFC_loss_after_2020")
557
+
558
+
559
+ # MODIS_fire_before_2020
560
+ def modis_fire_before_2020_prep():
561
+ modis_fire = ee.ImageCollection("MODIS/061/MCD64A1")
562
+ start_year = 2000
563
+ end_year = 2020
564
+ date_st = str(start_year) + "-01-01"
565
+ date_ed = str(end_year) + "-12-31"
566
+ return (
567
+ modis_fire.filterDate(date_st, date_ed)
568
+ .mosaic()
569
+ .select(["BurnDate"])
570
+ .gte(0)
571
+ .rename("MODIS_fire_before_2020")
572
+ )
573
+
574
+
575
+ # MODIS_fire_after_2020
576
+ def modis_fire_after_2020_prep():
577
+ modis_fire = ee.ImageCollection("MODIS/061/MCD64A1")
578
+ start_year = 2021
579
+ end_year = datetime.now().year
580
+ date_st = str(start_year) + "-01-01"
581
+ date_ed = str(end_year) + "-12-31"
582
+ return (
583
+ modis_fire.filterDate(date_st, date_ed)
584
+ .mosaic()
585
+ .select(["BurnDate"])
586
+ .gte(0)
587
+ .rename("MODIS_fire_after_2020")
588
+ )
589
+
590
+
591
+ # ESA_fire_before_2020
592
+ def esa_fire_before_2020_prep():
593
+ esa_fire = ee.ImageCollection("ESA/CCI/FireCCI/5_1")
594
+ start_year = 2000
595
+ end_year = 2020
596
+ date_st = str(start_year) + "-01-01"
597
+ date_ed = str(end_year) + "-12-31"
598
+ return (
599
+ esa_fire.filterDate(date_st, date_ed)
600
+ .mosaic()
601
+ .select(["BurnDate"])
602
+ .gte(0)
603
+ .rename("ESA_fire_before_2020")
604
+ )
605
+
606
+
607
+ # ###Combining datasets
608
+
609
+
610
+ def combine_datasets():
611
+ """Combines datasets into a single multiband image, with fallback if assets are missing."""
612
+ img_combined = ee.Image(1).rename(geometry_area_column)
613
+
614
+ # Combine images directly
615
+ for img in [func() for func in list_functions()]:
616
+ try:
617
+ img_combined = img_combined.addBands(img)
618
+ except ee.EEException as e:
619
+ # logger.error(f"Error adding image: {e}")
620
+ print(f"Error adding image: {e}")
621
+
622
+ try:
623
+ # Attempt to print band names to check for errors
624
+ print(img_combined.bandNames().getInfo())
625
+ except ee.EEException as e:
626
+ # logger.error(f"Error printing band names: {e}")
627
+ # logger.info("Running code for filtering to only valid datasets due to error in input")
628
+ print("using valid datasets filter due to error in input")
629
+ # Validate images
630
+ images_to_test = [func() for func in list_functions()]
631
+ valid_imgs = keep_valid_images(images_to_test) # Validate images
632
+
633
+ # Retry combining images after validation
634
+ img_combined = ee.Image(1).rename(geometry_area_column)
635
+ for img in valid_imgs:
636
+ img_combined = img_combined.addBands(img)
637
+
638
+ img_combined = img_combined.multiply(ee.Image.pixelArea())
639
+
640
+ return img_combined
641
+
642
+
643
+ ######helper functions to check images
644
+
645
+
646
+ # list all functions ending with "_prep" (in the current script)
647
+ def list_functions():
648
+ # Use the module's globals to get all defined functions
649
+ current_module = inspect.getmodule(inspect.currentframe())
650
+ functions = [
651
+ func
652
+ for name, func in inspect.getmembers(current_module, inspect.isfunction)
653
+ if name.endswith("_prep")
654
+ ]
655
+ return functions
656
+
657
+
658
+ def keep_valid_images(images):
659
+ """Keeps only valid images."""
660
+ valid_images = []
661
+ for img in images:
662
+ try:
663
+ img.getInfo() # This will raise an exception if the image is invalid
664
+ valid_images.append(img)
665
+ except ee.EEException as e:
666
+ # logger.error(f"Invalid image: {e}")
667
+ print(f"Invalid image: {e}")
668
+ return valid_images
669
+
670
+
671
+ # function to check if an image is valid
672
+ def ee_image_checker(image):
673
+ """
674
+ Tests if the input is a valid ee.Image.
675
+
676
+ Args:
677
+ image: An ee.Image object.
678
+
679
+ Returns:
680
+ bool: True if the input is a valid ee.Image, False otherwise.
681
+ """
682
+ try:
683
+ if ee.Algorithms.ObjectType(image).getInfo() == "Image":
684
+ # Trigger some action on the image to ensure it's a valid image
685
+ image.getInfo() # This will raise an exception if the image is invalid
686
+ return True
687
+ except ee.EEException as e:
688
+ print(f"Image validation failed with EEException: {e}")
689
+ except Exception as e:
690
+ print(f"Image validation failed with exception: {e}")
691
+ return False
692
+
693
+
694
+ # print(combine_valid_datasets().bandNames().getInfo())
695
+ # print(combine_datasets().bandNames().getInfo())