# Image-To-Image

Source <https://github.com/vllm-project/vllm-omni/tree/main/examples/online_serving/image_to_image>.


This example demonstrates how to deploy Qwen-Image-Edit model for online image editing service using vLLM-Omni.

For **multi-image** input editing, use **Qwen-Image-Edit-2509** (QwenImageEditPlusPipeline) and send multiple images in the user message content.

## Start Server

### Basic Start

```bash
vllm serve Qwen/Qwen-Image-Edit --omni --port 8092
```

!!! note
    If you encounter Out-of-Memory (OOM) issues or have limited GPU memory, you can enable VAE slicing and tiling to reduce memory usage, --vae-use-slicing --vae-use-tiling

### Multi-Image Edit (Qwen-Image-Edit-2509)

```bash
vllm serve Qwen/Qwen-Image-Edit-2509 --omni --port 8092
```

### Start with Parameters


Or use the startup script:

```bash
bash run_server.sh
```

To serve Qwen-Image-Edit-2509 with the script:

```bash
MODEL=Qwen/Qwen-Image-Edit-2509 bash run_server.sh
```

## API Calls

### Method 1: Using curl (Image Editing)

```bash
# Image editing
bash run_curl_image_edit.sh input.png "Convert this image to watercolor style"

# Or execute directly
IMG_B64=$(base64 -w0 input.png)

cat <<EOF > request.json
{
  "messages": [{
    "role": "user",
    "content": [
      {"type": "text", "text": "Convert this image to watercolor style"},
      {"type": "image_url", "image_url": {"url": "data:image/png;base64,$IMG_B64"}}
    ]
  }],
  "extra_body": {
    "height": 1024,
    "width": 1024,
    "num_inference_steps": 50,
    "guidance_scale": 1,
    "seed": 42
  }
}
EOF

curl -s http://localhost:8092/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d @request.json \
  | jq -r '.choices[0].message.content[0].image_url.url' \
  | cut -d',' -f2 | base64 -d > output.png
```

### Method 2: Using OpenAI Python SDK

```python
import base64
from openai import OpenAI

client = OpenAI(base_url="http://localhost:8092/v1", api_key="none")

with open("input.png", "rb") as f:
    img_b64 = base64.b64encode(f.read()).decode()

response = client.chat.completions.create(
    model="Qwen/Qwen-Image-Edit",
    messages=[{
        "role": "user",
        "content": [
            {"type": "text", "text": "Convert to watercolor style"},
            {"type": "image_url", "image_url": {
                "url": f"data:image/png;base64,{img_b64}"
            }},
        ],
    }],
    extra_body={
        "num_inference_steps": 50,
        "guidance_scale": 1,
        "seed": 42,
    },
)

img_url = response.choices[0].message.content[0].image_url.url
_, b64_data = img_url.split(",", 1)
with open("output.png", "wb") as f:
    f.write(base64.b64decode(b64_data))
```

### Method 3: Using Python Client Script

```bash
python openai_chat_client.py --input input.png --prompt "Convert to oil painting style" --output output.png

# Multi-image editing (Qwen-Image-Edit-2509 server required)
python openai_chat_client.py --input input1.png input2.png --prompt "Combine these images into a single scene" --output output.png
```

### Method 4: Using Gradio Demo

!!! note "Gradio is an optional dependency"
    The Gradio demo requires the `[demo]` extras. Install them first:

    ```bash
    pip install 'vllm-omni[demo]'
    ```

    Or, if installing from source: `pip install -e '.[demo]'`

```bash
python gradio_demo.py
# Visit http://localhost:7861
```

## Request Format

### Image Editing (Using image_url Format)

```json
{
  "messages": [
    {
      "role": "user",
      "content": [
        {"type": "text", "text": "Convert this image to watercolor style"},
        {"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}}
      ]
    }
  ]
}
```

### Image Editing (Using Simplified image Format)

```json
{
  "messages": [
    {
      "role": "user",
      "content": [
        {"text": "Convert this image to watercolor style"},
        {"image": "BASE64_IMAGE_DATA"}
      ]
    }
  ]
}
```

### Image Editing with Parameters

