Source code for luna_handlers.sdk.sdk_loop.models.face_sample

"""Module contains face sample models"""
from enum import Enum
from operator import add
from typing import List, Optional

import attr
from attr import dataclass
from lunavl.sdk.descriptors.descriptors import FaceDescriptor
from lunavl.sdk.detectors.facedetector import FaceDetection, Landmarks5
from lunavl.sdk.estimators.face_estimators.background import FaceDetectionBackground
from lunavl.sdk.estimators.face_estimators.basic_attributes import BasicAttributes
from lunavl.sdk.estimators.face_estimators.emotions import Emotion, Emotions
from lunavl.sdk.estimators.face_estimators.eyebrow_expressions import EyebrowExpressions
from lunavl.sdk.estimators.face_estimators.eyes import EyesEstimation, GazeDirection
from lunavl.sdk.estimators.face_estimators.facewarper import FaceWarpedImage
from lunavl.sdk.estimators.face_estimators.fisheye import Fisheye
from lunavl.sdk.estimators.face_estimators.glasses import Glasses
from lunavl.sdk.estimators.face_estimators.head_pose import HeadPose
from lunavl.sdk.estimators.face_estimators.headwear import Headwear
from lunavl.sdk.estimators.face_estimators.image_type import ImageColorType
from lunavl.sdk.estimators.face_estimators.livenessv1 import LivenessV1
from lunavl.sdk.estimators.face_estimators.mask import Mask, MaskState
from lunavl.sdk.estimators.face_estimators.mouth_state import MouthStates
from lunavl.sdk.estimators.face_estimators.natural_light import FaceNaturalLight
from lunavl.sdk.estimators.face_estimators.red_eye import RedEyes
from lunavl.sdk.estimators.face_estimators.warp_quality import Quality

from .filtration.base import FiltrationResults


