מדריך לניפוי באגים

האלגוריתמים שאתם יוצרים ב-Earth Engine פועלים בענן של Google ומופצים במחשבים רבים. ניפוי באגים יכול להיות מאתגר כי השגיאות יכולות להתרחש בקוד בצד הלקוח או בהפעלה של ההוראות המקודדות בצד השרת, והן נובעות מבעיות בהתאמה לעומס וגם משגיאות תחביריות או לוגיות. הקטעים של התוכנה שפועלים במקום כלשהו בענן לא זמינים לבדיקה, אלא אם מבקשים אותם. במסמך הזה מפורטות שיטות, כלים ופתרונות לניפוי באגים שיעזרו לכם לפתור שגיאות נפוצות ולנטרל באגים בסקריפטים של Earth Engine.

שגיאות תחביר

שגיאות תחביר מתרחשות כשהקוד מפר את הכללים של שפת התכנות (JavaScript או Python ב-Earth Engine). השגיאות האלה מונעות את הרצת הקוד, ובדרך כלל הן מתגלות לפני ההרצה. אם נתקלתם בשגיאת תחביר, כדאי לבדוק היטב את השורה או הודעת השגיאה המודגשות, ולהיעזר במקורות מידע כמו Python Language Reference או Google JavaScript Style Guide. גם כלי לניפוי שגיאות בקוד יכול לעזור לזהות את הבעיות האלה ולתקן אותן.

שגיאות בצד הלקוח

למרות שקוד תקין מבחינה תחבירית, יכולות להיות שגיאות שקשורות ליציבות או ללוגיקה של הסקריפט. בדוגמאות הבאות מופיעות שגיאות שנובעות משימוש במשתנה ובשיטה שלא קיימים.

שגיאה – הקוד הזה לא עובד!

Code Editor‏ (JavaScript)

// Load a Sentinel-2 image.
var image = ee.Image('USGS/SRTMGL1_003');

// Error: "bandNames" is not defined in this scope.
var display = image.visualize({bands: bandNames, min: 0, max: 9000});

// Error: image.selfAnalyze is not a function
var silly = image.selfAnalyze();

הגדרת Python

בדף סביבת Python מפורט מידע על Python API ועל השימוש ב-geemap לפיתוח אינטראקטיבי.

import ee
import geemap.core as geemap

Colab (Python)

# Load a Sentinel-2 image.
image = ee.Image('USGS/SRTMGL1_003')

# NameError: name 'band_names' is not defined.
display = image.visualize(bands=band_names, min=0, max=9000)

# AttributeError: 'Image' object has no attribute 'selfAnalyze'.
silly = image.selfAnalyze()

השגיאה הראשונה מראה שהמשתנה bandNames לא מוגדר בהיקף שבו הוא מופיע. כפתרון, מגדירים את המשתנה או מספקים ארגומנט רשימה לפרמטר bands. השגיאה השנייה ממחישה מה קורה כשקוראים לפונקציה selfAnalyze() שאינה קיימת. מכיוון שזו לא שיטה אמיתית בתמונות, השגיאה מראה שהיא לא פונקציה. בשני המקרים, השגיאה מתארת את הבעיה.

הטמעת סוג אובייקט לא ידוע

השגיאה ...is not a function עשויה להופיע אם ל-Earth Engine אין מידע על סוג המשתנה. ביטויים נפוצים של הבעיה הזו נובעים מהסיבות הבאות:

  • ביצוע פעולה כלשהי על אובייקט שהוחזר על ידי first() (סוג הרכיבים באוסף לא ידוע).
  • ביצוע פעולה כלשהי באובייקט שהוחזר על ידי get() (סוג הרכיב שמאוחסן בנכס לא ידוע).
  • ביצוע פעולה כלשהי בארגומנט של פונקציה (בפונקציה) כשסוג הארגומנט לא ידוע.

דוגמה לאפשרות הראשונה:

שגיאה – הקוד הזה לא עובד!

Code Editor‏ (JavaScript)

var collection = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017');

// Error: collection.first(...).area is not a function
var area = collection.first().area();

הגדרת Python

בדף סביבת Python מפורט מידע על Python API ועל השימוש ב-geemap לפיתוח אינטראקטיבי.

import ee
import geemap.core as geemap

Colab (Python)

collection = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017')

# AttributeError: 'Element' object has no attribute 'area'.
area = collection.first().area()

הפתרון בכל המקרים הוא להמיר את האובייקט מסוג לא ידוע באמצעות המבנה של הסוג הידוע. בהמשך לדוגמה הקודמת, הפתרון הוא להמיר ל-ee.Feature:

הפתרון – משתמשים בהעברה (cast).

Code Editor‏ (JavaScript)

var area = ee.Feature(collection.first()).area();

הגדרת Python

בדף סביבת Python מפורט מידע על Python API ועל השימוש ב-geemap לפיתוח אינטראקטיבי.

import ee
import geemap.core as geemap

Colab (Python)

area = ee.Feature(collection.first()).area()

(חשוב לציין שאפשר להפעיל כאן בבטחה כל שיטה ב-Element, כי זה מה ש-Earth Engine חושבת שהיא).

הימנעות מערבוב בין פונקציות של לקוח ושל שרת

הדוגמה הבאה פחות ברורה:

שגיאה – הקוד הזה לא עושה את מה שאתם רוצים

Code Editor‏ (JavaScript)

// Don't mix EE objects and JavaScript objects.
var image = ee.Image('USGS/SRTMGL1_003');
var nonsense = image + 2;

// You can print this, but it's not what you were hoping for.
print(nonsense);

// Error: g.eeObject.name is not a function
Map.addLayer(nonsense);

הגדרת Python