Wrap generation parameters inside `extra_body` in the request JSON:

```json
{
  "messages": [
    {
      "role": "user",
      "content": [
        {"type": "text", "text": "Convert to ink wash painting style"},
        {"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}}
      ]
    }
  ],
  "extra_body": {
    "height": 1024,
    "width": 1024,
    "num_inference_steps": 50,
    "guidance_scale": 7.5,
    "seed": 42
  }
}
```

!!! tip "Using the OpenAI SDK"
    When using the OpenAI Python SDK, pass these parameters via the `extra_body`
    keyword argument. The SDK merges them into the top-level request body automatically:

    ```python
    client.chat.completions.create(
        model="Qwen/Qwen-Image-Edit",
        messages=[...],
        extra_body={"num_inference_steps": 50, "guidance_scale": 7.5, "seed": 42},
    )
    ```

    For details on how generation parameters are handled across different clients, see the
    [Chat Completions API guide](../../../../serving/chat_completions_api.md).

### Layered Image Generation (Qwen-Image-Layered)

Qwen-Image-Layered generates multiple decomposed layers from a reference image and a text prompt.
Start the server with:

```bash
vllm serve Qwen/Qwen-Image-Layered --omni --port 8093
```

=== "curl"

    ```bash
    IMG_B64=$(base64 -w0 input.png)

    curl -sS http://localhost:8093/v1/chat/completions \
      -H "Content-Type: application/json" \
      -d "$(jq -n --arg img "$IMG_B64" '{
        messages: [{
          role: "user",
          content: [
            {type: "image_url", image_url: {url: ("data:image/png;base64," + $img)}},
            {type: "text", text: "a rabbit"}
          ]
        }],
        extra_body: {
          num_inference_steps: 50,
          cfg_scale: 4.0,
          seed: 0,
          layers: 4,
          resolution: 640
        }
      }')" \
      | jq -r '.choices[0].message.content[] | .image_url.url | split(",")[1]' \
      | while IFS= read -r b64; do
          ((i++)); echo "$b64" | base64 -d > "layer_${i}.png"
        done
    ```

=== "OpenAI SDK"

    ```python
    import base64
    from openai import OpenAI

    client = OpenAI(base_url="http://localhost:8093/v1", api_key="none")

    with open("input.png", "rb") as f:
        img_b64 = base64.b64encode(f.read()).decode()

    response = client.chat.completions.create(
        model="Qwen/Qwen-Image-Layered",
        messages=[{
            "role": "user",
            "content": [
                {"type": "image_url", "image_url": {
                    "url": f"data:image/png;base64,{img_b64}"
                }},
                {"type": "text", "text": "a rabbit"},
            ],
        }],
        extra_body={
            "num_inference_steps": 50,
            "cfg_scale": 4.0,
            "seed": 0,
            "layers": 4,
            "resolution": 640,
        },
    )

    for i, item in enumerate(response.choices[0].message.content):
        _, b64_data = item.image_url.url.split(",", 1)
        with open(f"layer_{i}.png", "wb") as f:
            f.write(base64.b64decode(b64_data))
    ```

=== "Python requests"

    ```python
    import base64
    import requests

    with open("input.png", "rb") as f:
        img_b64 = base64.b64encode(f.read()).decode()

    payload = {
        "messages": [{
            "role": "user",
            "content": [
                {"type": "image_url", "image_url": {
                    "url": f"data:image/png;base64,{img_b64}"
                }},
                {"type": "text", "text": "a rabbit"},
            ],
        }],
        "extra_body": {
            "num_inference_steps": 50,
            "cfg_scale": 4.0,
            "seed": 0,
            "layers": 4,
            "resolution": 640,
        },
    }

    resp = requests.post(
        "http://localhost:8093/v1/chat/completions",
        json=payload,
        timeout=600,
    )
    data = resp.json()

    for i, item in enumerate(data["choices"][0]["message"]["content"]):
        _, b64_data = item["image_url"]["url"].split(",", 1)
        with open(f"layer_{i}.png", "wb") as f:
            f.write(base64.b64decode(b64_data))
    ```

The response contains multiple images in `choices[0].message.content` — one per generated layer.

