Skip to content

face_attribute_processing 🗿

Extract attributes from face images.

Classes:

Name Description
FaceViews

Class for face views.

Functions:

Name Description
cfd_var_converter

Convert the CFD variable ID to a corresponding label name, and vice versa.

display_face

Display the CFD face given its head ID.

display_set_of_faces

Display a set of heads in a grid.

face_image_path

Construct the path to a face image.

get_cfd_code_table

Load the CFD code table.

get_cfd_features

Get CFD features.

get_cfd_features_for_models

Get CFD features for the given list of head models.

get_head_mapper_table

Get the table for mapping head numbers to head indices.

head_index_to_head_nr

Convert the head index ('idx' in 'headnmap.csv') to the head number ('Head#').

head_nr_to_index

Convert the head number ('Head#') to the head index ('idx' in 'headnmap.csv').

head_nr_to_main_matrix_index

Convert a head number to a corresponding matrix index in the main study.

head_nr_to_pilot_matrix_index

Convert a head number to a corresponding matrix index for the pilot studies.

heads_naming_converter_table

Load the table for head naming conversion.

list_faulty_heads

List faulty heads (i.e., heads for which the reconstruction is not optimal).

main_index_to_model_name

Convert the head index to a head model name in the main study.

main_matrix_index_to_head_nr

Convert the index to a corresponding head number in the main study.

pilot_index_to_model_name

Convert the head index to the head model name.

pilot_matrix_index_to_head_nr

Convert the matrix index of a head to a head number in the pilot studies.

FaceViews 🗿

FaceViews()

Class for face views.

Initialise FaceViews class.

Source code in code/facesim3d/modeling/face_attribute_processing.py
555
556
557
558
def __init__(self) -> None:
    """Initialise FaceViews class."""
    self.path_to_views = paths.data.cfd.faceviews
    self._check_path_to_views()

cfd_var_converter 🗿

cfd_var_converter(var_id_or_label: str) -> DataFrame

Convert the CFD variable ID to a corresponding label name, and vice versa.

Source code in code/facesim3d/modeling/face_attribute_processing.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
def cfd_var_converter(var_id_or_label: str) -> pd.DataFrame:
    """Convert the `CFD` variable ID to a corresponding label name, and vice versa."""
    cfd_code_tab = get_cfd_code_table()

    if var_id_or_label[1].isnumeric():
        # ID -> label name
        var_return = cfd_code_tab.VarLabel[cfd_code_tab.VarID.str.contains(var_id_or_label, case=False)].item()
    else:
        # label name -> ID
        var_return = cfd_code_tab.VarID[cfd_code_tab.VarLabel.str.match(var_id_or_label, case=False)]

        if len(var_return) > 1:
            print("Note, there are various IDs with that label name:", var_return.to_list())

    return var_return

display_face 🗿

display_face(
    head_id: str | int,
    data_mode: str = "3d-reconstructions",
    angle: str | float | None = None,
    interactive: bool = False,
    show: bool = True,
    verbose: bool = False,
) -> Image

Display the CFD face given its head ID.

Parameters:

Name Type Description Default
head_id str | int

either image name (e.g. "CFD-WF-001-003-N"), OR head number (e.g., "Head5"), OR head index

required
data_mode str

path to the "2d-original", "3d-reconstructions", or "3d-perspectives"

'3d-reconstructions'
angle str | float | None

viewing angle of face to display [only if data_mode == "3d-perspectives"]

None
interactive bool

if True display 3D (.obj) in interactive mode.

False
show bool

if True show image

True
verbose bool

if True print the path to the image.

