Product Image Variations
Download Recipe
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.# SPDX-License-Identifier: Apache-2.0# /// script# requires-python = ">=3.10"# dependencies = [# "data-designer",# ]# ///"""Product Image Variation RecipeGenerate a base apparel-on-person catalog image, then create inclusive fashioncatalog variations with an image-to-image model through ImageContext. Use this patternfor e-commerce apparel variants, fit and styling coverage, marketplacethumbnails, lookbook imagery, and creative QA workflows.For real product images, replace the `base_product_image` generation column witha seed dataset column containing your product image paths, URLs, or base64 data,then point `ImageContext(column_name=...)` at that seed column.Prerequisites:- An image-generation provider key for a model that supports image-to-imageediting through the chat-completions route. The defaults use OpenRouterand Gemini 3.1 Flash Image Preview, so set OPENROUTER_API_KEY before running.Run:uv run product_image_variations.py --num-records 5"""from __future__ import annotationsimport argparsefrom pathlib import Pathimport data_designer.config as ddfrom data_designer.interface import DataDesigner, DatasetCreationResultsDEFAULT_MODEL_PROVIDER = "openrouter"DEFAULT_MODEL_ID = "google/gemini-3.1-flash-image-preview"DEFAULT_MODEL_ALIAS = "product-image-model"def build_model_configs(*,model_provider: str,model_id: str,model_alias: str,image_size: str,aspect_ratio: str,max_parallel_requests: int,) -> list[dd.ModelConfig]:"""Build an image model config for text-to-image and image-to-image generation."""return [dd.ModelConfig(alias=model_alias,model=model_id,provider=model_provider,inference_parameters=dd.ImageInferenceParams(extra_body={"modalities": ["image", "text"],"image_config": {"aspect_ratio": aspect_ratio,"image_size": image_size,},},max_parallel_requests=max_parallel_requests,),skip_health_check=True,)]def add_category(config_builder: dd.DataDesignerConfigBuilder,name: str,values: list[str],weights: list[float] | None = None,) -> None:"""Add a categorical sampler column."""config_builder.add_column(dd.SamplerColumnConfig(name=name,sampler_type=dd.SamplerType.CATEGORY,params=dd.CategorySamplerParams(values=values, weights=weights),))def add_visual_variation_id(config_builder: dd.DataDesignerConfigBuilder) -> None:"""Add a unique row-level key that discourages duplicate image generations."""config_builder.add_column(dd.SamplerColumnConfig(name="visual_variation_id",sampler_type=dd.SamplerType.UUID,params=dd.UUIDSamplerParams(prefix="apparel-", short_form=True),))def build_config(*,model_provider: str = DEFAULT_MODEL_PROVIDER,model_id: str = DEFAULT_MODEL_ID,model_alias: str = DEFAULT_MODEL_ALIAS,image_size: str = "1K",aspect_ratio: str = "3:4",max_parallel_requests: int = 10,) -> dd.DataDesignerConfigBuilder:"""Build an apparel product image variation pipeline."""model_configs = build_model_configs(model_provider=model_provider,model_id=model_id,model_alias=model_alias,image_size=image_size,aspect_ratio=aspect_ratio,max_parallel_requests=max_parallel_requests,)config_builder = dd.DataDesignerConfigBuilder(model_configs=model_configs)add_visual_variation_id(config_builder)add_category(config_builder,"apparel_item",["organic cotton crewneck t-shirt","lightweight denim jacket","water-resistant rain jacket","relaxed-fit hoodie","wide-leg linen trousers","ribbed knit cardigan","quilted puffer vest","stretch woven workwear shirt","ankle-length everyday dress","adaptive zip-front jacket",],)add_category(config_builder,"base_colorway",["matte black","warm white","sage green","deep navy","brushed silver","terracotta","soft lavender","charcoal gray","sunflower yellow","denim blue",],)add_category(config_builder,"base_view",["front-facing standing full-body catalog photo with one synthetic adult model","three-quarter standing full-body catalog photo with one synthetic adult model","side-angle standing full-body catalog photo with one synthetic adult model","walking-pose full-body catalog photo with one synthetic adult model",],)add_category(config_builder,"base_model_profile",["young adult Black model with an athletic build","middle-aged East Asian model with an average build","older Latine model with a petite build","young adult South Asian model with a plus-size build","middle-aged Middle Eastern model with a tall build","young adult Indigenous model with a broad-shouldered build","older White model with a curvy build","young adult multiracial model with a slender build",],)add_category(config_builder,"variation_goal",["inclusive e-commerce catalog image on a clean white background","lifestyle lookbook image in an everyday urban setting","fit-guide image showing garment drape and silhouette","seasonal campaign image for cool-weather layering","adaptive-fashion catalog image emphasizing ease of wear","single-model adult age-inclusive catalog image","social media campaign image with bold colored backdrop","editorial fashion image with soft premium lighting",],)add_category(config_builder,"edit_scene_delta",["move from a neutral studio catalog reference into an outdoor urban lifestyle scene","move from a neutral studio catalog reference into a warm home entryway lookbook scene","move from a neutral studio catalog reference into a bold color-block campaign set","move from a neutral studio catalog reference into a smart-casual workplace corridor scene","move from a neutral studio catalog reference into a weekend park lookbook scene","move from a neutral studio catalog reference into a premium editorial studio set with draped fabric","move from a neutral studio catalog reference into a clean fit-guide scene with a new full-body pose","move from a neutral studio catalog reference into a seasonal layering scene with visible outerwear styling",],)add_category(config_builder,"model_age_group",["young adult model","middle-aged adult model","older adult model","senior adult model",],)add_category(config_builder,"model_ethnicity",["Black or African diaspora model","East Asian model","South Asian model","Latine model","Middle Eastern or North African model","Indigenous model","Pacific Islander model","White or European model","multiracial model",],)add_category(config_builder,"body_type",["petite build","tall build","plus-size build","athletic build","broad-shouldered build","curvy build","slender build","average build",],)add_category(config_builder,"accessibility_context",["standing model without visible mobility aids","model with no specific accessibility cue","standing model in a relaxed catalog pose","model walking naturally without visible mobility aids","model seated on a simple studio stool","model leaning lightly against a studio block","model holding a small neutral accessory","seated model using a wheelchair","model with a visible prosthetic limb","model using forearm crutches",],weights=[1.6, 1.6, 1.4, 1.4, 0.9, 0.9, 0.9, 0.2, 0.2, 0.2],)add_category(config_builder,"styling_context",["pure white seamless catalog background","soft neutral studio backdrop","outdoor morning city street","modern home entryway","adult campus casual setting","workplace smart-casual setting","weekend park setting","minimal geometric studio set",],)standing_compositions = ["front-facing full-body catalog pose with the entire person visible","three-quarter full-body pose with the entire person visible","side-angle full-body pose with clear garment silhouette",]seated_compositions = ["single seated full-body pose showing garment fit with the whole body visible",]wheelchair_compositions = ["single seated full-body pose showing garment fit with the whole body and wheelchair visible",]walking_compositions = ["single walking full-body pose with natural garment movement",]prosthetic_compositions = ["standing full-body pose with visible prosthetic limb and clear garment fit","single walking full-body pose with visible prosthetic limb and natural garment movement",]crutch_compositions = ["standing full-body pose with forearm crutches visible and clear garment fit","single walking full-body pose with forearm crutches visible and natural garment movement",]flexible_compositions = standing_compositions + seated_compositions + walking_compositionsconfig_builder.add_column(dd.SamplerColumnConfig(name="composition",sampler_type=dd.SamplerType.SUBCATEGORY,params=dd.SubcategorySamplerParams(category="accessibility_context",values={"standing model without visible mobility aids": standing_compositions,"model with no specific accessibility cue": flexible_compositions,"standing model in a relaxed catalog pose": standing_compositions,"model walking naturally without visible mobility aids": walking_compositions,"model seated on a simple studio stool": seated_compositions,"model leaning lightly against a studio block": standing_compositions,"model holding a small neutral accessory": standing_compositions,"seated model using a wheelchair": wheelchair_compositions,"model with a visible prosthetic limb": prosthetic_compositions,"model using forearm crutches": crutch_compositions,},),))add_category(config_builder,"lighting",["softbox studio lighting","natural window light","bright catalog lighting","warm golden-hour lighting","soft overcast outdoor light",],)config_builder.add_column(dd.ImageColumnConfig(name="base_product_image",prompt=BASE_PRODUCT_IMAGE_PROMPT,model_alias=model_alias,))config_builder.add_column(dd.ImageColumnConfig(name="product_variant_image",prompt=PRODUCT_VARIATION_PROMPT,model_alias=model_alias,multi_modal_context=[dd.ImageContext(column_name="base_product_image")],))return config_builderdef create_dataset(config_builder: dd.DataDesignerConfigBuilder,*,num_records: int,dataset_name: str,artifact_path: Path | str | None = None,) -> DatasetCreationResults:data_designer = DataDesigner(artifact_path=artifact_path)data_designer.validate(config_builder)return data_designer.create(config_builder, num_records=num_records, dataset_name=dataset_name)BASE_PRODUCT_IMAGE_PROMPT = """\Create a synthetic apparel catalog reference photo of a person wearing a {{ base_colorway }} {{ apparel_item }}.Image requirements:- Visual variation ID, for internal diversity only: {{ visual_variation_id }}- View: {{ base_view }}- Base model profile: {{ base_model_profile }}- Background: clean neutral studio background- Lighting: soft catalog lighting- Use exactly one synthetic adult model in neutral catalog styling.- Output must use vertical portrait framing, with a 3:4 portrait composition that is taller than it is wide.- The garment should be centered, worn naturally, fully visible, and isolated enough to edit later.- The frame must be a full-body image: show the model from head to toe with feet visible and comfortable margins around the body.- Show fabric texture, seams, silhouette, cuffs, closures, pockets, fit, drape, and other garment details when relevant.- Keep the model presentation neutral, fully clothed, non-sexualized, and commercially appropriate.- Do not include extra people, duplicate bodies, mannequins, cropped bodies, close-up crops, landscape frames, square frames, real brand logos, real trademarks, watermarks, price tags, text overlays, celebrity likenesses, or real people.- Use a plausible invented garment design with consistent shape, color, fit, and material details.- Generate exactly one final image for this row. Do not return alternate versions, a grid, a pair of examples, a before/after image, or multiple panels. Use the visual variation ID only as an internal diversity key; never render it as text."""PRODUCT_VARIATION_PROMPT = """\Edit the provided apparel-on-person catalog image into a new inclusive commercial fashion image.Variation requirements:- Visual variation ID, for internal diversity only: {{ visual_variation_id }}- Variation goal: {{ variation_goal }}- Required edit delta: {{ edit_scene_delta }}- Model age group: {{ model_age_group }}- Model ethnicity: {{ model_ethnicity }}- Body type: {{ body_type }}- Accessibility context: {{ accessibility_context }}- Styling context: {{ styling_context }}- Composition: {{ composition }}- Lighting: {{ lighting }}Use the provided image as a garment reference, not as a full-shot template.Preserve the garment's core identity from the reference image: same apparelitem, same primary colorway, same fabric cues, same silhouette, same fitbehavior, and same distinctive design details. Do not preserve the originalperson, original face, original hair, original stance, original camera angle,or original plain studio background unless that exact choice is requested bythe sampled controls.Create a visibly different commercial variant. The final image should make atleast three obvious changes from the reference photo: a different syntheticadult model, a different full-body pose or body orientation, a differentsetting or background, and a different lighting or campaign style. Change theperson wearing the garment to match the requested synthetic adult modelbackground, age group, body type, and accessibility context. Represent theadult model respectfully and without stereotypes. Every generated person mustclearly be 18 or older.The edited output must show exactly one person. The final image must be avertical 3:4 portrait full-body catalog image that is taller than it is wide:show the full head-to-toe body with feet visible, or the full seated body andfull mobility aid when the accessibility context calls for one. Do not crop atthe face, waist, knees, ankles, hands, or garment hem. Do not create landscapeframes, square frames, group shots, mirrored duplicates, before/aftercomposites, multiple models, mannequins, or background bystanders.Follow the required edit delta when changing the surrounding scene, styling,pose, background, and lighting. Keep the result realistic and commerciallyusable. Do not add real brand logos, real trademarks, watermarks, price tags,text overlays, sexualized styling, swimwear, underwear, lingerie, sheerclothing, or revealing poses.Generate exactly one final edited image for this row. Do not return alternateversions, a grid, a pair of examples, a before/after image, or multiple panels.Use the visual variation ID only as an internal diversity key; never render itas text."""def parse_args() -> argparse.Namespace:parser = argparse.ArgumentParser(description="Generate product image variations with image-to-image editing.")parser.add_argument("--num-records", type=int, default=5, help="Number of product variation rows to generate.")parser.add_argument("--dataset-name", default="product-image-variations", help="Output dataset name.")parser.add_argument("--artifact-path", type=Path, default=None, help="Optional Data Designer artifact directory.")parser.add_argument("--model-provider", default=DEFAULT_MODEL_PROVIDER, help="Image model provider name.")parser.add_argument("--model-id", default=DEFAULT_MODEL_ID, help="Provider model ID.")parser.add_argument("--model-alias", default=DEFAULT_MODEL_ALIAS, help="Alias used by image columns.")parser.add_argument("--image-size", default="1K", help="OpenRouter image size tier, such as 1K, 2K, or 4K.")parser.add_argument("--aspect-ratio", default="3:4", help="Provider-specific aspect ratio value.")parser.add_argument("--max-parallel-requests", type=int, default=10, help="Maximum parallel image requests.")return parser.parse_args()def main() -> None:args = parse_args()config_builder = build_config(model_provider=args.model_provider,model_id=args.model_id,model_alias=args.model_alias,image_size=args.image_size,aspect_ratio=args.aspect_ratio,max_parallel_requests=args.max_parallel_requests,)results = create_dataset(config_builder,num_records=args.num_records,dataset_name=args.dataset_name,artifact_path=args.artifact_path,)dataset = results.load_dataset()print(f"Generated {len(dataset)} product variation rows.")print(f"Dataset artifacts: {results.artifact_storage.base_dataset_path}")if __name__ == "__main__":main()