1. סקירה כללית
בשיעור הראשון של ה-Lab הזה, תעלו תמונות לקטגוריה. הפעולה הזו תיצור אירוע של יצירת קובץ, שיטופל על ידי פונקציה. הפונקציה תבצע קריאה ל-Vision API כדי לבצע ניתוח תמונות ולשמור את התוצאות במאגר נתונים.

מה תלמדו
- Cloud Storage
- Cloud Functions
- Cloud Vision API
- Cloud Firestore
2. הגדרה ודרישות
הגדרת סביבה בקצב אישי
- נכנסים ל-מסוף Google Cloud ויוצרים פרויקט חדש או משתמשים בפרויקט קיים. אם עדיין אין לכם חשבון Gmail או Google Workspace, אתם צריכים ליצור חשבון.



- שם הפרויקט הוא השם המוצג למשתתפים בפרויקט. זו מחרוזת תווים שלא נמצאת בשימוש ב-Google APIs. אפשר לעדכן את המיקום הזה בכל שלב.
- מזהה הפרויקט חייב להיות ייחודי לכל הפרויקטים ב-Google Cloud, והוא קבוע (אי אפשר לשנות אותו אחרי שהוא מוגדר). מסוף Cloud יוצר באופן אוטומטי מחרוזת ייחודית, ובדרך כלל לא צריך לדעת מה היא. ברוב ה-Codelabs, תצטרכו להפנות למזהה הפרויקט (בדרך כלל הוא מזוהה כ-
PROJECT_ID). אם אתם לא אוהבים את המזהה שנוצר, אתם יכולים ליצור מזהה אקראי אחר. אפשר גם לנסות שם משתמש משלכם ולבדוק אם הוא זמין. אי אפשר לשנות את ההגדרה הזו אחרי השלב הזה, והיא תישאר כזו למשך הפרויקט. - לידיעתכם, יש ערך שלישי, מספר פרויקט, שחלק מממשקי ה-API משתמשים בו. מידע נוסף על שלושת הערכים האלה מופיע במאמרי העזרה.
- בשלב הבא, תצטרכו להפעיל את החיוב במסוף Cloud כדי להשתמש במשאבי Cloud או בממשקי API של Cloud. העלות של התרגול הזה לא אמורה להיות גבוהה, ואולי לא תהיה עלות בכלל. כדי להשבית את המשאבים ולא לחייב אתכם אחרי שתסיימו את המדריך הזה, תוכלו למחוק את המשאבים שיצרתם או למחוק את כל הפרויקט. משתמשים חדשים ב-Google Cloud זכאים לתוכנית תקופת ניסיון בחינם בשווי 300$.
מפעילים את Cloud Shell
אפשר להפעיל את Google Cloud מרחוק מהמחשב הנייד, אבל ב-Codelab הזה נשתמש ב-Google Cloud Shell, סביבת שורת פקודה שפועלת בענן.
ב-מסוף Google Cloud, לוחצים על סמל Cloud Shell בסרגל הכלים שבפינה הימנית העליונה:

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

המכונה הווירטואלית הזו כוללת את כל הכלים שדרושים למפתחים. יש בה ספריית בית בנפח מתמיד של 5GB והיא פועלת ב-Google Cloud, מה שמשפר מאוד את הביצועים והאימות ברשת. אפשר לבצע את כל העבודה ב-codelab הזה בדפדפן. לא צריך להתקין שום דבר.
3. הפעלת ממשקי ה-API
בשיעור ה-Lab הזה תשתמשו ב-Cloud Functions וב-Vision API, אבל קודם צריך להפעיל אותם במסוף Cloud או באמצעות gcloud.
כדי להפעיל את Vision API במסוף Cloud, מחפשים את Cloud Vision API בסרגל החיפוש:

יוצג הדף Cloud Vision API:

לוחצים על הלחצן ENABLE.
אפשר גם להפעיל אותו ב-Cloud Shell באמצעות כלי שורת הפקודה gcloud.
ב-Cloud Shell, מריצים את הפקודה הבאה:
gcloud services enable vision.googleapis.com
הפעולה אמורה להסתיים בהצלחה:
Operation "operations/acf.12dba18b-106f-4fd2-942d-fea80ecc5c1c" finished successfully.
מפעילים גם את Cloud Functions:
gcloud services enable cloudfunctions.googleapis.com
4. יצירת הקטגוריה (מסוף)
יוצרים קטגוריית אחסון לתמונות. אפשר לעשות את זה דרך קונסולת Google Cloud Platform ( console.cloud.google.com) או באמצעות כלי שורת הפקודה gsutil מ-Cloud Shell או מסביבת הפיתוח המקומית.
ניווט לאחסון
בתפריט ההמבורגר (☰), עוברים לדף Storage.