False
Source code in code/facesim3d/modeling/face_attribute_processing.py
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
def display_face(
    head_id: str | int,
    data_mode: str = "3d-reconstructions",
    angle: str | float | None = None,
    interactive: bool = False,
    show: bool = True,
    verbose: bool = False,
) -> Image:
    """
    Display the `CFD` face given its head ID.

    :param head_id: either image name (e.g. "CFD-WF-001-003-N"), OR head number (e.g., "Head5"), OR
                    head index
    :param data_mode: path to the "2d-original", "3d-reconstructions", or "3d-perspectives"
    :param angle: viewing angle of face to display [only if data_mode == "3d-perspectives"]
    :param interactive: if True display 3D (.obj) in interactive mode.
    :param show: if True show image
    :param verbose: if True print the path to the image.
    """
    data_mode = data_mode.lower()
    if data_mode == "3d-perspectives" and angle is None:
        msg = "angle must be given for data_mode == '3d-perspectives'"
        raise ValueError(msg)

    original = "original" in data_mode or "3d-perspectives" in data_mode
    if original and interactive:
        cprint(string="For interactively displaying a 3D face mesh, set 'original' to False!", col="r")

    # Display image
    path_to_image, head_id = face_image_path(head_id=head_id, data_mode=data_mode, return_head_id=True, angle=angle)

    if original:
        face_image = Image.open(path_to_image)
        if show:
            face_image.show()

    else:
        path_to_3d_obj = path_to_image.replace(f"{head_id}_screenshot.png", f"{head_id}.obj")
        face_image = vedo.Mesh(inputobj=path_to_3d_obj)
        face_image.texture(tname=path_to_3d_obj.replace(".obj", ".png"), scale=0.1)

        if interactive:
            # This is buggy on Mac. Cannot be called multiple times in one session.
            # Documentation (incl. shortcuts): https://vedo.embl.es/autodocs/content/vedo/index.html
            cprint(string="\nPress 'q' to close window!", col="b")
            face_image.show().close()
        else:
            if not Path(path_to_image).is_file():
                vedo.settings.screenshotTransparentBackground = True
                plotter = vedo.Plotter(interactive=interactive, offscreen=True)
                plotter.show(face_image, zoom=1.8)
                plotter.screenshot(path_to_image, scale=1).close()
            face_image = Image.open(path_to_image)
            if show:
                face_image.show()

    if verbose:
        cprint(string=f"\nImage path of '{head_index_to_head_nr(face_idx=head_id)}': {path_to_image}", col="b")

    return face_image

display_set_of_faces 🗿

display_set_of_faces(
    list_head_ids: list[str | int],
    data_mode: str,
    num_suffix: str = "",
    verbose: bool = False,
) -> tuple[Any, ndarray]

Display a set of heads in a grid.

Source code in code/facesim3d/modeling/face_attribute_processing.py
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
def display_set_of_faces(
    list_head_ids: list[str | int], data_mode: str, num_suffix: str = "", verbose: bool = False
) -> tuple[Any, np.ndarray]:
    """Display a set of heads in a grid."""
    # Prepare data_mode
    data_mode = data_mode.lower()
    data_mode_suffix = (
        "original" if "original" in data_mode else "3D-reconstructed" if "3d-recon" in data_mode else "3D-perspectives"
    )

    # Load images
    list_of_imgs = []
    for head_id in list_head_ids:
        list_of_imgs.append(
            display_face(head_id=head_id, data_mode=data_mode, interactive=False, show=False, verbose=verbose)
        )

    # Plot in grid
    # TODO: use this function in display_representative_faces() in computational_choice_model.py  # noqa: FIX002
    grid_shape = get_n_cols_and_rows(n_plots=len(list_head_ids), square=True)
    fig, axes = plt.subplots(
        *grid_shape,
        figsize=(grid_shape[1] * 2, grid_shape[0] * 2),
        sharex=True,
        sharey=True,
        num=f"{len(list_head_ids)} of the {data_mode_suffix} CFD faces {num_suffix}",
    )

    for i, (face_id, face_img) in enumerate(zip(list_head_ids, list_of_imgs, strict=True)):
        axes.flatten()[i].imshow(face_img)
        axes.flatten()[i].set_xticks([])
        axes.flatten()[i].set_xlabel(face_id)
        axes.flatten()[i].yaxis.set_visible(False)
        for spine in axes.flatten()[i].spines.values():  # remove axes-box around image
            spine.set_visible(False)
        fig.tight_layout()
        plt.show()

    return fig, axes

