functions/image-analysis/csharp/Function.cs (72 lines of code) (raw):

// Copyright 2020, Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. using CloudNative.CloudEvents; using Google.Cloud.Functions.Framework; using Google.Cloud.Vision.V1; using Microsoft.Extensions.Logging; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using static Google.Cloud.Vision.V1.Feature.Types.Type; using Google.Cloud.Firestore; using Google.Events.Protobuf.Cloud.Storage.V1; namespace ImageAnalysis { /// <summary> /// Function to watch for new image files being added to a storage bucket, /// use the Google Cloud Vision API to determine if it's a safe image. // If so, extract labels and dominant colors to save to Firestore. /// </summary> public class Function : ICloudEventFunction<StorageObjectData> { private readonly FirestoreDb _firestoreDb; private readonly ImageAnnotatorClient _visionClient; private readonly ILogger _logger; /// <summary> /// Constructor accepting all our dependencies. The clients are configured in /// Startup.cs. /// </summary> public Function(ImageAnnotatorClient visionClient, FirestoreDb firestoreDb, ILogger<Function> logger) => (_visionClient, _firestoreDb, _logger) = (visionClient, firestoreDb, logger); /// <summary> /// Entry point for the function. This is called whenever a new storage file is created. /// </summary> /// <param name="payload">The storage object that's been uploaded.</param> /// <param name="context">Event context (event ID etc)</param> public async Task HandleAsync(CloudEvent cloudEvent, StorageObjectData data, CancellationToken cancellationToken) { _logger.LogInformation($"New picture uploaded {data.Name} in {data.Bucket}"); var annotations = await AnnotateImageAsync(data, cancellationToken); await ProcessAnnotations(data.Name, annotations); } /// <summary> /// Use the Vision API to annotate the image. /// </summary> private Task<AnnotateImageResponse> AnnotateImageAsync(StorageObjectData data, CancellationToken cancellationToken) { var features = new[] { LabelDetection, Feature.Types.Type.ImageProperties, SafeSearchDetection} .Select(type => new Feature { Type = type, MaxResults = 20 }); var request = new AnnotateImageRequest { Image = Image.FromUri($"gs://{data.Bucket}/{data.Name}"), Features = { features } }; return _visionClient.AnnotateAsync(request, cancellationToken); } /// <summary> /// Determin whether the image is safe, extract labels, dominant colors /// and save to Firestore. /// </summary> private async Task ProcessAnnotations(string filename, AnnotateImageResponse response) { var labels = response.LabelAnnotations .OrderBy(annotation => annotation.Score) .Select(annotation => annotation.Description); _logger.LogInformation($"Labels: {string.Join(",", labels)}"); var color = response.ImagePropertiesAnnotation.DominantColors.Colors .OrderByDescending(c => c.Score) .Select(c => c.Color) .First(); var colorHex = $"#{((int)color.Red):X2}{((int)color.Green):X2}{((int)color.Blue):X2}"; _logger.LogInformation($"Colors: {colorHex}"); var safeSearch = response.SafeSearchAnnotation; var isSafe = safeSearch.Adult < Likelihood.Possible && safeSearch.Medical < Likelihood.Possible && safeSearch.Racy < Likelihood.Possible && safeSearch.Spoof < Likelihood.Possible && safeSearch.Violence < Likelihood.Possible; _logger.LogInformation($"Safe? {isSafe}"); if (isSafe) { var pictureStore = _firestoreDb.Collection("pictures"); var doc = pictureStore.Document(filename); var metadata = new Dictionary<string, object>() { {"labels", labels.ToList()}, {"color", colorHex}, {"created", Timestamp.GetCurrentTimestamp()} }; await doc.SetAsync(metadata, SetOptions.MergeAll); _logger.LogInformation("Stored metadata in Firestore"); } } } }