Image-to-Image Editing

View as Markdown

🎨 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 ImageContext to 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_KEY in 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.config provides the configuration API.
  • DataDesigner is the main interface for generation.
Python
1import base64
2import os
3from pathlib import Path
4
5from IPython.display import Image as IPImage
6from IPython.display import display
7
8import data_designer.config as dd
9from data_designer.interface import DataDesigner
10

⚙️ Initialize the Data Designer interface

We initialize Data Designer without arguments here—the image model is configured explicitly in the next cell.

Python
1data_designer = DataDesigner()
2

🎛️ 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 ImageInferenceParams so 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_KEY in your environment.

Python
1MODEL_PROVIDER = "openrouter"
2MODEL_ID = "black-forest-labs/flux.2-pro"
3MODEL_ALIAS = "image-model"
4
5model_configs = [
6 dd.ModelConfig(
7 alias=MODEL_ALIAS,
8 model=MODEL_ID,
9 provider=MODEL_PROVIDER,
10 inference_parameters=dd.ImageInferenceParams(
11 extra_body={"height": 512, "width": 512},
12 ),
13 )
14]
15

🏗️ Build the configuration

We chain two image generation columns:

  1. Sampler columns — randomly sample animal types, accessories, settings, and art styles
  2. First image column — generate an animal portrait from a text prompt
  3. Second image column with context — edit the generated portrait using ImageContext
