Change expression

Change facial expressions with our advanced AI model

You will need an API token to send HTTP requests. See Authentication for instructions.

Quick start

Firstly, you'll need to upload the image you want to process. These parameters must be supplied as multipart form data:

ParameterExampleDescription
file@file.pngThe image file as binary data.
options"{\"flag_hair\":true,\"flag_sync\":true,\"mode\":\"keep\"}"Extra options for the generation process. See Options.

Please note that we only support the following formats: WEBP, JPEG and PNG. Note down these fields from the response:

Response
{
  "image_id": "67762577...", // IMAGE_ID
  "face_description_list": [
    {
      "a": { "Age": 25, "Gender": "male" }, 
      "f": 0                // FACE_ID
    }
  ],
  "faces": {
    "number_of_faces": 2,
    "coordinates_list": [
      {
        "id": 0             // FACE_ID
        // ...
      },
      {
        "id": 1             // FACE_ID
        // ...
      }
    ]
  }
}

For more information about the data contained in coordinates_list and how to process it, see Coordinates. To understand how to use the image_id parameter, also see Image ID. You can also find out how to use the objects in face_description_list in the Prompts section.


To change the expression on a face, you will have to call the endpoint once for each face you want to modify. This will start asynchronous processes that will be handled in the next step.

This is an example of request containing all required fields for the face with "id": 0 (zero):

Request
{
  "id_image": "67762577...",      // IMAGE_ID
  "id_face": "0",                 // FACE_ID as string
  "prompt": "{\"Expression\":\"happy\"}"
}

For more information on the usage of the prompt parameter, see Prompts.


Notification objects returned by this endpoint will inform you of the end of a generation on our servers. We recommend sending a request every 3 seconds at most, to not incur in rate limiting.

Request
{
  "name_list": ["new_generation"]
}

After the process is complete, you might see a similar response:

Response
{
  "notifications_list": [
    {
      "data": {
        "f": 0,                    // FACE_ID
        "g": 0,                    // GENERATION_ID
        "id_image": "67762577...", // IMAGE_ID
        "link": "https://...",
      },
      "name": "new_generation",
    },
  ]
}

Where link points to a cropped thumbnail of the face with the expression you requested in the previous step.

The generation_id field is a unique identifier for the process that was started in the previous step and has now ended. If you start another expression change, the result will have a different generation_id. This ID is needed in the next step.


When you're satisfied with a new expression from the previous step, you can request the server to place the newly generated expression on the original image you started with.

Request
{
  "id_image": "67762577...", // IMAGE_ID
  "id_face": "0",            // FACE_ID as string
  "id_generation": "2"       // GENERATION_ID
}

Uploading an image

Be careful!

All target images are deleted automatically after 24 hours of being uploaded. This action cannot be undone.

You can upload any image containing up to 30 people. Their faces will be automatically detected by the API. Please note that we only support the following formats: WEBP, JPEG and PNG.

import json
import requests

api_url = "https://api.piktid.com/api"
access_token = "your_access_token"
target_path = "path_to_image"

options = {
    "flag_hair": True,
    "flag_sync": True,
    "mode": "keep"
}

with open(target_path, "rb") as target:
    response = requests.post(
        api_url + "/upload_pro",
        headers={"Authorization": "Bearer " + access_token},
        files={"file": target},
        data={"options": json.dumps(options)},
    ).json()
    image_id = response.get("image_id")

Options

The options parameter in the request body must be formatted as a JSON-encoded string. It can contain the following parameters:

ParameterValuesDescription
flag_hairtrue or falseIf false, the server will carefully avoid drawing over the subject's hair.
flag_synctrue or falseIf false, the server will not run the detection step but only upload the image and create an image_id.
mode"keep" or "random"If set to "random", generates a new face instead of just changing the expression. See Anonymize for that.

Changing an expression

Starting an expression change process is easy, but make sure to handle it right. Everything is done asynchronously and the endpoint will return a success code if the process was started successfully.

import json
import requests

api_url = "https://api.piktid.com/api"
access_token = "your_access_token"

image_id = "67762577..."
expressions = {
    0: {"Expression": "happy"},
    1: {"Eyes": "wink_right"}
}