face_image_path 🗿

face_image_path(
    head_id: str | int,
    data_mode: str = "3d-reconstructions",
    return_head_id: bool = False,
    angle: float | str | None = None,
) -> str | tuple[str, int]

Construct the path to a face image.

Parameters:

Name Type Description Default
head_id str | int

Head number, as [str], e.g., "Head4", or as [int], e.g., 4.

required
data_mode str

path to the "2d-original", "3d-reconstructions", or "3d-perspectives"

'3d-reconstructions'
return_head_id bool

whether to return the head number (as it appears in the rating files, 'TrialResults.csv')

False
angle float | str | None

for data_mode=="3d-perspectives", a face angle needs to be given.

None

Returns:

Type Description
str | tuple[str, int]

path to the face image

Source code in code/facesim3d/modeling/face_attribute_processing.py
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
def face_image_path(
    head_id: str | int,
    data_mode: str = "3d-reconstructions",
    return_head_id: bool = False,
    angle: float | str | None = None,
) -> str | tuple[str, int]:
    """
    Construct the path to a face image.

    :param head_id: Head number, as [str], e.g., "Head4", or as [int], e.g., 4.
    :param data_mode: path to the "2d-original", "3d-reconstructions", or "3d-perspectives"
    :param return_head_id: whether to return the head number (as it appears in the rating files, '*TrialResults*.csv')
    :param angle: for data_mode=="3d-perspectives", a face angle needs to be given.
    :return: path to the face image
    """
    data_mode = data_mode.lower()

    # Get mapping table
    head_map_tab = get_head_mapper_table()

    if isinstance(head_id, str):
        # Map ID to head index
        if head_id.startswith("CFD-"):
            head_id = head_map_tab[head_map_tab.CFD_filename == head_id + ".jpg"].idx.item()
        elif head_id.startswith("Head"):
            head_id = head_map_tab[head_map_tab.head_nr == head_id].idx.item()
        else:
            msg = f"{head_id = } unknown!"
            raise ValueError(msg)

    if data_mode == "3d-perspectives":
        if angle is None:
            msg = "For data_mode == '3d-perspectives' angle must be given [int | float | str == 'frontal']!"
            raise ValueError(msg)

        if isinstance(angle, str):
            angle = angle.lower()

        if angle == "frontal":
            path_to_image = Path(paths.data.cfd.faceviews, f"head-{head_id:03d}_frontal.png")

        else:
            # even for floats (e.g., 348.75), integer parts are all unique, so search for those
            angle = round(float(angle), 2)  # 348.7512 -> 348.75, 315 -> 315.00
            angle = int(angle) if angle.is_integer() else angle  # in case of int, map back: 315.00 -> 315
            path_to_image = Path(paths.data.cfd.faceviews, f"head-{head_id:03d}_angle-{angle}.png")

    else:
        th_gender = 60  # threshold
        path_to_pid = Path(paths.data.unity.cfd, "female" if head_id <= th_gender else "male", str(head_id))

        path_to_image = str(
            path_to_pid / f"{head_id}_inputs.jpg"
            if "original" in data_mode
            else path_to_pid / f"{head_id}_screenshot.png"
        )

    if return_head_id:
        return path_to_image, head_id
    return path_to_image

get_cfd_code_table cached 🗿

get_cfd_code_table()

Load the CFD code table.

Source code in code/facesim3d/modeling/face_attribute_processing.py
29
30
31
32
33
34
35
36
37
38
@cache
def get_cfd_code_table():
    """Load the `CFD` code table."""
    __cfd_code_tab = pd.read_excel(
        io=Path(paths.data.CFD, "CFD 3.0 Norming Data and Codebook.xlsx"),
        sheet_name="CFD 3.0 Codebook",
        header=11,
        usecols=range(6),
    )
    return __cfd_code_tab.drop(index=0)

get_cfd_features 🗿

get_cfd_features(
    set_name: str = "main",
    drop_nan_col: bool = True,
    physical_attr_only: bool = True,
) -> DataFrame

Get CFD features.

Parameters:

Name Type Description Default
set_name str

CFD set name

'main'
drop_nan_col bool

whether to drop columns that contain only nan

True
physical_attr_only bool

whether to drop columns that do not represent physical attributes

True

Returns:

Type Description
DataFrame

CFD feature table

Source code in code/facesim3d/modeling/face_attribute_processing.py
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
def get_cfd_features(
    set_name: str = "main", drop_nan_col: bool = True, physical_attr_only: bool = True
) -> pd.DataFrame:
    """
    Get `CFD` features.

    :param set_name: CFD set name
    :param drop_nan_col: whether to drop columns that contain only nan
    :param physical_attr_only: whether to drop columns that do not represent physical attributes
    :return: CFD feature table
    """
    set_name = set_name.lower()
    set_dict = {
        "main": "CFD U.S. Norming Data",
        "mr": "CFD-MR U.S. Norming Data",
        "india-us": "CFD-I U.S. Norming Data",
        "india-ind": "CFD-I INDIA Norming Data",
    }
    set_names = list(set_dict.keys())
    if set_name not in set_names:
        msg = f"set_name must be in {set_names}!"
        raise ValueError(msg)
    cfd_tab = pd.read_excel(
        io=Path(paths.data.CFD, "CFD 3.0 Norming Data and Codebook.xlsx"),
        sheet_name=set_dict[set_name],
        header=6,
        index_col=0,
    )
    cfd_tab = cfd_tab.drop(index=np.nan)
    cfd_tab = cfd_tab.drop(index="Model")
    cfd_tab.index = cfd_tab.index.set_names(names="Model")

    if drop_nan_col:
        for col in cfd_tab.columns:
            if (not cfd_tab[col].notna().any()) or (physical_attr_only and col[0] != "P"):
                # if all entries in column are nan OR it is not a physical attribute: drop column
                cfd_tab = cfd_tab.drop(columns=col)

    return cfd_tab

get_cfd_features_for_models 🗿

get_cfd_features_for_models(
    list_of_models: list[Any] | None = None,
    physical_attr_only: bool = True,
) -> DataFrame

Get CFD features for the given list of head models.

Parameters:

Name Type Description Default
list_of_models list[Any] | None

list of models

None
physical_attr_only bool

whether to drop columns that do not represent physical attributes

True

Returns:

Type Description
DataFrame

feature table for a list of models

Source code in code/facesim3d/modeling/face_attribute_processing.py
 99
100
101
102
103
104
105
106
107
108
109
110
111
def get_cfd_features_for_models(
    list_of_models: list[Any] | None = None, physical_attr_only: bool = True
) -> pd.DataFrame:
    """
    Get `CFD` features for the given list of head models.

    :param list_of_models: list of models
    :param physical_attr_only: whether to drop columns that do not represent physical attributes
    :return: feature table for a list of models
    """
    _feat_tab = get_cfd_features(physical_attr_only=physical_attr_only)
    list_of_models = _feat_tab.index if list_of_models is None else list_of_models
    return _feat_tab.loc[list_of_models]

get_head_mapper_table cached 🗿

get_head_mapper_table()

Get the table for mapping head numbers to head indices.

Source code in code/facesim3d/modeling/face_attribute_processing.py
114
115
116
117
@cache
def get_head_mapper_table():
    """Get the table for mapping head numbers to head indices."""
    return pd.read_csv(Path(paths.data.unity.cfd, "headnmap.csv"), names=["CFD_filename", "idx", "unknown", "head_nr"])

head_index_to_head_nr 🗿

head_index_to_head_nr(face_idx: int) -> str

Convert the head index ('idx' in 'headnmap.csv') to the head number ('Head#').

Note

This is the inverse function of head_nr_to_index().

For previous pilot experiments

This is not being used for the pilot experiment with fewer heads.

Returns:

Type Description
str

head number

Source code in code/facesim3d/modeling/face_attribute_processing.py
222
223
224
225
226
227
228
229
230
231
232
233
234
235
def head_index_to_head_nr(face_idx: int) -> str:
    """
    Convert the head index (`'idx'` in `'headnmap.csv'`) to the head number (`'Head#'`).

    !!! note
        This is the inverse function of `head_nr_to_index()`.

    !!! note "For previous pilot experiments"
        This is not being used for the pilot experiment with fewer heads.

    :return: head number
    """
    map_tab = get_head_mapper_table()
    return map_tab.loc[map_tab.idx == face_idx, "head_nr"].values[0]  # 'Head#'  # noqa: PD011

head_nr_to_index 🗿

head_nr_to_index(head_id: str | int | None) -> int

Convert the head number ('Head#') to the head index ('idx' in 'headnmap.csv').

Parameters:

Name Type Description Default
head_id str | int | None

Head number, as [str], e.g., "Head4", or as [int], e.g., 4.

required

Returns:

Type Description
int

head index

Source code in code/facesim3d/modeling/face_attribute_processing.py
238
239
240
241
242
243
244
245
246
247
248
def head_nr_to_index(head_id: str | int | None) -> int:
    """
    Convert the head number (`'Head#'`) to the head index (`'idx'` in `'headnmap.csv'`).

    :param head_id: Head number, as [str], e.g., "Head4", or as [int], e.g., 4.
    :return: head index
    """
    map_tab = get_head_mapper_table()
    if isinstance(head_id, int):
        head_id = f"Head{head_id}"
    return map_tab.loc[map_tab.head_nr == head_id, "idx"].values[0]  # noqa: PD011

head_nr_to_main_matrix_index 🗿

head_nr_to_main_matrix_index(
    head_id: str | int | None,
) -> int

Convert a head number to a corresponding matrix index in the main study.

Convert the head number (as they appear in the rating files, '*TrialResults*.csv') to the index as it appears, e.g., in the similarity matrix.

Parameters:

Name Type Description Default
head_id str | int | None

Head number, as [str], e.g., "Head4", or as [int], e.g., 4.

required

Returns:

Type Description
int

head index

Source code in code/facesim3d/modeling/face_attribute_processing.py
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
def head_nr_to_main_matrix_index(head_id: str | int | None) -> int:
    """
    Convert a head number to a corresponding matrix index in the main study.

    Convert the head number (as they appear in the rating files, `'*TrialResults*.csv'`) to the index as it
    appears, e.g., in the similarity matrix.

    :param head_id: Head number, as [str], e.g., "Head4", or as [int], e.g., 4.
    :return: head index
    """
    if isinstance(head_id, str):
        head_id = int(head_id.title().removeprefix("Head"))
    if head_id not in range(1, params.main.n_faces + 1):
        msg = "head_id is out of range!"
        raise ValueError(msg)
    head_idx = head_id - 1
    return int(head_idx)

head_nr_to_pilot_matrix_index 🗿

head_nr_to_pilot_matrix_index(
    head_id: str | int | None, pilot_version: int = 2
) -> int

Convert a head number to a corresponding matrix index for the pilot studies.

Convert the head number (as they appear in the rating files, '*TrialResults*.csv') to the pilot index as it appears, e.g., in the similarity matrix.

Parameters:

Name Type Description Default
head_id str | int | None

Head number, as [str], e.g., "Head4", or as [int], e.g., 4.

required
pilot_version int

version of pilot (v1, v2)

2

Returns:

Type Description
int

head index