#### Qwen-Image-Layered Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `layers` | int | 4 | Number of layers to decompose |
| `resolution` | int | 640 | Resolution for dimension calculation (640 or 1024) |
| `cfg_scale` | float | 4.0 | Classifier-free guidance scale (alias for `true_cfg_scale`) |
| `num_inference_steps` | int | 50 | Number of denoising steps |
| `seed` | int | None | Random seed for reproducibility |

### Multi-Image Editing (Qwen-Image-Edit-2509)

Provide multiple images in `content` (order matters):

```json
{
  "messages": [
    {
      "role": "user",
      "content": [
        {"type": "text", "text": "Combine these images into a single scene"},
        {"type": "image_url", "image_url": {"url": "data:image/png;base64,..."} },
        {"type": "image_url", "image_url": {"url": "data:image/png;base64,..."} }
      ]
    }
  ]
}
```

## Generation Parameters

When using `/v1/chat/completions`, pass these inside `extra_body` in the curl
JSON, or via the `extra_body` keyword argument in the OpenAI Python SDK (see the
[Chat Completions API guide](../../../../serving/chat_completions_api.md)).
When using the dedicated [`/v1/images/edits`](../../../../serving/image_edit_api.md)
endpoint, pass the supported generation controls as top-level form fields
directly. For image dimensions and count, use `size` and `n` rather than
`height`, `width`, or `num_outputs_per_prompt`.

| Parameter                | Type  | Default | Description                           |
| ------------------------ | ----- | ------- | ------------------------------------- |
| `height`                 | int   | None    | Output image height in pixels         |
| `width`                  | int   | None    | Output image width in pixels          |
| `size`                   | str   | None    | Output image size (e.g., "1024x1024") |
| `num_inference_steps`    | int   | 50      | Number of denoising steps             |
| `guidance_scale`         | float | 1.0     | CFG guidance scale                    |
| `seed`                   | int   | None    | Random seed (reproducible)            |
| `negative_prompt`        | str   | None    | Negative prompt                       |
| `num_outputs_per_prompt` | int   | 1       | Number of images to generate          |
| `layers`                 | int   | 4       | Number of layers (Qwen-Image-Layered) |
| `resolution`             | int   | 640     | Resolution, 640 or 1024 (Qwen-Image-Layered) |

## Response Format

```json
{
  "id": "chatcmpl-xxx",
  "created": 1234567890,
  "model": "Qwen/Qwen-Image-Edit",
  "choices": [{
    "index": 0,
    "message": {
      "role": "assistant",
      "content": [{
        "type": "image_url",
        "image_url": {
          "url": "data:image/png;base64,..."
        }
      }]
    },
    "finish_reason": "stop"
  }],
  "usage": {...}
}
```

## Common Editing Instructions Examples

| Instruction                              | Description      |
| ---------------------------------------- | ---------------- |
| `Convert this image to watercolor style` | Style transfer   |
| `Convert the image to black and white`   | Desaturation     |
| `Enhance the color saturation`           | Color adjustment |
| `Convert to cartoon style`               | Cartoonization   |
| `Add vintage filter effect`              | Filter effect    |
| `Convert daytime scene to nighttime`     | Scene conversion |

## File Description

| File                     | Description                  |
| ------------------------ | ---------------------------- |
| `run_server.sh`          | Server startup script        |
| `run_curl_image_edit.sh` | curl image editing example   |
| `openai_chat_client.py`  | Python client                |
| `gradio_demo.py`         | Gradio interactive interface |

## Example materials

??? abstract "gradio_demo.py"
    ``````py
    --8<-- "examples/online_serving/image_to_image/gradio_demo.py"
    ``````
??? abstract "openai_chat_client.py"
    ``````py
    --8<-- "examples/online_serving/image_to_image/openai_chat_client.py"
    ``````
??? abstract "run_curl_image_edit.sh"
    ``````sh
    --8<-- "examples/online_serving/image_to_image/run_curl_image_edit.sh"
    ``````
??? abstract "run_server.sh"
    ``````sh
    --8<-- "examples/online_serving/image_to_image/run_server.sh"
    ``````
