1. Przegląd
W pierwszym module prześlesz zdjęcia do zasobnika. Spowoduje to wygenerowanie zdarzenia utworzenia pliku, które zostanie obsłużone przez funkcję. Funkcja wywoła interfejs Vision API, aby przeprowadzić analizę obrazu i zapisać wyniki w magazynie danych.

Czego się nauczysz
- Cloud Storage
- Cloud Functions
- Cloud Vision API
- Cloud Firestore
2. Konfiguracja i wymagania
Samodzielne konfigurowanie środowiska
- Zaloguj się w konsoli Google Cloud i utwórz nowy projekt lub użyj istniejącego. Jeśli nie masz jeszcze konta Gmail ani Google Workspace, musisz je utworzyć.



- Nazwa projektu to wyświetlana nazwa uczestników tego projektu. Jest to ciąg znaków, który nie jest używany przez interfejsy API Google. Możesz ją zaktualizować w dowolnym momencie.
- Identyfikator projektu musi być unikalny we wszystkich projektach Google Cloud i jest niezmienny (nie można go zmienić po ustawieniu). Konsola Cloud automatycznie generuje unikalny ciąg znaków. Zwykle nie musisz się nim przejmować. W większości ćwiczeń z programowania musisz odwoływać się do identyfikatora projektu (zwykle jest on oznaczony jako
PROJECT_ID). Jeśli wygenerowany identyfikator Ci się nie podoba, możesz wygenerować inny losowy identyfikator. Możesz też spróbować własnej nazwy i sprawdzić, czy jest dostępna. Po tym kroku nie można go zmienić i będzie obowiązywać przez cały czas trwania projektu. - Warto wiedzieć, że istnieje też trzecia wartość, czyli numer projektu, z której korzystają niektóre interfejsy API. Więcej informacji o tych 3 wartościach znajdziesz w dokumentacji.
- Następnie musisz włączyć płatności w konsoli Cloud, aby korzystać z zasobów i interfejsów API Google Cloud. Ukończenie tego laboratorium nie powinno wiązać się z dużymi kosztami, a nawet z żadnymi. Aby wyłączyć zasoby i uniknąć naliczania opłat po zakończeniu tego samouczka, możesz usunąć utworzone zasoby lub cały projekt. Nowi użytkownicy Google Cloud mogą skorzystać z bezpłatnego okresu próbnego, w którym mają do dyspozycji środki w wysokości 300 USD.
Uruchamianie Cloud Shell
Z Google Cloud można korzystać zdalnie na laptopie, ale w tym module praktycznym będziesz używać Google Cloud Shell, czyli środowiska wiersza poleceń działającego w chmurze.
W konsoli Google Cloud kliknij ikonę Cloud Shell na pasku narzędzi w prawym górnym rogu:

Uzyskanie dostępu do środowiska i połączenie się z nim powinno zająć tylko kilka chwil. Po zakończeniu powinno wyświetlić się coś takiego:

Ta maszyna wirtualna zawiera wszystkie potrzebne narzędzia dla programistów. Zawiera również stały katalog domowy o pojemności 5 GB i działa w Google Cloud, co znacznie zwiększa wydajność sieci i usprawnia proces uwierzytelniania. Wszystkie zadania w tym laboratorium możesz wykonać w przeglądarce. Nie musisz niczego instalować.
3. Włącz interfejsy API
W tym module będziesz korzystać z Cloud Functions i interfejsu Vision API, ale najpierw musisz je włączyć w konsoli Google Cloud lub za pomocą gcloud.
Aby włączyć interfejs Vision API w konsoli Cloud, na pasku wyszukiwania wpisz Cloud Vision API:

Wyświetli się strona Cloud Vision API:

Kliknij przycisk ENABLE.
Możesz też włączyć go w Cloud Shell za pomocą narzędzia wiersza poleceń gcloud.
W Cloud Shell uruchom to polecenie:
gcloud services enable vision.googleapis.com
Operacja powinna zakończyć się powodzeniem:
Operation "operations/acf.12dba18b-106f-4fd2-942d-fea80ecc5c1c" finished successfully.
Włącz też Cloud Functions:
gcloud services enable cloudfunctions.googleapis.com
4. Tworzenie zasobnika (konsola)
Utwórz zasobnik na zdjęcia. Możesz to zrobić w konsoli Google Cloud Platform ( console.cloud.google.com) lub za pomocą narzędzia wiersza poleceń gsutil w Cloud Shell lub lokalnym środowisku programistycznym.
Otwórz Miejsce na dane
W menu „hamburger” (☰) otwórz stronę Storage.