בדף סביבת Python מפורט מידע על Python API ועל השימוש ב-geemap לפיתוח אינטראקטיבי.

import ee
import geemap.core as geemap

Colab (Python)

# Don't mix EE objects and Python objects.
image = ee.Image('USGS/SRTMGL1_003')
nonsense = image + 2

# TypeError: unsupported operand type(s) for +: 'Image' and 'int'.
display(nonsense)

# TypeError: unsupported operand type(s) for +: 'Image' and 'int'.
m = geemap.Map()
m.add_layer(nonsense)
m

נניח שהמחבר של הקוד הזה התכוון להוסיף את הערך 2 לכל פיקסל בתמונה, זו לא הדרך הנכונה לעשות זאת. באופן ספציפי, הקוד הזה משלב באופן שגוי אובייקט מצד השרת (image) עם אופרטור מצד הלקוח (+). התוצאות עשויות להיות מפתיעות. במקרה הראשון, ההדפסה של nonsense בכלי לעריכת קוד JavaScript תבצע את הפעולה המבוקשת (+) על ידי המרת image ו-2 למחרוזות, ואז שרשור שלהן. המחרוזת שמתקבלת היא לא מכוונה (ב-Python מתרחשת הטלת TypeError). במקרה השני, הוספת nonsense למפה, השגיאה החידתית g.eeObject.name is not a function מוצגת בכלי לעריכת קוד JavaScript כי האובייקט שנוסף למפה, nonsense, הוא מחרוזת ולא אובייקט EE (ב-Python נזרקת הודעת TypeError). כדי למנוע תוצאות לא רצויות ושגיאות לא מפורטות, אל תערבבו בין אובייקטים ופונקציות של שרת לבין אובייקטים, פונקציות או פרימיטיבים של לקוח. הפתרון בדוגמה הזו הוא להשתמש בפונקציית שרת.

הפתרון – שימוש בפונקציית שרת.

Code Editor‏ (JavaScript)

Map.addLayer(image.add(2));

הגדרת Python

בדף סביבת Python מפורט מידע על Python API ועל השימוש ב-geemap לפיתוח אינטראקטיבי.

import ee
import geemap.core as geemap

Colab (Python)

m = geemap.Map()
m.add_layer(image.add(2))
m

פרטים נוספים זמינים בדף לקוח לעומת שרת.

נעילה של דפדפן ב-JavaScript Code Editor

הדפדפן עשוי להקפיא או לנעול אם זמן הריצה של JavaScript בלקוח ארוך מדי, או אם ממתינים למשהו מ-Earth Engine. שני מקורות נפוצים לשגיאה הזו הם לולאות for ו/או getInfo() בקוד של JavaScript Code Editor, כשהתרחיש הגרוע ביותר הוא getInfo() בתוך לולאת for. לולאות For יכולות לגרום לנעילת הדפדפן כי הקוד פועל במחשב. לעומת זאת, הפונקציה getInfo() שולחת בקשה לסנכרון של תוצאת החישוב מ-Earth Engine, וחוסמת עד לקבלת התוצאה. אם החישוב נמשך זמן רב, החסימה עשויה לגרום לנעילת הדפדפן. מומלץ להימנע משימוש בלולאות for וגם ב-getInfo() בזמן העבודה ב-Code Editor. לפרטים נוספים, ראו את הדף לקוח לעומת שרת.

שגיאות בצד השרת

למרות העקביות הלוגית בקוד הלקוח, יכול להיות שיהיו באגים שייחשפו רק בזמן הריצה בשרת. בדוגמה הבאה מוסבר מה קורה כשמנסים לקבל להקה שלא קיימת.

שגיאה – הקוד הזה לא עובד!

Code Editor‏ (JavaScript)

// Load a Sentinel-2 image.
var s2image = ee.Image(
    'COPERNICUS/S2_HARMONIZED/20160625T100617_20160625T170310_T33UVR');

// Error: Image.select: Pattern 'nonBand' did not match any bands.
print(s2image.select(['nonBand']));

הגדרת Python

בדף סביבת Python מפורט מידע על Python API ועל השימוש ב-geemap לפיתוח אינטראקטיבי.

import ee
import geemap.core as geemap

Colab (Python)

# Load a Sentinel-2 image.
s2image = ee.Image(
    'COPERNICUS/S2_HARMONIZED/20160625T100617_20160625T170310_T33UVR'
)

# EEException: Image.select: Band pattern 'non_band' did not match any bands.
print(s2image.select(['non_band']).getInfo())

בדוגמה הזו, השגיאה מעדכנת שאין להקה בשם nonBand. הפתרון הברור הוא לציין שם של להקה שקיימת. כדי למצוא את שמות הלהקות, אפשר להדפיס את התמונה ולבדוק אותה במסוף, או להדפיס את רשימת שמות הלהקות שמוחזרת על ידי image.bandNames().

אי-אפשרות לשינוי

אובייקטים בצד השרת שאתם יוצרים ב-Earth Engine הם immutable. (כל ee.Object הוא Object בצד השרת). כלומר, אם רוצים לבצע שינוי באובייקט, צריך לשמור את המצב המשתנה במשתנה חדש. לדוגמה, לא ניתן להשתמש בקוד הזה כדי להגדיר נכס בתמונה של Sentinel-2:

שגיאה – הקוד הזה לא עושה את מה שאתם רוצים!

Code Editor‏ (JavaScript)

var s2image = ee.Image(
    'COPERNICUS/S2_HARMONIZED/20160625T100617_20160625T170310_T33UVR');
s2image.set('myProperty', 'This image is not assigned to a variable');

// This will not result in an error, but will not find 'myProperty'.
print(s2image.get('myProperty')); // null

הגדרת Python

