Refactored ImageGenerator

Option "StableDiffusionModel" zu AppSettings hinzugefügt
This commit is contained in:
Simon Lübeß 2023-10-13 12:20:24 +02:00
parent 80c7dca613
commit fa75985014
4 changed files with 56 additions and 31 deletions

View File

@ -19,7 +19,7 @@ public class BildInfoData
/// Fügt die gegebene BildInfo zur Datenbank hinzu und aktualisiert das <see cref="BildInfoModel.Id"/>-Feld mit dem entsprechenden Wert. /// Fügt die gegebene BildInfo zur Datenbank hinzu und aktualisiert das <see cref="BildInfoModel.Id"/>-Feld mit dem entsprechenden Wert.
/// </summary> /// </summary>
/// <param name="bildInfo">Die BildInfo, die zur Datenbank hinzugefügt werden soll.</param> /// <param name="bildInfo">Die BildInfo, die zur Datenbank hinzugefügt werden soll.</param>
public async Task AddBildInfoAsync(BildInfoModel bildInfo) public async Task InsertBildInfoAsync(BildInfoModel bildInfo)
{ {
var id = await _db.LoadData<int, BildInfoModel>("dbo.spBildInfo_Insert", bildInfo); var id = await _db.LoadData<int, BildInfoModel>("dbo.spBildInfo_Insert", bildInfo);
bildInfo.Id = id.Single(); bildInfo.Id = id.Single();

View File

@ -10,8 +10,13 @@ public class ImageGenerator
private readonly HttpClient _client = new(); private readonly HttpClient _client = new();
private readonly BildInfoData _bildInfoData; private readonly BildInfoData _bildInfoData;
private readonly IConfiguration _config;
private string ModelName => _config.GetValue<string>("StableDiffusionModel") ?? "No Model Defined";
public ImageGenerator(IConfiguration config, BildInfoData bildInfoData) public ImageGenerator(IConfiguration config, BildInfoData bildInfoData)
{ {
_config = config;
_bildInfoData = bildInfoData; _bildInfoData = bildInfoData;
string? inferenceApiKey = config.GetValue<string>("API:HF_Inference"); string? inferenceApiKey = config.GetValue<string>("API:HF_Inference");
@ -21,13 +26,18 @@ public class ImageGenerator
} }
/// <summary> /// <summary>
/// Geneiert das Bild für den aktuellen <see cref="_imagePrompt"/> /// Geneiert ein Bild für den gegebenen Wunsch.
/// </summary> /// </summary>
public async Task<string?> GenerateImageAsync(string prompt, string negativePromt, int? width, int? height, WunschInfoModel wunschInfo, bool isRetry = false) /// <param name="wunschInfo">Der Wunsch zu dem ein Bild generiert werden soll.</param>
/// <param name="width">Die breite des zu generierenden Bildes. <see langword="null"/> für Standardbreite des Modells</param>
/// <param name="height">Die höhe des zu generierenden Bildes. <see langword="null"/> für Standardhöhe des Modells</param>
/// <param name="negativePromt">Begriffe, die explizit nicht generiert werden sollen.</param>
/// <returns>Die BildInfo des generierten Bildes; oder null, wenn ein Fehler auftrat.</returns>
public async Task<BildInfoModel?> GenerateImageAsync(WunschInfoModel wunschInfo, int? width = null, int? height = null, string negativePromt = "")
{ {
var postData = new var postData = new
{ {
inputs = prompt, inputs = wunschInfo.BildPrompt,
parameters = new parameters = new
{ {
negative_prompt = negativePromt, //"photorealistic, highly detailed, 8K, portrait", negative_prompt = negativePromt, //"photorealistic, highly detailed, 8K, portrait",
@ -36,9 +46,11 @@ public class ImageGenerator
}, },
options = new options = new
{ {
// https://huggingface.co/docs/api-inference/detailed_parameters
// Cache deaktivieren, damit Huggingface für den selben Prompt unterschiedliche Ergebnisse liefert // Cache deaktivieren, damit Huggingface für den selben Prompt unterschiedliche Ergebnisse liefert
use_cache = false, use_cache = false,
// Erst wenn wir bereits in einem retry sind warten wir implizit auf das Model. (ignoriert quasi 503-Fehler) // Ignoriert 503-Fehler. Diese treten auf wenn Hugging Face das Model noch nicht geladen hat.
// Mit true ignorieren wir den Fehler und schlagen erst fehl, wenn wir einen timeout haben.
wait_for_model = true wait_for_model = true
} }
}; };
@ -47,10 +59,7 @@ public class ImageGenerator
try try
{ {
//const string modelName = "Nacken/kkkk-sdxl-5000"; var inferenceModelUrl = $"https://api-inference.huggingface.co/models/{ModelName}";
const string modelName = "Nacken/kkk-sdxl-18000";
var inferenceModelUrl = $"https://api-inference.huggingface.co/models/{modelName}";
var response = await _client.PostAsync(inferenceModelUrl, content); var response = await _client.PostAsync(inferenceModelUrl, content);
@ -58,29 +67,20 @@ public class ImageGenerator
{ {
await using Stream imageStream = await response.Content.ReadAsStreamAsync(); await using Stream imageStream = await response.Content.ReadAsStreamAsync();
using Image image = await Image.LoadAsync(imageStream);
DateTime imageDate = DateTime.Now; DateTime imageDate = DateTime.Now;
BildInfoModel bildInfo = new() BildInfoModel bildInfo = new()
{ {
// Die tatsächliche Url wird in SaveImageStreamAsync gesetzt.
Dateiname = "PlaceHolder", Dateiname = "PlaceHolder",
Datum = imageDate, Datum = imageDate,
ImageModel = modelName, ImageModel = ModelName,
WunschId = wunschInfo.Id WunschId = wunschInfo.Id
}; };
await _bildInfoData.AddBildInfoAsync(bildInfo); await SaveImageStreamAsync(imageStream, bildInfo);
string imgUrl = $"generated_images/Image_{bildInfo.Id}.jpg"; return bildInfo;
string mapPath = $"./wwwroot/{imgUrl}";
await image.SaveAsJpegAsync(mapPath);
bildInfo.Dateiname = imgUrl;
await _bildInfoData.UpdateBildInfoDateinameAsync(bildInfo);
return imgUrl;
} }
else else
{ {
@ -102,4 +102,26 @@ public class ImageGenerator
return null; return null;
} }
} }
/// <summary>
/// Speichert den Inhalt des gegebenen Datenstroms als Bild ab.
/// </summary>
/// <param name="dataStream">Der Datenstrom, der das Bild enthält.</param>
/// <param name="bildInfo">Die BildInfo des Bildes. Der Dateiname wird nach dem Speichern aktualisiert.</param>
private async Task SaveImageStreamAsync(Stream dataStream, BildInfoModel bildInfo)
{
using Image image = await Image.LoadAsync(dataStream);
// Speichert BildInfo in der Datenbank, aktualisiert Id der bildInfo
await _bildInfoData.InsertBildInfoAsync(bildInfo);
string imageUrl = $"generated_images/Image_{bildInfo.Id}.jpg";
string imageFilePath = $"./wwwroot/{imageUrl}";
await image.SaveAsJpegAsync(imageFilePath);
// Speichert den geänderten Dateinamen in der Datenbank
bildInfo.Dateiname = imageUrl;
await _bildInfoData.UpdateBildInfoDateinameAsync(bildInfo);
}
} }

View File

@ -305,7 +305,7 @@
// Vier Bilder generieren // Vier Bilder generieren
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
{ {
_imageUrls[i] = await ImageGenerator.GenerateImageAsync(_imagePrompt, "", width, height, wunschInfo); _imageUrls[i] = (await ImageGenerator.GenerateImageAsync(wunschInfo, width, height))?.Dateiname;
// Kein Bild -> Fehler // Kein Bild -> Fehler
if (_imageUrls[i] == null) if (_imageUrls[i] == null)
@ -317,12 +317,14 @@
if (retry == true) if (retry == true)
{ {
await ImageGenerator.GenerateImageAsync(_imagePrompt, "", width, height, wunschInfo); _imageUrls[i] = (await ImageGenerator.GenerateImageAsync(wunschInfo, width, height))?.Dateiname;
} }
} }
generatedImages++; if (_imageUrls[i] != null)
generatedImages++;
// TODO: Fehler anzeigen
_imageStates[i] = ImageState.FadeIn; _imageStates[i] = ImageState.FadeIn;
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
} }

View File

@ -1,15 +1,16 @@
{ {
"Logging": { "Logging": {
"LogLevel": { "LogLevel": {
"Default": "Information", "Default": "Information",
"Microsoft.AspNetCore": "Warning" "Microsoft.AspNetCore": "Warning"
} }
}, },
"AllowedHosts": "*", "AllowedHosts": "*",
"API": { "API": {
"OpenAI": "<put OpenAI Key here>", "OpenAI": "<put OpenAI Key here>",
"HF_Inference": "<put Hugging Face inference API Key here>" "HF_Inference": "<put Hugging Face inference API Key here>"
}, },
"StableDiffusionModel": "Nacken/kkk-sdxl-18000",
"ConnectionStrings": { "ConnectionStrings": {
"Default": "<put Connection String here>" "Default": "<put Connection String here>"
} }