.ipynb
Load FHIBE Dataset¶
This notebook loads the Sony AI’s “Fair Human-Centric Image Benchmark” dataset as a 3LC Table, including keypoints, segmentation, bounding boxes, as well as rich subject metadata.

To download the dataset, you need to register at fairnessbenchmark.ai.sony. To read the original research paper, see here.
Several versions of the dataset exist, for this tutorial we will use version from fhibe.20250716.u.gT5_rFTA_downsampled_public.tar.gz, but the ingestion script should work for any version of the dataset, as the internal layout of the dataset is the same.
We include as much as possible of the metadata contained in the dataset, omitting only a few attributes in the name of simplicity, specifically the <attr>_QA_annotator_id fields have been left out.
The data can be categorized as follows:
Main image
Geometric annotations (instance segmentations, keypoints, facial bounding box)
Image-level metadata (shutter speed, camera manufacturer, weather conditions, etc.)
Subject-level metadata (ancestry, hair color, age, etc.)
This script reads all this data from per-subject JSON files and converts it to a format suitable for a 3LC Table. Several of the columns are stored as “categorical strings” (e.g. hair color “Blond”, “Gray”, “White”, …), these values are converted to integers, with their corresponding string values stored in the schema. This makes it easier to filter and work with these values in the 3LC Dashboard.
Install dependencies¶
[ ]:
%pip install -q 3lc
Imports¶
Project setup¶
[ ]:
PROJECT_NAME = "3LC Tutorials - FHIBE"
DATASET_NAME = "FHIBE"
TABLE_NAME = "initial"
MAX_SAMPLES = None
DOWNLOAD_PATH = "../../transient_data"
Prepare data¶
[ ]:
FHIBE_ROOT = Path(DOWNLOAD_PATH) / "fhibe"
DATA_ROOT = FHIBE_ROOT / "data/raw/fhibe_downsampled"
if not FHIBE_ROOT.exists():
raise FileNotFoundError(f"FHIBE_ROOT does not exist: {FHIBE_ROOT}")
if not DATA_ROOT.exists():
raise FileNotFoundError(f"DATA_ROOT does not exist: {DATA_ROOT}")
annotation_paths = list(DATA_ROOT.glob("**/main_annos_*.json"))
print(f"Found {len(annotation_paths)} annotation files")
Prepare value mappings¶
We performed an initial scan of the dataset to determine all unique values for all categorical columns. The string values are mapped to arbitrary integer values for storage in a 3LC Table.
Image-level mappings¶
[ ]:
camera_position_value_map = {"Typical": 0, "Atypical High": 1, "Atypical Low": 2}
camera_distance_value_map = {"CD I": 0, "CD II": 1, "CD III": 2, "CD IV": 3, "CD V": 4}
lighting_value_map = {
"Lighting from above the head/face": 0,
"Lighting from below the head/face": 1,
"Lighting from in front of the head/face": 2,
"Lighting from behind the head/face": 3,
"Lighting from the left of the head/face": 4,
"Lighting from the right of the head/face": 5,
}
weather_value_map = {"Fog": 0, "Haze": 1, "Snow/hail": 2, "Rain": 3, "Humid": 4, "Cloud": 5, "Clear": 6}
user_hour_captured_value_map = {"0000-0559": 0, "0600-1159": 1, "1200-1759": 2, "1800-2359": 3}
scene_value_map = {
"Outdoor Water, ice, snow": 0,
"Outdoor Mountains, hills, desert, sky": 1,
"Outdoor Forest, field, jungle": 2,
"Outdoor Man-made elements": 3,
"Outdoor Transportation": 4,
"Outdoor Cultural or historical building/place": 5,
"Outdoor Sports fields, parks, leisure spaces": 6,
"Outdoor Industrial and construction": 7,
"Outdoor Houses, cabins, gardens, and farms": 8,
"Outdoor Commercial buildings, shops, markets, cities, and towns": 9,
"Indoor Shopping and dining": 10,
"Indoor Workplace": 11,
"Indoor Home or hotel": 12,
"Indoor Transportation": 13,
"Indoor Sports and leisure": 14,
"Indoor Cultural": 15,
}
Subject-level mappings¶
[ ]:
segments_value_map = {
"Face skin": 0.0,
"Upper body skin": 1.0,
"Right eye": 2.0,
"Nose": 3.0,
"Upper lip": 4.0,
"Lower lip": 5.0,
"Inner mouth": 6.0,
"Left shoe": 7.0,
"Right shoe": 8.0,
"Left arm skin": 9.0,
"Upper body clothes": 10.0,
"Lower body clothes": 11.0,
"Sock or legwarmer": 12.0,
"Jewelry or timepiece": 13.0,
"Right arm skin": 14.0,
"Left leg skin": 15.0,
"Right leg skin": 16.0,
"Head hair": 17.0,
"Left eyebrow": 18.0,
"Right eyebrow": 19.0,
"Left eye": 20.0,
"Bag": 21.0,
"Eyewear": 22.0,
"Full body clothes": 23.0,
"Headwear": 24.0,
"Mask": 25.0,
"Neckwear": 26.0,
"Glove": 27.0,
}
pronoun_value_map = {
"She/her/hers": 0,
"He/him/his": 1,
"They/them/their": 2,
"Ze/zir/zirs": 3,
"None of the above": 4,
"Prefer not to say": 5,
}
head_pose_value_map = {
"Typical": 0,
"Atypical": 1,
}
facial_marks_value_map = {
"None": 0,
"Tattoos": 1,
"Birthmarks": 2,
"Scars": 3,
"Burns": 4,
"Growths": 5,
"Make-up": 6,
"Face paint": 7,
"Acne": 8,
"Not listed": 9,
"Free-text": 10,
}
ancestry_value_map = {
"Africa": 0,
"Eastern Africa": 1,
"Northern Africa": 2,
"Middle Africa": 3,
"Southern Africa": 4,
"Western Africa": 5,
"Americas": 6,
"Caribbean": 7,
"Central America": 8,
"South America": 9,
"Northern America": 10,
"Asia": 11,
"Central Asia": 12,
"Eastern Asia": 13,
"South-eastern Asia": 14,
"Southern Asia": 15,
"Western Asia": 16,
"Europe": 17,
"Eastern Europe": 18,
"Northern Europe": 19,
"Southern Europe": 20,
"Western Europe": 21,
"Oceania": 22,
"Australia and New Zealand": 23,
"Polynesia": 24,
}
skin_color_value_map = {
"[102, 78, 65]": 0,
"[136, 105, 81]": 1,
"[164, 131, 103]": 2,
"[175, 148, 120]": 3,
"[189, 163, 137]": 4,
"[198, 180, 157]": 5,
}
haircolor_value_map = {
"None": 0,
"Very light blond": 1,
"Light blond": 2,
"Blond": 3,
"Dark blond": 4,
"Light brown to medium brown": 5,
"Dark brown/black": 6,
"Red": 7,
"Gray": 8,
"Red blond": 9,
"White": 10,
"Not listed": 11,
"Free-text": 12,
}
hairstyle_value_map = {
"None": 0,
"Buzz cut": 1,
"Up (Short)": 10,
"Half-up (Short)": 11,
"Down (Short)": 12,
"Not listed(Short)": 13,
"Up (Medium)": 14,
"Half-up (Medium)": 15,
"Down (Medium)": 2,
"Not listed(Medium)": 3,
"Up (Long)": 4,
"Half-up (Long)": 5,
"Down (Long)": 6,
"Not listed(Long)": 7,
"Not listed": 8,
"Free-text": 9,
}
facial_hairstyle_value_map = {
"None": 0,
"Beard": 1,
"Mustache": 2,
"Goatee": 3,
}
hair_type_value_map = {
"None": 0,
"Straight": 1,
"Wavy": 2,
"Curly": 3,
"Kinky-coily": 4,
"Not listed": 5,
"Free-text": 6,
}
action_body_pose_value_map = {
"Standing": 0,
"Sitting": 1,
"Walking": 2,
"Bending/bowing": 3,
"Lying down/sleeping": 4,
"Performing martial/fighting arts": 5,
"Dancing": 6,
"Running/jogging": 7,
"Crouching/kneeling": 8,
"Getting up": 9,
"Jumping/leaping": 10,
"Falling down": 11,
"Crawling": 12,
"Swimming": 13,
"Not listed": 14,
"Free-text": 15,
}
action_subject_object_interaction_value_map = {
"None": 0,
"Riding": 1,
"Driving": 2,
"Watching": 3,
"Smoking": 4,
"Eating": 5,
"Drinking": 6,
"Opening or closing": 7,
"Lifting/picking up or putting down": 8,
"Writing/drawing or painting": 9,
"Catching or throwing": 10,
"Pushing, pulling or extracting": 11,
"Putting on or taking off clothing": 12,
"Entering or exiting": 13,
"Climbing": 14,
"Pointing at": 15,
"Shooting at": 16,
"Digging/shoveling": 17,
"Playing with pets/animals": 18,
"Playing musical instrument": 19,
"Playing": 20,
"Using an electronic device": 21,
"Cutting or chopping": 22,
"Cooking": 23,
"Fishing": 24,
"Rowing": 25,
"Sailing": 26,
"Brushing teeth": 27,
"Hitting": 28,
"Kicking": 29,
"Turning": 30,
"Not listed": 31,
"Free-text": 32,
}
eye_color_value_map = {
"None": 0,
"Blue": 1,
"Gray": 2,
"Green": 3,
"Hazel": 4,
"Brown": 5,
"Red and violet": 6,
"Not listed": 7,
"Free-text": 8,
}
Consolidation of country spelling variations¶
[ ]:
# Taken from https://github.com/SonyResearch/fhibe_evaluation_api/blob/main/fhibe_eval_api/datasets/fhibe.py
loc_country_name_mapping = {
"Abgola": "Angola",
"Abuja": "Nigeria",
"Argentiina": "Argentina",
"Australie": "Australia",
"Autsralia": "Australia",
"Auustralia": "Australia",
"Bahamas, The": "Bahamas",
"Caanada": "Canada",
"Canadad": "Canada",
"French": "France",
"Hanoi Vietnam": "Viet Nam",
"Ho Chi Min": "Viet Nam",
"Hong Kong": "China, Hong Kong Special Administrative Region",
"I Go": None,
"Italiana": "Italy",
"Keenya": "Kenya",
"Kenyan": "Kenya",
"Kiambu": "Kenya",
"Lagos": "Nigeria",
"Lceland": "Iceland",
"Mexican": "Mexico",
"Micronesia": "Micronesia (Federated States of)",
"Mironesi": "Micronesia (Federated States of)",
"Mironesia": "Micronesia (Federated States of)",
"Morroco": "Morocco",
"Muranga": "Kenya",
"Nairobi Nairobi": "Kenya",
"Netherlands": "Netherlands (Kingdom of the)",
"Nigerian": "Nigeria",
"Nigeriia": "Nigeria",
"Niheria": "Nigeria",
"Nugeria": "Nigeria",
"Nyari": "Kenya",
"Owow Disable Abilities Off Level Up": None,
"Pakisan": "Pakistan",
"Pakisatn": "Pakistan",
"Pakistain": "Pakistan",
"Paksitan": "Pakistan",
"Phillipines": "Philippines",
"Punjab": "Pakistan",
"South Afica": "South Africa",
"South Afria": "South Africa",
"South African": "South Africa",
"Southern Africa": "South Africa",
"South Korea": "Republic of Korea",
"Tanzania": "United Republic of Tanzania",
"Trinidad And Tobago": "Trinidad and Tobago",
"Turkey": "Türkiye",
"Ua": "Ukraine",
"Uae": "United Arab Emirates",
"Ugnd": "Uganda",
"Uk": "United Kingdom of Great Britain and Northern Ireland",
"United Kingdom": "United Kingdom of Great Britain and Northern Ireland",
"Ukaine": "Ukraine",
"United States": "United States of America",
"Usa": "United States of America",
"Venezuela": "Venezuela (Bolivarian Republic of)",
"Veitnam": "Viet Nam",
"Vienam": "Viet Nam",
"Vietam": "Viet Nam",
"Vietnam": "Viet Nam",
"Vietname": "Viet Nam",
"Viietnam": "Viet Nam",
"Vitenam": "Viet Nam",
"Vitnam": "Viet Nam",
"Viwtnam": "Viet Nam",
}
def fix_location_country(country: str) -> str:
"""Format the location_country attribute string.
Some countries are misspelled or inconsistently formatted.
Args:
country: The original string annotation
Return:
The re-formatted string
"""
if country in loc_country_name_mapping:
return loc_country_name_mapping[country]
country_fmt = country.strip().title()
if country_fmt in loc_country_name_mapping:
return loc_country_name_mapping[country_fmt]
else:
return country_fmt
Define data processing steps¶
[ ]:
def clean_str(s):
if ". " in s:
# Many FHIBE strings are numbered, e.g. "1. Right eye inner". For readability, remove the numbering
s = s.split(". ")[1]
# MapElements in 3LC do not support ":" in the internal name
return s.replace(":", "")
[ ]:
NUM_KEYPOINTS = 33
# fmt: off
# Add connecting lines between connected keypoints
SKELETON = [
11, 12,
11, 13,
13, 15,
12, 14,
14, 16,
12, 24,
11, 23,
23, 24,
24, 26,
26, 28,
23, 25,
25, 27,
27, 29,
29, 31,
28, 30,
30, 32,
]
# fmt: on
KEYPOINTS = [
"Nose",
"Right eye inner",
"Right eye",
"Right eye outer",
"Left eye inner",
"Left eye",
"Left eye outer",
"Right ear",
"Left ear",
"Mouth right",
"Mouth left",
"Right shoulder",
"Left shoulder",
"Right elbow",
"Left elbow",
"Right wrist",
"Left wrist",
"Right pinky knuckle",
"Left pinky knuckle",
"Right index knuckle",
"Left index knuckle",
"Right thumb knuckle",
"Left thumb knuckle",
"Right hip",
"Left hip",
"Right knee",
"Left knee",
"Right ankle",
"Left ankle",
"Right heel",
"Left heel",
"Right foot index",
"Left foot index",
]
def process_keypoints(keypoints, image_width, image_height):
"""Convert keypoints to 3LC format"""
keypoints = {clean_str(kpt_name): v for kpt_name, v in keypoints.items()}
kpts_arr = np.zeros((NUM_KEYPOINTS, 3), dtype=np.float32)
for i, kpt_name in enumerate(KEYPOINTS):
if kpt_name not in keypoints:
continue
x, y, viz = keypoints[kpt_name]
viz = 2 if viz else 0
kpts_arr[i, :] = [x, y, viz]
instances = tlc.Keypoints2DInstances.create_empty(
image_width=image_width,
image_height=image_height,
include_keypoint_visibilities=True,
include_instance_bbs=False,
)
instances.add_instance(
keypoints=kpts_arr,
label=0, # all instances have the same label: "person"
)
return instances.to_row()
def process_segments(segments, image_width, image_height):
"""Convert segments to 3LC format"""
polygons = []
labels = []
for segment in segments:
class_name = clean_str(segment["class_name"])
polygon = segment["polygon"]
# Convert the polygon to a flattened list of coordinates
poly_2_tuples = [[p["x"], p["y"]] for p in polygon]
flattened_poly = [item for sublist in poly_2_tuples for item in sublist]
polygons.append(flattened_poly)
labels.append(segments_value_map[class_name])
segs = tlc.SegmentationPolygonsDict(
image_width=image_width,
image_height=image_height,
polygons=polygons,
instance_properties={"label": labels},
)
return segs
def process_bboxes(face_bbox, image_width, image_height):
"""Convert bounding box coordinates to 3LC format"""
bboxes = {
tlc.IMAGE_WIDTH: image_width,
tlc.IMAGE_HEIGHT: image_height,
tlc.BOUNDING_BOX_LIST: [
{
tlc.X0: face_bbox[0],
tlc.Y0: face_bbox[1],
tlc.X1: face_bbox[2],
tlc.Y1: face_bbox[3],
tlc.LABEL: 0,
},
],
}
return bboxes
def map_categorical(value, mapping):
"""Helper function to map categorical values to their corresponding integer values"""
if isinstance(value, list):
return [mapping[clean_str(v)] for v in value]
return mapping[clean_str(value)]
[ ]:
def process_image_annotation(image_annotation):
"""Process the image annotation dictionary
Categorical or list of categorical values are cleaned and converted to their
corresponding integer values, plain numerical values or strings are left as is.
"""
image_annotation_dict = {
"aperture_value": image_annotation["aperture_value"],
"camera_distance": map_categorical(image_annotation["camera_distance"], camera_distance_value_map),
"camera_position": map_categorical(image_annotation["camera_position"], camera_position_value_map),
"focal_length": image_annotation["focal_length"],
"iso_speed_ratings": image_annotation["iso_speed_ratings"],
"lighting": map_categorical(image_annotation["lighting"], lighting_value_map),
"location_country": fix_location_country(image_annotation["location_country"]),
"location_region": image_annotation["location_region"],
"manufacturer": image_annotation["manufacturer"],
"model": image_annotation["model"],
"scene": map_categorical(image_annotation["scene"], scene_value_map),
"shutter_speed_value": image_annotation["shutter_speed_value"],
"user_date_captured": image_annotation["user_date_captured"],
"user_hour_captured": map_categorical(image_annotation["user_hour_captured"], user_hour_captured_value_map),
"weather": map_categorical(image_annotation["weather"], weather_value_map),
}
return image_annotation_dict
[ ]:
# ruff: noqa: E501
# fmt: off
def process_subject_annotation(subject_annotation, image_height, image_width):
"""Process the subject annotation dictionary
Categorical or list of categorical values are cleaned and converted to their
corresponding integer values, plain numerical values or strings are left as is.
"""
subject_annotation_dict = {
"bbs": process_bboxes(subject_annotation["face_bbox"], image_height, image_width),
"segments": process_segments(subject_annotation["segments"], image_height, image_width),
"keypoints": process_keypoints(subject_annotation["keypoints"], image_height, image_width),
"subject_id": subject_annotation["subject_id"],
"age": subject_annotation["age"],
"nationality": subject_annotation["nationality"],
"ancestry": map_categorical(subject_annotation["ancestry"], ancestry_value_map),
"pronoun": map_categorical(subject_annotation["pronoun"], pronoun_value_map),
"natural_skin_color": map_categorical(subject_annotation["natural_skin_color"], skin_color_value_map),
"apparent_skin_color": map_categorical(subject_annotation["apparent_skin_color"], skin_color_value_map),
"hairstyle": map_categorical(subject_annotation["hairstyle"], hairstyle_value_map),
"natural_hair_type": map_categorical(subject_annotation["natural_hair_type"], hair_type_value_map),
"apparent_hair_type": map_categorical(subject_annotation["apparent_hair_type"], hair_type_value_map),
"natural_hair_color": map_categorical(subject_annotation["natural_hair_color"], haircolor_value_map),
"apparent_hair_color": map_categorical(subject_annotation["apparent_hair_color"], haircolor_value_map),
"facial_hairstyle": map_categorical(subject_annotation["facial_hairstyle"], facial_hairstyle_value_map),
"natural_facial_hair_color": map_categorical(subject_annotation["natural_facial_haircolor"], haircolor_value_map),
"apparent_facial_hair_color": map_categorical(subject_annotation["apparent_facial_haircolor"], haircolor_value_map),
"natural_left_eye_color": map_categorical(subject_annotation["natural_left_eye_color"], eye_color_value_map),
"apparent_left_eye_color": map_categorical(subject_annotation["apparent_left_eye_color"], eye_color_value_map),
"natural_right_eye_color": map_categorical(subject_annotation["natural_right_eye_color"], eye_color_value_map),
"apparent_right_eye_color": map_categorical(subject_annotation["apparent_right_eye_color"], eye_color_value_map),
"facial_marks": map_categorical(subject_annotation["facial_marks"], facial_marks_value_map),
"action_body_pose": map_categorical(subject_annotation["action_body_pose"], action_body_pose_value_map),
"action_subject_object_interaction": map_categorical(subject_annotation["action_subject_object_interaction"], action_subject_object_interaction_value_map),
"head_pose": map_categorical(subject_annotation["head_pose"], head_pose_value_map),
}
return subject_annotation_dict
# ruff: noqa: enable
# fmt: on
Load data¶
This is the main loop where we iterate over annotation files, extract and process annotations, and store the processed data in a list of rows.
[ ]:
rows = []
total = len(annotation_paths) if MAX_SAMPLES is None else min(len(annotation_paths), MAX_SAMPLES)
for annotation_path in tqdm(annotation_paths, total=total, desc="Processing annotations"):
with open(annotation_path) as f:
annotations = json.load(f)
image_path = annotation_path.with_name(f"main_{annotations['image']['file_name']}")
image_annotation = annotations["image_annotation"]
subject_annotations = annotations["subject_annotation"]
image_annotation_dict = process_image_annotation(image_annotation)
for subject_annotation in subject_annotations:
subject_annotation_dict = process_subject_annotation(
subject_annotation, image_annotation["image_height"], image_annotation["image_width"]
)
rows.append(
{
"image": tlc.Url(image_path).to_relative().to_str(),
**image_annotation_dict,
**subject_annotation_dict,
}
)
if MAX_SAMPLES is not None and len(rows) >= MAX_SAMPLES:
break
else:
continue
break # Max samples reached
Write 3LC Table¶
We are now ready to define our schemas, create a TableWriter, and add our rows to the Table.
[ ]:
# By default, 3LC Schemas are visible and writable. Since this dataset has a
# large number of columns, we set the default visibility to False. We also make
# the columns read-only in the UI. Columns can easily be made visible in the UI
# by selecting them from the "wrench" menu.
default_schema_args = {
"default_visible": False,
"writable": False,
}
# Override color palette for the skin-color related columns
def tuple2hex(t: str):
"""Convert a serialized list of integers to a hex string: '[255, 255, 255]' -> '#FFFFFF'"""
return "#{:02X}{:02X}{:02X}".format(*(int(c) for c in t.strip("[]").split(",")))
# fmt: off
# ruff: noqa: E501
metadata_schemas = {
# Image annotations
"aperture_value": tlc.Float32Schema(**default_schema_args),
"camera_distance": tlc.CategoricalLabelSchema(classes=camera_distance_value_map.keys(), **default_schema_args),
"camera_position": tlc.CategoricalLabelSchema(classes=camera_position_value_map.keys(), **default_schema_args),
"focal_length": tlc.Float32Schema(**default_schema_args),
"iso_speed_ratings": tlc.Int32Schema(**default_schema_args),
"lighting": tlc.CategoricalLabelListSchema(classes=lighting_value_map.keys(), **default_schema_args),
"location_country": tlc.StringSchema(**default_schema_args),
"location_region": tlc.StringSchema(**default_schema_args),
"manufacturer": tlc.StringSchema(**default_schema_args),
"model": tlc.StringSchema(**default_schema_args),
"scene": tlc.CategoricalLabelSchema(classes=scene_value_map.keys(), **default_schema_args),
"shutter_speed_value": tlc.Float32Schema(**default_schema_args),
"user_date_captured": tlc.StringSchema(**default_schema_args),
"user_hour_captured": tlc.CategoricalLabelSchema(classes=user_hour_captured_value_map.keys(), **default_schema_args),
"weather": tlc.CategoricalLabelListSchema(classes=weather_value_map.keys(), **default_schema_args),
# Subject annotations
"subject_id": tlc.StringSchema(**default_schema_args),
"age": tlc.Int32Schema(**default_schema_args),
"nationality": tlc.StringListSchema(**default_schema_args),
"ancestry": tlc.CategoricalLabelListSchema(classes=ancestry_value_map.keys(), **default_schema_args),
"pronoun": tlc.CategoricalLabelListSchema(classes=pronoun_value_map.keys(), **default_schema_args),
"natural_skin_color": tlc.CategoricalLabelSchema(
classes={v: tlc.MapElement(k, display_color=tuple2hex(k)) for k, v in skin_color_value_map.items()},
**default_schema_args,
),
"apparent_skin_color": tlc.CategoricalLabelSchema(
classes={v: tlc.MapElement(k, display_color=tuple2hex(k)) for k, v in skin_color_value_map.items()},
**default_schema_args,
),
"hairstyle": tlc.CategoricalLabelSchema(classes=hairstyle_value_map.keys(), **default_schema_args),
"natural_hair_type": tlc.CategoricalLabelSchema(classes=hair_type_value_map.keys(), **default_schema_args),
"apparent_hair_type": tlc.CategoricalLabelSchema(classes=hair_type_value_map.keys(), **default_schema_args),
"natural_hair_color": tlc.CategoricalLabelListSchema(classes=haircolor_value_map.keys(), **default_schema_args),
"apparent_hair_color": tlc.CategoricalLabelListSchema(classes=haircolor_value_map.keys(), **default_schema_args),
"facial_hairstyle": tlc.CategoricalLabelListSchema(classes=facial_hairstyle_value_map.keys(), **default_schema_args),
"natural_facial_hair_color": tlc.CategoricalLabelListSchema(classes=haircolor_value_map.keys(), **default_schema_args),
"apparent_facial_hair_color": tlc.CategoricalLabelListSchema(classes=haircolor_value_map.keys(), **default_schema_args),
"natural_left_eye_color": tlc.CategoricalLabelListSchema(classes=eye_color_value_map.keys(), **default_schema_args),
"apparent_left_eye_color": tlc.CategoricalLabelListSchema(classes=eye_color_value_map.keys(), **default_schema_args),
"natural_right_eye_color": tlc.CategoricalLabelListSchema(classes=eye_color_value_map.keys(), **default_schema_args),
"apparent_right_eye_color": tlc.CategoricalLabelListSchema(classes=eye_color_value_map.keys(), **default_schema_args),
"facial_marks": tlc.CategoricalLabelListSchema(classes=facial_marks_value_map.keys(), **default_schema_args),
"action_body_pose": tlc.CategoricalLabelSchema(classes=action_body_pose_value_map.keys(), **default_schema_args),
"action_subject_object_interaction": tlc.CategoricalLabelListSchema(classes=action_subject_object_interaction_value_map.keys(), **default_schema_args),
"head_pose": tlc.CategoricalLabelSchema(classes=head_pose_value_map.keys(), **default_schema_args),
}
# ruff: noqa: enable
# fmt: on
[ ]:
table_writer = tlc.TableWriter(
table_name=TABLE_NAME,
dataset_name=DATASET_NAME,
project_name=PROJECT_NAME,
column_schemas={
"image": tlc.ImageUrlSchema(),
"keypoints": tlc.Keypoints2DSchema(
classes=["person"],
num_keypoints=NUM_KEYPOINTS,
lines=SKELETON,
point_attributes=KEYPOINTS,
include_per_point_visibility=True,
),
"bbs": tlc.BoundingBoxListSchema(
label_value_map={0: tlc.MapElement("face")},
include_segmentation=False,
x1_number_role=tlc.NUMBER_ROLE_BB_SIZE_X,
y1_number_role=tlc.NUMBER_ROLE_BB_SIZE_Y,
),
"segments": tlc.SegmentationSchema(
label_value_map={v: tlc.MapElement(k) for k, v in segments_value_map.items()},
),
**metadata_schemas,
},
)
for row in tqdm(rows, total=len(rows), desc="Writing rows"):
table_writer.add_row(row)
table = table_writer.finalize()