Nazwij zasobnik
Kliknij przycisk CREATE BUCKET.

Kliknij CONTINUE.
Wybierz lokalizację

Utwórz zasobnik z wieloma regionami w wybranym regionie (w tym przypadku Europe).
Kliknij CONTINUE.
Wybierz domyślną klasę pamięci masowej

Wybierz klasę pamięci masowej Standard dla swoich danych.
Kliknij CONTINUE.
Ustawianie kontroli dostępu

Będziesz pracować z publicznie dostępnymi obrazami, więc chcesz, aby wszystkie zdjęcia przechowywane w tym zasobniku miały takie same, jednolite ustawienia kontroli dostępu.
Wybierz opcję kontroli dostępu Uniform.
Kliknij CONTINUE.
Ustawianie ochrony/szyfrowania

Zachowaj ustawienie domyślne (Google-managed key)), ponieważ nie będziesz używać własnych kluczy szyfrowania.
Kliknij CREATE, aby zakończyć tworzenie zasobnika.
Dodawanie allUsers jako przeglądającego miejsce na dane
Otwórz kartę Permissions:

Dodaj do zasobnika użytkownika allUsers z rolą Storage > Storage Object Viewer w ten sposób:

Kliknij SAVE.
5. Tworzenie zasobnika (gsutil)
Do tworzenia zasobników możesz też używać narzędzia wiersza poleceń gsutil w Cloud Shell.
W Cloud Shell ustaw zmienną dla unikalnej nazwy zasobnika. W Cloud Shell zmienna GOOGLE_CLOUD_PROJECT jest już ustawiona na Twój unikalny identyfikator projektu. Możesz dodać go do nazwy zasobnika.
Na przykład:
export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}
Utwórz standardową strefę wieloregionową w Europie:
gsutil mb -l EU gs://${BUCKET_PICTURES}
Sprawdź, czy jest włączony jednolity dostęp na poziomie zasobnika:
gsutil uniformbucketlevelaccess set on gs://${BUCKET_PICTURES}
Ustaw zasobnik jako publiczny:
gsutil iam ch allUsers:objectViewer gs://${BUCKET_PICTURES}
Jeśli przejdziesz do sekcji Cloud Storage w konsoli, powinien być tam publiczny uploaded-pictures:

Sprawdź, czy możesz przesyłać zdjęcia do zasobnika i czy przesłane zdjęcia są dostępne publicznie, jak opisano w poprzednim kroku.
6. Testowanie dostępu publicznego do zasobnika
Wracając do przeglądarki pamięci, zobaczysz na liście swój zasobnik z dostępem „Publiczny” (wraz z ostrzeżeniem, że każdy ma dostęp do zawartości tego zasobnika).

Zasobnik jest gotowy do odbierania zdjęć.
Jeśli klikniesz nazwę zasobnika, zobaczysz jego szczegóły.

Możesz tam kliknąć przycisk Upload files, aby sprawdzić, czy możesz dodać obraz do zasobnika. Pojawi się wyskakujące okienko z prośbą o wybranie pliku. Po wybraniu plik zostanie przesłany do zasobnika i ponownie zobaczysz public dostęp, który został automatycznie przypisany do tego nowego pliku.

Obok etykiety dostępu Public zobaczysz też małą ikonę linku. Po kliknięciu przeglądarka przejdzie do publicznego adresu URL tego obrazu, który będzie miał postać:
https://storage.googleapis.com/BUCKET_NAME/PICTURE_FILE.png
gdzie BUCKET_NAME to niepowtarzalna globalnie nazwa zasobnika, a następnie nazwa pliku obrazu.
Kliknięcie pola wyboru obok nazwy obrazu spowoduje włączenie przycisku DELETE, dzięki czemu możesz usunąć pierwszy obraz.
7. Tworzenie funkcji
W tym kroku utworzysz funkcję, która reaguje na zdarzenia przesyłania obrazów.
Otwórz sekcję Cloud Functions w konsoli Google Cloud. Gdy ją otworzysz, usługa Cloud Functions zostanie automatycznie włączona.

Kliknij Create function.
Wybierz nazwę (np. picture-uploaded) i region (pamiętaj, aby był zgodny z regionem wybranym dla zasobnika):