בדף סביבת Python מפורט מידע על Python API ועל השימוש ב-geemap לפיתוח אינטראקטיבי.

import ee
import geemap.core as geemap

Colab (Python)

s2image = ee.Image(
    'COPERNICUS/S2_HARMONIZED/20160625T100617_20160625T170310_T33UVR'
)
s2image.set('my_property', 'This image is not assigned to a variable')

# This will not result in an error, but will not find 'my_property'.
display(s2image.get('my_property'))  # None

בדוגמה הזו, הפונקציה s2image.set() מחזירה עותק של התמונה עם המאפיין החדש, אבל התמונה ששמורה במשתנה s2image לא משתנה. צריך לשמור את התמונה שמוחזרת על ידי s2image.set() במשתנה חדש. לדוגמה:

הפתרון – שומרים את התוצאה במשתנה.

Code Editor‏ (JavaScript)

s2image = s2image.set('myProperty', 'OK');
print(s2image.get('myProperty')); // OK

הגדרת Python

בדף סביבת Python מפורט מידע על Python API ועל השימוש ב-geemap לפיתוח אינטראקטיבי.

import ee
import geemap.core as geemap

Colab (Python)

s2image = s2image.set('my_property', 'OK')
display(s2image.get('my_property'))  # OK

פונקציות ממופה

הקשר נוסף שבו פונקציות של לקוח ושל שרת לא מתערבבות הוא בפונקציות ממופה. באופן ספציפי, הפעולות שצוינו על ידי הפונקציה הממופת פועלות בענן, ולכן פונקציות לקוח כמו getInfo ו-Export (וגם print והשיטה ב-Map וב-Chart ב-JavaScript Code Editor) לא יפעלו בפונקציות ממפות. לדוגמה:

שגיאה – הקוד הזה לא עובד!

Code Editor‏ (JavaScript)

var collection = ee.ImageCollection('MODIS/006/MOD44B');

// Error: A mapped function's arguments cannot be used in client-side operations
var badMap3 = collection.map(function(image) {
  print(image);
  return image;
});

הגדרת Python

בדף סביבת Python מפורט מידע על Python API ועל השימוש ב-geemap לפיתוח אינטראקטיבי.

import ee
import geemap.core as geemap

Colab (Python)

collection = ee.ImageCollection('MODIS/006/MOD44B')

# Error: A mapped function's arguments cannot be used in client-side operations.
bad_map_3 = collection.map(lambda image: print(image.getInfo()))

השגיאה הזו, שקצת קשה להבין אותה, נובעת מהתהליך שבו Earth Engine משתמשת כדי להפוך את הקוד הזה למערך של הוראות שאפשר להריץ בשרתי Google. אי אפשר להשתמש בפונקציות ובמבני בקרה מצד הלקוח כדי לבצע פעולות על תמונת הארגומנט שמועברת לפונקציה הממופת. כדי להימנע מהשגיאה הזו, מומלץ להימנע משימוש בפונקציות בצד הלקוח בפונקציות ממופה. מידע נוסף על ההבדל בין פונקציות לקוח לפונקציות שרת זמין בדף לקוח לעומת שרת.

לפונקציות ממופה יש דרישות נוספות. לדוגמה, פונקציות ממופה חייבות להחזיר משהו:

שגיאה – הקוד הזה לא עובד!

Code Editor‏ (JavaScript)

var collection = ee.ImageCollection('MODIS/006/MOD44B');

// Error: User-defined methods must return a value.
var badMap1 = collection.map(function(image) {
  // Do nothing.
});

הגדרת Python

בדף סביבת Python מפורט מידע על Python API ועל השימוש ב-geemap לפיתוח אינטראקטיבי.

import ee
import geemap.core as geemap

Colab (Python)

collection = ee.ImageCollection('MODIS/006/MOD44B')

# Error: User-defined methods must return a value.
bad_map_1 = collection.map(lambda image: None)

הפתרון הברור הוא להחזיר משהו. אבל לא כל דבר אפשר להחזיר. באופן ספציפי, פונקציות שממופות על ImageCollection או על FeatureCollection חייבות להחזיר Image או Feature. לדוגמה, אי אפשר להחזיר תאריך מפונקציה שממופת על ImageCollection:

שגיאה – הקוד הזה לא עובד!

Code Editor‏ (JavaScript)

var collection = ee.ImageCollection('MODIS/006/MOD44B');

var badMap2 = collection.map(function(image) {
  return image.date();
});

// Error: Collection.map: A mapped algorithm must return a Feature or Image.
print(badMap2);

הגדרת Python

בדף סביבת Python מפורט מידע על Python API ועל השימוש ב-geemap לפיתוח אינטראקטיבי.

import ee
import geemap.core as geemap

Colab (Python)

collection = ee.ImageCollection('MODIS/006/MOD44B')

bad_map_2 = collection.map(lambda image: image.date())

# EEException: Collection.map:
# A mapped algorithm must return a Feature or Image.
print(bad_map_2.getInfo())

כדי למנוע זאת, צריך להחזיר את קובץ התמונה של הקלט עם קבוצת נכסים חדשה. לאחר מכן, אם אתם צריכים רשימה של התאריכים של התמונות באוסף, תוכלו להשתמש ב-aggregate_array():

הפתרון – מגדירים נכס.

Code Editor‏ (JavaScript)

var collection = ee.ImageCollection('MODIS/006/MOD44B');

var okMap2 = collection.map(function(image) {
  return image.set('date', image.date());
});
print(okMap2);

// Get a list of the dates.
var datesList = okMap2.aggregate_array('date');
print(datesList);

הגדרת Python

בדף סביבת Python מפורט מידע על Python API ועל השימוש ב-geemap לפיתוח אינטראקטיבי.

