private static async Task <List <Face> > GetMsFaces(TraceWriter log, PhotoToAnalyze photoToAnalyze, HttpClient httpClient) { Stopwatch stopwatch = Stopwatch.StartNew(); string faceApiKey = ConfigurationManager.AppSettings["FaceApiKey"]; httpClient.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", faceApiKey); StringContent stringContent = new StringContent($"{{\"url\":\"{photoToAnalyze.Url}\"}}", Encoding.UTF8, "application/json"); HttpResponseMessage response = await httpClient.PostAsync( "https://northeurope.api.cognitive.microsoft.com/face/v1.0/detect?returnFaceId=true&returnFaceLandmarks=false&returnFaceAttributes=" + "age,gender,headPose,smile,facialHair,glasses,emotion,hair,makeup,occlusion,accessories,blur,exposure,noise", stringContent); HttpContent responseContent = response.Content; string msDetectResponseString = await responseContent.ReadAsStringAsync(); List <Face> msFaces; try { msFaces = JsonConvert.DeserializeObject <List <Face> >(msDetectResponseString); } catch (Exception ex) { string failure = $"Error while trying to deserialize MS detect response. Response string was: \"{msDetectResponseString}\""; log.Error(failure, ex); throw; } log.Info($"MS Faces for {photoToAnalyze.Url} got in {stopwatch.ElapsedMilliseconds}ms"); stopwatch.Restart(); return(msFaces); }
public void Send(PhotoToAnalyze photoToAnalyze) { string jsonMessage = JsonConvert.SerializeObject(photoToAnalyze); CloudQueueMessage message = new CloudQueueMessage(jsonMessage); photoToAnalyzeQueue.AddMessage(message); }
public static async Task <string> AnalyzePhoto(TraceWriter log, PhotoToAnalyze photoToAnalyze, ImageAnalysisTableAdapter imageAnalysisTableAdapter) { using (HttpClient httpClient = new HttpClient()) { Stopwatch stopwatch = Stopwatch.StartNew(); VisionApiResponse visionApiResponse = await GetGoogleVisionApi(log, photoToAnalyze, httpClient); List <Face> msFaces = await GetMsFaces(log, photoToAnalyze, httpClient); Analysis msAnalysis = await GetMsAnalysis(log, photoToAnalyze, httpClient); if (visionApiResponse?.Responses.Count == 1 && msAnalysis != null) { ImageAnalysis canonicalImageAnalysis = new ImageAnalysis(visionApiResponse.Responses[0], msAnalysis, msFaces); ImageAnalysisEntity imageAnalysisEntity = new ImageAnalysisEntity { // for canonical truncate decimal precision to 4 decimal places, for others keep original precision CanonicalJson = JsonConvert.SerializeObject(canonicalImageAnalysis, JsonUtils.AnalysisSerializerSettings), GoogleVisionApiJson = JsonConvert.SerializeObject(visionApiResponse.Responses[0], JsonUtils.JsonSerializerSettings), MsCognitiveFaceDetectJson = JsonConvert.SerializeObject(msFaces, JsonUtils.JsonSerializerSettings), MsAnalysisJson = JsonConvert.SerializeObject(msAnalysis, JsonUtils.JsonSerializerSettings) }; if (imageAnalysisEntity.GoogleVisionApiJson.Length > 30000) { log.Warning($"Google vision API response JSON is {imageAnalysisEntity.GoogleVisionApiJson.Length} chars, removing TextAnnotations"); visionApiResponse.Responses[0].TextAnnotations = null; imageAnalysisEntity.GoogleVisionApiJson = JsonConvert.SerializeObject(visionApiResponse.Responses[0], JsonUtils.JsonSerializerSettings); } if (imageAnalysisEntity.GoogleVisionApiJson.Length > 45000) { log.Warning($"GoogleVisionApiJson still is {imageAnalysisEntity.GoogleVisionApiJson.Length} chars after removing TextAnnotations"); } if (imageAnalysisEntity.CanonicalJson.Length > 45000) { log.Warning($"CanonicalJson is {imageAnalysisEntity.CanonicalJson.Length} chars"); } if (imageAnalysisEntity.MsCognitiveFaceDetectJson.Length > 45000) { log.Warning($"MsCognitiveFaceDetectJson is {imageAnalysisEntity.MsCognitiveFaceDetectJson.Length} chars"); } if (imageAnalysisEntity.MsAnalysisJson.Length > 45000) { log.Warning($"MsAnalysisJson is {imageAnalysisEntity.MsAnalysisJson.Length} chars"); } imageAnalysisTableAdapter.InsertImageAnalysis(imageAnalysisEntity, photoToAnalyze.Url); imageAnalysisTableAdapter.InsertBlogImageAnalysis(SanityHelper.SanitizeSourceBlog(photoToAnalyze.Blog), photoToAnalyze.Url); log.Info($"All analyses for {photoToAnalyze.Url} saved in {stopwatch.ElapsedMilliseconds}ms"); return(null); } log.Warning("Failed to get all responses"); return("Failed to get all responses"); } }
public static HttpResponseMessage Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = "analyze/{blogname}")] HttpRequestMessage req, string blogname, TraceWriter log) { Startup.Init(); PostsTableAdapter postsTableAdapter = new PostsTableAdapter(); postsTableAdapter.Init(log); PhotoToAnalyzeQueueAdapter photoToAnalyzeQueueAdapter = new PhotoToAnalyzeQueueAdapter(); photoToAnalyzeQueueAdapter.Init(); string blobBaseUrl = ConfigurationManager.AppSettings["BlobBaseUrl"]; List <PostEntity> posts = postsTableAdapter.GetAll(blogname); log.Info($"Loaded {posts.Count} posts"); int messageCount = 0; foreach (PostEntity postEntity in posts) { if (string.IsNullOrEmpty(postEntity.PhotoBlobUrls)) { continue; } List <Photo> sitePhotos = JsonConvert.DeserializeObject <List <Photo> >(postEntity.PhotoBlobUrls); foreach (Photo photo in sitePhotos) { List <PhotoSize> sortedSizes = photo.Sizes.OrderByDescending(x => x.Nominal).ToList(); PhotoSize original = sortedSizes.FirstOrDefault(); if (original == null) { continue; } string url = blobBaseUrl + "/" + original.Container + "/" + photo.Name + "_" + original.Nominal + "." + photo.Extension; PhotoToAnalyze message = new PhotoToAnalyze { Blog = blogname, PostDate = postEntity.Date, Url = url }; photoToAnalyzeQueueAdapter.Send(message); log.Info($"Published PhotoToAnalyze message with URL {url}"); messageCount++; } } return(req.CreateResponse(HttpStatusCode.OK, $"Processed {posts.Count} posts, sent {messageCount} messages")); }
private static async Task <Analysis> GetMsAnalysis(TraceWriter log, PhotoToAnalyze photoToAnalyze, HttpClient httpClient) { Stopwatch stopwatch = Stopwatch.StartNew(); string visionApiKey = ConfigurationManager.AppSettings["ComputerVisionApiKey"]; httpClient.DefaultRequestHeaders.Remove("Ocp-Apim-Subscription-Key"); httpClient.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", visionApiKey); StringContent stringContent = new StringContent($"{{\"url\":\"{photoToAnalyze.Url}\"}}", Encoding.UTF8, "application/json"); HttpResponseMessage response = await httpClient.PostAsync( "https://northeurope.api.cognitive.microsoft.com/vision/v2.0/analyze?visualFeatures=Description,ImageType,Adult,Categories,Tags,Objects,Color&language=en", stringContent); HttpContent responseContent = response.Content; string msAnalyzeResponseString = await responseContent.ReadAsStringAsync(); Analysis msAnalysis = JsonConvert.DeserializeObject <Analysis>(msAnalyzeResponseString); log.Info($"MS Analysis for {photoToAnalyze.Url} got in {stopwatch.ElapsedMilliseconds}ms"); return(msAnalysis); }
private static async Task UpdateBlogInfo(PhotoToAnalyze photoToAnalyze, BlogInfoTableAdapter tableAdapter) { BlogEntity info = await tableAdapter.GetBlog(photoToAnalyze.Blog); bool changed = false; if (info.AnalyzedStartingFrom == null || info.AnalyzedStartingFrom > photoToAnalyze.PostDate) { info.AnalyzedStartingFrom = photoToAnalyze.PostDate; changed = true; } if (info.AnalyzedUntil == null || info.AnalyzedUntil < photoToAnalyze.PostDate) { info.AnalyzedUntil = photoToAnalyze.PostDate; changed = true; } if (changed) { tableAdapter.InsertBlog(info); } }
private static async Task <VisionApiResponse> GetGoogleVisionApi(TraceWriter log, PhotoToAnalyze photoToAnalyze, HttpClient httpClient) { Stopwatch stopwatch = Stopwatch.StartNew(); string apiKey = ConfigurationManager.AppSettings["GoogleApiKey"]; string url = "https://vision.googleapis.com/v1/images:annotate?key=" + apiKey; VisionApiRequest request = VisionApiRequest.CreateFromImageUris(photoToAnalyze.Url); VisionApiResponse visionApiResponse = await MakeVisionApiRequest(httpClient, request, url, log); if (visionApiResponse?.Responses[0].Error != null) { if (visionApiResponse.Responses[0].Error.Message.Contains("download the content and pass it in")) { httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("image/*")); log.Info($"Google API error, downloading content for {photoToAnalyze.Url}"); byte[] photoBytes = await httpClient.GetByteArrayAsync(photoToAnalyze.Url); log.Info($"Got {photoBytes.Length} bytes, making Google API request with Content"); request = VisionApiRequest.CreateFromContent(photoBytes); visionApiResponse = await MakeVisionApiRequest(httpClient, request, url, log); } if (visionApiResponse?.Responses[0].Error != null) { string failure = $"Got error response from Google API: {visionApiResponse.Responses[0].Error.Message}"; log.Warning(failure); throw new InvalidOperationException(failure); } } log.Info($"Google Vision for {photoToAnalyze.Url} got in {stopwatch.ElapsedMilliseconds}ms"); stopwatch.Restart(); return(visionApiResponse); }
public static HttpResponseMessage Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = "analyzerandomphotos")] HttpRequestMessage req, TraceWriter log) { Startup.Init(); PostsTableAdapter postsTableAdapter = new PostsTableAdapter(); postsTableAdapter.Init(log); ImageAnalysisTableAdapter imageAnalysisTableAdapter = new ImageAnalysisTableAdapter(); imageAnalysisTableAdapter.Init(); PhotoToAnalyzeQueueAdapter photoToAnalyzeQueueAdapter = new PhotoToAnalyzeQueueAdapter(); photoToAnalyzeQueueAdapter.Init(); string blobBaseUrl = ConfigurationManager.AppSettings["BlobBaseUrl"]; int blogsLimit = 50; int photosInBlogLimit = 10; BlogInfoTableAdapter blogInfoTableAdapter = new BlogInfoTableAdapter(); blogInfoTableAdapter.Init(); List <BlogStats> blogStats = blogInfoTableAdapter.GetBlogStats(); log.Info($"Got {blogStats.Count} blogs to index"); Random random = new Random(); blogStats.Shuffle(random); blogStats = blogStats.Take(blogsLimit).ToList(); int totalCount = 0; foreach (string blogname in blogStats.Select(x => x.RowKey)) { int analyzedInBlogCount = 0; List <PostEntity> noteCounts = postsTableAdapter.GetPostNoteCounts(blogname).OrderByDescending(x => x.NoteCount).ToList(); log.Info($"Got note counts for {noteCounts.Count} posts in blog {blogname}"); foreach (PostEntity noteCountPost in noteCounts) { PostEntity postEntity = postsTableAdapter.GetPost(blogname, noteCountPost.RowKey); if (postEntity == null) { log.Warning($"Post {blogname}/{noteCountPost.RowKey} not found, skipping"); continue; } if (string.IsNullOrEmpty(postEntity.PhotoBlobUrls)) { continue; } List <Photo> sitePhotos = JsonConvert.DeserializeObject <List <Photo> >(postEntity.PhotoBlobUrls); foreach (Photo photo in sitePhotos) { List <PhotoSize> sortedSizes = photo.Sizes.OrderByDescending(x => x.Nominal).ToList(); PhotoSize original = sortedSizes.FirstOrDefault(); if (original == null) { continue; } string url = blobBaseUrl + "/" + original.Container + "/" + photo.Name + "_" + original.Nominal + "." + photo.Extension; if (imageAnalysisTableAdapter.GetImageAnalysis(url) != null) { log.Info($"Image {url} already analyzed"); continue; } PhotoToAnalyze message = new PhotoToAnalyze { Blog = blogname, PostDate = postEntity.Date, Url = url }; photoToAnalyzeQueueAdapter.Send(message); log.Info($"Published PhotoToAnalyze message with URL {url}"); analyzedInBlogCount++; totalCount++; } if (analyzedInBlogCount >= photosInBlogLimit) { break; } } } return(req.CreateResponse(HttpStatusCode.OK, $"Will analyze {totalCount} new photos")); }
public static HttpResponseMessage Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = "analyze-from-likes/{blogname}")] HttpRequestMessage req, string blogname, TraceWriter log) { Startup.Init(); LikeIndexTableAdapter likeIndexTableAdapter = new LikeIndexTableAdapter(); likeIndexTableAdapter.Init(); PostsTableAdapter postsTableAdapter = new PostsTableAdapter(); postsTableAdapter.Init(log); ImageAnalysisTableAdapter imageAnalysisTableAdapter = new ImageAnalysisTableAdapter(); imageAnalysisTableAdapter.Init(); PhotoToAnalyzeQueueAdapter photoToAnalyzeQueueAdapter = new PhotoToAnalyzeQueueAdapter(); photoToAnalyzeQueueAdapter.Init(); string blobBaseUrl = ConfigurationManager.AppSettings["BlobBaseUrl"]; string afterParam = req.GetQueryNameValuePairs().FirstOrDefault(q => q.Key.Equals("after", StringComparison.OrdinalIgnoreCase)).Value; List <LikeIndexEntity> likes; if (!string.IsNullOrEmpty(afterParam) && long.TryParse(afterParam, out long afterTimestamp)) { log.Info($"Getting likes newer than timestamp {afterTimestamp}"); likes = likeIndexTableAdapter.GetNewerThan(blogname, afterTimestamp); } else { likes = likeIndexTableAdapter.GetAll(blogname); } log.Info($"Loaded {likes.Count} posts"); int messageCount = 0; foreach (LikeIndexEntity like in likes) { if (like.LikedBlogName == null || like.LikedPostId == null) { continue; } PostEntity postEntity = postsTableAdapter.GetPost(like.LikedBlogName, like.LikedPostId); if (postEntity == null) { log.Warning($"Post {like.LikedBlogName}/{like.LikedPostId} not found, skipping"); continue; } if (string.IsNullOrEmpty(postEntity.PhotoBlobUrls)) { continue; } List <Photo> sitePhotos = JsonConvert.DeserializeObject <List <Photo> >(postEntity.PhotoBlobUrls); foreach (Photo photo in sitePhotos) { List <PhotoSize> sortedSizes = photo.Sizes.OrderByDescending(x => x.Nominal).ToList(); PhotoSize original = sortedSizes.FirstOrDefault(); if (original == null) { continue; } string url = blobBaseUrl + "/" + original.Container + "/" + photo.Name + "_" + original.Nominal + "." + photo.Extension; if (imageAnalysisTableAdapter.GetImageAnalysis(url) != null) { log.Info($"Image {url} already analyzed"); continue; } PhotoToAnalyze message = new PhotoToAnalyze { Blog = blogname, PostDate = postEntity.Date, Url = url }; photoToAnalyzeQueueAdapter.Send(message); log.Info($"Published PhotoToAnalyze message with URL {url}"); messageCount++; } } return(req.CreateResponse(HttpStatusCode.OK, $"Processed {likes.Count} posts, sent {messageCount} messages")); }
public async Task SendToPoisonQueue(PhotoToAnalyze photoToAnalyze) { string jsonMessage = JsonConvert.SerializeObject(photoToAnalyze); CloudQueueMessage message = new CloudQueueMessage(jsonMessage); await photoToAnalyzePoisonQueue.AddMessageAsync(message); }
public static async Task Run([TimerTrigger("0 15 * * * *")] TimerInfo myTimer, TraceWriter log) { Startup.Init(); PhotoToAnalyzeQueueAdapter photoToAnalyzeQueueAdapter = new PhotoToAnalyzeQueueAdapter(); photoToAnalyzeQueueAdapter.Init(); CloudQueueMessage message = null; PhotoToAnalyze photoToAnalyze = null; try { ImageAnalysisTableAdapter imageAnalysisTableAdapter = new ImageAnalysisTableAdapter(); imageAnalysisTableAdapter.Init(); BlogInfoTableAdapter blogInfoTableAdapter = new BlogInfoTableAdapter(); blogInfoTableAdapter.Init(); int processedCount = 0; do { message = await photoToAnalyzeQueueAdapter.GetNextMessage(); if (message == null) { return; } photoToAnalyze = JsonConvert.DeserializeObject <PhotoToAnalyze>(message.AsString); if (imageAnalysisTableAdapter.GetImageAnalysis(photoToAnalyze.Url) != null) { log.Info($"Image {photoToAnalyze.Url} already analyzed, aborting"); await UpdateBlogInfo(photoToAnalyze, blogInfoTableAdapter); continue; } string failure = await ImageAnalyzer.AnalyzePhoto(log, photoToAnalyze, imageAnalysisTableAdapter); if (string.IsNullOrEmpty(failure)) { await UpdateBlogInfo(photoToAnalyze, blogInfoTableAdapter); } else { photoToAnalyze.Error = failure; await photoToAnalyzeQueueAdapter.SendToPoisonQueue(photoToAnalyze); log.Info($"Message for {photoToAnalyze.Url} stored to poison queue"); } processedCount++; await photoToAnalyzeQueueAdapter.DeleteMessage(message); } while (processedCount < 10); } catch (Exception ex) { log.Error("Error in ProcessPhotosToAnalyze: " + ex.Message, ex); if (message != null && photoToAnalyze != null) { photoToAnalyze.Error = ex.Message; photoToAnalyze.StackTrace = ex.StackTrace.Substring(0, Math.Max(36000, ex.StackTrace.Length)); await photoToAnalyzeQueueAdapter.SendToPoisonQueue(photoToAnalyze); log.Info($"Message for {photoToAnalyze.Url} stored to poison queue"); await photoToAnalyzeQueueAdapter.DeleteMessage(message); log.Info($"Failed message deleted successfully from main queue"); message = null; } } }