Istnieją 2 rodzaje funkcji:
- funkcje HTTP, które można wywoływać za pomocą adresu URL (np. interfejs API sieciowy);
- Funkcje działające w tle, które mogą być wywoływane przez określone zdarzenie.
Chcesz utworzyć funkcję w tle, która będzie wywoływana, gdy do zasobnika Cloud Storage zostanie przesłany nowy plik:

Interesuje Cię typ zdarzenia Finalize/Create, czyli zdarzenie wywoływane, gdy plik jest tworzony lub aktualizowany w zasobniku:

Wybierz utworzony wcześniej zasobnik, aby poinformować Cloud Functions o otrzymywaniu powiadomień o utworzeniu lub zaktualizowaniu pliku w tym zasobniku:

Kliknij Select, aby wybrać utworzony wcześniej zasobnik, a następnie Save.

Zanim klikniesz Dalej, możesz rozwinąć i zmodyfikować ustawienia domyślne (256 MB pamięci) w sekcji Ustawienia środowiska wykonawczego, kompilacji, połączeń i zabezpieczeń i zmienić je na 1 GB.

Po kliknięciu Next możesz dostosować środowisko wykonawcze, kod źródłowy i punkt wejścia.
Zachowaj Inline editor dla tej funkcji:

Wybierz jedno ze środowisk wykonawczych Node.js:

Kod źródłowy składa się z index.jspliku JavaScript i package.jsonpliku, który zawiera różne metadane i zależności.
Pozostaw domyślny fragment kodu: rejestruje on nazwę pliku przesłanego zdjęcia:

Na potrzeby testowania pozostaw nazwę funkcji do wykonania ustawioną na helloGCS.
Aby utworzyć i wdrożyć funkcję, kliknij Deploy. Po pomyślnym wdrożeniu na liście funkcji powinien pojawić się zielony znacznik wyboru w kółku:

8. Testowanie funkcji
W tym kroku sprawdzisz, czy funkcja reaguje na zdarzenia związane z pamięcią.
W menu (☰) wróć na stronę Storage.
Kliknij zasobnik obrazów, a potem Upload files, aby przesłać obraz.

W konsoli w chmurze ponownie otwórz stronę Logging > Logs Explorer.
W selektorze Log Fields wybierz Cloud Function, aby wyświetlić logi dotyczące Twoich funkcji. Przewiń w dół sekcję Log Fields (Pola logów). Możesz nawet wybrać konkretną funkcję, aby uzyskać bardziej szczegółowy widok logów powiązanych z funkcjami. Wybierz funkcję picture-uploaded.
Powinny być widoczne wpisy w logu dotyczące utworzenia funkcji, czasu jej rozpoczęcia i zakończenia oraz nasza rzeczywista instrukcja logowania:

W naszym dzienniku widnieje wpis: Processing file: pic-a-daily-architecture-events.png, co oznacza, że zdarzenie związane z utworzeniem i zapisaniem tego zdjęcia zostało wywołane zgodnie z oczekiwaniami.
9. Przygotowywanie bazy danych
Informacje o zdjęciu podane przez interfejs Vision API zapiszesz w bazie danych Cloud Firestore, czyli szybkiej, w pełni zarządzanej, bezserwerowej, chmurowej bazie danych dokumentów NoSQL. Przygotuj bazę danych, przechodząc do sekcji Firestore w konsoli Cloud:

Dostępne są 2 opcje: Native mode lub Datastore mode. Używaj trybu natywnego, który oferuje dodatkowe funkcje, takie jak obsługa offline i synchronizacja w czasie rzeczywistym.
Kliknij SELECT NATIVE MODE.

Wybierz region obejmujący wiele lokalizacji (w tym przypadku w Europie, ale najlepiej co najmniej ten sam region, w którym znajdują się Funkcje i zasobnik pamięci).
Kliknij przycisk CREATE DATABASE.
Po utworzeniu bazy danych zobaczysz te informacje:

Utwórz nową kolekcję, klikając przycisk + START COLLECTION.
Nazwij kolekcję pictures.

Nie musisz tworzyć dokumentu. Dodasz je programowo, gdy nowe zdjęcia będą przechowywane w Cloud Storage i analizowane przez interfejs Vision API.
Kliknij Save.
Firestore tworzy pierwszy domyślny dokument w nowo utworzonej kolekcji. Możesz go bezpiecznie usunąć, ponieważ nie zawiera żadnych przydatnych informacji:

Dokumenty, które zostaną utworzone programowo w naszej kolekcji, będą zawierać 4 pola:
- name (string): nazwa pliku przesłanego zdjęcia, która jest też kluczem dokumentu.
- labels (tablica ciągów znaków): etykiety rozpoznanych elementów przez Vision API.
- color (string): szesnastkowy kod koloru dominującego (np. #ab12ef)
- created (data): sygnatura czasowa wskazująca, kiedy metadane tego obrazu zostały zapisane.
- thumbnail (wartość logiczna): pole opcjonalne, które będzie obecne i będzie miało wartość „true”, jeśli dla tego zdjęcia została wygenerowana miniatura.
Będziemy wyszukiwać w Firestore zdjęcia, dla których dostępne są miniatury, i sortować je według daty utworzenia, więc musimy utworzyć indeks wyszukiwania.
Możesz utworzyć indeks za pomocą tego polecenia w Cloud Shell:
gcloud firestore indexes composite create \
--collection-group=pictures \
--field-config field-path=thumbnail,order=descending \
--field-config field-path=created,order=descending
Możesz też to zrobić w konsoli Google Cloud, klikając Indexes w kolumnie nawigacyjnej po lewej stronie, a następnie tworząc indeks złożony w sposób pokazany poniżej:

Kliknij Create. Tworzenie indeksu może potrwać kilka minut.
10. Aktualizowanie funkcji
Wróć na stronę Functions, aby zaktualizować funkcję, która będzie wywoływać interfejs Vision API w celu analizowania zdjęć i przechowywania metadanych w Firestore.
W menu (☰) przejdź do sekcji Cloud Functions, kliknij nazwę funkcji, wybierz kartę Source, a następnie kliknij przycisk EDIT.
Najpierw zmień plik package.json, który zawiera listę zależności naszej funkcji Node.JS. Zaktualizuj kod, aby dodać zależność NPM interfejsu Cloud Vision API:
{
"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"
}
}
Po zaktualizowaniu zależności możesz pracować nad kodem funkcji, aktualizując plik index.js.
Zastąp kod w pliku index.js kodem poniżej. Wyjaśnimy to w następnym kroku.
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. Poznaj funkcję
Przyjrzyjmy się bliżej różnym interesującym częściom.
Najpierw wymagamy potrzebnych modułów: Vision, Storage i Firestore:
const vision = require('@google-cloud/vision');
const Storage = require('@google-cloud/storage');
const Firestore = require('@google-cloud/firestore');
Następnie przygotowujemy klienta do korzystania z interfejsu Vision API:
const client = new vision.ImageAnnotatorClient();
Teraz przejdźmy do struktury naszej funkcji. Uczynimy ją funkcją asynchroniczną, ponieważ korzystamy z możliwości async / await wprowadzonych w Node.js 8:
exports.vision_analysis = async (event, context) => {
...
const filename = event.name;
const filebucket = event.bucket;
...
}
Zwróć uwagę na sygnaturę, ale też na to, jak pobieramy nazwę pliku i zasobnika, które wywołały funkcję w Cloud Functions.
Oto przykładowy ładunek zdarzenia:
{
"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"
}
Przygotowujemy żądanie do wysłania za pomocą klienta Vision:
const request = {
image: { source: { imageUri: `gs://${filebucket}/${filename}` } },
features: [
{ type: 'LABEL_DETECTION' },
{ type: 'IMAGE_PROPERTIES' },
{ type: 'SAFE_SEARCH_DETECTION' }
]
};
Prosimy o 3 kluczowe funkcje interfejsu Vision API:
- Wykrywanie etykiet: aby zrozumieć, co znajduje się na zdjęciach.
- Właściwości obrazu: aby podać interesujące atrybuty obrazu (interesuje nas dominujący kolor obrazu).
- Bezpieczne wyszukiwanie: aby sprawdzić, czy obraz jest bezpieczny do wyświetlenia (nie powinien zawierać treści dla dorosłych, medycznych, o charakterze seksualnym ani treści przedstawiających przemoc).
W tym momencie możemy wywołać interfejs Vision API:
const [response] = await client.annotateImage(request);
Oto przykład odpowiedzi interfejsu 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
}
Jeśli nie zostanie zwrócony żaden błąd, możemy przejść dalej. Dlatego mamy ten blok if:
if (response.error === null) {
...
} else {
throw new Error(`Vision API error: code ${response.error.code},
message: "${response.error.message}"`);
}
Pobierzemy etykiety rzeczy, kategorii lub motywów rozpoznanych na zdjęciu:
const labels = response.labelAnnotations
.sort((ann1, ann2) => ann2.score - ann1.score)
.map(ann => ann.description)
Etykiety są sortowane według najwyższego wyniku.
Chcemy poznać dominujący kolor zdjęcia:
const color = response.imagePropertiesAnnotation.dominantColors.colors
.sort((c1, c2) => c2.score - c1.score)[0].color;
const colorHex = decColorToHex(color.red, color.green, color.blue);
Ponownie sortujemy kolory według wyniku i wybieramy pierwszy z nich.
Używamy też funkcji użytkowej do przekształcania wartości czerwonej, zielonej i niebieskiej w szesnastkowy kod koloru, którego możemy używać w arkuszach stylów CSS.
Sprawdźmy, czy zdjęcie jest bezpieczne:
const safeSearch = response.safeSearchAnnotation;
const isSafe = ["adult", "spoof", "medical", "violence", "racy"]
.every(k => !['LIKELY', 'VERY_LIKELY'].includes(safeSearch[k]));
Sprawdzamy atrybuty dotyczące treści dla dorosłych, parodii, treści medycznych, przemocy i treści o charakterze erotycznym, aby określić, czy prawdopodobnie lub bardzo prawdopodobnie nie są one zgodne z zasadami.
Jeśli wynik bezpiecznego wyszukiwania jest prawidłowy, możemy zapisać metadane w 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. Wdrażanie funkcji
Czas wdrożyć funkcję.