import ee
import geemap.core as geemap

Colab (Python)

collection = ee.ImageCollection('MODIS/006/MOD44B')

ok_map_2 = collection.map(lambda image: image.set('date', image.date()))
print(ok_map_2.getInfo())

# Get a list of the dates.
dates_list = ok_map_2.aggregate_array('date')
print(dates_list.getInfo())

שגיאות פרוצדורליות

התבנית הוחלתה על תמונה ללא פסים

השגיאה "Pattern 'my_band' was applied to an Image with no bands" מציינת שיש קריאה ל-ee.Image.select() של תמונה עם רשימת פס ריק. כדי לטפל בבעיה, אפשר לבצע את הפעולות הבאות:

  • אם התמונה נוצרת מ-ImageCollection עם reducer או באמצעות הקריאות first() או toBands(), חשוב לוודא שקולקציית המקור לא ריקה.
  • אם התמונה נוצרת ממילון באמצעות ee.Dictionary().toImage(), צריך לוודא שהמילון לא ריק.
  • אם התמונה היא עצמאית, צריך לוודא שיש בה נתונים (ולא רק ee.Image(0)).

שגיאות בהתאמה לעומס

יכול להיות שסקריפט תקין מבחינה תחבירית, ללא שגיאות לוגיות, ומייצג קבוצה תקינה של הוראות לשרת, אבל במהלך ביצוע החישובים במקביל, יכול להיות שהאובייקטים שייווצרו יהיו גדולים מדי, רבים מדי או שהחישוב שלהם יימשך זמן רב מדי. במקרה כזה, תוצג הודעת שגיאה על כך שאי אפשר לשנות את קנה המידה של האלגוריתם. בדרך כלל קשה יותר לאבחן ולפתור את השגיאות האלה. דוגמאות לשגיאות מהסוג הזה:

  • פג הזמן הקצוב לחישוב
  • יותר מדי צבירויות בו-זמנית
  • חריגה ממגבלת הזיכרון של המשתמש
  • אירעה שגיאה פנימית

שיפור התאמת הקוד לעומס יאפשר לכם לקבל תוצאות מהר יותר, וגם לשפר את הזמינות של משאבי המחשוב לכל המשתמשים. כל סוג של שגיאה מתואר בקטעים הבאים, אחרי סקירה קצרה על reduceRegion(), פונקציה נפוצה שידועה ביכולתה לגרום לכל סוג של שגיאה שקשורה להתאמה לעומס.

reduceRegion()

הבעיה היא ש-reduceRegion() צורך כמות גדולה של פיקסלים, וכתוצאה מכך הוא יכול לגרום למגוון רחב של שגיאות. עם זאת, יש גם פרמטרים שאפשר להשתמש בהם כדי לשלוט בחשבון, וכך למנוע את השגיאות. לדוגמה, כדאי להימנע מההפחתה הבאה:

שגיאה – הקוד הזה לא עובד!

Code Editor‏ (JavaScript)

var absurdComputation = ee.Image(1).reduceRegion({
  reducer: 'count',
  geometry: ee.Geometry.Rectangle([-180, -90, 180, 90], null, false),
  scale: 100,
});

// Error: Image.reduceRegion: Too many pixels in the region.
//        Found 80300348117, but only 10000000 allowed.
print(absurdComputation);

הגדרת Python

בדף סביבת Python מפורט מידע על Python API ועל השימוש ב-geemap לפיתוח אינטראקטיבי.

import ee
import geemap.core as geemap

Colab (Python)

absurd_computation = ee.Image(1).reduceRegion(
    reducer='count',
    geometry=ee.Geometry.Rectangle([-180, -90, 180, 90], None, False),
    scale=100,
)

# EEException: Image.reduceRegion: Too many pixels in the region.
#        Found 80300348117, but only 10000000 allowed.
print(absurd_computation.getInfo())

הדוגמה הזו היא רק להמחשה. מטרת השגיאה הזו היא לשאול אתכם אם אתם באמת רוצים לצמצם 80300348117 פיקסלים (כלומר 80 מיליארד). אם לא, צריך להגדיל את הערך של scale (גודל הפיקסל במטרים) בהתאם, או להגדיר את bestEffort כ-true כדי לחשב מחדש את קנה המידה הגדול יותר באופן אוטומטי. פרטים נוספים על הפרמטרים האלה מופיעים בדף reduceRegion().

פג הזמן הקצוב לחישוב

נניח שצריך את כל הפיקסלים האלה לצורך החישוב. במקרה כזה, אפשר להגדיל את הפרמטר maxPixels כדי לאפשר את החישוב. עם זאת, יידרשו ל-Earth Engine כמה זמן כדי לסיים את החישוב. כתוצאה מכך, עשויה להופיע השגיאה 'התוקף של החישוב פג':

רע – אל תעשו את זה!

Code Editor‏ (JavaScript)

var ridiculousComputation = ee.Image(1).reduceRegion({
  reducer: 'count',
  geometry: ee.Geometry.Rectangle([-180, -90, 180, 90], null, false),
  scale: 100,
  maxPixels: 1e11
});

// Error: Computation timed out.
print(ridiculousComputation);

הגדרת Python

בדף סביבת Python מפורט מידע על Python API ועל השימוש ב-geemap לפיתוח אינטראקטיבי.

import ee
import geemap.core as geemap

Colab (Python)

ridiculous_computation = ee.Image(1).reduceRegion(
    reducer='count',
    geometry=ee.Geometry.Rectangle([-180, -90, 180, 90], None, False),
    scale=100,
    maxPixels=int(1e11),
)

# Error: Computation timed out.
print(ridiculous_computation.getInfo())

