## MAI Image 2.5 (Private Preview) — Edit + Generate Notebook

**Model card date:** May 2026  
**Release date:** June 2, 2026

MAI-Image-2.5 is a **20B-parameter diffusion model** for both:
- high-quality **text-to-image generation**, and
- precise, controllable **image-to-image editing**.

Model-card highlights reflected in this notebook:
- Architecture: diffusion + flow-matching objective
- Inputs: text, plus image input for editing
- Context length: 32K tokens
- Output limit: total pixels must be <= 1,048,576 (equivalent to 1024x1024). Either dimension may exceed 1024 if total stays within budget.
- Strong focus areas: surgical edits, layout preservation, text updates, artifact cleanup, portrait/product quality
- Quality signal: top-3 ranking on LMArena text-to-image and image-editing leaderboards (per model card)

## Private Preview Terms (must follow)
- Internal-only use; no external end-user access.
- Private preview models are not for production deployments.
- Follow Microsoft license terms and Generative AI code of conduct.
- Abuse monitoring and content filtering remain enabled.
- Preview is as-is and may change or be discontinued without notice.
- Do not send regulated personal data in preview feedback.


## 1. Setup

### Environment variables

| Variable | Required | Secret | Purpose |
|---|---|---|---|
| `MAI_IMAGE_25_ENDPOINT` | Yes | No | MAI Image 2.5 endpoint (Cognitive endpoint format recommended). |
| `MAI_IMAGE_25_API_KEY` | Yes | **Yes** | API key for MAI Image 2.5 preview calls. |
| `MAI_IMAGE_25_DEPLOYMENT_NAME` | Yes | No | Deployment/model name used in request payloads. |
| `IMAGE_OUTPUT_DIR` | Optional | No | Output directory for generated files; defaults to `media/mai-image-2-5`. |
| `MAI_IMAGE_25_INPUT_IMAGE` | Optional | No | Input image for edit flow; defaults to `media/mai-image-2-5/01-generated-example.png`. |
| `MAI_IMAGE_25_EDIT_SIZE` | Optional | No | Edit image size (for example `1024x1024`). |

Do not commit `.env` or `deployment.env` files with secrets.


```python
# %pip install -q requests python-dotenv pillow

```

```python
import os
import base64
import mimetypes
import socket
from urllib.parse import urlparse
from pathlib import Path
import requests
from dotenv import load_dotenv
from IPython.display import Image as IPImage, display

ENV_PATH = 'deployment.env' if os.path.exists('deployment.env') else os.path.join('..', 'deployment.env')
load_dotenv(ENV_PATH, override=True)

MAI_IMAGE_25_ENDPOINT = os.getenv('MAI_IMAGE_25_ENDPOINT')
MAI_IMAGE_25_DEPLOYMENT_NAME = os.getenv('MAI_IMAGE_25_DEPLOYMENT_NAME')
MAI_IMAGE_25_API_KEY = os.getenv('MAI_IMAGE_25_API_KEY')

image_output_env = os.getenv('IMAGE_OUTPUT_DIR')
IMAGE_OUTPUT_DIR = Path(image_output_env) if image_output_env else Path('media') / 'mai-image-2-5'
IMAGE_OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
input_image_env = os.getenv('MAI_IMAGE_25_INPUT_IMAGE')
INPUT_IMAGE_PATH = Path(input_image_env) if input_image_env else IMAGE_OUTPUT_DIR / '01-generated-example.png'
DEFAULT_EDIT_SIZE = os.getenv('MAI_IMAGE_25_EDIT_SIZE', '1024x1024')

assert MAI_IMAGE_25_ENDPOINT, 'Set MAI_IMAGE_25_ENDPOINT in deployment.env'
assert MAI_IMAGE_25_API_KEY, 'Set MAI_IMAGE_25_API_KEY in deployment.env (MAI Image 2.5 currently uses API key auth in preview)'

def auth_headers(content_type: str | None = None) -> dict:
    h = {'api-key': MAI_IMAGE_25_API_KEY}
    if content_type:
        h['Content-Type'] = content_type
    return h

def preflight_endpoint_dns(endpoint: str) -> None:
    host = urlparse(endpoint).hostname
    try:
        socket.getaddrinfo(host, 443)
    except Exception as ex:
        raise RuntimeError(
            f'Endpoint DNS resolution failed for {host}: {ex}. '
            'Use the Cognitive endpoint format (e.g. https://westcentralus.api.cognitive.microsoft.com/).'
        )

preflight_endpoint_dns(MAI_IMAGE_25_ENDPOINT)

print('Endpoint:', MAI_IMAGE_25_ENDPOINT)
print('Deployment:', MAI_IMAGE_25_DEPLOYMENT_NAME)
print('Auth mode: API key')
print('Output dir:', IMAGE_OUTPUT_DIR)
print('Default edit size:', DEFAULT_EDIT_SIZE)

```

## 2. API behavior note (important)

- **Image edit endpoint** (`/mai/v1/images/edits`) uses **`size`** (for example, `1024x1024`).
- **Text-to-image generation endpoint** (`/mai/v1/images/generations`) uses **`width`** and **`height`**.
- For both paths, keep total pixel budget <= **1024x1024**.


Illustrative examples from one run:

The images below are illustrative examples from one run. Your output may differ because image generation and editing are non-deterministic.

![Generated image example](media/mai-image-2-5/01-generated-example.png)

