Use case: Hair and face segmentation#

If you’re training a neural network to differentiate between a human face and human hair, you probably want to keep the identity of an actor static while adjusting their surroundings, their worn accessories, the tilt and location of their head, their facial expression, their eyes, and their hair - both head hair and facial hair.

The code on this page demonstrates how to use our SDK to build a data request with that goal in mind. The data request will generate 5 datapoints for each of 10 different actors.

When you submit the data request to our platform, each actor will be rendered 5 times, with the following variance:

  • Different hairstyles and colors

  • Different accessories

  • Different tilt of the head

  • Different location of the human head in the frame

  • Different expressions on the human’s face

  • Different eyes

  • Different backgrounds

  • Different facial hair

Each image will of course be accompanied by metadata giving you its ground truth: the actor’s optical and visual gaze vectors as well as their target of gaze in 3D space.


Click here to download the raw python code used in this sample:

This page contains an interactive Python kernel that installs our PyPi package on pageload.

!pip install datagen-tech -q
from datagen import api as datapoint_api
from datagen.api import catalog, assets
from datagen.api.catalog import attributes
from datagen.api.catalog.attributes import Gender, Ethnicity, Age
from datagen_protocol.schema.humans.human import HumanAttributes, HairAttributes
import random
# random.seed(5263)

# Getting catalogs
BACKGROUNDS = catalog.backgrounds.get()
BEARDS = catalog.beards.get()
EYES = catalog.eyes.get()

def random_sampler(options, obj=None):
    A helper function to randomize the attributes of an asset
    :param options: the attributes that we want to randomize. They are specified in each of the functions below
    :param obj: an object from our assets/attributes catalogs
    :return: a float (for "RANDOM": sampling method) or the chosen/updated asset

    result = None
    params = {}

    for attribute, range_parameters in options.items():
        if type(range_parameters) != tuple:
            params[attribute] = options[attribute]
        elif range_parameters[-1] == "RANDOM":
            result = random.random()
        elif range_parameters[-1] == "ASSET_UNIFORM_CHOICE":
            result = random.choice(range_parameters[0])
        elif range_parameters[-1] == "UNIFORM":
            params[attribute] = random.uniform(range_parameters[0], range_parameters[1])
        elif range_parameters[-1] == "UNIFORM_CHOICE":
            params[attribute] = random.choice(range_parameters[0])
        elif range_parameters[-1] == "WEIGHTED_CHOICE":
            params[attribute] = random.choices(range_parameters[0], weights=range_parameters[1])[0]
            print(f"{range_parameters[-1]} is not a supported sampling method")
            raise NameError

    if result:
        return result

    params = obj(**params)
    return params