המשמעות של השגיאה הזו היא שמערכת Earth Engine חיכתה כחמש דקות לפני שהפסיקה את החישוב. הייצוא מאפשר ל-Earth Engine לבצע את החישוב בסביבה עם זמני ריצה ארוכים יותר (אבל לא עם יותר זיכרון). מכיוון שערך ההחזרה של reduceRegion() הוא מילון, אפשר להשתמש במילון כדי להגדיר את המאפיינים של תכונה עם גיאומטריה null:

טוב – אפשר להשתמש ב-Export.

Code Editor‏ (JavaScript)

Export.table.toDrive({
  collection: ee.FeatureCollection([
    ee.Feature(null, ridiculousComputation)
  ]),
  description: 'ridiculousComputation',
  fileFormat: 'CSV'
});

הגדרת Python

בדף סביבת Python מפורט מידע על Python API ועל השימוש ב-geemap לפיתוח אינטראקטיבי.

import ee
import geemap.core as geemap

Colab (Python)

task = ee.batch.Export.table.toDrive(
    collection=ee.FeatureCollection([ee.Feature(None, ridiculous_computation)]),
    description='ridiculous_computation',
    fileFormat='CSV',
)
# task.start()

יותר מדי צבירויות בו-זמנית

החלק 'צבירויות' בשגיאה הזו מתייחס לפעולות שמפוזרות במספר מכונות (למשל, הפחתות שמתפרשות על פני יותר מטריקס אחד). ב-Earth Engine יש מגבלות כדי למנוע הפעלה של יותר מדי צבירויות כאלה בו-זמנית. בדוגמה הזו, השגיאה 'Too many concurrent aggregations' מופעלת על ידי הפחתה במפה:

רע – אל תעשו את זה!

Code Editor‏ (JavaScript)

var collection = ee.ImageCollection('LANDSAT/LT05/C02/T1')
    .filterBounds(ee.Geometry.Point([-123, 43]));

var terribleAggregations = collection.map(function(image) {
  return image.set(image.reduceRegion({
    reducer: 'mean',
    geometry: image.geometry(),
    scale: 30,
    maxPixels: 1e9
  }));
});

// Error: Quota exceeded: Too many concurrent aggregations.
print(terribleAggregations);

הגדרת Python

בדף סביבת Python מפורט מידע על Python API ועל השימוש ב-geemap לפיתוח אינטראקטיבי.

import ee
import geemap.core as geemap

Colab (Python)

collection = ee.ImageCollection('LANDSAT/LT05/C02/T1').filterBounds(
    ee.Geometry.Point([-123, 43])
)


def apply_mean_aggregation(image):
  return image.set(
      image.reduceRegion(
          reducer='mean',
          geometry=image.geometry(),
          scale=30,
          maxPixels=int(1e9),
      )
  )


terrible_aggregations = collection.map(apply_mean_aggregation)

# EEException: Computation timed out.
print(terrible_aggregations.getInfo())

בהנחה שהמטרה של הקוד הזה היא לקבל נתונים סטטיסטיים של תמונות לכל תמונה, פתרון אפשרי אחד הוא Export את התוצאה. לדוגמה, על סמך העובדה ש-ImageCollection הוא גם FeatureCollection, אפשר לייצא את המטא-נתונים שמשויכים לתמונות כטבלה:

טוב – אפשר להשתמש ב-Export.

Code Editor‏ (JavaScript)

Export.table.toDrive({
  collection: terribleAggregations,
  description: 'terribleAggregations',
  fileFormat: 'CSV'
});

הגדרת Python

בדף סביבת Python מפורט מידע על Python API ועל השימוש ב-geemap לפיתוח אינטראקטיבי.

import ee
import geemap.core as geemap

Colab (Python)

task = ee.batch.Export.table.toDrive(
    collection=terrible_aggregations,
    description='terrible_aggregations',
    fileFormat='CSV',
)
# task.start()

חריגה ממגבלת הזיכרון של המשתמש

אחת מהדרכים להרצה במקביל של אלגוריתמים ב-Earth Engine היא לפצל את הקלט לתמונות מרובעות, להריץ את אותו חישוב בנפרד בכל תמונה מרובעת ואז לשלב את התוצאות. כתוצאה מכך, כל הקלט הנדרש לחישוב של משבצת פלט צריך להיכנס לזיכרון. לדוגמה, אם הקלט הוא תמונה עם הרבה פסים, יכול להיות שיידרש הרבה זיכרון אם כל הפסים ישמשו לחישוב. כדי להמחיש את הבעיה, בדוגמה הזו נעשה שימוש בזיכרון רב מדי, כי המערכת מאלצת (באופן מיותר) להציג קולקציה שלמה של תמונות באריח אחד:

רע – אל תעשו את זה!

Code Editor‏ (JavaScript)

var bands = ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'];
var memoryHog = ee.ImageCollection('LANDSAT/LT05/C02/T1').select(bands)
  .toArray()
  .arrayReduce(ee.Reducer.mean(), [0])
  .arrayProject([1])
  .arrayFlatten([bands])
  .reduceRegion({
    reducer: 'mean',
    geometry: ee.Geometry.Point([-122.27, 37.87]).buffer(1000),
    scale: 1,
    bestEffort: true,
  });

// Error: User memory limit exceeded.
print(memoryHog);

הגדרת Python

בדף סביבת Python מפורט מידע על Python API ועל השימוש ב-geemap לפיתוח אינטראקטיבי.

import ee
import geemap.core as geemap

Colab (Python)