for face_id, prompt in expressions.items():
    requests.post(
        api_url + "/ask_new_expression",
        headers={"Authorization": "Bearer " + access_token},
        json={
            "id_image": image_id,
            "id_face": face_id,
            "flag_sync": False,
            "prompt": json.dumps(prompt)
        },
    )

For more information on the usage of the prompt parameter, see Prompts.

Prompts

Expression prompts are defined as a single key-value pair, where:

  • the key is a facial feature
  • the value is an expression or physical state of the key

To better explain this concepts, here are the currently defined prompts as programming language type definitions:

from dataclasses import dataclass
from typing import Literal


@dataclass
class Prompt:
    Expression: Literal[
        "happy",
        "angry",
        "astonished",
        "cry",
        "disgusted",
        "fearful",
        "pathetic",
        "surprised",
        "suspicious",
        "neutral",
        "sad"
    ]
    Eyes: Literal[
        "closed",
        "open",
        "wink_left",
        "wink_right"
    ]
    Gaze: Literal[
        "left",
        "right",
        "up",
        "down"
    ]
type Prompt = Partial<{
  Expression:
    | "happy"
    | "angry"
    | "astonished"
    | "cry"
    | "disgusted"
    | "fearful"
    | "pathetic"
    | "surprised"
    | "suspicious"
    | "neutral"
    | "sad"

  Eyes:
  	| "closed"
    | "open"
    | "wink_left"
    | "wink_right"

  Gaze:
    | "left"
    | "right"
    | "up"
    | "down"
}>

In both languages, the following are valid prompt objects:

{
  "Expression": "happy"
}
{
  "Eyes": "wink_left"
}

Waiting for an expression change

Be careful!

Notifications are automatically deleted after exactly 10 minutes (600 seconds) from their creation.

Results are shared to clients via "notifications". Clients are expected to poll for notifications and handle them in the allowed time limit before they are automatically dismissed by the API.

import time
import requests

api_url = "https://api.piktid.com/api"
access_token = "your_access_token"

image_id = "67762577..."


def process_notifications(response, image_id):
    for notification in response.get("notifications_list", []):
        if notification["name"] != "new_generation":
            return None, None

        if notification["data"]["id_image"] != image_id:
            return None, None

        return notification["data"], notification["id"]

    return None, None


def delete_notification(notification_id):
    requests.delete(
        api_url + "/notification/delete_json",
        headers={"Authorization": "Bearer " + access_token},
        json={"id": notification_id},
    )


count = 0
# Loops for 300 seconds, even though changing expressions generally
# takes much less time.
for _ in range(300):
    response = requests.post(
        api_url + "/notification_by_name_json",
        headers={"Authorization": "Bearer " + access_token},
        json={"name_list": "new_generation,error"},
    ).json()

    data, notification_id = process_notifications(response, image_id)
    if data is not None:
        # Delete the notification to avoid reading it again
        delete_notification(notification_id)

        print(f"Face with ID {data['f']} finished processing!")
        print(data["link"])

        count += 1
        if count == len(expressions):
            break

    time.sleep(1)

Remember that the results you just received are only relative to a single face and the response does not contain the final image!

Assembling the final image

Be careful!

All generated images are deleted automatically after 24 hours of being created. This action cannot be undone. Make sure to download them!

To assemble the final image, you need to "pick" faces from the ones you generated so far (remember generation_id?). Each request picks a single face, so you will need multiple requests to fully assemble the final image.

The following examples assumes that:

  • the face with face_id equals to 0 has been regenerated twice and generation 2 is chosen for the final image
  • the face with face_id equals to 1 has been regenerated once and generation 1 is chosen for the final image
import json
import requests

api_url = "https://api.piktid.com/api"
access_token = "your_access_token"

image_id = "67762577..."
# Keys are FACE_ID, values are GENERATION_ID
picked_generations = {
    0: "2",
    1: "1"
}
final_link = None

for face_id, generation_id in picked_generations.items():
    response = requests.post(
        api_url + "/pick_face2",
        headers={"Authorization": "Bearer " + access_token},
        json={
            "id_image": image_id,
            "id_face": face_id,
            "id_generation": generation_id,
            # "flag_watermark": 0
        },
    ).json()
    final_link = response["links"]["l"]

print("Finished processing: ", final_link)

PRO users can also disable watermark application on the final image.