Kliknij przycisk DEPLOY, a nowa wersja zostanie wdrożona. Możesz śledzić postęp:

13. Ponowne testowanie funkcji
Po pomyślnym wdrożeniu funkcji opublikujesz obraz w Cloud Storage, sprawdzisz, czy funkcja została wywołana, co zwraca interfejs Vision API i czy metadane są przechowywane w Firestore.
Wróć do Cloud Storage i kliknij zasobnik utworzony na początku modułu:

Na stronie z informacjami o zasobniku kliknij przycisk Upload files, aby przesłać zdjęcie.

W menu (☰) przejdź do Logging > Logs Eksploratora.
W selektorze Log Fields wybierz Cloud Function, aby wyświetlić logi dotyczące Twoich funkcji. Przewiń w dół sekcję Log Fields (Pola logów). Możesz nawet wybrać konkretną funkcję, aby uzyskać bardziej szczegółowy widok logów powiązanych z funkcjami. Wybierz funkcję picture-uploaded.

Na liście logów widać, że nasza funkcja została wywołana:

Logi wskazują początek i koniec wykonania funkcji. Pomiędzy nimi widzimy logi, które umieściliśmy w funkcji za pomocą instrukcji console.log(). Widzimy:
- szczegóły zdarzenia, które wywołało naszą funkcję,
- Nieprzetworzone wyniki wywołania interfejsu Vision API.
- etykiety znalezione na przesłanym przez nas zdjęciu;
- informacje o kolorach dominujących,
- czy obraz jest bezpieczny do wyświetlenia,
- Metadane obrazu zostały ostatecznie zapisane w Firestore.

Ponownie w menu „hamburger” (☰) otwórz sekcję Firestore. W podsekcji Data (wyświetlanej domyślnie) powinna pojawić się kolekcja pictures z dodanym nowym dokumentem odpowiadającym właśnie przesłanemu zdjęciu:

14. Zwalnianie miejsca (opcjonalnie)
Jeśli nie zamierzasz kontynuować pracy z innymi ćwiczeniami z tej serii, możesz usunąć zasoby, aby zaoszczędzić pieniądze i być dobrym użytkownikiem chmury. Możesz zwolnić miejsce, czyszcząc poszczególne zasoby w ten sposób:
Usuń zasobnik:
gsutil rb gs://${BUCKET_PICTURES}
Usuń funkcję:
gcloud functions delete picture-uploaded --region europe-west1 -q
Usuń kolekcję Firestore, wybierając opcję Usuń kolekcję:

Możesz też usunąć cały projekt:
gcloud projects delete ${GOOGLE_CLOUD_PROJECT}
15. Gratulacje!
Gratulacje! Udało Ci się wdrożyć pierwszą kluczową usługę projektu.
Omówione zagadnienia
- Cloud Storage
- Cloud Functions
- Cloud Vision API
- Cloud Firestore