bands = ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7']
memory_hog = (
    ee.ImageCollection('LANDSAT/LT05/C02/T1')
    .select(bands)
    .toArray()
    .arrayReduce(ee.Reducer.mean(), [0])
    .arrayProject([1])
    .arrayFlatten([bands])
    .reduceRegion(
        reducer=ee.Reducer.mean(),
        geometry=ee.Geometry.Point([-122.27, 37.87]).buffer(1000),
        scale=1,
        bestEffort=True,
    )
)

# EEException: User memory limit exceeded.
print(memory_hog.getInfo())

הקוד הגרוע הזה הוא דוגמה אחת לכך שלא כדאי להשתמש במערכים אלא אם אתם באמת צריכים (ראו גם את הקטע 'הימנעות ממרה מיותרת של טיפוסים'). כשהאוסף הזה מומר למערך עצום, צריך לטעון את המערך לזיכרון בבת אחת. מכיוון שמדובר בסדרה ארוכה של תמונות, המערך גדול ולא יתאים לזיכרון.

פתרון אפשרי אחד הוא להגדיר את הפרמטר tileScale לערך גבוה יותר. ערכים גבוהים יותר של tileScale יוצרים משבצות קטנות יותר ביחס של tileScale^2. לדוגמה, הקוד הבא מאפשר לחישוב להצליח:

Code Editor‏ (JavaScript)

var bands = ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'];
var smallerHog = ee.ImageCollection('LANDSAT/LT05/C02/T1').select(bands)
  .toArray()
  .arrayReduce(ee.Reducer.mean(), [0])
  .arrayProject([1])
  .arrayFlatten([bands])
  .reduceRegion({
    reducer: 'mean',
    geometry: ee.Geometry.Point([-122.27, 37.87]).buffer(1000),
    scale: 1,
    bestEffort: true,
    tileScale: 16
  });

print(smallerHog);

הגדרת Python

בדף סביבת Python מפורט מידע על Python API ועל השימוש ב-geemap לפיתוח אינטראקטיבי.

import ee
import geemap.core as geemap

Colab (Python)

bands = ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7']
smaller_hog = (
    ee.ImageCollection('LANDSAT/LT05/C02/T1')
    .select(bands)
    .toArray()
    .arrayReduce(ee.Reducer.mean(), [0])
    .arrayProject([1])
    .arrayFlatten([bands])
    .reduceRegion(
        reducer=ee.Reducer.mean(),
        geometry=ee.Geometry.Point([-122.27, 37.87]).buffer(1000),
        scale=1,
        bestEffort=True,
        tileScale=16,
    )
)

print(smaller_hog.getInfo())

עם זאת, הפתרון המועדף הוא לא להשתמש במערכים ללא צורך, כך שלא תצטרכו להתעסק ב-tileScale בכלל:

טוב – הימנעו מ-Arrays!

Code Editor‏ (JavaScript)

var bands = ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'];
var okMemory = ee.ImageCollection('LANDSAT/LT05/C02/T1').select(bands)
  .mean()
  .reduceRegion({
    reducer: 'mean',
    geometry: ee.Geometry.Point([-122.27, 37.87]).buffer(1000),
    scale: 1,
    bestEffort: true,
  });

print(okMemory);

הגדרת Python

בדף סביבת Python מפורט מידע על Python API ועל השימוש ב-geemap לפיתוח אינטראקטיבי.

import ee
import geemap.core as geemap

Colab (Python)

bands = ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7']
ok_memory = (
    ee.ImageCollection('LANDSAT/LT05/C02/T1')
    .select(bands)
    .mean()
    .reduceRegion(
        reducer=ee.Reducer.mean(),
        geometry=ee.Geometry.Point([-122.27, 37.87]).buffer(1000),
        scale=1,
        bestEffort=True,
    )
)

print(ok_memory.getInfo())

אלא אם צריך לפתור שגיאת זיכרון, לא כדאי להגדיר את tileScale כי משבצות קטנות יותר גורמות גם לעומס צדדי גדול יותר.

שגיאות פנימיות

יכול להיות שתופיע שגיאה שנראית כך:

אם מופיעה השגיאה הזו, לוחצים על הקישור 'דיווח על שגיאה' שמופיע במסוף של עורך הקוד של JavaScript. אפשר גם לשלוח משוב מהלחצן עזרה. השגיאה הזו יכולה לנבוע משגיאות לוגיות בסקריפט שמתגלות רק בזמן הריצה, או מבעיה בפעולה הפנימית של Earth Engine. בכל מקרה, השגיאה לא מספקת מידע ועליך לדווח עליה כדי שנוכל לתקן אותה.

שגיאות פנימיות כוללות מזהה request, כמו זה:

המחרוזות האלה משמשות כמזהים ייחודיים שיעזרו לצוות Earth Engine לזהות בעיות ספציפיות. צריך לכלול את המחרוזת הזו בדוחות על באגים.

שיטות לניפוי באגים

כתבתם את הקוד של הניתוח, הפעלתם אותו וקיבלתם שגיאה. מה עכשיו? בקטע הזה מתוארות שיטות כלליות לניפוי באגים כדי לבודד את הבעיה ולתקן אותה.

בדיקת משתנים ושכבות מפה

נניח שיש לכם ניתוח מורכב מאוד שמניב שגיאה. אם לא ברור מהי המקור של השגיאה, אסטרטגיה ראשונית טובה היא להדפיס או להציג באופן חזותי את האובייקטים הביניים ולבדוק אותם כדי לוודא שהמבנה של האובייקט תואם ללוגיקה שבסקריפט. באופן ספציפי, אפשר לבדוק את ערכי הפיקסלים של השכבות שנוספו למפה באמצעות הכלי Code Editor או הכלי geemap inspector. אם אתם רוצים להדפיס משהו, חשוב להרחיב את המאפיינים שלו באמצעות הסמלים של ה-zippies (▶). כדאי לבדוק את הדברים הבאים:

  • שמות של להקות. האם שמות הלהקות בתמונות תואמים לקוד שלכם?
  • ערכי פיקסלים. האם לנתונים יש את הטווח הנכון? האם הוא מוצפן כראוי?
  • Null. האם יש ערכים null שלא אמורים להיות?
  • מידות. האם הגודל אפס כשלא צריך להיות?

