Skip to content

Commit 3ad402f

Browse files
authored
Feat input fidelity (#20)
* feat: add input fidelity parameter for image editing * fix: update import statement for CorsRule in AzureBlobStorageService * fix: clear existing CORS rules before setting new ones in AzureBlobStorageService * feat: add image dimensions to metadata and enhance image handling in AzureBlobStorageService
1 parent c9198a0 commit 3ad402f

File tree

15 files changed

+266
-146
lines changed

15 files changed

+266
-146
lines changed

backend/api/endpoints/images.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,8 @@ async def edit_image(request: ImageEditRequest):
213213
params["output_format"] = request.output_format
214214
if request.output_format in ["webp", "jpeg"] and request.output_compression != 100:
215215
params["output_compression"] = request.output_compression
216+
if request.input_fidelity and request.input_fidelity != "low":
217+
params["input_fidelity"] = request.input_fidelity
216218
if request.user:
217219
params["user"] = request.user
218220

@@ -268,11 +270,24 @@ async def edit_image_upload(
268270
size: str = Form("auto"),
269271
quality: str = Form("auto"),
270272
output_format: str = Form("png"),
273+
input_fidelity: str = Form("low"),
271274
image: List[UploadFile] = File(...),
272275
mask: Optional[UploadFile] = File(None)
273276
):
274-
"""Edit input images uploaded via multipart form data"""
277+
"""Edit input images uploaded via multipart form data
278+
279+
Supports input_fidelity parameter for gpt-image-1:
280+
- 'low' (default): Standard fidelity, faster processing
281+
- 'high': Better reproduction of input image features, additional cost (~$0.04-$0.06 per image)
282+
"""
275283
try:
284+
# Validate input_fidelity parameter
285+
if input_fidelity not in ["low", "high"]:
286+
raise HTTPException(
287+
status_code=400,
288+
detail="input_fidelity must be either 'low' or 'high'"
289+
)
290+
276291
# Validate file size for all images
277292
max_file_size_mb = settings.GPT_IMAGE_MAX_FILE_SIZE_MB
278293
temp_files = []
@@ -366,6 +381,8 @@ async def edit_image_upload(
366381
# Add quality parameter for gpt-image-1
367382
if model == "gpt-image-1":
368383
params["quality"] = quality
384+
if input_fidelity and input_fidelity != "low":
385+
params["input_fidelity"] = input_fidelity
369386
# Note: output_format is not supported for image editing in the OpenAI API
370387
# Keeping this commented to document the limitation
371388
# if output_format != "png":
@@ -526,6 +543,10 @@ async def save_generated_images(
526543
img_format = img.format or "PNG"
527544
has_transparency = img.mode == 'RGBA' and 'A' in img.getbands()
528545

546+
# Add width and height to metadata
547+
metadata["width"] = str(img.width)
548+
metadata["height"] = str(img.height)
549+
529550
if has_transparency:
530551
metadata["has_transparency"] = "true"
531552
# Ensure PNG format for transparent images
@@ -580,9 +601,14 @@ async def save_generated_images(
580601
"Content-Type", "image/png")
581602
ext = content_type.split("/")[-1]
582603

583-
# Check if image has transparency using PIL
604+
# Check if image has transparency using PIL and get dimensions
584605
with Image.open(img_file) as img:
585606
has_transparency = img.mode == 'RGBA' and 'A' in img.getbands()
607+
608+
# Add width and height to metadata
609+
metadata["width"] = str(img.width)
610+
metadata["height"] = str(img.height)
611+
586612
if has_transparency:
587613
metadata["has_transparency"] = "true"
588614

backend/core/azure_storage.py

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
import uuid
3+
import logging
34
from typing import Dict, BinaryIO, Optional, Union, List, Tuple
45
from fastapi import UploadFile
56
from azure.storage.blob import BlobServiceClient, ContentSettings
@@ -8,6 +9,8 @@
89

910
from backend.core.config import settings
1011

12+
logger = logging.getLogger(__name__)
13+
1114

1215
class AzureBlobStorageService:
1316
"""Service for handling Azure Blob Storage operations"""
@@ -47,9 +50,18 @@ def _configure_cors(self) -> None:
4750
from frontend domains
4851
"""
4952
try:
50-
from azure.storage.blob._models import CorsRule
53+
from azure.storage.blob import CorsRule
5154

52-
# Define CORS rules
55+
# First, clear any existing CORS rules to avoid conflicts
56+
try:
57+
print("Clearing existing CORS rules...")
58+
self.blob_service_client.set_service_properties(cors=[])
59+
print("Existing CORS rules cleared successfully")
60+
except Exception as clear_error:
61+
print(
62+
f"Warning: Could not clear existing CORS rules: {clear_error}")
63+
64+
# Define CORS rules with individual origins (not comma-separated)
5365
cors_rules = [
5466
CorsRule(
5567
allowed_origins=[
@@ -74,16 +86,21 @@ def _configure_cors(self) -> None:
7486
)
7587
]
7688

89+
print(
90+
f"Setting CORS rules with {len(cors_rules[0].allowed_origins)} origins...")
91+
print(f"Origins: {cors_rules[0].allowed_origins}")
92+
7793
# Set CORS rules
78-
self.blob_service_client.set_service_properties(
79-
cors=cors_rules
80-
)
94+
self.blob_service_client.set_service_properties(cors=cors_rules)
8195

8296
print("Successfully configured CORS for Azure Blob Storage")
8397

8498
except Exception as e:
8599
print(
86100
f"Warning: Could not configure CORS for Azure Blob Storage: {e}")
101+
# Print more details for debugging
102+
import traceback
103+
print(f"Full error traceback: {traceback.format_exc()}")
87104
# Don't fail if CORS configuration fails, as it might be due to permissions
88105

89106
def list_blobs(self, container_name: str, prefix: Optional[str] = None,
@@ -365,6 +382,21 @@ async def upload_asset(self, file: UploadFile, asset_type: str = "image",
365382

366383
# Upload the file
367384
file_content = await file.read()
385+
386+
# For images, add width and height to metadata if not already present
387+
if asset_type == "image" and "width" not in upload_metadata:
388+
try:
389+
from PIL import Image
390+
import io
391+
392+
# Get image dimensions using PIL
393+
with Image.open(io.BytesIO(file_content)) as img:
394+
upload_metadata["width"] = str(img.width)
395+
upload_metadata["height"] = str(img.height)
396+
except Exception as e:
397+
# If we can't get dimensions, log but continue
398+
logger.warning(f"Could not get image dimensions: {str(e)}")
399+
368400
blob_client.upload_blob(
369401
data=file_content,
370402
content_settings=content_settings,

backend/core/gpt_image.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ def edit_image(self, **kwargs):
188188
n = kwargs.get("n", 1)
189189
size = kwargs.get("size", "auto")
190190
quality = kwargs.get("quality", "auto")
191+
input_fidelity = kwargs.get("input_fidelity", "low")
191192

192193
# Azure OpenAI requires REST API for image edits
193194
if self.provider == "azure":
@@ -235,15 +236,19 @@ def edit_image(self, **kwargs):
235236
if quality != "auto":
236237
data["quality"] = quality
237238

239+
# Add input_fidelity if not default
240+
if input_fidelity != "low":
241+
data["input_fidelity"] = input_fidelity
242+
238243
# Add any other supported parameters (excluding image/mask which are in files)
239244
for key, value in kwargs.items():
240-
if key not in ["prompt", "image", "mask", "n", "size", "quality", "model"] and value is not None:
245+
if key not in ["prompt", "image", "mask", "n", "size", "quality", "input_fidelity", "model"] and value is not None:
241246
data[key] = value
242247

243248
logger.info(
244249
f"Editing image with Azure REST API, deployment: {self.deployment_name}, "
245250
f"{'multiple' if isinstance(image, list) else 'single'} image(s), "
246-
f"quality: {quality}, size: {size}"
251+
f"quality: {quality}, size: {size}, input_fidelity: {input_fidelity}"
247252
)
248253

249254
# Send the request
@@ -265,7 +270,8 @@ def edit_image(self, **kwargs):
265270
logger.info(
266271
f"Editing image with OpenAI SDK, model {model}, "
267272
f"{len(kwargs.get('image', [])) if isinstance(kwargs.get('image', []), list) else '1'} "
268-
f"reference image(s), quality: {kwargs.get('quality', 'auto')}, size: {size}"
273+
f"reference image(s), quality: {kwargs.get('quality', 'auto')}, size: {size}, "
274+
f"input_fidelity: {input_fidelity}"
269275
)
270276

271277
# Call the OpenAI API to edit the image

backend/models/images.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,17 @@ class ImageEditRequest(ImageGenerationRequest):
9393
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..."
9494
])
9595

96+
# gpt-image-1 specific edit parameters:
97+
input_fidelity: Optional[str] = Field("low",
98+
description="Input fidelity setting for image editing: 'low' (default, faster), 'high' (better reproduction of input image features, additional cost). Only available for image editing operations.",
99+
examples=["low", "high"])
100+
101+
@validator('input_fidelity')
102+
def validate_input_fidelity(cls, v):
103+
if v is not None and v not in ["low", "high"]:
104+
raise ValueError("input_fidelity must be either 'low' or 'high'")
105+
return v
106+
96107

97108
class InputTokensDetails(BaseModel):
98109
"""Details about input tokens for image generation"""

frontend/app/edit-image/components/GenerateForm.tsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export default function GenerateForm({
3131
const [quality, setQuality] = useState('auto');
3232
const [size] = useState('auto');
3333
const [outputFormat, setOutputFormat] = useState('png');
34+
const [inputFidelity, setInputFidelity] = useState('low');
3435
const [showPromptIdeas, setShowPromptIdeas] = useState(false);
3536

3637
// Create the debug mask URL on component mount
@@ -230,6 +231,7 @@ export default function GenerateForm({
230231
formData.append('quality', quality);
231232
formData.append('size', size);
232233
formData.append('output_format', outputFormat);
234+
formData.append('input_fidelity', inputFidelity);
233235

234236
// Submit the form data
235237
await onSubmit(formData);
@@ -319,7 +321,7 @@ export default function GenerateForm({
319321
</div>
320322

321323
<div className="flex items-center gap-3 mt-4">
322-
<div className="grid grid-cols-2 gap-3 flex-1">
324+
<div className="grid grid-cols-3 gap-3 flex-1">
323325
<div>
324326
<Label className="text-sm font-medium mb-1.5 block">
325327
Image Quality
@@ -358,6 +360,24 @@ export default function GenerateForm({
358360
</SelectContent>
359361
</Select>
360362
</div>
363+
364+
<div>
365+
<Label className="text-sm font-medium mb-1.5 block">
366+
Input Fidelity
367+
</Label>
368+
<Select
369+
value={inputFidelity}
370+
onValueChange={setInputFidelity}
371+
>
372+
<SelectTrigger>
373+
<SelectValue placeholder="Select fidelity" />
374+
</SelectTrigger>
375+
<SelectContent>
376+
<SelectItem value="low">Low</SelectItem>
377+
<SelectItem value="high">High</SelectItem>
378+
</SelectContent>
379+
</Select>
380+
</div>
361381
</div>
362382

363383
<Button

frontend/app/new-video/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ function NewVideoPageContent() {
167167
setIsLoadingMore(false);
168168
setIsRefreshing(false);
169169
}
170-
}, [limit, offset, folderParam, videos.length]);
170+
}, [limit, offset, folderParam, videos, loading]);
171171

172172
// Register for upload completion notifications
173173
useEffect(() => {

frontend/components/ImageCreationContainer.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ interface ImageGenerationSettings {
3232
background: string;
3333
outputFormat: string;
3434
quality: string;
35+
inputFidelity: string;
3536
sourceImages?: File[];
3637
brandsList?: string[];
3738
}
@@ -181,7 +182,8 @@ export function ImageCreationContainer({ className = "", onImagesSaved }: ImageC
181182
generationPrompt, // Use protected prompt for generation
182183
newSettings.variations, // Number of variations from dropdown
183184
newSettings.imageSize, // Use selected size
184-
newSettings.quality // Quality parameter
185+
newSettings.quality, // Quality parameter
186+
newSettings.inputFidelity // Input fidelity parameter
185187
);
186188

187189
// Update the loading toast to success

frontend/components/ImageDetailView.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,8 @@ export function ImageDetailView({
516516
ref={imageRef}
517517
src={image.src}
518518
alt={image.title || image.name}
519+
width={image.originalItem?.metadata?.width ? parseInt(image.originalItem.metadata.width) : image.width || 1024}
520+
height={image.originalItem?.metadata?.height ? parseInt(image.originalItem.metadata.height) : image.height || 1024}
519521
className={`max-h-full max-w-full object-contain rounded-lg ${image.originalItem?.metadata?.has_transparency === "true" ? 'z-10' : ''}`}
520522
onLoad={() => setIsLoading(false)}
521523
onError={() => {

0 commit comments

Comments
 (0)