[docs]class ScoreOperator(Enum): """Score operator enum.""" # sum values concat = add # get higher value higher = max
[docs]@dataclass(slots=True) class FaceSample: """Face attributes containers""" # face detetction (None if origin is face warp) detection: Optional[FaceDetection] = None # eyes estimation eyes: Optional[EyesEstimation] = None # gaze direction estimation gaze: Optional[GazeDirection] = None # emotions estimation emotions: Optional[Emotions] = None # mouth state estimation mouthState: Optional[MouthStates] = None # basic attributes estimation basicAttributes: Optional[BasicAttributes] = None # face warp quality estimation warpQuality: Optional[Quality] = None # mask estimation mask: Optional[Mask] = None # glasses estimation glasses: Optional[Glasses] = None # head pose estimation headPose: Optional[HeadPose] = None # transformed landmarks5 transformedLandmarks5: Optional[Landmarks5] = None # livenessV1 estimation livenessV1: Optional[LivenessV1] = None # face warp estimation warp: Optional[FaceWarpedImage] = None # face descriptor estimation descriptor: Optional[FaceDescriptor] = None # headwear estimation headwear: Optional[Headwear] = None # fisheye estimation fisheye: Optional[Fisheye] = None # red-eyes estimation redEyes: Optional[RedEyes] = None # eyebrow expression estimation eyebrowExpression: Optional[EyebrowExpressions] = None # face natural light estimation naturalLight: Optional[FaceNaturalLight] = None # detection background estimation detectionBackground: Optional[FaceDetectionBackground] = None # image color type estimation imageColorType: Optional[ImageColorType] = None # filtration results filters: FiltrationResults = attr.field(factory=FiltrationResults) # dynamic range dynamicRange: Optional[float] = None
[docs]@dataclass(slots=True) class AggregateAttributesCounter: """Container class for counting aggregate attributes.""" # attribute score score: float # top attribute index index: int # count of attribute count: int = 1 # score operator operator: ScoreOperator = ScoreOperator.concat # highest score for predominant attribute _highestScore: float = attr.field(init=False, repr=False) def __attrs_post_init__(self): """post init attributes""" self._highestScore = self.score
[docs] def update(self, score: float, index: int) -> None: """ Update attribute score and set top index. Args: score: next attribute score index: attribute index """ self.count += 1 if score > self._highestScore: self._highestScore = score self.index = index self.score = self.operator.value(self.score, score)
# helper map: enum to attribute name Mask _MASK_TO_ATTR_NAME_MAP = { MaskState.Occluded: "occluded", MaskState.Missing: "missing", MaskState.MedicalMask: "medicalMask", } # helper map: enum to jsom name _MASK_TO_DICT_NAME_MAP = { MaskState.Occluded: "occluded", MaskState.Missing: "missing", MaskState.MedicalMask: "medical_mask", }
[docs]class AggregatedMask: """ Aggregated mask Attributes: predominateMask (MaskState): predominant aggregated mask medicalMask (float): aggregated mask score occluded (float): aggregated occluded score missing (float): aggregated missing score """ __slots__ = ("medicalMask", "missing", "occluded", "predominateMask", "faceOcclusion") def __init__(self, masks: list[Mask]): predominantToCountAndScore = {} # get most frequent mask. If there are several mask we will choose with higher sum score for index, mask in enumerate(masks): predominant = mask.predominateMask score = mask.__getattribute__(_MASK_TO_ATTR_NAME_MAP[predominant]) if predominant in predominantToCountAndScore: predominantToCountAndScore[predominant].update(score=score, index=index) else: predominantToCountAndScore[predominant] = AggregateAttributesCounter( score=score, index=index, operator=ScoreOperator.concat ) topAttributeIndex = sorted( sorted(predominantToCountAndScore.values(), key=lambda attrib: attrib.score, reverse=True), key=lambda attrib: attrib.count, reverse=True, )[0].index predominantMask = masks[topAttributeIndex] self.predominateMask = predominantMask.predominateMask self.medicalMask = predominantMask.medicalMask self.occluded = predominantMask.occluded self.missing = predominantMask.missing # get most frequent occlusion. If there are several mask we will choose with higher sum score for index, mask in enumerate(masks): predominant = mask.faceOcclusion.predominantOcclusion score = mask.faceOcclusion.__getattribute__(predominant.name.lower()) if predominant in predominantToCountAndScore: predominantToCountAndScore[predominant].update(score=score, index=index) else: predominantToCountAndScore[predominant] = AggregateAttributesCounter( score=score, index=index, operator=ScoreOperator.concat ) topAttributeIndex = sorted( sorted(predominantToCountAndScore.values(), key=lambda attrib: attrib.score, reverse=True), key=lambda attrib: attrib.count, reverse=True, )[0].index predominantOcclusion = masks[topAttributeIndex].faceOcclusion self.faceOcclusion = predominantOcclusion def __repr__(self) -> str: """ Representation. Returns: str(self.asDict()) """ return str(self.asDict())
[docs] def asDict(self) -> dict: """Convert aggregated mask to dict""" return { "predominant_mask": _MASK_TO_DICT_NAME_MAP[self.predominateMask], "estimations": {"medical_mask": self.medicalMask, "missing": self.missing, "occluded": self.occluded}, "face_occlusion": self.faceOcclusion.asDict(), }
# helper map: enum to an Emontions attribute name _EMOTION_TO_NAME_MAP = { Emotion.Anger: "anger", Emotion.Disgust: "disgust", Emotion.Fear: "fear", Emotion.Happiness: "happiness", Emotion.Neutral: "neutral", Emotion.Sadness: "sadness", Emotion.Surprise: "surprise", }
[docs]class AggregatedEmotions: """ Aggregated emotions Attributes: predominateEmotion (Emotion): predominant aggregated emotion anger (float): aggregated anger score disgust (float): aggregated disgust score fear (float): aggregated fear score happiness (float): aggregated happiness score neutral (float): aggregated neutral score sadness (float): aggregated sadness score surprise (float): aggregated surprise score """ __slots__ = ("anger", "disgust", "fear", "happiness", "neutral", "sadness", "surprise", "predominateEmotion") def __init__(self, emotions: list[Emotions]): predominantToCountAndScore = {} for index, emotion in enumerate(emotions): predominant = emotion.predominateEmotion score = emotion.__getattribute__(_EMOTION_TO_NAME_MAP[predominant]) if predominant in predominantToCountAndScore: predominantToCountAndScore[predominant].update(score=score, index=index) else: predominantToCountAndScore[predominant] = AggregateAttributesCounter( score=score, index=index, operator=ScoreOperator.higher ) topAttributeIndex = sorted( sorted(predominantToCountAndScore.values(), key=lambda attrib: attrib.score, reverse=True), key=lambda attrib: attrib.count, reverse=True, )[0].index predominateEmotion = emotions[topAttributeIndex] self.predominateEmotion = predominateEmotion.predominateEmotion self.anger = predominateEmotion.anger self.disgust = predominateEmotion.disgust self.fear = predominateEmotion.fear self.sadness = predominateEmotion.sadness self.surprise = predominateEmotion.surprise self.neutral = predominateEmotion.neutral self.happiness = predominateEmotion.happiness
[docs] def asDict(self) -> dict: """Convert aggregated emotions to dict""" return { "predominant_emotion": _EMOTION_TO_NAME_MAP[self.predominateEmotion], "estimations": { "anger": self.anger, "disgust": self.disgust, "fear": self.fear, "sadness": self.sadness, "surprise": self.surprise, "neutral": self.neutral, "happiness": self.happiness, }, }
def __repr__(self) -> str: """ Representation. Returns: str(self.asDict()) """ return str(self.asDict())
[docs]@dataclass(slots=True) class AggregatedFaceSample: """Aggregated face sample contaiter""" # origin samples samples: List[FaceSample] = attr.field(factory=list) # aggregated liveness liveness: Optional[LivenessV1] = None # aggregated descriptor descriptor: Optional[FaceDescriptor] = None # aggregated basic attributes basicAttributes: Optional[BasicAttributes] = None # applied filters filters: FiltrationResults = attr.field(factory=FiltrationResults) # aggregated mask mask: Optional[AggregatedMask] = None # aggregated emotions emotions: Optional[AggregatedEmotions] = None