aside()

יכול להיות שזה יהיה קשה להעביר כל שלב ביניים בניתוח למשתנה כדי שאפשר יהיה להדפיס אותו ולבדוק אותו. כדי להדפיס ערכים ביניים משרשור ארוך של קריאות לפונקציות, אפשר להשתמש בשיטה aside(). לדוגמה:

Code Editor‏ (JavaScript)

var image = ee.Image(ee.ImageCollection('COPERNICUS/S2')
    .filterBounds(ee.Geometry.Point([-12.29, 168.83]))
    .aside(print)
    .filterDate('2011-01-01', '2016-12-31')
    .first());

הגדרת Python

בדף סביבת Python מפורט מידע על Python API ועל השימוש ב-geemap לפיתוח אינטראקטיבי.

import ee
import geemap.core as geemap

Colab (Python)

image = ee.Image(
    ee.ImageCollection('COPERNICUS/S2_HARMONIZED')
    .filterBounds(ee.Geometry.Point([-12.29, 168.83]))
    .aside(display)
    .filterDate('2011-01-01', '2016-12-31')
    .first()
)

חשוב לזכור ש-aside(print) (JavaScript Code Editor) ו-aside(display) (Python geemap) קוראים לפונקציות בצד הלקוח, ועדיין הקוד ייכשל בפונקציות הממופות. אפשר גם להשתמש ב-aside עם פונקציות מוגדרות על ידי משתמשים. לדוגמה:

Code Editor‏ (JavaScript)

var composite = ee.ImageCollection('LANDSAT/LC08/C02/T1_TOA')
    .filterBounds(ee.Geometry.Point([106.91, 47.91]))
    .map(function(image) {
      return image.addBands(image.normalizedDifference(['B5', 'B4']));
    })
    .aside(Map.addLayer, {bands: ['B4', 'B3', 'B2'], max: 0.3}, 'collection')
    .qualityMosaic('nd');

Map.setCenter(106.91, 47.91, 11);
Map.addLayer(composite, {bands: ['B4', 'B3', 'B2'], max: 0.3}, 'composite');

הגדרת Python

בדף סביבת Python מפורט מידע על Python API ועל השימוש ב-geemap לפיתוח אינטראקטיבי.

import ee
import geemap.core as geemap

Colab (Python)

m = geemap.Map()
m.set_center(106.91, 47.91, 11)

composite = (
    ee.ImageCollection('LANDSAT/LC08/C02/T1_TOA')
    .filterBounds(ee.Geometry.Point([106.91, 47.91]))
    .map(lambda image: image.addBands(image.normalizedDifference(['B5', 'B4'])))
    .aside(m.add_layer, {'bands': ['B4', 'B3', 'B2'], 'max': 0.3}, 'collection')
    .qualityMosaic('nd')
)

m.add_layer(composite, {'bands': ['B4', 'B3', 'B2'], 'max': 0.3}, 'composite')
m

הפעלת פונקציה ב-first()

הדפסה והצגה חזותית הן שימושיות לניפוי באגים כשהן זמינות, אבל כשמנפים באגים בפונקציה שממופתת על אוסף, אי אפשר להדפיס בפונקציה, כפי שמתואר בקטע על פונקציות ממפות. במקרה כזה, כדאי לבודד את הרכיבים הבעייתיים באוסף ולבדוק את הפונקציה הממופת ברכיב בודד. כשבודקים את הפונקציה בלי למפות אותה, אפשר להשתמש בהצהרות print כדי להבין את הבעיה. הנה דוגמה.

שגיאה – הקוד הזה לא עובד!

Code Editor‏ (JavaScript)

var image = ee.Image(
    'COPERNICUS/S2_HARMONIZED/20150821T111616_20160314T094808_T30UWU');

var someFeatures = ee.FeatureCollection([
  ee.Feature(ee.Geometry.Point([-2.02, 48.43])),
  ee.Feature(ee.Geometry.Point([-2.80, 48.37])),
  ee.Feature(ee.Geometry.Point([-1.22, 48.29])),
  ee.Feature(ee.Geometry.Point([-1.73, 48.65])),
]);

var problem = someFeatures.map(function(feature) {

  var dictionary = image.reduceRegion({
    reducer: 'first',
    geometry: feature.geometry(),
    scale: 10,
  });

  return feature.set({
    result: ee.Number(dictionary.get('B5'))
                .divide(dictionary.get('B4'))
  });
});

// Error in map(ID=2):
//  Number.divide: Parameter 'left' is required.
print(problem);

הגדרת Python

בדף סביבת Python מפורט מידע על Python API ועל השימוש ב-geemap לפיתוח אינטראקטיבי.

import ee
import geemap.core as geemap

Colab (Python)

image = ee.Image(
    'COPERNICUS/S2_HARMONIZED/20150821T111616_20160314T094808_T30UWU'
)

some_features = ee.FeatureCollection([
    ee.Feature(ee.Geometry.Point([-2.02, 48.43])),
    ee.Feature(ee.Geometry.Point([-2.80, 48.37])),
    ee.Feature(ee.Geometry.Point([-1.22, 48.29])),
    ee.Feature(ee.Geometry.Point([-1.73, 48.65])),
])