Source code in code/facesim3d/modeling/face_attribute_processing.py
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
def head_nr_to_pilot_matrix_index(head_id: str | int | None, pilot_version: int = 2) -> int:
    """
    Convert a head number to a corresponding matrix index for the pilot studies.

    Convert the head number (as they appear in the rating files, `'*TrialResults*.csv'`) to the pilot index
    as it appears, e.g., in the similarity matrix.

    :param head_id: Head number, as [str], e.g., "Head4", or as [int], e.g., 4.
    :param pilot_version: version of pilot (v1, v2)
    :return: head index
    """
    pv1, pv2 = 1, 2
    if pilot_version not in {pv1, pv2}:
        msg = "pilot_version must be 1 or 2!"
        raise ValueError(msg)

    n_fm = (12, 13) if pilot_version == pv2 else (15, 15)  # number of faces per gender (f, m)

    if isinstance(head_id, str):
        head_id = int(head_id.title().removeprefix("Head"))

    if head_id not in range(1, n_fm[0] + 1) and head_id not in range(51, 51 + n_fm[1]):
        msg = "head_id is out of range!"
        raise ValueError(msg)

    n_face_split = params.main.n_faces // 2
    head_idx = (
        head_id - 1 if head_id <= n_face_split else head_id - (n_face_split + 1) + n_fm[0]
    )  # female or male faces

    return int(head_idx)

heads_naming_converter_table 🗿

heads_naming_converter_table(
    pilot_version: int | None = None,
) -> DataFrame

Load the table for head naming conversion.

Parameters:

Name Type Description Default
pilot_version int | None

None: for the main experiment, OR for pilot: 1, OR: 2.

None

Returns:

Type Description
DataFrame

converter table

Source code in code/facesim3d/modeling/face_attribute_processing.py
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
def heads_naming_converter_table(pilot_version: int | None = None) -> pd.DataFrame:
    """
    Load the table for head naming conversion.

    :param pilot_version: None: for the main experiment, OR for pilot: 1, OR: 2.
    :return: converter table
    """
    pv1, pv2 = 1, 2

    # Load mapping table
    heads_tab = get_head_mapper_table()
    # Keep only samples where reconstructions are computed/used
    heads_tab = heads_tab[~heads_tab.head_nr.str.match("_")]
    # Keep only the necessary columns
    heads_tab = heads_tab.drop(columns=["idx", "unknown"], inplace=False).copy()

    if isinstance(pilot_version, int):
        # Extract pilot heads
        if pilot_version == pv1:
            heads_tab = heads_tab[heads_tab.head_nr.str.fullmatch(r"Head([1-9]|1[0-5]|5[1-9]|6[0-5])")]
        elif pilot_version == pv2:
            heads_tab = heads_tab[heads_tab.head_nr.str.fullmatch(r"Head([1-9]|1[0-2]|5[1-9]|6[0-3])")]
        else:
            msg = f"pilot_version must be None, {pv1}, OR {pv2}."
            raise ValueError(msg)

        # # Remove .jpg suffix

    # Extract and save naming of Model
    heads_tab["Model"] = [fn[4:10] for fn in heads_tab.CFD_filename]

    return heads_tab

list_faulty_heads 🗿

list_faulty_heads(
    run: bool = False, suffix: str = ""
) -> DataFrame

List faulty heads (i.e., heads for which the reconstruction is not optimal).

DECA has a reported reconstructed error in the eyes (misalignment of the eyes), and open mouth, which is not open in the original image.

Parameters:

Name Type Description Default
run bool

if True: open all images and fill in the faulty heads in the table.

False
suffix str

path suffix hinting to the focus of the observation among the face stimuli (e.g., 'eyes')

''

Returns:

Type Description
DataFrame

the list of faulty heads (IDs)