Python
1config_builder = dd.DataDesignerConfigBuilder(model_configs=model_configs)
2
3# 1. Sampler columns for diversity
4config_builder.add_column(
5 dd.SamplerColumnConfig(
6 name="animal",
7 sampler_type=dd.SamplerType.CATEGORY,
8 params=dd.CategorySamplerParams(
9 values=["cat", "dog", "fox", "owl", "rabbit", "panda"],
10 ),
11 )
12)
13
14config_builder.add_column(
15 dd.SamplerColumnConfig(
16 name="accessory",
17 sampler_type=dd.SamplerType.CATEGORY,
18 params=dd.CategorySamplerParams(
19 values=[
20 "a tiny top hat",
21 "oversized sunglasses",
22 "a red bow tie",
23 "a knitted beanie",
24 "a flower crown",
25 "a monocle and mustache",
26 "a pirate hat and eye patch",
27 "a chef hat",
28 ],
29 ),
30 )
31)
32
33config_builder.add_column(
34 dd.SamplerColumnConfig(
35 name="setting",
36 sampler_type=dd.SamplerType.CATEGORY,
37 params=dd.CategorySamplerParams(
38 values=[
39 "a cozy living room",
40 "a sunny park",
41 "a photo studio with soft lighting",
42 "a red carpet event",
43 "a holiday card backdrop with snowflakes",
44 "a tropical beach at sunset",
45 ],
46 ),
47 )
48)
49
50config_builder.add_column(
51 dd.SamplerColumnConfig(
52 name="art_style",
53 sampler_type=dd.SamplerType.CATEGORY,
54 params=dd.CategorySamplerParams(
55 values=[
56 "a photorealistic style",
57 "a Disney Pixar 3D render",
58 "a watercolor painting",
59 "a pop art poster",
60 ],
61 ),
62 )
63)
64
65# 2. Generate animal portrait from text
66config_builder.add_column(
67 dd.ImageColumnConfig(
68 name="animal_portrait",
69 prompt="A close-up portrait photograph of a {{ animal }} looking at the camera, studio lighting, high quality.",
70 model_alias=MODEL_ALIAS,
71 )
72)
73
74# 3. Edit the generated portrait
75config_builder.add_column(
76 dd.ImageColumnConfig(
77 name="edited_portrait",
78 prompt=(
79 "Edit this {{ animal }} portrait photo. "
80 "Add {{ accessory }} on the animal. "
81 "Place the {{ animal }} in {{ setting }}. "
82 "Render the result in {{ art_style }}. "
83 "Keep the animal's face, expression, and features faithful to the original photo."
84 ),
85 model_alias=MODEL_ALIAS,
86 multi_modal_context=[dd.ImageContext(column_name="animal_portrait")],
87 )
88)
89
90data_designer.validate(config_builder)
91
Output
[17:24:31] [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.

Python
1preview = data_designer.preview(config_builder, num_records=2)
2
Output
[17:24:31] [INFO] 🔭 Preview generation in progress
[17:24:31] [INFO]   |-- 🔒 Jinja rendering engine: secure
[17:24:31] [INFO] ✅ Validation passed
[17:24:31] [INFO] ⛓️ Sorting column configs into a Directed Acyclic Graph
[17:24:31] [INFO] Skipping model health checks because DATA_DESIGNER_SKIP_MODEL_HEALTH_CHECKS=1
[17:24:31] [INFO] ⚡ Using async task-queue preview
[17:24:31] [INFO] 🖼️ image model config for column 'animal_portrait'
[17:24:31] [INFO]   |-- model: 'black-forest-labs/flux.2-pro'
[17:24:31] [INFO]   |-- model alias: 'image-model'
[17:24:31] [INFO]   |-- model provider: 'openrouter'
[17:24:31] [INFO]   |-- inference parameters:
[17:24:31] [INFO]   |  |-- generation_type=image
[17:24:31] [INFO]   |  |-- max_parallel_requests=4
[17:24:31] [INFO]   |  |-- extra_body={'height': 512, 'width': 512}
[17:24:31] [INFO] 🖼️ image model config for column 'edited_portrait'
[17:24:31] [INFO]   |-- model: 'black-forest-labs/flux.2-pro'
[17:24:31] [INFO]   |-- model alias: 'image-model'
[17:24:31] [INFO]   |-- model provider: 'openrouter'
[17:24:31] [INFO]   |-- inference parameters:
[17:24:31] [INFO]   |  |-- generation_type=image
[17:24:31] [INFO]   |  |-- max_parallel_requests=4
[17:24:31] [INFO]   |  |-- extra_body={'height': 512, 'width': 512}
[17:24:31] [INFO] ⚡️ Async generation: 2 column(s) (column 'animal_portrait', column 'edited_portrait'), 4 tasks across 1 row group(s)
[17:24:31] [INFO] 🚀 (1/1) Dispatching with 2 records
[17:24:31] [INFO] 🎲 (1/1) Preparing samplers to generate 2 records across 4 columns
[17:24:40] [INFO] 📊 Progress [8.7s]:
[17:24:40] [INFO]   |-- 😐 column 'animal_portrait': 1/2 (50%) 0.1 rec/s
[17:24:40] [INFO]   |-- 🥚 column 'edited_portrait': 0/2 (0%) 0.0 rec/s
[17:24:53] [INFO] 📊 Progress [22.3s]:
[17:24:53] [INFO]   |-- 🤩 column 'animal_portrait': 2/2 (100%) 0.1 rec/s
[17:24:53] [INFO]   |-- 🐥 column 'edited_portrait': 1/2 (50%) 0.0 rec/s
[17:24:54] [INFO] 📊 Progress [23.4s]:
[17:24:54] [INFO]   |-- 🤩 column 'animal_portrait': 2/2 (100%) 0.1 rec/s
[17:24:54] [INFO]   |-- 🐔 column 'edited_portrait': 2/2 (100%) 0.1 rec/s
[17:24:54] [INFO] ✅ Async generation complete [23.4s]: 4 ok, 0 failed across 2 column(s)
[17:24:54] [INFO] 📊 Model usage summary:
[17:24:54] [INFO]   |-- model: black-forest-labs/flux.2-pro
[17:24:54] [INFO]   |-- tokens: input=9201, output=12288, reasoning=0, total=21489, tps=918
[17:24:54] [INFO]   |-- requests: success=4, failed=0, total=4, rpm=10
[17:24:54] [INFO]   |-- images: total=4
[17:24:54] [INFO] 📐 Measuring dataset column statistics:
[17:24:54] [INFO]   |-- 🎲 column: 'animal'
[17:24:54] [INFO]   |-- 🎲 column: 'accessory'
[17:24:54] [INFO]   |-- 🎲 column: 'setting'
[17:24:54] [INFO]   |-- 🎲 column: 'art_style'
[17:24:54] [INFO]   |-- 🖼️ column: 'animal_portrait'
[17:24:54] [INFO]   |-- 🖼️ column: 'edited_portrait'
[17:24:54] [INFO] 🎆 Preview complete!
Python
1for i in range(len(preview.dataset)):
2 preview.display_sample_record()
3
Output
[index: 0]
                                                                                                              
                                              Generated Columns                                               
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Name                               Value                                                                  ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ animal                            │ fox                                                                    │
├───────────────────────────────────┼────────────────────────────────────────────────────────────────────────┤
│ accessory                         │ a knitted beanie                                                       │
├───────────────────────────────────┼────────────────────────────────────────────────────────────────────────┤
│ setting                           │ a red carpet event                                                     │
├───────────────────────────────────┼────────────────────────────────────────────────────────────────────────┤
│ art_style                         │ a watercolor painting                                                  │
└───────────────────────────────────┴────────────────────────────────────────────────────────────────────────┘
                                                                                                              
                                                                                                              
                                                    Images                                                    
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Name                                    Preview                                                           ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ animal_portrait                        │ [0] <base64, 1742696 chars>                                       │
├────────────────────────────────────────┼───────────────────────────────────────────────────────────────────┤
│ edited_portrait                        │ [0] <base64, 2196400 chars>                                       │
└────────────────────────────────────────┴───────────────────────────────────────────────────────────────────┘
                                                                                                              
🖼️ animal_portrait[0]
🖼️ edited_portrait[0]
[index: 1]
                                                                                                              
                                              Generated Columns                                               
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Name                              Value                                                                   ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ animal                           │ rabbit                                                                  │
├──────────────────────────────────┼─────────────────────────────────────────────────────────────────────────┤
│ accessory                        │ a red bow tie                                                           │
├──────────────────────────────────┼─────────────────────────────────────────────────────────────────────────┤
│ setting                          │ a sunny park                                                            │
├──────────────────────────────────┼─────────────────────────────────────────────────────────────────────────┤
│ art_style                        │ a photorealistic style                                                  │
└──────────────────────────────────┴─────────────────────────────────────────────────────────────────────────┘
                                                                                                              
                                                                                                              
                                                    Images                                                    
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Name                                    Preview                                                           ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ animal_portrait                        │ [0] <base64, 1552364 chars>                                       │
├────────────────────────────────────────┼───────────────────────────────────────────────────────────────────┤
│ edited_portrait                        │ [0] <base64, 1822156 chars>                                       │
└────────────────────────────────────────┴───────────────────────────────────────────────────────────────────┘
                                                                                                              
🖼️ animal_portrait[0]
🖼️ edited_portrait[0]
Python
1preview.dataset
2
Output
animal accessory setting art_style animal_portrait edited_portrait
0 fox a knitted beanie a red carpet event a watercolor painting [iVBORw0KGgoAAAANSUhEUgAABAAAAAMACAIAAAA12IJaA... [iVBORw0KGgoAAAANSUhEUgAABAAAAAMACAIAAAA12IJaA...
1 rabbit a red bow tie a sunny park a photorealistic style [iVBORw0KGgoAAAANSUhEUgAABAAAAAMACAIAAAA12IJaA... [iVBORw0KGgoAAAANSUhEUgAABAAAAAMACAIAAAA12IJaA...

🔎 Compare original vs edited

Let's display the generated animal portraits next to their edited versions.

Python
1def display_image(image_value, base_path: Path | None = None) -> None:
2 """Display an image from base64 (preview mode) or file path (create mode)."""
3 values = [image_value] if isinstance(image_value, str) else list(image_value)
4 for value in values:
5 if base_path is not None:
6 display(IPImage(filename=str(base_path / value)))
7 else:
8 display(IPImage(data=base64.b64decode(value)))
9
10
11def display_before_after(row, index: int, base_path: Path | None = None) -> None:
12 """Display original portrait vs edited version for a single record."""
13 print(f"\n{'=' * 60}")
14 print(f"Record {index}: {row['animal']} wearing {row['accessory']}")
15 print(f"Setting: {row['setting']}, Style: {row['art_style']}")
16 print(f"{'=' * 60}")
17
18 print("\n📷 Generated portrait:")
19 display_image(row["animal_portrait"], base_path)
20
21 print("\n🎨 Edited version:")
22 display_image(row["edited_portrait"], base_path)
23
Python
1for index, row in preview.dataset.iterrows():
2 display_before_after(row, index)
3
Output

============================================================
Record 0: fox wearing a knitted beanie
Setting: a red carpet event, Style: a watercolor painting
============================================================

📷 Generated portrait:
Output

🎨 Edited version:
Output

============================================================
Record 1: rabbit wearing a red bow tie
Setting: a sunny park, Style: a photorealistic style
============================================================

📷 Generated portrait:
Output

🎨 Edited version:
Output

🆙 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.

Python
1create_num_records = int(os.environ.get("DATA_DESIGNER_FLUX_2_PRO_CREATE_NUM_RECORDS", "5"))
2results = data_designer.create(
3 config_builder,
4 num_records=create_num_records,
5 dataset_name="tutorial-6-edited-images",
6)
7
Output
[17:24:55] [INFO] OpenTelemetry metrics available at http://127.0.0.1:9464/metrics
[17:24:55] [INFO] 🎨 Creating Data Designer dataset
[17:24:55] [INFO]   |-- 🔒 Jinja rendering engine: secure
[17:24:55] [INFO] ✅ Validation passed
[17:24:55] [INFO] ⛓️ Sorting column configs into a Directed Acyclic Graph
[17:24:55] [INFO] Skipping model health checks because DATA_DESIGNER_SKIP_MODEL_HEALTH_CHECKS=1
[17:24:55] [INFO] ⚡ Using async task-queue builder
[17:24:55] [INFO] 🖼️ image model config for column 'animal_portrait'
[17:24:55] [INFO]   |-- model: 'black-forest-labs/flux.2-pro'
[17:24:55] [INFO]   |-- model alias: 'image-model'
[17:24:55] [INFO]   |-- model provider: 'openrouter'
[17:24:55] [INFO]   |-- inference parameters:
[17:24:55] [INFO]   |  |-- generation_type=image
[17:24:55] [INFO]   |  |-- max_parallel_requests=4
[17:24:55] [INFO]   |  |-- extra_body={'height': 512, 'width': 512}
[17:24:55] [INFO] 🖼️ image model config for column 'edited_portrait'
[17:24:55] [INFO]   |-- model: 'black-forest-labs/flux.2-pro'
[17:24:55] [INFO]   |-- model alias: 'image-model'
[17:24:55] [INFO]   |-- model provider: 'openrouter'
[17:24:55] [INFO]   |-- inference parameters:
[17:24:55] [INFO]   |  |-- generation_type=image
[17:24:55] [INFO]   |  |-- max_parallel_requests=4
[17:24:55] [INFO]   |  |-- extra_body={'height': 512, 'width': 512}
[17:24:55] [INFO] ⚡️ Async generation: 2 column(s) (column 'animal_portrait', column 'edited_portrait'), 10 tasks across 1 row group(s)
[17:24:55] [INFO] 🚀 (1/1) Dispatching with 5 records
[17:24:55] [INFO] 🎲 (1/1) Preparing samplers to generate 5 records across 4 columns
[17:25:02] [INFO] 📊 Progress [7.4s]:
[17:25:02] [INFO]   |-- 🌑 column 'animal_portrait': 1/5 (20%) 0.1 rec/s
[17:25:02] [INFO]   |-- 🚶 column 'edited_portrait': 0/5 (0%) 0.0 rec/s
[17:25:12] [INFO] 📊 Progress [16.9s]:
[17:25:12] [INFO]   |-- 🌕 column 'animal_portrait': 5/5 (100%) 0.3 rec/s
[17:25:12] [INFO]   |-- 🚶 column 'edited_portrait': 0/5 (0%) 0.0 rec/s
[17:25:22] [INFO] 📊 Progress [27.8s]:
[17:25:22] [INFO]   |-- 🌕 column 'animal_portrait': 5/5 (100%) 0.2 rec/s
[17:25:22] [INFO]   |-- ✈️ column 'edited_portrait': 4/5 (80%) 0.1 rec/s
[17:25:27] [INFO] 📊 Progress [32.6s]:
[17:25:27] [INFO]   |-- 🌕 column 'animal_portrait': 5/5 (100%) 0.2 rec/s
[17:25:27] [INFO]   |-- 🚀 column 'edited_portrait': 5/5 (100%) 0.2 rec/s
[17:25:27] [INFO] ✅ Async generation complete [32.6s]: 10 ok, 0 failed across 2 column(s)
[17:25:28] [INFO] 📊 Model usage summary:
[17:25:28] [INFO]   |-- model: black-forest-labs/flux.2-pro
[17:25:28] [INFO]   |-- tokens: input=23019, output=30720, reasoning=0, total=53739, tps=1632
[17:25:28] [INFO]   |-- requests: success=10, failed=0, total=10, rpm=18
[17:25:28] [INFO]   |-- images: total=10
[17:25:28] [INFO] 📐 Measuring dataset column statistics:
[17:25:28] [INFO]   |-- 🎲 column: 'animal'
[17:25:28] [INFO]   |-- 🎲 column: 'accessory'
[17:25:28] [INFO]   |-- 🎲 column: 'setting'
[17:25:28] [INFO]   |-- 🎲 column: 'art_style'
[17:25:28] [INFO]   |-- 🖼️ column: 'animal_portrait'
[17:25:28] [INFO]   |-- 🖼️ column: 'edited_portrait'
Python
1dataset = results.load_dataset()
2dataset.head()
3
Output
animal accessory setting art_style animal_portrait edited_portrait
0 fox a flower crown a cozy living room a pop art poster ['images/animal_portrait/87a6a607-741d-4dfe-ac... ['images/edited_portrait/da318b18-ec07-42a5-94...
1 panda a pirate hat and eye patch a photo studio with soft lighting a pop art poster ['images/animal_portrait/6cc94401-b1bb-4d55-b7... ['images/edited_portrait/d89f5674-7341-4969-82...
2 cat oversized sunglasses a sunny park a photorealistic style ['images/animal_portrait/c972ec6a-b442-473d-a7... ['images/edited_portrait/7ae318e3-db05-4fc2-90...
3 owl a chef hat a holiday card backdrop with snowflakes a photorealistic style ['images/animal_portrait/fdfbfc6c-131e-4063-81... ['images/edited_portrait/6587d181-a377-404d-a2...
4 panda a flower crown a holiday card backdrop with snowflakes a photorealistic style ['images/animal_portrait/012b3b5c-5829-4d03-b2... ['images/edited_portrait/6a81be87-a56b-43ac-89...
Python
1for index, row in dataset.head(10).iterrows():
2 display_before_after(row, index, base_path=results.artifact_storage.base_dataset_path)
3
Output

============================================================
Record 0: fox wearing a flower crown
Setting: a cozy living room, Style: a pop art poster
============================================================

📷 Generated portrait:
Output

🎨 Edited version:
Output

============================================================
Record 1: panda wearing a pirate hat and eye patch
Setting: a photo studio with soft lighting, Style: a pop art poster
============================================================

📷 Generated portrait:
Output

🎨 Edited version:
Output

============================================================
Record 2: cat wearing oversized sunglasses
Setting: a sunny park, Style: a photorealistic style
============================================================

📷 Generated portrait:
Output

🎨 Edited version:
Output

============================================================
Record 3: owl wearing a chef hat
Setting: a holiday card backdrop with snowflakes, Style: a photorealistic style
============================================================

📷 Generated portrait:
Output

🎨 Edited version:
Output

============================================================
Record 4: panda wearing a flower crown
Setting: a holiday card backdrop with snowflakes, Style: a photorealistic style
============================================================

📷 Generated portrait:
Output

🎨 Edited version:
Output

⏭️ 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: