Ennek a logikáját követjük: https://github.com/microsoft/AcademicContent/blob/5a77cd0fcb18137f39a2c0f95c7b91bd30edb603/Labs/Azure%20Services/Azure%20Storage/Azure%20Storage%20and%20Cognitive%20Services%20(MVC).md
Sajnos már teljesen elavult a leírás :disappointed:
Újabb laborfeladatok: https://docs.microsoft.com/en-us/learn/paths/store-data-in-azure/
Azure SDK for .NET csomagok: https://azure.github.io/azure-sdk/releases/latest/all/dotnet.html
A jobb felső részen fogaskerék ikonra bökve. Érdemes angolra állítani.
https://docs.microsoft.com/en-us/azure/billing/billing-check-free-service-usage (Student előfieztésen nem működik a free usage monitor) Ebben laborban:
Ingyenes szolgáltatások: https://azure.microsoft.com/en-us/free/ Azure sponsorship portál: https://www.microsoftazuresponsorships.com/
Tehát az egész labort ingyenes erőforrásokkal végig lehet csinálni.
Bizonyos erőforrásoknak globálisan vagy a régióban egyedi neve kell legyen. Így könnyen előfordulhat, hogy a név már foglalt. Érdemes ilyenkor valamilyen személyre egyedi prefixet/postfixet alkalmazni pl. neptun kód vagy monogram.
Intellipix
)mkdir intellipix
dotnet new webapp
VSCode-ban a könyvtár megnyitása. Dotnet assetek generáltatása.
dotnet add package SixLabors.ImageSharp
dotnet add package Azure.Storage.Blobs
dotnet add package System.Interactive.Async
dotnet add package Microsoft.Extensions.Azure
dotnet add package Flurl
dotnet user-secrets init
dotnet user-secrets set "AzStore:connectionString" "connstring"
builder.Services
sor alábuilder.Services.AddAzureClients(azb =>
{
azb.AddBlobServiceClient(builder.Configuration.GetSection("AzStore"));
});
IndexModel
-ben elkérjük a klienstprivate readonly BlobServiceClient _blobSvc;
public IndexModel(ILogger<IndexModel> logger, BlobServiceClient blobSvc)
{
_logger = logger;
_blobSvc = blobSvc;
}
BlobInfo
egy új Models
alkönyvtárbanamespace intellipix.Models;
public class BlobInfo
{
public string ImageUri { get; set; }
public string ThumbnailUri { get; set; }
public string? Caption { get; set; }
public BlobInfo(string imageUri, string thumbnailUri, string? caption=default)
{
ImageUri = imageUri;
ThumbnailUri = thumbnailUri;
Caption = caption;
}
}
IndexModel
-beAz IndexModel
tetejére:
using Flurl;
using intellipix.Models;
Cseréljük az eredeti OnGet
-et erre:
public IEnumerable<BlobInfo> Blobs { get; set; } = Enumerable.Empty<BlobInfo>();
public async Task OnGet()
{
BlobContainerClient blobccP = _blobSvc.GetBlobContainerClient("photos");
BlobContainerClient blobccT = _blobSvc.GetBlobContainerClient("thumbnails");
Blobs = await blobccP.GetBlobsAsync()
.Select(b => new BlobInfo(
blobccP.Uri.AppendPathSegment(b.Name)
,blobccT.Uri.AppendPathSegment(b.Name))
)
.ToListAsync();
}
<div class="container" style="padding-top: 24px">
<div class="row">
<div class="col-sm-8">
<form method="post" enctype="multipart/form-data">
<input type="file" asp-for="Upload" id="upload" style="display: none" onchange="$('#submit').click();"/>
<input type="button" value="Upload a Photo" class="btn btn-primary btn-lg" onclick="$('#upload').click();" />
<input type="submit" id="submit" style="display: none" asp-page-handler="Upload"/>
</form>
</div>
<div class="col-sm-4 float-right">
</div>
</div>
<hr />
<div class="row">
<div class="col-sm-12">
@foreach (BlobInfo blob in Model.Blobs ?? Enumerable.Empty<BlobInfo>())
{
<img src="@blob.ThumbnailUri" width="192" title="@blob.Caption" style="padding-right: 16px; padding-bottom: 16px" />
}
</div>
</div>
</div>
IndexModel
-be[BindProperty]
public IFormFile? Upload { get; set; }
public async Task<IActionResult> OnPostUploadAsync()
{
if (Upload != null)
{
BlobContainerClient blobccP = _blobSvc.GetBlobContainerClient("photos");
BlobClient blobc = blobccP.GetBlobClient(Upload.FileName);
using Stream stream = Upload.OpenReadStream();
var resp = await blobc.UploadAsync(stream);
/*ide jön majd a computer vision logika*/
stream.Seek(0, SeekOrigin.Begin);
using Image image = Image.Load(stream);
image.Mutate(x => x.Resize(192, 0));
BlobContainerClient blobccT = _blobSvc.GetBlobContainerClient("thumbnails");
using MemoryStream memoryStream = new MemoryStream();
image.Save(memoryStream, image.Metadata.DecodedImageFormat);
memoryStream.Seek(0, SeekOrigin.Begin);
await blobccT.UploadBlobAsync(Upload.FileName, memoryStream);
}
return RedirectToAction("Index");
}
Példaképek letöltése
Nézzük meg mit műveltünk Azure Storage Explorer-ben és a weboldal forrásában is. Ha átírjuk a thumbnail URI-ban a /thumbnail/-t /photos/-ra, megkapjuk az eredeti képet.
dotnet user-secrets set "AzVision:Endpoint" "https://valami.cognitiveservices.azure.com/"
dotnet user-secrets set "AzVision:Key" "titok"
dotnet add package Microsoft.Azure.CognitiveServices.Vision.ComputerVision
Ez a csomag az API 3.x-es verzióját hívja, a legújabb 4-es API verzióval kompatibilis csomag jelenleg még beta állapotban van.
Startup.ConfigureServices
-benbuilder.Services.AddSingleton(provider => {
return new ComputerVisionClient(new ApiKeyServiceClientCredentials(builder.Configuration["AzVision:Key"]))
{Endpoint = builder.Configuration["AzVision:Endpoint"]};
});
IndexModel
konstruktorbanprivate readonly ComputerVisionClient _visionClient;
public IndexModel(ILogger<IndexModel> logger, BlobServiceClient blobSvc, ComputerVisionClient visionClient)
{
_logger = logger;
_blobSvc = blobSvc;
_visionClient = visionClient;
}
Az OnPostUploadAsync
elejére:
VisualFeatureTypes?[] features = new VisualFeatureTypes?[] { VisualFeatureTypes.Description };
Ugyanezen függvényben a kommenttel jelzett helyre:
var analResult = await _visionClient.AnalyzeImageAsync(blobc.Uri.ToString(), features);
var blobMetaDict = analResult.Description.Tags
.Select((t, i) => new KeyValuePair<string, string>(nameof(analResult.Description.Tags) + i, t))
.Concat(new Dictionary<string, string> { { nameof(analResult.Description.Captions), analResult.Description.Captions[0].Text } })
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
await blobc.SetMetadataAsync(blobMetaDict);
public async Task OnGet()
{
BlobContainerClient blobccP = _blobSvc.GetBlobContainerClient("photos");
BlobContainerClient blobccT = _blobSvc.GetBlobContainerClient("thumbnails");
Blobs = await blobccP.GetBlobsAsync(BlobTraits.Metadata)
.Select(b => new BlobInfo(
blobccP.Uri.AppendPathSegment(b.Name)
,blobccT.Uri.AppendPathSegment(b.Name)
,b.Metadata.ContainsKey("Captions")? b.Metadata["Captions"]:b.Name)
)
.ToListAsync();
}
<div class="col-sm-4 float-right">
<form method="post" enctype="multipart/form-data" class="form-inline">
<div class="input-group">
<input type="text" class="form-control" placeholder="Search photos" asp-for="SearchTerm" style="max-width: 800px">
<span class="input-group-append">
<button class="btn btn-primary" type="submit" asp-page-handler="Search">Go</button>
</span>
</div>
</form>
</div>
IndexModel
-be, átirányítjuk a lekérdező műveletre.[BindProperty]
public string? SearchTerm {get; set;}
public ActionResult OnPostSearchAsync()
{
return RedirectToAction("Index", new { term = SearchTerm });
}
public async Task OnGet(string? term)
/**/{
/**/ BlobContainerClient blobccP = _blobSvc.GetBlobContainerClient("photos");
/**/ BlobContainerClient blobccT = _blobSvc.GetBlobContainerClient("thumbnails");
/**/ Blobs = await blobccP.GetBlobsAsync(BlobTraits.Metadata)
.Where(b=>string.IsNullOrEmpty(term) ||
b.Metadata.Any(m=>m.Key.StartsWith("Tags")
&& m.Value.Equals(term, StringComparison.InvariantCultureIgnoreCase))
)
/**/ .Select(b => new BlobInfo(
/**/ blobccP.Uri.AppendPathSegment(b.Name)
/**/ ,blobccT.Uri.AppendPathSegment(b.Name)
/**/ ,b.Metadata.ContainsKey("Captions")? b.Metadata["Captions"]:b.Name)
/**/ )
/**/ .ToListAsync();
SearchTerm = term;
/**/}
Ezzel így lekérdezzük az összes blob összes metaadatát és memóriában szűrünk. Alternatív lehetőségnek tűnhet a blob index tags töltése, amivel már Azure oldalon tudnánk szűrni. Sajnos a fenti szűrést nem lehet egy az egyben átfordítani szűrőkifejezéssé. A kulcsokat explicit meg kell adni, nem lehet a kulcsokra kifejezést megadni.