Name your bucket (שם הקטגוריה)
לוחצים על הלחצן CREATE BUCKET.

לוחצים על CONTINUE.
בחירת מיקום

יוצרים קטגוריה במספר אזורים באזור שבחרתם (בדוגמה הזו Europe).
לוחצים על CONTINUE.
בחירת סוג האחסון (storage class) שמוגדר כברירת מחדל

בוחרים את Standard סוג האחסון (storage class) של הנתונים.
לוחצים על CONTINUE.
הגדרת בקרת גישה

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

משאירים את ברירת המחדל (Google-managed key)), כי לא תשתמשו במפתחות הצפנה משלכם.
לוחצים על CREATE כדי לסיים את יצירת הקטגוריה.
הוספת allUsers כצופים בנפח האחסון
עוברים לכרטיסייה Permissions:

מוסיפים את חבר הקבוצה allUsers לקטגוריה, עם התפקיד Storage > Storage Object Viewer, באופן הבא:

לוחצים על SAVE.
5. יצירת הקטגוריה (gsutil)
אפשר גם להשתמש בכלי שורת הפקודה gsutil ב-Cloud Shell כדי ליצור מאגרי מידע.
ב-Cloud Shell, מגדירים משתנה לשם הייחודי של הקטגוריה. ב-Cloud Shell, הערך GOOGLE_CLOUD_PROJECT כבר מוגדר למזהה הפרויקט הייחודי שלכם. אפשר להוסיף את זה לשם הקטגוריה.
לדוגמה:
export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}
יצירת אזור רגיל במספר אזורים באירופה:
gsutil mb -l EU gs://${BUCKET_PICTURES}
מוודאים שיש גישה אחידה ברמת הקטגוריה:
gsutil uniformbucketlevelaccess set on gs://${BUCKET_PICTURES}
הופכים את הקטגוריה לקטגוריה גלויה לכולם:
gsutil iam ch allUsers:objectViewer gs://${BUCKET_PICTURES}
אם נכנסים לקטע Cloud Storage במסוף, אמורה להיות קטגוריה uploaded-pictures שגלוי לכולם:

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

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

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

לצד תווית הגישה Public, יופיע גם סמל קטן של קישור. כשלוחצים על התמונה, הדפדפן עובר לכתובת ה-URL הציבורית של התמונה, שתהיה מהצורה:
https://storage.googleapis.com/BUCKET_NAME/PICTURE_FILE.png
כאשר BUCKET_NAME הוא השם הייחודי הגלובלי שבחרתם לקטגוריה, ואחריו שם הקובץ של התמונה.
כדי למחוק את התמונה הראשונה, לוחצים על תיבת הסימון לצד שם התמונה ואז על הלחצן DELETE.
7. יצירת הפונקציה
בשלב הזה יוצרים פונקציה שמגיבה לאירועים של העלאת תמונות.
נכנסים לקטע Cloud Functions במסוף Google Cloud. כשנכנסים אליו, שירות Cloud Functions מופעל באופן אוטומטי.

לוחצים על Create function.
בוחרים שם (למשל picture-uploaded) ואת האזור (חשוב לבחור את אותו אזור כמו בדלי):

יש שני סוגים של פונקציות:
- פונקציות HTTP שאפשר להפעיל באמצעות כתובת URL (כלומר, web API),
- פונקציות ברקע שאפשר להפעיל באמצעות אירוע מסוים.
אתם רוצים ליצור פונקציית רקע שמופעלת כשמעלים קובץ חדש לקטגוריית Cloud Storage:

אתם מתעניינים בסוג האירוע Finalize/Create, שהוא האירוע שמופעל כשקובץ נוצר או מתעדכן בדלי:

בוחרים את הקטגוריה שיצרתם קודם, כדי להגדיר ל-Cloud Functions לקבל התראה כשקובץ נוצר או מתעדכן בקטגוריה הזו:

לוחצים על Select כדי לבחור את הקטגוריה שיצרתם קודם, ואז על Save.

לפני שלוחצים על Next, אפשר להרחיב ולשנות את הגדרות ברירת המחדל (זיכרון של 256MB) בקטע Runtime, build, connections and security settings ולעדכן אותו ל-1GB.

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

בוחרים אחת מסביבות זמן הריצה של Node.js:

קוד המקור מורכב מקובץ JavaScript index.js ומקובץ package.json שמספק מטא-נתונים ותלויות שונים.
משאירים את קטע הקוד שמוגדר כברירת מחדל: הוא מתעד ביומן את שם הקובץ של התמונה שהועלתה:

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

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

עוברים שוב במסוף Cloud לדף Logging > Logs Explorer.
בבורר Log Fields, בוחרים באפשרות Cloud Function כדי לראות את היומנים שמוקדשים לפונקציות שלכם. גוללים למטה בשדות היומן ואפשר אפילו לבחור פונקציה ספציפית כדי לקבל תצוגה מפורטת יותר של היומנים שקשורים לפונקציה. בוחרים בפונקציה picture-uploaded.
אמורים להופיע פריטי היומן שבהם מוזכרת יצירת הפונקציה, שעות ההתחלה והסיום של הפונקציה והצהרת היומן בפועל:

פקודת הרישום ביומן שלנו היא: Processing file: pic-a-daily-architecture-events.png, כלומר האירוע שקשור ליצירה ולאחסון של התמונה הזו אכן הופעל כמצופה.
9. הכנת מסד הנתונים
תאחסנו מידע על התמונה שמתקבל מ-Vision API במסד הנתונים Cloud Firestore, שהוא מסד נתונים מהיר, מנוהל, בלי שרת (serverless) ולא יחסי (NoSQL) של מסמכים מבוסס-ענן. כדי להכין את מסד הנתונים, עוברים לקטע Firestore ב-Cloud Console:

יש שתי אפשרויות: Native mode או Datastore mode. משתמשים במצב המקורי, שמציע תכונות נוספות כמו תמיכה באופליין וסנכרון בזמן אמת.
לוחצים על SELECT NATIVE MODE.

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

כדי ליצור אוסף חדש, לוחצים על הלחצן + START COLLECTION.
איסוף שמות pictures.

לא צריך ליצור מסמך. התמונות יתווספו באופן אוטומטי כשהן יישמרו ב-Cloud Storage וינותחו על ידי Vision API.
לוחצים על Save.
Firestore יוצר מסמך ברירת מחדל ראשון באוסף החדש שנוצר. אפשר למחוק את המסמך הזה בבטחה כי הוא לא מכיל מידע שימושי:

המסמכים שייווצרו באופן אוטומטי באוסף שלנו יכללו 4 שדות:
- name (מחרוזת): שם הקובץ של התמונה שהועלתה, שהוא גם המפתח של המסמך
- labels (מערך של מחרוזות): התוויות של פריטים שזוהו על ידי Vision API
- color (מחרוזת): קוד הצבע ההקסדצימלי של הצבע הדומיננטי (למשל #ab12ef)
- created (תאריך): חותמת הזמן של מועד שמירת המטא-נתונים של התמונה
- thumbnail (בוליאני): שדה אופציונלי שיופיע ויקבל את הערך true אם נוצרה תמונה ממוזערת לתמונה הזו
כדי שנוכל לחפש ב-Firestore תמונות שיש להן תמונות ממוזערות ולמיין אותן לפי תאריך היצירה, נצטרך ליצור אינדקס חיפוש.
אפשר ליצור את האינדקס באמצעות הפקודה הבאה ב-Cloud Shell:
gcloud firestore indexes composite create \
--collection-group=pictures \
--field-config field-path=thumbnail,order=descending \
--field-config field-path=created,order=descending
אפשר גם לעשות את זה מ-Cloud Console. לשם כך, לוחצים על Indexes בעמודת הניווט בצד ימין, ואז יוצרים אינדקס מורכב כמו שמוצג בהמשך:

לוחצים על Create. יצירת האינדקס יכולה להימשך כמה דקות.
10. עדכון הפונקציה
חוזרים לדף Functions כדי לעדכן את הפונקציה להפעלת Vision API לצורך ניתוח התמונות ולאחסון המטא-נתונים ב-Firestore.
בתפריט ההמבורגר (☰), עוברים לקטע Cloud Functions, לוחצים על שם הפונקציה, בוחרים בכרטיסייה Source ואז לוחצים על הלחצן EDIT.
קודם כול, עורכים את הקובץ package.json שבו מפורטים יחסי התלות של פונקציית Node.JS. מעדכנים את הקוד כדי להוסיף את התלות ב-Cloud Vision API NPM:
{
"name": "picture-analysis-function",
"version": "0.0.1",
"dependencies": {
"@google-cloud/storage": "^1.6.0",
"@google-cloud/vision": "^1.8.0",
"@google-cloud/firestore": "^3.4.1"
}
}
עכשיו, אחרי שעדכנתם את התלויות, תצטרכו לעבוד על הקוד של הפונקציה שלנו ולעדכן את הקובץ index.js.
מחליפים את הקוד ב-index.js בקוד שבהמשך. הסבר על כך מופיע בשלב הבא.
const vision = require('@google-cloud/vision');
const Storage = require('@google-cloud/storage');
const Firestore = require('@google-cloud/firestore');
const client = new vision.ImageAnnotatorClient();
exports.vision_analysis = async (event, context) => {
console.log(`Event: ${JSON.stringify(event)}`);
const filename = event.name;
const filebucket = event.bucket;
console.log(`New picture uploaded ${filename} in ${filebucket}`);
const request = {
image: { source: { imageUri: `gs://${filebucket}/${filename}` } },
features: [
{ type: 'LABEL_DETECTION' },
{ type: 'IMAGE_PROPERTIES' },
{ type: 'SAFE_SEARCH_DETECTION' }
]
};
// invoking the Vision API
const [response] = await client.annotateImage(request);
console.log(`Raw vision output for: ${filename}: ${JSON.stringify(response)}`);
if (response.error === null) {
// listing the labels found in the picture
const labels = response.labelAnnotations
.sort((ann1, ann2) => ann2.score - ann1.score)
.map(ann => ann.description)
console.log(`Labels: ${labels.join(', ')}`);
// retrieving the dominant color of the picture
const color = response.imagePropertiesAnnotation.dominantColors.colors
.sort((c1, c2) => c2.score - c1.score)[0].color;
const colorHex = decColorToHex(color.red, color.green, color.blue);
console.log(`Colors: ${colorHex}`);
// determining if the picture is safe to show
const safeSearch = response.safeSearchAnnotation;
const isSafe = ["adult", "spoof", "medical", "violence", "racy"].every(k =>
!['LIKELY', 'VERY_LIKELY'].includes(safeSearch[k]));
console.log(`Safe? ${isSafe}`);
// if the picture is safe to display, store it in Firestore
if (isSafe) {
const pictureStore = new Firestore().collection('pictures');
const doc = pictureStore.doc(filename);
await doc.set({
labels: labels,
color: colorHex,
created: Firestore.Timestamp.now()
}, {merge: true});
console.log("Stored metadata in Firestore");
}
} else {
throw new Error(`Vision API error: code ${response.error.code}, message: "${response.error.message}"`);
}
};
function decColorToHex(r, g, b) {
return '#' + Number(r).toString(16).padStart(2, '0') +
Number(g).toString(16).padStart(2, '0') +
Number(b).toString(16).padStart(2, '0');
}
11. הסבר על הפונקציה
בואו נבחן מקרוב את החלקים המעניינים השונים.
קודם כל, נדרשים המודולים הדרושים, ל-Vision, ל-Storage ול-Firestore:
const vision = require('@google-cloud/vision');
const Storage = require('@google-cloud/storage');
const Firestore = require('@google-cloud/firestore');
לאחר מכן, מכינים לקוח ל-Vision API:
const client = new vision.ImageAnnotatorClient();
עכשיו נראה את המבנה של הפונקציה. הפכנו אותה לפונקציה אסינכרונית, כי אנחנו משתמשים ביכולות async / await שהוצגו ב-Node.js 8:
exports.vision_analysis = async (event, context) => {
...
const filename = event.name;
const filebucket = event.bucket;
...
}
שימו לב לחתימה, אבל גם לאופן שבו אנחנו מאחזרים את השם של הקובץ והקטגוריה שהפעילו את Cloud Function.
לעיון, כך נראה המטען הייעודי של האירוע:
{
"bucket":"uploaded-pictures",
"contentType":"image/png",
"crc32c":"efhgyA==",
"etag":"CKqB956MmucCEAE=",
"generation":"1579795336773802",
"id":"uploaded-pictures/Screenshot.png/1579795336773802",
"kind":"storage#object",
"md5Hash":"PN8Hukfrt6C7IyhZ8d3gfQ==",
"mediaLink":"https://www.googleapis.com/download/storage/v1/b/uploaded-pictures/o/Screenshot.png?generation=1579795336773802&alt=media",
"metageneration":"1",
"name":"Screenshot.png",
"selfLink":"https://www.googleapis.com/storage/v1/b/uploaded-pictures/o/Screenshot.png",
"size":"173557",
"storageClass":"STANDARD",
"timeCreated":"2020-01-23T16:02:16.773Z",
"timeStorageClassUpdated":"2020-01-23T16:02:16.773Z",
"updated":"2020-01-23T16:02:16.773Z"
}
אנחנו מכינים בקשה לשליחה דרך לקוח Vision:
const request = {
image: { source: { imageUri: `gs://${filebucket}/${filename}` } },
features: [
{ type: 'LABEL_DETECTION' },
{ type: 'IMAGE_PROPERTIES' },
{ type: 'SAFE_SEARCH_DETECTION' }
]
};
אנחנו מבקשים 3 יכולות מרכזיות של Vision API:
- זיהוי תוויות: כדי להבין מה יש בתמונות
- מאפייני התמונה: כדי לספק מאפיינים מעניינים של התמונה (אנחנו מתעניינים בצבע הדומיננטי של התמונה)
- חיפוש בטוח: כדי לדעת אם התמונה בטוחה להצגה (אסור שהיא תכיל תוכן למבוגרים בלבד, תוכן רפואי, תוכן נועז או תוכן אלים)
בשלב הזה, אפשר לשלוח קריאה ל-Vision API:
const [response] = await client.annotateImage(request);
לעיון, הנה דוגמה לתשובה מ-Vision API:
{
"faceAnnotations": [],
"landmarkAnnotations": [],
"logoAnnotations": [],
"labelAnnotations": [
{
"locations": [],
"properties": [],
"mid": "/m/01yrx",
"locale": "",
"description": "Cat",
"score": 0.9959855675697327,
"confidence": 0,
"topicality": 0.9959855675697327,
"boundingPoly": null
},
✄ - - - ✄
],
"textAnnotations": [],
"localizedObjectAnnotations": [],
"safeSearchAnnotation": {
"adult": "VERY_UNLIKELY",
"spoof": "UNLIKELY",
"medical": "VERY_UNLIKELY",
"violence": "VERY_UNLIKELY",
"racy": "VERY_UNLIKELY",
"adultConfidence": 0,
"spoofConfidence": 0,
"medicalConfidence": 0,
"violenceConfidence": 0,
"racyConfidence": 0,
"nsfwConfidence": 0
},
"imagePropertiesAnnotation": {
"dominantColors": {
"colors": [
{
"color": {
"red": 203,
"green": 201,
"blue": 201,
"alpha": null
},
"score": 0.4175916016101837,
"pixelFraction": 0.44456374645233154
},
✄ - - - ✄
]
}
},
"error": null,
"cropHintsAnnotation": {
"cropHints": [
{
"boundingPoly": {
"vertices": [
{ "x": 0, "y": 118 },
{ "x": 1177, "y": 118 },
{ "x": 1177, "y": 783 },
{ "x": 0, "y": 783 }
],
"normalizedVertices": []
},
"confidence": 0.41695669293403625,
"importanceFraction": 1
}
]
},
"fullTextAnnotation": null,
"webDetection": null,
"productSearchResults": null,
"context": null
}
אם לא מוחזרת שגיאה, אפשר להמשיך, ולכן יש לנו את בלוק ה-if הזה:
if (response.error === null) {
...
} else {
throw new Error(`Vision API error: code ${response.error.code},
message: "${response.error.message}"`);
}
אנחנו הולכים לקבל את התוויות של הדברים, הקטגוריות או הנושאים שזוהו בתמונה:
const labels = response.labelAnnotations
.sort((ann1, ann2) => ann2.score - ann1.score)
.map(ann => ann.description)
אנחנו ממיינים את התוויות לפי הציון הגבוה ביותר.
נשמח לדעת מהו הצבע הדומיננטי בתמונה:
const color = response.imagePropertiesAnnotation.dominantColors.colors
.sort((c1, c2) => c2.score - c1.score)[0].color;
const colorHex = decColorToHex(color.red, color.green, color.blue);
אנחנו שוב ממיינים את הצבעים לפי הניקוד ובוחרים את הראשון.
אנחנו משתמשים גם בפונקציית כלי עזר כדי להמיר את הערכים של אדום, ירוק וכחול לקוד צבע הקסדצימלי שאפשר להשתמש בו בגיליונות סגנונות של CSS.
בוא נבדוק אם אפשר להציג את התמונה:
const safeSearch = response.safeSearchAnnotation;
const isSafe = ["adult", "spoof", "medical", "violence", "racy"]
.every(k => !['LIKELY', 'VERY_LIKELY'].includes(safeSearch[k]));
אנחנו בודקים את המאפיינים 'תוכן למבוגרים בלבד', 'זיוף', 'רפואי', 'אלימות' ו'תוכן מיני' כדי לוודא שהם לא סביר או סביר מאוד.
אם התוצאה של החיפוש הבטוח תקינה, אפשר לאחסן את המטא-נתונים ב-Firestore:
if (isSafe) {
const pictureStore = new Firestore().collection('pictures');
const doc = pictureStore.doc(filename);
await doc.set({
labels: labels,
color: colorHex,
created: Firestore.Timestamp.now()
}, {merge: true});
}
12. פריסת הפונקציה
הגיע הזמן לפרוס את הפונקציה.

לוחצים על הלחצן DEPLOY והגרסה החדשה תופעל. אפשר לראות את ההתקדמות:

13. בדיקה חוזרת של הפונקציה
אחרי שהפונקציה תיפרס בהצלחה, תפרסמו תמונה ב-Cloud Storage, תבדקו אם הפונקציה שלנו מופעלת, מה Vision API מחזיר ואם המטא-נתונים נשמרים ב-Firestore.
חוזרים אל Cloud Storage ולוחצים על הדלי שיצרנו בתחילת שיעור ה-Lab:

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

בתפריט ההמבורגר (☰), עוברים אל Logging > Logs Explorer.
בבורר Log Fields, בוחרים באפשרות Cloud Function כדי לראות את היומנים שמוקדשים לפונקציות שלכם. גוללים למטה בשדות היומן ואפשר אפילו לבחור פונקציה ספציפית כדי לקבל תצוגה מפורטת יותר של היומנים שקשורים לפונקציה. בוחרים בפונקציה picture-uploaded.

ואכן, ברשימת היומנים אפשר לראות שהפונקציה שלנו הופעלה:

היומנים מציינים את ההתחלה והסיום של הפעלת הפונקציה. בין לבין, אפשר לראות את היומנים שהוספנו לפונקציה באמצעות ההצהרות console.log(). הנתונים שמוצגים הם:
- פרטי האירוע שמפעיל את הפונקציה,
- התוצאות הגולמיות מקריאה ל-Vision API,
- התוויות שנמצאו בתמונה שהעלינו,
- מידע על הצבעים הדומיננטיים,
- אם התמונה בטוחה לצפייה,
- ובסופו של דבר המטא-נתונים האלה לגבי התמונה מאוחסנים ב-Firestore.

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

14. ניקוי (אופציונלי)
אם אתם לא מתכוונים להמשיך עם שאר המעבדות בסדרה, אתם יכולים לנקות את המשאבים כדי לחסוך בעלויות ולשמור על סביבת ענן נקייה. כדי לנקות משאבים בנפרד, פועלים לפי השלבים הבאים.
מוחקים את הקטגוריה:
gsutil rb gs://${BUCKET_PICTURES}
מוחקים את הפונקציה:
gcloud functions delete picture-uploaded --region europe-west1 -q
כדי למחוק את אוסף Firestore, בוחרים באפשרות 'מחיקת האוסף' מתוך האוסף:

אפשר גם למחוק את כל הפרויקט:
gcloud projects delete ${GOOGLE_CLOUD_PROJECT}
15. מעולה!
מעולה! הטמעת בהצלחה את שירות המפתח הראשון של הפרויקט.
מה נכלל
- Cloud Storage
- Cloud Functions
- Cloud Vision API
- Cloud Firestore