Skip to content

Commit

Permalink
Merge pull request #4 from aghimir3/issues/5657-itexttoimage-ag
Browse files Browse the repository at this point in the history
.NET Add Style and Quality Parameters Support with Execution Settings
  • Loading branch information
RogerBarreto committed Aug 14, 2024
2 parents 0af318e + ef12678 commit 9a47d7c
Show file tree
Hide file tree
Showing 3 changed files with 196 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net.Http;
using System.Text.Json;
using System.Threading;
Expand Down Expand Up @@ -94,9 +95,72 @@ public Task<string> GenerateImageAsync(string description, int width, int height
}

/// <inheritdoc/>
public Task<IReadOnlyList<ImageContent>> GetImageContentsAsync(TextContent input, PromptExecutionSettings? executionSettings = null, Kernel? kernel = null, CancellationToken cancellationToken = default)
public async Task<IReadOnlyList<ImageContent>> GetImageContentsAsync(
TextContent input,
PromptExecutionSettings? executionSettings = null,
Kernel? kernel = null,
CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
// Ensure the input is valid
Verify.NotNull(input);

// Convert the generic execution settings to OpenAI-specific settings
var imageSettings = OpenAITextToImageExecutionSettings.FromExecutionSettings(executionSettings);

// Determine the size of the image based on the width and height settings
var size = (imageSettings.Width, imageSettings.Height) switch
{
(256, 256) => "256x256",
(512, 512) => "512x512",
(1024, 1024) => "1024x1024",
(1792, 1024) => "1792x1024",
(1024, 1792) => "1024x1792",
_ => throw new NotSupportedException($"The provided size is not supported: {imageSettings.Width}x{imageSettings.Height}")
};

// Validate quality and style
var supportedQualities = new[] { "standard", "hd" };
var supportedStyles = new[] { "vivid", "natural" };

if (!string.IsNullOrEmpty(imageSettings.Quality) && !supportedQualities.Contains(imageSettings.Quality))
{
throw new NotSupportedException($"The provided quality '{imageSettings.Quality}' is not supported.");
}

if (!string.IsNullOrEmpty(imageSettings.Style) && !supportedStyles.Contains(imageSettings.Style))
{
throw new NotSupportedException($"The provided style '{imageSettings.Style}' is not supported.");
}

// Create the request body for the image generation
var requestBody = JsonSerializer.Serialize(new TextToImageRequest
{
Model = imageSettings.ModelId ?? this._modelId,
Prompt = input.Text ?? string.Empty,
Size = size,
Count = imageSettings.ImageCount ?? 1,
Quality = imageSettings.Quality ?? "standard",
Style = imageSettings.Style ?? "vivid"
});

// Execute the request using the core client and return Image objects
var imageStrings = await this._core.ExecuteImageGenerationRequestAsync(OpenAIEndpoint, requestBody, x => x.Url ?? x.AsBase64, cancellationToken).ConfigureAwait(false);

// Convert the strings to ImageContent objects
var images = new List<ImageContent>();
foreach (var imageString in imageStrings)
{
if (Uri.TryCreate(imageString, UriKind.Absolute, out var uriResult) && (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps))
{
images.Add(new ImageContent(uriResult));
}
else
{
images.Add(new ImageContent($"data:;base64,{imageString}"));
}
}

return images.AsReadOnly();
}

private async Task<string> GenerateImageAsync(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,16 @@ internal sealed class TextToImageRequest
/// </summary>
[JsonPropertyName("response_format")]
public string Format { get; set; } = "url";

/// <summary>
/// Image quality, "standard" or "hd"
/// </summary>
[JsonPropertyName("quality")]
public string Quality { get; set; } = "standard";

/// <summary>
/// Image style, "vivid" or "natural"
/// </summary>
[JsonPropertyName("style")]
public string Style { get; set; } = "vivid";
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Moq;
using Xunit;
Expand Down Expand Up @@ -81,6 +83,122 @@ public async Task GenerateImageWorksCorrectlyAsync(int width, int height, bool e
}
}

[Fact]
public async Task GetImageContentsAsyncWithValidInputReturnsImageContentsAsync()
{
// Arrange
var service = new OpenAITextToImageService("api-key", "organization", "dall-e-3", this._httpClient);
Assert.Equal("dall-e-3", service.Attributes["ModelId"]);

this._messageHandlerStub.ResponseToReturn = new HttpResponseMessage(System.Net.HttpStatusCode.OK)
{
Content = new StringContent("""
{
"created": 1702575371,
"data": [
{
"url": "https://image-url"
}
]
}
""", Encoding.UTF8, "application/json")
};

var input = new TextContent("A cute baby sea otter");
var executionSettings = new OpenAITextToImageExecutionSettings
{
Width = 1024,
Height = 1024,
Quality = "hd",
Style = "natural",
ImageCount = 1
};

// Act
var result = await service.GetImageContentsAsync(input, executionSettings);

// Assert
Assert.NotNull(result);
Assert.Single(result);
Assert.Equal(new Uri("https://image-url"), result[0].Uri);
}

[Theory]
[InlineData(1024, 1024, "hd", "natural", 1, "https://image-url", false)]
[InlineData(123, 456, "hd", "natural", 1, "", true)]
[InlineData(1024, 1024, "hd", "natural", 2, "https://image-url1|https://image-url2", false)]
[InlineData(1024, 1024, "ultra", "natural", 1, "", true)]
[InlineData(1024, 1024, "hd", "artistic", 1, "", true)]
public async Task GetImageContentsReturnsExpectedResultsAsync(
int width,
int height,
string quality,
string style,
int imageCount,
string expectedUrls,
bool expectException)
{
// Arrange
var service = new OpenAITextToImageService("api-key", "organization", "dall-e-3", this._httpClient);

if (!expectException)
{
var urls = expectedUrls.Split('|').Select(url =>
{
return url.StartsWith("http", StringComparison.OrdinalIgnoreCase) ?
$"{{ \"url\": \"{url}\" }}" :
$"{{ \"b64_json\": \"{url}\" }}";
});
var jsonResponse = $"{{ \"created\": 1702575371, \"data\": [ {string.Join(",", urls)} ] }}";

this._messageHandlerStub.ResponseToReturn = new HttpResponseMessage(System.Net.HttpStatusCode.OK)
{
Content = new StringContent(jsonResponse, Encoding.UTF8, "application/json")
};
}

var input = new TextContent("A picturesque landscape");
var executionSettings = new OpenAITextToImageExecutionSettings
{
Width = width,
Height = height,
Quality = quality,
Style = style,
ImageCount = imageCount
};

// Act & Assert
if (expectException)
{
await Assert.ThrowsAsync<NotSupportedException>(async () =>
{
await service.GetImageContentsAsync(input, executionSettings);
});
}
else
{
var result = await service.GetImageContentsAsync(input, executionSettings);

Assert.NotNull(result);
Assert.Equal(imageCount, result.Count);

var expectedUrlList = expectedUrls.Split('|').ToList();
for (int i = 0; i < result.Count; i++)
{
if (Uri.TryCreate(expectedUrlList[i], UriKind.Absolute, out var uriResult) &&
(uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps))
{
Assert.Equal(uriResult, result[i].Uri);
}
else
{
Assert.StartsWith("data:;base64,", result[i].DataUri);
Assert.Contains(expectedUrlList[i], result[i].DataUri);
}
}
}
}

public void Dispose()
{
this._httpClient.Dispose();
Expand Down

0 comments on commit 9a47d7c

Please sign in to comment.