Source code in code/facesim3d/modeling/face_attribute_processing.py
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
def list_faulty_heads(run: bool = False, suffix: str = "") -> pd.DataFrame:
    """
    List faulty heads (i.e., heads for which the reconstruction is not optimal).

    DECA has a reported reconstructed error in the eyes (misalignment of the eyes), and open mouth,
    which is not open in the original image.

    :param run: if True: open all images and fill in the faulty heads in the table.
    :param suffix: path suffix hinting to the focus of the observation among the face stimuli (e.g., 'eyes')
    :return: the list of faulty heads (IDs)
    """
    # Set path to faulty heads table
    suffix = f"_{suffix}" if suffix else ""
    path_to_table = Path(paths.data.unity.cfd, f"faulty_heads{suffix}.csv")

    if run:
        if path_to_table.is_file():
            print(f"{path_to_table} exists already!")
            df_faulty_heads = pd.read_csv(path_to_table, index_col=0)
            if not ask_true_false("Do you want to add missing heads to table?"):
                return df_faulty_heads
            # In case all heads shall be replaced, delete table manually

        else:
            df_faulty_heads = pd.DataFrame(columns=["head_nr", "faulty"])
            df_faulty_heads = df_faulty_heads.set_index("head_nr")

        for i in range(1, 159 + 1):
            # TODO: ?  # noqa: FIX002
            head_nr = head_index_to_head_nr(face_idx=i)

            if head_nr in df_faulty_heads.index or head_nr == "_":
                continue

            try:
                display_face(head_id=head_nr, data_mode="3d-reconstructions", interactive=False, verbose=True)
                q = suffix or "eyes and/or mouth"
                df_faulty_heads.loc[head_nr, "faulty"] = int(ask_true_false(f"{head_nr}: Are/is {q} corrupted?"))
                if platform.system().lower() == "darwin":
                    os.system("""/usr/bin/osascript -e 'tell app "Preview" to close (first window)' """)  # noqa: S605
            except AttributeError:
                cprint(string=f"ID '{head_nr}' not valid!", col="r")

            # Save table
            df_faulty_heads.to_csv(path_to_table)

    else:
        # Load existing table
        df_faulty_heads = pd.read_csv(path_to_table, index_col=0)

    return df_faulty_heads

main_index_to_model_name 🗿

main_index_to_model_name(face_idx: int) -> str

Convert the head index to a head model name in the main study.

Convert the main study index as it appears, e.g., in the similarity matrix to the name of the corresponding head model (as it appears in the CFD feature table (PFA)).

Parameters:

Name Type Description Default
face_idx int

head index in pilot

required

Returns:

Type Description
str

name of the head model

Source code in code/facesim3d/modeling/face_attribute_processing.py
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
def main_index_to_model_name(face_idx: int) -> str:
    """
    Convert the head index to a head model name in the main study.

    Convert the main study index as it appears, e.g., in the similarity matrix to the name of the corresponding head
    model (as it appears in the `CFD` feature table (`PFA`)).

    :param face_idx: head index in pilot
    :return: name of the head model
    """
    if face_idx not in range(100):
        msg = "face_idx is out of range!"
        raise ValueError(msg)

    head_nr = main_matrix_index_to_head_nr(face_idx=face_idx)
    convert_tab = get_head_mapper_table()
    model_name = convert_tab[convert_tab.head_nr == head_nr].CFD_filename.item()
    return model_name.removeprefix("CFD-")[:6]

main_matrix_index_to_head_nr 🗿

main_matrix_index_to_head_nr(face_idx: int) -> str

Convert the index to a corresponding head number in the main study.

Convert the main index as it appears, e.g., in the similarity matrix to the corresponding head number (as they appear in the rating files, '*TrialResults*.csv').

Note

This is the inverse function of head_nr_to_main_matrix_index().

Parameters:

Name Type Description Default
face_idx int

head index in the main study

required

Returns:

Type Description
str

the head number

Source code in code/facesim3d/modeling/face_attribute_processing.py
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
def main_matrix_index_to_head_nr(face_idx: int) -> str:
    """
    Convert the index to a corresponding head number in the main study.

    Convert the main index as it appears, e.g., in the similarity matrix to the corresponding head number
    (as they appear in the rating files, `'*TrialResults*.csv'`).

    !!! note
        This is the inverse function of `head_nr_to_main_matrix_index()`.

    :param face_idx: head index in the main study
    :return: the head number
    """
    if face_idx not in range(100):
        msg = "face_idx is out of range!"
        raise ValueError(msg)
    head_id = face_idx + 1
    return f"Head{head_id}"