# Define a function to be mapped over the collection.
def function_to_map(feature):
  dictionary = image.reduceRegion(
      reducer=ee.Reducer.first(), geometry=feature.geometry(), scale=10
  )

  return feature.set(
      {'result': ee.Number(dictionary.get('B5')).divide(dictionary.get('B4'))}
  )


problem = some_features.map(function_to_map)

# EEException: Error in map(ID=2):
#  Number.divide: Parameter 'left' is required.
print(problem.getInfo())

כדי לנפות באגים, כדאי לבדוק את השגיאה. למרבה המזל, הודעת השגיאה הזו מאפשרת לכם לדעת שיש בעיה בתכונה עם ID=2. כדי להמשיך לבדוק את הנושא, כדאי לבצע קצת רפאקציה של הקוד. באופן ספציפי, אי אפשר להשתמש בהצהרות print בפונקציה כשהיא ממופה לאוסף, כפי שמתואר בקטע הזה. מטרת ניפוי הבאגים היא לבודד את התכונה הבעייתית ולהריץ את הפונקציה עם כמה הצהרות print. עם אותה תמונה ותכונות שהוגדרו בעבר:

Code Editor‏ (JavaScript)

// Define a function to be mapped over the collection.
var functionToMap = function(feature) {

  var dictionary = image.reduceRegion({
    reducer: 'first',
    geometry: feature.geometry(),
    scale: 10,
  });

  // Debug:
  print(dictionary);

  return feature.set({
    result: ee.Number(dictionary.get('B5'))
                .divide(dictionary.get('B4'))
  });
};

// Isolate the feature that's creating problems.
var badFeature = ee.Feature(someFeatures
    .filter(ee.Filter.eq('system:index', '2'))
    .first());

// Test the function with print statements added.
functionToMap(badFeature);

// Inspect the bad feature in relation to the image.
Map.centerObject(badFeature, 11);
Map.addLayer(badFeature, {}, 'bad feature');
Map.addLayer(image, {bands: ['B4', 'B3', 'B2'], max: 3000}, 'image');

הגדרת Python

בדף סביבת Python מפורט מידע על Python API ועל השימוש ב-geemap לפיתוח אינטראקטיבי.

import ee
import geemap.core as geemap

Colab (Python)

# Define a function to be mapped over the collection.
def function_to_map(feature):
  dictionary = image.reduceRegion(
      reducer=ee.Reducer.first(), geometry=feature.geometry(), scale=10
  )

  # Debug:
  display(dictionary)

  return feature.set(
      {'result': ee.Number(dictionary.get('B5')).divide(dictionary.get('B4'))}
  )


# Isolate the feature that's creating problems.
bad_feature = ee.Feature(
    some_features.filter(ee.Filter.eq('system:index', '2')).first()
)

# Test the function with print statements added.
function_to_map(bad_feature)

# Inspect the bad feature in relation to the image.
m = geemap.Map()
m.center_object(bad_feature, 11)
m.add_layer(bad_feature, {}, 'bad feature')
m.add_layer(image, {'bands': ['B4', 'B3', 'B2'], 'max': 3000}, 'image')
m

עכשיו, מכיוון שהפונקציה פועלת רק על תכונה אחת, אפשר להוסיף קריאה ל-print‏ (`display` ב-Python geemap). בודקים את האובייקט המודפס ומגלים (אהה!) שהאובייקט שמוחזר על ידי reduceRegion() מכיל ערכים null לכל הלהקות. זה מסביר למה החילוק נכשל: אי אפשר לחלוק null ב-null. למה הוא null מלכתחילה? כדי לבדוק את הבעיה, מוסיפים למפה את קובץ התמונה ואת התכונה הלא תקינה ומרכזים את התצוגה על התכונה הלא תקינה. כך תוכלו לגלות שהבעיה נובעת מהעובדה שהנקודה נמצאת מחוץ לגבולות התמונה. על סמך הגילוי הזה, הקוד שבו בוצע ניפוי באגים הוא:

Code Editor‏ (JavaScript)

var functionToMap = function(feature) {
  var dictionary = image.reduceRegion({
    reducer: 'first',
    geometry: feature.geometry(),
    scale: 10,
  });
  return feature.set({
    result: ee.Number(dictionary.get('B5'))
                .divide(dictionary.get('B4'))
  });
};

var noProblem = someFeatures
    .filterBounds(image.geometry())
    .map(functionToMap);

print(noProblem);

הגדרת Python

בדף סביבת Python מפורט מידע על Python API ועל השימוש ב-geemap לפיתוח אינטראקטיבי.

import ee
import geemap.core as geemap

Colab (Python)

def function_to_map(feature):
  dictionary = image.reduceRegion(
      reducer=ee.Reducer.first(), geometry=feature.geometry(), scale=10
  )

  return feature.set(
      {'result': ee.Number(dictionary.get('B5')).divide(dictionary.get('B4'))}
  )


no_problem = some_features.filterBounds(image.geometry()).map(function_to_map)

display(no_problem)

כלי לניתוח ביצועים (profiler)

כלי הניתוח מספק מידע על זמן ה-EECU ועל השימוש בזיכרון (לכל אלגוריתם ונכס) שנובעים מהחישובים שבוצעו בזמן שהוא מופעל. כדי למצוא מידע על הפעולות שצורכות הכי הרבה משאבים, מחפשים את הרשומות בחלק העליון של הכלי לניתוח פרופיל. בסקריפטים ארוכים או לא יעילים, הרשומות בחלק העליון של הכלי למעקב אחר ביצועים מספקות רמזים לגבי המקומות שבהם כדאי להתמקד כדי לבצע אופטימיזציה של הסקריפט. הערה חשובה: הכלי למעקב ביצועים משפיע בעצמו על הביצועים של הסקריפט, לכן צריך להפעיל אותו רק כשצריך.