-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #6 from Hekku2/image-analyzer
Add support for cognitive services
- Loading branch information
Showing
18 changed files
with
351 additions
and
132 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
@minLength(5) | ||
param baseName string | ||
|
||
@description('Location for all resources.') | ||
param location string = resourceGroup().location | ||
|
||
@description('Identities of which are assigned with Blob Data Reader to the containers.') | ||
param cognitiveServiceUserIdentityNames string[] | ||
|
||
var cognitiveServicesUserRoleDefinitionId = subscriptionResourceId( | ||
'Microsoft.Authorization/roleDefinitions', | ||
'a97b65f3-24c7-4388-baec-2e87135dc908' | ||
) | ||
|
||
resource identities 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-07-31-preview' existing = [ | ||
for identityName in cognitiveServiceUserIdentityNames: { | ||
name: identityName | ||
scope: resourceGroup() | ||
} | ||
] | ||
|
||
var cognitiveServiceName = 'aisa-${baseName}' | ||
resource cognitiveServiceAccount 'Microsoft.CognitiveServices/accounts@2023-05-01' = { | ||
name: cognitiveServiceName | ||
location: location | ||
kind: 'ComputerVision' | ||
identity: { | ||
type: 'SystemAssigned' | ||
} | ||
sku: { | ||
name: 'F0' | ||
} | ||
properties: { | ||
publicNetworkAccess: 'Enabled' | ||
customSubDomainName: cognitiveServiceName | ||
} | ||
} | ||
|
||
resource cognitiveServiceAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [ | ||
for (identityName, i) in cognitiveServiceUserIdentityNames: { | ||
scope: cognitiveServiceAccount | ||
name: guid(identities[i].id, cognitiveServicesUserRoleDefinitionId, cognitiveServiceAccount.id) | ||
properties: { | ||
principalId: identities[i].properties.principalId | ||
principalType: 'ServicePrincipal' | ||
roleDefinitionId: cognitiveServicesUserRoleDefinitionId | ||
} | ||
} | ||
] | ||
|
||
output cognitiveServiceAccountName string = cognitiveServiceAccount.name |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
namespace DiscordImagePoster.Common.Discord; | ||
|
||
public interface IDiscordImagePoster | ||
{ | ||
Task SendImage(Stream stream, string fileName, string? description); | ||
} | ||
namespace DiscordImagePoster.Common.Discord; | ||
|
||
public interface IDiscordImagePoster | ||
{ | ||
Task SendImage(Stream stream, string fileName, string? description); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,24 @@ | ||
namespace DiscordImagePoster.Common.Discord; | ||
|
||
/// <summary> | ||
/// A no-op implementation of <see cref="IDiscordImagePoster"/>. | ||
/// </summary> | ||
public class NoOpDiscordImagePoster : IDiscordImagePoster | ||
{ | ||
public Task SendImage(Stream stream, string fileName, string? description) | ||
{ | ||
return Task.CompletedTask; | ||
} | ||
} | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace DiscordImagePoster.Common.Discord; | ||
|
||
/// <summary> | ||
/// A no-op implementation of <see cref="IDiscordImagePoster"/>. | ||
/// </summary> | ||
public class NoOpDiscordImagePoster : IDiscordImagePoster | ||
{ | ||
private readonly ILogger<NoOpDiscordImagePoster> _logger; | ||
|
||
public NoOpDiscordImagePoster( | ||
ILogger<NoOpDiscordImagePoster> logger | ||
) | ||
{ | ||
_logger = logger; | ||
} | ||
|
||
public Task SendImage(Stream stream, string fileName, string? description) | ||
{ | ||
_logger.LogWarning("Discord sending is disabled. Filename was {fileName} with description {description}.", fileName, description); | ||
return Task.CompletedTask; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
namespace DiscordImagePoster.Common.ImageAnalysis; | ||
|
||
/// <summary> | ||
/// Service for analyzing images. | ||
/// </summary> | ||
public interface IImageAnalysisService | ||
{ | ||
/// <summary> | ||
/// Analyzes the image in stream. | ||
/// </summary> | ||
/// <param name="stream">The stream containing the image.</param> | ||
/// <returns>The analysis results.</returns> | ||
Task<ImageAnalysisResults> AnalyzeImageAsync(Stream stream); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
namespace DiscordImagePoster.Common.ImageAnalysis; | ||
|
||
/// <summary> | ||
/// Configuration for image analysis service. | ||
/// </summary> | ||
public class ImageAnalysisConfiguration | ||
{ | ||
/// <summary> | ||
/// The endpoint of the image analysis service. | ||
/// For example https://service-name-here.cognitiveservices.azure.com/ | ||
/// </summary> | ||
public required string Endpoint { get; set; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
namespace DiscordImagePoster.Common.ImageAnalysis; | ||
|
||
/// <summary> | ||
/// Result of image analysis. Basically a subset of the data received from the service. | ||
/// </summary> | ||
public class ImageAnalysisResults | ||
{ | ||
/// <summary> | ||
/// The caption of the image. | ||
/// </summary> | ||
public required string Caption { get; set; } | ||
|
||
/// <summary> | ||
/// The tags of the image. | ||
/// </summary> | ||
public required string[] Tags { get; set; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
using System.Text.Json; | ||
using Azure.AI.Vision.ImageAnalysis; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace DiscordImagePoster.Common.ImageAnalysis; | ||
|
||
/// <summary> | ||
/// Image analysis service using Azure Cognitive Services. | ||
/// </summary> | ||
public class ImageAnalysisService : IImageAnalysisService | ||
{ | ||
private readonly ILogger<ImageAnalysisService> _logger; | ||
private readonly ImageAnalysisClient _imageAnalysisClient; | ||
|
||
public ImageAnalysisService( | ||
ILogger<ImageAnalysisService> logger, | ||
ImageAnalysisClient imageAnalysisClient) | ||
{ | ||
_logger = logger; | ||
_imageAnalysisClient = imageAnalysisClient; | ||
} | ||
|
||
/// <inheritdoc/> | ||
public async Task<ImageAnalysisResults> AnalyzeImageAsync(Stream stream) | ||
{ | ||
_logger.LogDebug("Analyzing image..."); | ||
var result = await _imageAnalysisClient.AnalyzeAsync(BinaryData.FromStream(stream), VisualFeatures.Caption | VisualFeatures.Tags); | ||
var tags = result.Value.Tags.Values.Select(tag => $"{tag.Name} {tag.Confidence}").ToArray(); | ||
_logger.LogDebug("Image analysis result with caption: {result}, and tags {tags}", result.Value.Caption.Text, tags); | ||
_logger.LogTrace("Image analysis result: {result}", JsonSerializer.Serialize(result)); | ||
|
||
return new ImageAnalysisResults | ||
{ | ||
Caption = result.Value.Caption.Text, | ||
Tags = result.Value.Tags.Values.Select(tag => tag.Name).ToArray() | ||
}; | ||
} | ||
} |
100 changes: 50 additions & 50 deletions
100
src/Common/IndexService/BlobStorageIndexStorageService.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,50 +1,50 @@ | ||
using System.Text.Json; | ||
using Azure.Storage.Blobs; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Logging; | ||
using Microsoft.Extensions.Options; | ||
|
||
namespace DiscordImagePoster.Common.IndexService; | ||
|
||
public class BlobStorageIndexStorageService : IIndexStorageService | ||
{ | ||
private readonly ILogger<BlobStorageIndexStorageService> _logger; | ||
private readonly ImageIndexOptions _options; | ||
private readonly BlobContainerClient _blobContainerClient; | ||
|
||
public BlobStorageIndexStorageService | ||
( | ||
ILogger<BlobStorageIndexStorageService> logger, | ||
IOptions<ImageIndexOptions> options, | ||
[FromKeyedServices(KeyedServiceConstants.ImageIndexBlobContainerClient)] BlobContainerClient blobContainerClient | ||
) | ||
{ | ||
_logger = logger; | ||
_options = options.Value; | ||
_blobContainerClient = blobContainerClient; | ||
} | ||
|
||
public async Task<ImageIndex?> GetImageIndexAsync() | ||
{ | ||
_logger.LogTrace("Getting image index from {IndexFileName}", _options.IndexFileName); | ||
var blobClient = _blobContainerClient.GetBlobClient(_options.IndexFileName); | ||
var exists = await blobClient.ExistsAsync(); | ||
if (!exists) | ||
{ | ||
_logger.LogWarning("Index blob not found."); | ||
return null; | ||
} | ||
|
||
using var stream = await blobClient.OpenReadAsync(); | ||
return await JsonSerializer.DeserializeAsync<ImageIndex>(stream); | ||
} | ||
|
||
public async Task UpdateIndexAsync(ImageIndex index) | ||
{ | ||
_logger.LogTrace("Updating image index to {IndexFileName}", _options.IndexFileName); | ||
await _blobContainerClient.CreateIfNotExistsAsync(); | ||
var bytes = JsonSerializer.SerializeToUtf8Bytes(index); | ||
var blobClient = _blobContainerClient.GetBlobClient(_options.IndexFileName); | ||
await blobClient.UploadAsync(new MemoryStream(bytes), true); | ||
} | ||
} | ||
using System.Text.Json; | ||
using Azure.Storage.Blobs; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Logging; | ||
using Microsoft.Extensions.Options; | ||
|
||
namespace DiscordImagePoster.Common.IndexService; | ||
|
||
public class BlobStorageIndexStorageService : IIndexStorageService | ||
{ | ||
private readonly ILogger<BlobStorageIndexStorageService> _logger; | ||
private readonly ImageIndexOptions _options; | ||
private readonly BlobContainerClient _blobContainerClient; | ||
|
||
public BlobStorageIndexStorageService | ||
( | ||
ILogger<BlobStorageIndexStorageService> logger, | ||
IOptions<ImageIndexOptions> options, | ||
[FromKeyedServices(KeyedServiceConstants.ImageIndexBlobContainerClient)] BlobContainerClient blobContainerClient | ||
) | ||
{ | ||
_logger = logger; | ||
_options = options.Value; | ||
_blobContainerClient = blobContainerClient; | ||
} | ||
|
||
public async Task<ImageIndex?> GetImageIndexAsync() | ||
{ | ||
_logger.LogTrace("Getting image index from {IndexFileName}", _options.IndexFileName); | ||
var blobClient = _blobContainerClient.GetBlobClient(_options.IndexFileName); | ||
var exists = await blobClient.ExistsAsync(); | ||
if (!exists) | ||
{ | ||
_logger.LogWarning("Index blob not found."); | ||
return null; | ||
} | ||
|
||
using var stream = await blobClient.OpenReadAsync(); | ||
return await JsonSerializer.DeserializeAsync<ImageIndex>(stream); | ||
} | ||
|
||
public async Task UpdateIndexAsync(ImageIndex index) | ||
{ | ||
_logger.LogTrace("Updating image index to {IndexFileName}", _options.IndexFileName); | ||
await _blobContainerClient.CreateIfNotExistsAsync(); | ||
var bytes = JsonSerializer.SerializeToUtf8Bytes(index); | ||
var blobClient = _blobContainerClient.GetBlobClient(_options.IndexFileName); | ||
await blobClient.UploadAsync(new MemoryStream(bytes), true); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.