🎨 Data Designer Tutorial: Image-to-Image Editing¶
📚 What you'll learn¶
This notebook shows how to chain image generation columns: first generate animal portraits from text, then edit those generated images by adding accessories and changing styles—all without loading external datasets.
- 🖼️ Text-to-image generation: Generate images from text prompts
- 🔗 Chaining image columns: Use
ImageContextto pass generated images to a follow-up editing column - 🎲 Sampler-driven diversity: Combine sampled accessories and settings for varied edits
This tutorial uses an autoregressive model (one that supports both text-to-image and image-to-image generation via the chat completions API). Diffusion models (DALL·E, Stable Diffusion, etc.) do not support image context—see Tutorial 5 for text-to-image generation with diffusion models.
Prerequisites: This tutorial uses OpenRouter with the Flux 2 Pro model. Set
OPENROUTER_API_KEYin your environment before running.
If this is your first time using Data Designer, we recommend starting with the first notebook in this tutorial series.
📦 Import Data Designer¶
data_designer.configprovides the configuration API.DataDesigneris the main interface for generation.
import base64
from pathlib import Path
from IPython.display import Image as IPImage
from IPython.display import display
import data_designer.config as dd
from data_designer.interface import DataDesigner
⚙️ Initialize the Data Designer interface¶
We initialize Data Designer without arguments here—the image model is configured explicitly in the next cell.
data_designer = DataDesigner()
🎛️ Define an image model¶
We need an autoregressive model that supports both text-to-image and image-to-image generation via the chat completions API. This lets us generate images from text and then pass those images as context for editing.
- Use
ImageInferenceParamsso Data Designer treats this model as an image generator. - Image-specific options are model-dependent; pass them via
extra_body.
Note: This tutorial uses the Flux 2 Pro model via OpenRouter. Set
OPENROUTER_API_KEYin your environment.
MODEL_PROVIDER = "openrouter"
MODEL_ID = "black-forest-labs/flux.2-pro"
MODEL_ALIAS = "image-model"
model_configs = [
dd.ModelConfig(
alias=MODEL_ALIAS,
model=MODEL_ID,
provider=MODEL_PROVIDER,
inference_parameters=dd.ImageInferenceParams(
extra_body={"height": 512, "width": 512},
),
)
]
🏗️ Build the configuration¶
We chain two image generation columns:
- Sampler columns — randomly sample animal types, accessories, settings, and art styles
- First image column — generate an animal portrait from a text prompt
- Second image column with context — edit the generated portrait using
ImageContext
config_builder = dd.DataDesignerConfigBuilder(model_configs=model_configs)
# 1. Sampler columns for diversity
config_builder.add_column(
dd.SamplerColumnConfig(
name="animal",
sampler_type=dd.SamplerType.CATEGORY,
params=dd.CategorySamplerParams(
values=["cat", "dog", "fox", "owl", "rabbit", "panda"],
),
)
)
config_builder.add_column(
dd.SamplerColumnConfig(
name="accessory",
sampler_type=dd.SamplerType.CATEGORY,
params=dd.CategorySamplerParams(
values=[
"a tiny top hat",
"oversized sunglasses",
"a red bow tie",
"a knitted beanie",
"a flower crown",
"a monocle and mustache",
"a pirate hat and eye patch",
"a chef hat",
],
),
)
)
config_builder.add_column(
dd.SamplerColumnConfig(
name="setting",
sampler_type=dd.SamplerType.CATEGORY,
params=dd.CategorySamplerParams(
values=[
"a cozy living room",
"a sunny park",
"a photo studio with soft lighting",
"a red carpet event",
"a holiday card backdrop with snowflakes",
"a tropical beach at sunset",
],
),
)
)
config_builder.add_column(
dd.SamplerColumnConfig(
name="art_style",
sampler_type=dd.SamplerType.CATEGORY,
params=dd.CategorySamplerParams(
values=[
"a photorealistic style",
"a Disney Pixar 3D render",
"a watercolor painting",
"a pop art poster",
],
),
)
)
# 2. Generate animal portrait from text
config_builder.add_column(
dd.ImageColumnConfig(
name="animal_portrait",
prompt="A close-up portrait photograph of a {{ animal }} looking at the camera, studio lighting, high quality.",
model_alias=MODEL_ALIAS,
)
)
# 3. Edit the generated portrait
config_builder.add_column(
dd.ImageColumnConfig(
name="edited_portrait",
prompt=(
"Edit this {{ animal }} portrait photo. "
"Add {{ accessory }} on the animal. "
"Place the {{ animal }} in {{ setting }}. "
"Render the result in {{ art_style }}. "
"Keep the animal's face, expression, and features faithful to the original photo."
),
model_alias=MODEL_ALIAS,
multi_modal_context=[dd.ImageContext(column_name="animal_portrait")],
)
)
data_designer.validate(config_builder)
[21:21:26] [INFO] ✅ Validation passed
🔁 Preview: quick iteration¶
In preview mode, generated images are stored as base64 strings in the dataframe. Use this to iterate on your prompts, accessories, and sampler values before scaling up.
preview = data_designer.preview(config_builder, num_records=2)
[21:21:26] [INFO] 🧐 Preview generation in progress
[21:21:26] [INFO] |-- 🔒 Jinja rendering engine: secure
[21:21:26] [INFO] ✅ Validation passed
[21:21:26] [INFO] ⛓️ Sorting column configs into a Directed Acyclic Graph
[21:21:26] [INFO] 🩺 Running health checks for models...
[21:21:26] [INFO] |-- 👀 Checking 'black-forest-labs/flux.2-pro' in provider named 'openrouter' for model alias 'image-model'...
[21:21:35] [INFO] |-- ✅ Passed!
[21:21:35] [INFO] ⚡ DATA_DESIGNER_ASYNC_ENGINE is enabled - using async task-queue preview
[21:21:35] [INFO] 🖼️ image model config for column 'animal_portrait'
[21:21:35] [INFO] |-- model: 'black-forest-labs/flux.2-pro'
[21:21:35] [INFO] |-- model alias: 'image-model'
[21:21:35] [INFO] |-- model provider: 'openrouter'
[21:21:35] [INFO] |-- inference parameters:
[21:21:35] [INFO] | |-- generation_type=image
[21:21:35] [INFO] | |-- max_parallel_requests=4
[21:21:35] [INFO] | |-- extra_body={'height': 512, 'width': 512}
[21:21:35] [INFO] 🖼️ image model config for column 'edited_portrait'
[21:21:35] [INFO] |-- model: 'black-forest-labs/flux.2-pro'
[21:21:35] [INFO] |-- model alias: 'image-model'
[21:21:35] [INFO] |-- model provider: 'openrouter'
[21:21:35] [INFO] |-- inference parameters:
[21:21:35] [INFO] | |-- generation_type=image
[21:21:35] [INFO] | |-- max_parallel_requests=4
[21:21:35] [INFO] | |-- extra_body={'height': 512, 'width': 512}
[21:21:35] [INFO] ⚡️ Async generation: 2 column(s) (animal_portrait, edited_portrait), 4 tasks across 1 row group(s)
[21:21:35] [INFO] 🚀 (1/1) Dispatching with 2 records
[21:21:35] [INFO] 🎲 (1/1) Preparing samplers to generate 2 records across 4 columns
[21:21:55] [INFO] 📊 Progress [19.4s]:
[21:21:55] [INFO] |-- 🌗 animal_portrait: 1/2 (50%) 0.1 rec/s
[21:21:55] [INFO] |-- 🥚 edited_portrait: 0/2 (0%) 0.0 rec/s
[21:22:22] [INFO] 📊 Progress [46.5s]:
[21:22:22] [INFO] |-- 🌗 animal_portrait: 1/2 (50%) 0.0 rec/s
[21:22:22] [INFO] |-- 🐥 edited_portrait: 1/2 (50%) 0.0 rec/s
[21:24:44] [INFO] 📊 Progress [188.1s]:
[21:24:44] [INFO] |-- 🌕 animal_portrait: 2/2 (100%) 0.0 rec/s
[21:24:44] [INFO] |-- 🐥 edited_portrait: 1/2 (50%) 0.0 rec/s
[21:24:57] [INFO] 📊 Progress [201.9s]:
[21:24:57] [INFO] |-- 🌕 animal_portrait: 2/2 (100%) 0.0 rec/s
[21:24:57] [INFO] |-- 🐔 edited_portrait: 2/2 (100%) 0.0 rec/s
[21:24:57] [INFO] ✅ Async generation complete [201.9s]: 4 ok, 0 failed across 2 column(s)
[21:24:57] [INFO] 📊 Model usage summary:
[21:24:57] [INFO] |-- model: black-forest-labs/flux.2-pro
[21:24:57] [INFO] |-- tokens: input=9198, output=12288, total=21486, tps=106
[21:24:57] [INFO] |-- requests: success=4, failed=0, total=4, rpm=1
[21:24:57] [INFO] |-- images: total=4
[21:24:57] [INFO] 📐 Measuring dataset column statistics:
[21:24:57] [INFO] |-- 🎲 column: 'animal'
[21:24:57] [INFO] |-- 🎲 column: 'accessory'
[21:24:57] [INFO] |-- 🎲 column: 'setting'
[21:24:57] [INFO] |-- 🎲 column: 'art_style'
[21:24:57] [INFO] |-- 🖼️ column: 'animal_portrait'
[21:24:57] [INFO] |-- 🖼️ column: 'edited_portrait'
[21:24:57] [INFO] 🏆 Preview complete!
for i in range(len(preview.dataset)):
preview.display_sample_record()
Generated Columns ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ Name ┃ Value ┃ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ │ animal │ fox │ ├──────────────────────────────────┼─────────────────────────────────────────────────────────────────────────┤ │ accessory │ a chef hat │ ├──────────────────────────────────┼─────────────────────────────────────────────────────────────────────────┤ │ setting │ a sunny park │ ├──────────────────────────────────┼─────────────────────────────────────────────────────────────────────────┤ │ art_style │ a photorealistic style │ └──────────────────────────────────┴─────────────────────────────────────────────────────────────────────────┘ Images ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ Name ┃ Preview ┃ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ │ animal_portrait │ [0] <base64, 1907000 chars> │ ├────────────────────────────────────────┼───────────────────────────────────────────────────────────────────┤ │ edited_portrait │ [0] <base64, 1757024 chars> │ └────────────────────────────────────────┴───────────────────────────────────────────────────────────────────┘
Generated Columns ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ Name ┃ Value ┃ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ │ animal │ owl │ ├──────────────────────────────────┼─────────────────────────────────────────────────────────────────────────┤ │ accessory │ a knitted beanie │ ├──────────────────────────────────┼─────────────────────────────────────────────────────────────────────────┤ │ setting │ a sunny park │ ├──────────────────────────────────┼─────────────────────────────────────────────────────────────────────────┤ │ art_style │ a photorealistic style │ └──────────────────────────────────┴─────────────────────────────────────────────────────────────────────────┘ Images ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ Name ┃ Preview ┃ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ │ animal_portrait │ [0] <base64, 1880376 chars> │ ├────────────────────────────────────────┼───────────────────────────────────────────────────────────────────┤ │ edited_portrait │ [0] <base64, 2095592 chars> │ └────────────────────────────────────────┴───────────────────────────────────────────────────────────────────┘
preview.dataset
| animal | accessory | setting | art_style | animal_portrait | edited_portrait | |
|---|---|---|---|---|---|---|
| 0 | fox | a chef hat | a sunny park | a photorealistic style | [iVBORw0KGgoAAAANSUhEUgAABAAAAAMACAIAAAA12IJaA... | [iVBORw0KGgoAAAANSUhEUgAABAAAAAMACAIAAAA12IJaA... |
| 1 | owl | a knitted beanie | a sunny park | a photorealistic style | [iVBORw0KGgoAAAANSUhEUgAABAAAAAMACAIAAAA12IJaA... | [iVBORw0KGgoAAAANSUhEUgAABAAAAAMACAIAAAA12IJaA... |
🔎 Compare original vs edited¶
Let's display the generated animal portraits next to their edited versions.
def display_image(image_value, base_path: Path | None = None) -> None:
"""Display an image from base64 (preview mode) or file path (create mode)."""
values = [image_value] if isinstance(image_value, str) else list(image_value)
for value in values:
if base_path is not None:
display(IPImage(filename=str(base_path / value)))
else:
display(IPImage(data=base64.b64decode(value)))
def display_before_after(row, index: int, base_path: Path | None = None) -> None:
"""Display original portrait vs edited version for a single record."""
print(f"\n{'=' * 60}")
print(f"Record {index}: {row['animal']} wearing {row['accessory']}")
print(f"Setting: {row['setting']}, Style: {row['art_style']}")
print(f"{'=' * 60}")
print("\n📷 Generated portrait:")
display_image(row["animal_portrait"], base_path)
print("\n🎨 Edited version:")
display_image(row["edited_portrait"], base_path)
for index, row in preview.dataset.iterrows():
display_before_after(row, index)
============================================================ Record 0: fox wearing a chef hat Setting: a sunny park, Style: a photorealistic style ============================================================ 📷 Generated portrait:
🎨 Edited version:
============================================================ Record 1: owl wearing a knitted beanie Setting: a sunny park, Style: a photorealistic style ============================================================ 📷 Generated portrait:
🎨 Edited version:
🆙 Create at scale¶
In create mode, images are saved to disk in images/<column_name>/ folders with UUID filenames. The dataframe stores relative paths. ImageContext auto-detection handles this transparently—generated file paths are resolved to base64 before being sent to the model for editing.
results = data_designer.create(config_builder, num_records=5, dataset_name="tutorial-6-edited-images")
[21:24:58] [INFO] 🎨 Creating Data Designer dataset
[21:24:58] [INFO] |-- 🔒 Jinja rendering engine: secure
[21:24:58] [INFO] ✅ Validation passed
[21:24:58] [INFO] ⛓️ Sorting column configs into a Directed Acyclic Graph
[21:24:58] [INFO] 🩺 Running health checks for models...
[21:24:58] [INFO] |-- 👀 Checking 'black-forest-labs/flux.2-pro' in provider named 'openrouter' for model alias 'image-model'...
[21:25:08] [INFO] |-- ✅ Passed!
[21:25:08] [INFO] ⚡ DATA_DESIGNER_ASYNC_ENGINE is enabled - using async task-queue builder
[21:25:08] [INFO] 🖼️ image model config for column 'animal_portrait'
[21:25:08] [INFO] |-- model: 'black-forest-labs/flux.2-pro'
[21:25:08] [INFO] |-- model alias: 'image-model'
[21:25:08] [INFO] |-- model provider: 'openrouter'
[21:25:08] [INFO] |-- inference parameters:
[21:25:08] [INFO] | |-- generation_type=image
[21:25:08] [INFO] | |-- max_parallel_requests=4
[21:25:08] [INFO] | |-- extra_body={'height': 512, 'width': 512}
[21:25:08] [INFO] 🖼️ image model config for column 'edited_portrait'
[21:25:08] [INFO] |-- model: 'black-forest-labs/flux.2-pro'
[21:25:08] [INFO] |-- model alias: 'image-model'
[21:25:08] [INFO] |-- model provider: 'openrouter'
[21:25:08] [INFO] |-- inference parameters:
[21:25:08] [INFO] | |-- generation_type=image
[21:25:08] [INFO] | |-- max_parallel_requests=4
[21:25:08] [INFO] | |-- extra_body={'height': 512, 'width': 512}
[21:25:08] [INFO] ⚡️ Async generation: 2 column(s) (animal_portrait, edited_portrait), 10 tasks across 1 row group(s)
[21:25:08] [INFO] 🚀 (1/1) Dispatching with 5 records
[21:25:08] [INFO] 🎲 (1/1) Preparing samplers to generate 5 records across 4 columns
[21:25:17] [INFO] 📊 Progress [9.0s]:
[21:25:17] [INFO] |-- 🌧️ animal_portrait: 1/5 (20%) 0.1 rec/s
[21:25:17] [INFO] |-- 😴 edited_portrait: 0/5 (0%) 0.0 rec/s
[21:25:36] [INFO] 📊 Progress [27.5s]:
[21:25:36] [INFO] |-- 🌤️ animal_portrait: 4/5 (80%) 0.1 rec/s
[21:25:36] [INFO] |-- 😴 edited_portrait: 1/5 (20%) 0.0 rec/s
[21:25:42] [INFO] 📊 Progress [34.3s]:
[21:25:42] [INFO] |-- 🌤️ animal_portrait: 4/5 (80%) 0.1 rec/s
[21:25:42] [INFO] |-- 😐 edited_portrait: 3/5 (60%) 0.1 rec/s
[21:25:51] [INFO] 📊 Progress [43.4s]:
[21:25:51] [INFO] |-- 🌤️ animal_portrait: 4/5 (80%) 0.1 rec/s
[21:25:51] [INFO] |-- 😊 edited_portrait: 4/5 (80%) 0.1 rec/s
[21:28:07] [INFO] 📊 Progress [178.4s]:
[21:28:07] [INFO] |-- ☀️ animal_portrait: 5/5 (100%) 0.0 rec/s
[21:28:07] [INFO] |-- 😊 edited_portrait: 4/5 (80%) 0.0 rec/s
[21:28:28] [INFO] 📊 Progress [200.0s]:
[21:28:28] [INFO] |-- ☀️ animal_portrait: 5/5 (100%) 0.0 rec/s
[21:28:28] [INFO] |-- 🤩 edited_portrait: 5/5 (100%) 0.0 rec/s
[21:28:28] [INFO] ✅ Async generation complete [200.0s]: 10 ok, 0 failed across 2 column(s)
[21:28:28] [INFO] 📊 Model usage summary:
[21:28:28] [INFO] |-- model: black-forest-labs/flux.2-pro
[21:28:28] [INFO] |-- tokens: input=23009, output=30720, total=53729, tps=268
[21:28:28] [INFO] |-- requests: success=10, failed=0, total=10, rpm=2
[21:28:28] [INFO] |-- images: total=10
[21:28:28] [INFO] 📐 Measuring dataset column statistics:
[21:28:28] [INFO] |-- 🎲 column: 'animal'
[21:28:28] [INFO] |-- 🎲 column: 'accessory'
[21:28:28] [INFO] |-- 🎲 column: 'setting'
[21:28:28] [INFO] |-- 🎲 column: 'art_style'
[21:28:28] [INFO] |-- 🖼️ column: 'animal_portrait'
[21:28:28] [INFO] |-- 🖼️ column: 'edited_portrait'
dataset = results.load_dataset()
dataset.head()
| animal | accessory | setting | art_style | animal_portrait | edited_portrait | |
|---|---|---|---|---|---|---|
| 0 | dog | oversized sunglasses | a sunny park | a Disney Pixar 3D render | ['images/animal_portrait/f6412a8a-02b5-4a13-a6... | ['images/edited_portrait/20d7c77f-729d-4964-84... |
| 1 | dog | a monocle and mustache | a tropical beach at sunset | a pop art poster | ['images/animal_portrait/2a4cbc2e-e449-4e51-8e... | ['images/edited_portrait/e1f15253-f7cc-4850-8a... |
| 2 | cat | a red bow tie | a sunny park | a photorealistic style | ['images/animal_portrait/cc4133f6-61b6-4575-9e... | ['images/edited_portrait/848816a0-7d0b-4fcc-88... |
| 3 | dog | a flower crown | a photo studio with soft lighting | a watercolor painting | ['images/animal_portrait/024a2d9e-62a1-4876-86... | ['images/edited_portrait/51f2d157-fa6c-4de2-98... |
| 4 | dog | a monocle and mustache | a sunny park | a Disney Pixar 3D render | ['images/animal_portrait/d42e49f5-0a64-445b-90... | ['images/edited_portrait/dfe96c4e-fa6f-41c3-98... |
for index, row in dataset.head(10).iterrows():
display_before_after(row, index, base_path=results.artifact_storage.base_dataset_path)
============================================================ Record 0: dog wearing oversized sunglasses Setting: a sunny park, Style: a Disney Pixar 3D render ============================================================ 📷 Generated portrait:
🎨 Edited version:
============================================================ Record 1: dog wearing a monocle and mustache Setting: a tropical beach at sunset, Style: a pop art poster ============================================================ 📷 Generated portrait:
🎨 Edited version:
============================================================ Record 2: cat wearing a red bow tie Setting: a sunny park, Style: a photorealistic style ============================================================ 📷 Generated portrait:
🎨 Edited version:
============================================================ Record 3: dog wearing a flower crown Setting: a photo studio with soft lighting, Style: a watercolor painting ============================================================ 📷 Generated portrait:
🎨 Edited version:
============================================================ Record 4: dog wearing a monocle and mustache Setting: a sunny park, Style: a Disney Pixar 3D render ============================================================ 📷 Generated portrait:
🎨 Edited version:
⏭️ Next steps¶
- Experiment with different autoregressive models for image generation and editing
- Try more creative editing prompts (style transfer, background replacement, artistic filters)
- Combine image generation with text generation (e.g., generate captions using an LLM-Text column with
ImageContext) - Chain more than two image columns for multi-step editing pipelines
Related tutorials:
- The basics: samplers and LLM text columns
- Providing images as context: image-to-text with VLMs
- Generating images: text-to-image generation with diffusion models