![Edited image example](media/mai-image-2-5/02-edited-example.png)


## 3. Text-to-Image Helper (`width` + `height`)

```python
def _validate_pixel_budget(width: int, height: int, source: str) -> None:
    assert width > 0 and height > 0, f'{source}: width and height must be positive'
    assert width * height <= 1_048_576, f'{source}: width*height must be <= 1,048,576'


def generate_image_25(prompt: str, out_file: str, width: int = 1024, height: int = 1024) -> Path:
    _validate_pixel_budget(width, height, 'generation')

    url = f"{MAI_IMAGE_25_ENDPOINT.rstrip('/')}/mai/v1/images/generations"
    payload = {
        'model': MAI_IMAGE_25_DEPLOYMENT_NAME,
        'prompt': prompt,
        'width': width,
        'height': height,
    }
    resp = requests.post(url, headers=auth_headers('application/json'), json=payload, timeout=180)
    if not resp.ok:
        raise requests.HTTPError(f'Generation failed with {resp.status_code}: {resp.text}', response=resp)
    data = resp.json().get('data', [])
    assert data and data[0].get('b64_json'), f'No image output in response: {resp.text[:500]}'
    out_path = IMAGE_OUTPUT_DIR / out_file
    out_path.write_bytes(base64.b64decode(data[0]['b64_json']))
    return out_path

```

## 4. Image Edit Helper (`size`)

```python
def _parse_size(size: str) -> tuple[int, int]:
    parts = size.lower().split('x')
    assert len(parts) == 2, "size must be in '<width>x<height>' format, e.g. 1024x1024"
    w, h = int(parts[0]), int(parts[1])
    return w, h


def edit_image_25(prompt: str, input_image: Path, out_file: str, size: str = DEFAULT_EDIT_SIZE) -> Path:
    w, h = _parse_size(size)
    _validate_pixel_budget(w, h, 'edit')

    url = f"{MAI_IMAGE_25_ENDPOINT.rstrip('/')}/mai/v1/images/edits"
    mime = mimetypes.guess_type(str(input_image))[0] or 'image/png'
    assert input_image.exists(), f'Input image not found: {input_image}'

    headers = auth_headers()
    with input_image.open('rb') as f:
        files = {'image': (input_image.name, f, mime)}
        form = {
            'prompt': prompt,
            'model': MAI_IMAGE_25_DEPLOYMENT_NAME,
            'size': size,
        }
        resp = requests.post(url, headers=headers, files=files, data=form, timeout=300)

    if not resp.ok:
        raise requests.HTTPError(f'Edit failed with {resp.status_code}: {resp.text}', response=resp)
    data = resp.json().get('data', [])
    assert data and data[0].get('b64_json'), 'b64_json missing or null (common with bad decoding flow)'
    out_path = IMAGE_OUTPUT_DIR / out_file
    out_path.write_bytes(base64.b64decode(data[0]['b64_json']))
    return out_path

```

## 5. Run a generation sample

```python
generated = generate_image_25(
    prompt='A photorealistic concept art poster of an Azure AI lab at sunset, cinematic lighting',
    out_file='mai_image25_generation.png',
    width=1024,
    height=1024,
)
print('Generated image:', generated)
display(IPImage(filename=str(generated), width=512))
INPUT_IMAGE_PATH=generated
```

Example response shape:

```json
{
  "created": 0,
  "data": [
    {
      "b64_json": "<redacted-base64-image>"
    }
  ]
}
```


## 6. Run an edit sample (requires input image)

```python
if INPUT_IMAGE_PATH.exists():
    edited = edit_image_25(
        prompt='Turn this into a clean futuristic product shot with studio lighting',
        input_image=INPUT_IMAGE_PATH,
        out_file='mai_image25_edited.png',
        size=DEFAULT_EDIT_SIZE,
    )
    print('Edited image:', edited)
    display(IPImage(filename=str(edited), width=512))
else:
    print(f'Skipping edit run: input image not found at {INPUT_IMAGE_PATH}')

```

Example response shape:

```json
{
  "created": 0,
  "data": [
    {
      "b64_json": "<redacted-base64-image>"
    }
  ]
}
```


## 7. Troubleshooting

| Error | Resolution |
|---|---|
| `DeploymentModelNotSupported` | Subscription not whitelisted or model name/version mismatch. Verify deployment and model availability. |
| Output image is 0-3 bytes | `b64_json` is null or decode flow is wrong. Save response JSON first, then decode. |
| `base64: unrecognized option --ignore-garbage` | On macOS use `base64 -d` instead of GNU flags. |
| `401/403` | Invalid/expired API key or wrong endpoint. Re-check `MAI_IMAGE_25_API_KEY` and `MAI_IMAGE_25_ENDPOINT`. |


## 8. Usage, safety, and out-of-scope reminders

**Primary use cases aligned to model card:**
- Creative text-to-image generation
- Surgical image editing (object removal/replacement, inpainting, text updates, attribute changes)
- Product/branding visuals, portraits, and production design workflows

**Out-of-scope / not allowed:**
- Deceptive impersonation or misleading identity content
- Illegal or policy-violating content generation
- Harmful or abusive content generation

**Responsible AI notes:**
- This model includes layered safety guardrails (prompt and output filtering) in deployed systems.
- Continue to review outputs and prompts for policy compliance before downstream use.