def get_single_camera(res=(512, 512), fov=(15, 15), cam_loc=assets.Point(x=0, y=-1.6, z=0.12), cam_rot=assets.Rotation(yaw=0.0, pitch=0.0, roll=0.0)):
    Sets the camera according to the chosen intrinsic and extrinsic parameters
    :return: a camera asset
    return assets.Camera(

# Randomizing functions: in each of the following functions, define the weights, the ranges and the options lists as
#                        indicated in each function below.
def get_random_human(gender_probs=(1 / 2, 1 / 2), age_probs=(1 / 3, 1 / 3, 1 / 3), eth_probs=(1 / 6, 1 / 6, 1 / 6, 1 / 6, 1 / 6, 1 / 6)):
    Gets a random ID according to the specified filters below
    Edit the weights in order to change the distribution of the generated identity
    :return: a human asset according to the chosen guidelines
    human_controls = {
        "gender": ([Gender.FEMALE, Gender.MALE], list(gender_probs), "WEIGHTED_CHOICE"),
        "age": ([Age.YOUNG, Age.ADULT, Age.OLD], list(age_probs), "WEIGHTED_CHOICE"),
        "ethnicity": ([Ethnicity.AFRICAN, Ethnicity.HISPANIC, Ethnicity.MEDITERRANEAN, Ethnicity.NORTH_EUROPEAN,
                       Ethnicity.SOUTHEAST_ASIAN, Ethnicity.SOUTH_ASIAN],
                      list(eth_probs), "WEIGHTED_CHOICE")
    human_attrs = random_sampler(options=human_controls, obj=HumanAttributes)  # FIXME: human attrs?
    random_human = random_sampler(options={"human": (catalog.humans.get(gender=human_attrs.gender, age=human_attrs.age, ethnicity=human_attrs.ethnicity), "ASSET_UNIFORM_CHOICE")})
    return random_human

def set_random_head_rotation(yaw_angle_range=(-30, 30), pitch_angle_range=(-20, 20), roll_angle_range=(-10, 10)):
    Sets a random rotation to the head.
    Edit the yaw, pitch and roll ranges.
    Please refer to our documentation for more information (such as min/max ranges values).
    Default values: (yaw=0, pitch=0, roll=0)
    :return: The updated HeadRotation asset
    head_rotation_controls = {
        "yaw": (yaw_angle_range[0], yaw_angle_range[1], "UNIFORM"),
        "pitch": (pitch_angle_range[0], pitch_angle_range[1], "UNIFORM"),
        "roll": (roll_angle_range[0], roll_angle_range[1], "UNIFORM")
    return random_sampler(options=head_rotation_controls, obj=assets.Rotation)

def set_random_head_location(x_range=(-0.1, 0.1), y_range=(-0.0, 0.0), z_range=(-0.08, 0.08)):
    Sets a random location to the head.
    Edit the yaw, pitch and roll ranges.
    Please refer to our documentation for more information (such as min/max ranges values).
    Default values: (yaw=0, pitch=0, roll=0)
    :return: The updated HeadRotation asset
    head_location_controls = {
        "x": (x_range[0], x_range[1], "UNIFORM"),
        "y": (y_range[0], y_range[1], "UNIFORM"),
        "z": (z_range[0], z_range[1], "UNIFORM")
    return random_sampler(options=head_location_controls, obj=assets.Point)

def set_random_hair_color(mel_range=(0, 1), red_range=(0, 1), white_range=(0, 1), rough_range=(0.15, 0.5), ior_range=(1.4, 1.65)):
    Sets a random hair color to the chosen human.
    Please refer to our documentation in order to learn more about how we control the hair color.
    If not used, each identity has its own default hair color.
    :return: The updated HairColor asset
    hair_color_controls = {
        "melanin": (mel_range[0], mel_range[1], "UNIFORM"),
        "redness": (red_range[0], red_range[1], "UNIFORM"),
        "whiteness": (white_range[0], white_range[1], "UNIFORM"),
        "roughness": (rough_range[0], rough_range[1], "UNIFORM"),
        "index_of_refraction": (ior_range[0], ior_range[1], "UNIFORM")

    return random_sampler(options=hair_color_controls, obj=assets.HairColor)

def set_random_expression(intensity_range=(0.1, 1)):
    Sets a random expressions and intensity.
    Default value: NEUTRAL
    :return: The updated ExpressionName asset
    expression_controls = {
        "name": (list(assets.ExpressionName), "UNIFORM_CHOICE"),
        "intensity": (intensity_range[0], intensity_range[1], "UNIFORM")
    return random_sampler(options=expression_controls, obj=assets.Expression)

def randomize_glasses_controls(chosen_glasses, metalness_range=(0, 1), reflec_range=(0, 1), transparency_range=(0, 1)):
    Randomizes the glasses attributes
    Please refer to our documentation in order to learn more about how we control the different glasses attributes
    :param chosen_glasses: a random Glasses asset after it has been chosen
    :return: The updated Glasses asset
    if chosen_glasses is not None:
        glasses_controls = {
            "frame_color": (list(assets.Color), "UNIFORM_CHOICE"),
            "frame_metalness": (metalness_range[0], metalness_range[1], "UNIFORM"),
            "lens_color": (list(assets.Color), "UNIFORM_CHOICE"),
            "lens_reflectivity": (reflec_range[0], reflec_range[1], "UNIFORM"),
            "lens_transparency": (transparency_range[0], transparency_range[1], "UNIFORM"),
            "position": (list(assets.GlassesPosition), "UNIFORM_CHOICE")

        chosen_glasses = random_sampler(options=glasses_controls, obj=assets.Glasses)

    return chosen_glasses

def randomize_mask_controls(chosen_mask, roughness_range=(0, 1)):
    Randomizes the mask attributes
    Please refer to our documentation in order to learn more about how we control the different mask attributes
    :param chosen_mask: a Mask asset after it has been chosen
    :return: The updated Mask asset
    if chosen_mask is not None:
        mask_controls = {
            "color": (list(assets.Color), "UNIFORM_CHOICE"),
            "texture": (list(assets.MaskTexture), "UNIFORM_CHOICE"),
            "position": (list(assets.MaskPosition), "UNIFORM_CHOICE"),
            "roughness": (roughness_range[0], roughness_range[1], "UNIFORM"),
        chosen_mask = random_sampler(options=mask_controls, obj=assets.Mask)
    return chosen_mask

def randomize_background_controls(chosen_background, rot_range=(0, 359), transparent_background="False"):
    Randomizes the rotation angle of the background's HDRI and defines whether the background is transparent
    :param chosen_background: a Background asset after it has been chosen
    :param rot_range: angle range for the HDRI rotation
    :param transparent_background: choose if the background is transparent or not
    :return: The updated Background asset
    background_controls = {
        "rotation": (rot_range[0], rot_range[1], "UNIFORM"),
        "transparent": transparent_background
    return random_sampler(options=background_controls, obj=assets.Background)

def set_random_gaze_direction(x_range=(-0.5, 0.5), y_range=(-1.0, -0.85), z_range=(-0.5, 0.3)):
    Control the eyes gaze vector values
    Please refer to our documentation for more information and examples.
    Default values: x=0, y=-1, z=0
    :return: The updated Gaze asset
    eyes_direction = {
        "x": (x_range[0], x_range[1], "UNIFORM"),
        "y": (y_range[0], y_range[0], "UNIFORM"),
        "z": (z_range[0], z_range[0], "UNIFORM"),

    return assets.Gaze(direction=random_sampler(options=eyes_direction, obj=assets.Vector))

def randomize_eyes_controls(eyes):
    gaze_direction = set_random_gaze_direction()

    if gaze_direction.direction.z >= 0:
        closure = (0.0, 0.0, "UNIFORM")
        min_closure = -gaze_direction.direction.z
        max_closure = 1.0
        closure = (min_closure, max_closure, "UNIFORM")

    eyes_controls = {
        "eyelid_closure": closure,
        "target_of_gaze": gaze_direction
    return random_sampler(options=eyes_controls, obj=assets.Eyes)

def get_random_eyes(eyes_arr):
    return random_sampler(options={"eyes": (eyes_arr, "ASSET_UNIFORM_CHOICE")})

def get_random_background(backgrounds_arr):
    return random_sampler(options={"background": (backgrounds_arr, "ASSET_UNIFORM_CHOICE")})

def get_random_accessory(chosen_human, accessory_prob=0.0, glasses_prob=0.0):
    glasses = None
    mask = None

    if random_sampler(options={"random": ("RANDOM",)}) < accessory_prob:
        if random_sampler(options={"random": ("RANDOM",)}) < glasses_prob:
            glasses = random_sampler(options={"glasses": (catalog.glasses.get(gender=chosen_human.attributes.gender), "ASSET_UNIFORM_CHOICE")})
            mask = random_sampler(options={"mask": (catalog.masks.get(gender=chosen_human.attributes.gender), "ASSET_UNIFORM_CHOICE")})
    return glasses, mask

def get_random_hairstyle(chosen_human):
    return random_sampler(options={"hair": (, gender_match=chosen_human.attributes.gender, ethnicity_match=chosen_human.attributes.ethnicity), "ASSET_UNIFORM_CHOICE")})

def get_random_beard(chosen_human):
    chosen_human.head.facial_hair = random_sampler(options={"beard": (BEARDS, "ASSET_UNIFORM_CHOICE")})
    chosen_human.head.facial_hair.color_settings =
    return chosen_human.head.facial_hair

First we will define the basic parameters of our dataset:

from datagen import api as datapoint_api
from datagen.api import catalog, assets
from datagen.api.catalog import attributes
from datagen.api.catalog.attributes import Gender, Ethnicity, Age
from datagen_protocol.schema.humans.human import HumanAttributes, HairAttributes
import random
ACCESSORY_PROB = 0.6  # Probability that a datapoint will include an accessory
                      # (glasses or mask, but not both)
GLASSES_PROB = 0.4  # When there is an accessory, mask probability is: 1 - GLASSES_PROB
BEARD_PROB = 0.7  # Probability of facial hair if ID is male and without a mask
IMAGE_RESOLUTION = (512, 512)  # (width, height)
DST_JSON_PATH = 'dataset_request_face_hair_segmentation.json'  # Path to where we will save the
                                                               # data request as a JSON file

Next, we will create a function that randomizes only specific parts of the scene.


This block of code uses a number of functions from

def get_single_random_datapoint(chosen_human, cam): = get_random_hairstyle(chosen_human) = set_random_hair_color() # Randomizes the color and light
                                                                    # reflection and refraction of the hair

    glasses, mask = get_random_accessory(chosen_human,
                                         glasses_prob=GLASSES_PROB) # Either glasses, mask, or none

    if mask is None and chosen_human.attributes.gender == Gender.MALE and \
        random_sampler(options={"random": ("RANDOM",)}) < BEARD_PROB:
        chosen_human.head.facial_hair = get_random_beard(chosen_human)
        human.head.facial_hair = None

    chosen_human.head.rotation = set_random_head_rotation() # Chooses a random yaw, pitch, and roll
                                                            # for the tilt of the actor's head
    chosen_human.head.location = set_random_head_location() # Chooses a random location
                                                            # in the scene for the actor's head (x, y, z)

    background = get_random_background(BACKGROUNDS)
    background = randomize_background_controls(background) # Randomizes the rotation and transparency

    chosen_human.head.eyes = randomize_eyes_controls(chosen_human.head.eyes) # Chooses a random gaze direction
                                                                             # and eyelid closure
    chosen_human.head.expression = set_random_expression() # Chooses a random expression type and intensity

    kwargs = {'human': chosen_human, 'camera': cam, 'background': background}
    if mask is not None:
        kwargs['mask'] = mask
    if glasses is not None:
        kwargs['glasses'] = glasses

    random_dp = datapoint_api.create_datapoint(**kwargs)

    return random_dp

Now we can run the for loop to create our request for 5 randomized images of 10 humans:

datapoints = []
camera = get_single_camera(res=IMAGE_RESOLUTION)

for i in range(NUMBER_OF_IDS):
    print(f'Processing ID number {i+1} out of {NUMBER_OF_IDS}...')
    human = get_random_human()
    for _ in range(NUMBER_OF_VARIATIONS_PER_ID):
        random_datapoint = get_single_random_datapoint(human, camera)

And finally we dump the complete data request into a json file:

datapoint_api.dump(assets.DataRequest(datapoints=datapoints), path=DST_JSON_PATH)

Because this browser-based notebook runs the code in an isolated kernel, we need to take one last step to extract the JSON file for download. (This step is not necessary when you are working in your local environment.)

from IPython.display import HTML
import os, base64

def make_download_link(DST_JSON_PATH):
  filename = os.path.basename(DST_JSON_PATH)
  with open(DST_JSON_PATH, "rb") as f:
    data = base64.b64encode(
    return HTML(f'<h4>Click here to download '
                f'<a href="data:application/binary;base64,{data}" '


Take this JSON file and upload it to the Datagen platform. Generation of your data will start immediately.