pilot_index_to_model_name 🗿

pilot_index_to_model_name(
    pilot_face_idx: int, pilot_version: int = 2
) -> str

Convert the head index to the head model name.

Convert the pilot index as it appears, e.g., in the similarity matrix to the corresponding name of the head model (as it appears in the CFD feature table (PFA)).

Parameters:

Name Type Description Default
pilot_face_idx int

head index in the pilot experiment

required
pilot_version int

the version of pilot experiment (v1, v2)

2

Returns:

Type Description
str

name of the head model

Source code in code/facesim3d/modeling/face_attribute_processing.py
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
def pilot_index_to_model_name(pilot_face_idx: int, pilot_version: int = 2) -> str:
    """
    Convert the head index to the head model name.

    Convert the pilot index as it appears, e.g., in the similarity matrix to the corresponding name of the head model
    (as it appears in the `CFD` feature table (`PFA`)).

    :param pilot_face_idx: head index in the pilot experiment
    :param pilot_version: the version of pilot experiment (v1, v2)
    :return: name of the head model
    """
    pv1, pv2 = 1, 2
    if pilot_version not in {pv1, pv2}:
        msg = "pilot_version must be 1 or 2!"
        raise ValueError(msg)

    n_fm = (12, 13) if pilot_version == pv2 else (15, 15)  # number of faces per gender (f, m)

    if pilot_face_idx not in range(n_fm[0] + n_fm[1]):
        msg = "pilot_face_idx is out of range!"
        raise ValueError(msg)

    head_nr = pilot_matrix_index_to_head_nr(pilot_face_idx=pilot_face_idx, pilot_version=pilot_version)
    convert_tab = get_head_mapper_table()
    model_name = convert_tab[convert_tab.head_nr == head_nr].CFD_filename.item()
    return model_name.removeprefix("CFD-")[:6]

pilot_matrix_index_to_head_nr 🗿

pilot_matrix_index_to_head_nr(
    pilot_face_idx: int, pilot_version: int = 2
) -> str

Convert the matrix index of a head to a head number in the pilot studies.

Convert the pilot index as it appears, e.g., in the similarity matrix to the corresponding head number (as they appear in the rating files, '*TrialResults*.csv').

Note

This is the inverse function of head_nr_to_pilot_matrix_index().

Parameters:

Name Type Description Default
pilot_face_idx int

head index in the pilot matrix

required
pilot_version int

version of pilot experiment (v1, v2)

2

Returns:

Type Description
str

the head number

Source code in code/facesim3d/modeling/face_attribute_processing.py
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
def pilot_matrix_index_to_head_nr(pilot_face_idx: int, pilot_version: int = 2) -> str:
    """
    Convert the matrix index of a head to a head number in the pilot studies.

    Convert the pilot index as it appears, e.g., in the similarity matrix to the corresponding head number
    (as they appear in the rating files, `'*TrialResults*.csv'`).

    !!! note
        This is the inverse function of `head_nr_to_pilot_matrix_index()`.

    :param pilot_face_idx: head index in the pilot matrix
    :param pilot_version: version of pilot experiment (v1, v2)
    :return: the head number
    """
    pv1, pv2 = 1, 2
    if pilot_version not in {pv1, pv2}:
        msg = f"pilot_version must be {pv1} or {pv2}!"
        raise ValueError(msg)

    n_fm = (12, 13) if pilot_version == pv2 else (15, 15)  # number of faces per gender (f, m)

    if pilot_face_idx not in range(n_fm[0] + n_fm[1]):
        msg = "pilot_face_idx is out of range!"
        raise ValueError(msg)

    head_id = pilot_face_idx + 1 if pilot_face_idx < n_fm[0] else pilot_face_idx + 51 - n_fm[0]  # female or male faces

    return f"Head{head_id}"