from typing import List, Optional, Union
from luna3.common.http_objs import MatchPolicy
from luna3.python_matcher.match_objects import Candidates, FaceFilters, Reference
from sanic.response import HTTPResponse
from app.handlers.custom_query_getters import listUUID4sGetter, uuid4Getter
from app.handlers.handlers_mixin import HandlersMixin
from app.handlers.matcher_basic_handler import MatcherBasicHandler
from app.mixins import CachedHandlerMixin, InputDataMixin
from classes.enums import HandlerType, ListType
from classes.handler_defaults import ImageProcessingParams, SearchHandlerPolicies
from crutches_on_wheels.cow.errors.errors import Error
from crutches_on_wheels.cow.errors.exception import VLException
from crutches_on_wheels.cow.web.query_getters import boolFrom01Getter, int01Getter
[docs]class MatchingVerifyHandler(MatcherBasicHandler):
"""
Verify handler
Resource: "/{api_version}/matching/verify"
"""
[docs] async def post(self) -> HTTPResponse:
"""
Verify a descriptor by a person, see `spec_matching_verify`_.
.. _`spec_matching_verify`:
_static/api.html#operation/matchingVerify
"""
descriptorId = self.getQueryParam("descriptor_id", require=True, validator=uuid4Getter)
personId = self.getQueryParam("person_id", require=True, validator=uuid4Getter)
personDescriptors = await self.getPersonDescriptors(personId=personId)
if not personDescriptors:
return self.generateEmptyMatchResponse()
reference = Reference(referenceType="face", referenceId=descriptorId)
candidates = Candidates(filters=FaceFilters(faceIds=personDescriptors), targets=["face_id", "similarity"])
matchResult = (
await self.lunaApiClient.match(references=[reference], candidates=[candidates], raiseError=True)
).json
return self.success(
statusCode=201, outputJson={"candidates": await self.generateVerifyMatchResult(matchResult, personId)}
)
[docs]class MatchHandler(MatcherBasicHandler):
"""
Match handler
Resource: "/{api_version}/matching/match"
"""
[docs] async def post(self) -> HTTPResponse:
"""
Match a descriptor or person by descriptors, see `spec_matching_match`_.
.. _`spec_matching_match`:
_static/api.html#operation/matchingMatch
Raises:
VLException(Error.RequiredQueryParameterNotFound, 400, isCriticalError=False)
if required query parameter not found
"""
descriptorId = self.getQueryParam("descriptor_id", validator=uuid4Getter)
personId = self.getQueryParam("person_id", validator=uuid4Getter)
listId = self.getQueryParam("list_id", validator=uuid4Getter)
descriptorIds = self.getQueryParam("descriptor_ids", validator=listUUID4sGetter)
limit = self.getQueryParam("limit", validator=self.candidateLimitGetter, default=3)
if listId is None and descriptorIds is None:
raise VLException(
Error.RequiredQueryParameterNotFound.format(["list_id", "descriptor_ids"]),
statusCode=400,
isCriticalError=False,
)
if personId is None and descriptorId is None:
raise VLException(
Error.RequiredQueryParameterNotFound.format(["person_id", "descriptor_id"]),
statusCode=400,
isCriticalError=False,
)
if personId is not None:
descriptorsForMatching = await self.getPersonDescriptors(personId)
if not descriptorsForMatching:
return self.generateEmptyMatchResponse()
else:
descriptorsForMatching = [descriptorId]
references = [Reference(referenceType="face", referenceId=descriptor) for descriptor in descriptorsForMatching]
if listId:
candidates = await self.getListAsCandidates(listId, limit)
else:
candidates = await self.generateCandidatesFromDescriptors(descriptorIds, limit)
matchResult = (
await self.lunaApiClient.match(references=references, candidates=[candidates], raiseError=True)
).json
return self.success(
statusCode=201, outputJson={"candidates": self.generateMatchByDescriptorsResult(matchResult)}
)
[docs]class MatchingIdentifyHandler(MatcherBasicHandler):
"""
Identify handler
Resource: "/{api_version}/matching/identify"
"""
[docs] async def post(self) -> HTTPResponse:
"""
Match a descriptor or person by persons, see `spec_matching_identify`_.
.. _`spec_matching_identify`:
_static/api.html#operation/matchingIdentify
Raises:
VLException(Error.RequiredQueryParameterNotFound, 400, isCriticalError=False)
if required query parameter not found
"""
descriptorId = self.getQueryParam("descriptor_id", validator=uuid4Getter)
personId = self.getQueryParam("person_id", validator=uuid4Getter)
listId = self.getQueryParam("list_id", validator=uuid4Getter)
personIds = self.getQueryParam("person_ids", validator=listUUID4sGetter)
limit = self.getQueryParam("limit", validator=self.candidateLimitGetter, default=3)
if personId is None and descriptorId is None:
raise VLException(
Error.RequiredQueryParameterNotFound.format(["person_id", "descriptor_id"]),
statusCode=400,
isCriticalError=False,
)
if listId is None and personIds is None:
raise VLException(
Error.RequiredQueryParameterNotFound.format(["list_id", "person_ids"]),
statusCode=400,
isCriticalError=False,
)
if personId is not None:
descriptorsForMatching = await self.getPersonDescriptors(personId)
if not descriptorsForMatching:
return self.generateEmptyMatchResponse()
else:
descriptorsForMatching = [descriptorId]
references = [Reference(referenceType="face", referenceId=descriptor) for descriptor in descriptorsForMatching]
if listId is not None:
candidates = await self.getListAsCandidates(listId, limit, ListType.persons)
else:
candidates = await self.getPersonsAsCandidates(personIds, limit)
if candidates is None:
return self.generateEmptyMatchResponse()
matchResult = (
await self.lunaApiClient.match(references=references, candidates=[candidates], raiseError=True)
).json
return self.success(
statusCode=201, outputJson={"candidates": await self.generateMatchByPersonsResult(matchResult)}
)
[docs]class MatchingSearchHandler(CachedHandlerMixin, MatcherBasicHandler, HandlersMixin, InputDataMixin):
"""
Search handler
Resource: "/{api_version}/matching/search"
"""
[docs] async def getMatchByPersons(self, listId: Optional[str] = None, personIds: Optional[List[str]] = None) -> bool:
"""
Get match by persons
Args:
listId: list id
personIds: person ids
Returns:
True if match by persons else False
Raises:
VLException(Error.ListNotFound, 400, isCriticalError=False) if list not found
"""
if listId:
try:
listType = await self.getCachedListType(listId)
except VLException as e:
if e.error == Error.ListNotFound:
e.statusCode = 400
raise e
raise
if listType == ListType.persons:
return True
return False
if personIds is not None:
return True
return False
[docs] @staticmethod
def getHandlerPolicies(
imageProcessingParams: ImageProcessingParams,
matchPolicy: MatchPolicy,
limit: int,
listId: Optional[str] = None,
personIds: Optional[List[str]] = None,
descriptorIds: Optional[List[str]] = None,
) -> SearchHandlerPolicies:
"""
Get policies as dict
Args:
imageProcessingParams: prepared image processing params
matchPolicy: prepared match policy
limit: matching limit
listId: list id
personIds: person ids
descriptorIds: descriptor ids
Returns:
search handler policies
"""
return SearchHandlerPolicies(
matchPolicy=matchPolicy,
limit=limit,
listId=listId,
personIds=personIds,
descriptorIds=descriptorIds,
**imageProcessingParams.asDictSnakeCaseKeys(),
)
[docs] async def getMatchPolicy(
self,
limit: int,
listId: Optional[str] = None,
personIds: Optional[List[str]] = None,
descriptorIds: Optional[List[str]] = None,
) -> Union[MatchPolicy, None]:
"""
Get match policy depends on input parameters
Args:
limit: limit
listId: list id
personIds: person ids
descriptorIds: descriptor ids
Returns:
match policy or None if got only person ids and persons have not descriptors
"""
if listId is not None:
return await self.generateMatchPolicyFromList(listId, limit)
if personIds is not None:
return await self.generateMatchPolicyFromPersons(personIds, limit)
return await self.generateMatchPolicyFromDescriptors(descriptorIds, limit)
[docs] async def post(self) -> HTTPResponse:
"""
Search a face on image by persons or descriptors, see `spec_matching_search`_.
.. _`spec_matching_search`:
_static/api.html#operation/matchingSearch
Raises:
VLException(Error.RequiredQueryParameterNotFound, isCriticalError=400, False)
if required query parameter not found
VLException(Error.UnsupportedQueryParam, 400, isCriticalError=False)
if unsupported query parameter found
"""
if not self.getQueryParam("extract_descriptor", validator=int01Getter, default=1):
raise VLException(
error=Error.UnsupportedQueryParam.format("extract_descriptor"), statusCode=400, isCriticalError=False
)
personIds = self.getQueryParam("person_ids", validator=listUUID4sGetter)
limit = self.getQueryParam("limit", validator=self.candidateLimitGetter, default=3)
descriptorIds = self.getQueryParam("descriptor_ids", validator=listUUID4sGetter)
listId = self.getQueryParam("list_id", validator=uuid4Getter)
noCache = self.getQueryParam("no_cache", boolFrom01Getter, default=False)
if listId is None and personIds is None and descriptorIds is None:
raise VLException(
Error.RequiredQueryParameterNotFound.format(["list_id", "person_ids", "descriptor_ids"]),
statusCode=400,
isCriticalError=False,
)
matchPolicy = await self.getMatchPolicy(limit, listId, personIds, descriptorIds)
if matchPolicy is None:
return self.generateEmptyMatchResponse()
data = await self.getRawDataContainer()
handler = await self.getHandler(HandlerType.search, noCache)
handlerId = handler["handler_id"]
imageProcessingParams = self.getImageProcessingParamsFromQuery()
handlerPolicies = self.getHandlerPolicies(
imageProcessingParams, matchPolicy, limit, listId, personIds, descriptorIds
)
events, exif = await self.emitEvents(
handlerType=HandlerType.search,
handlerId=handlerId,
inputData=data,
handlerPolicies=handlerPolicies,
warpedImage=imageProcessingParams.warpedImage,
)
unpreparedResult = await self.getFacesFromEstimation(
eventsFromReply=events, imageProcessingParams=imageProcessingParams, data=data
)
if len(unpreparedResult["faces"]) > 1:
error = Error.ManyFaces
error.detail = unpreparedResult
raise VLException(error=error, isCriticalError=False, statusCode=400)
result = {"face": unpreparedResult["faces"][0]}
if exif is not None:
result["exif"] = exif
resultCandidates = events[0]["matches"][0]["candidates"]
matchByPersons = await self.getMatchByPersons(listId, personIds)
if matchByPersons:
result["candidates"] = await self.generateMatchByPersonsResult(resultCandidates, aggregateMatches=False)
else:
result["candidates"] = self.generateMatchByDescriptorsResult(resultCandidates, aggregateMatches=False)
return self.success(statusCode=201, outputJson=result, extraHeaders=self.getExtraHeaders())