From b31c7681ae97f07f2e10d18d613d4132d8a843ab Mon Sep 17 00:00:00 2001 From: Mert <101130780+mertalev@users.noreply.github.com> Date: Wed, 7 Feb 2024 10:56:39 -0500 Subject: [PATCH] feat(server): optimize face re-queueing (#6961) * do not defer faces with no matches * move comment --- .../src/domain/person/person.service.spec.ts | 31 ++++++++++++++++--- server/src/domain/person/person.service.ts | 10 ++++-- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/server/src/domain/person/person.service.spec.ts b/server/src/domain/person/person.service.spec.ts index e1937524af..9d55abc8e6 100644 --- a/server/src/domain/person/person.service.spec.ts +++ b/server/src/domain/person/person.service.spec.ts @@ -866,11 +866,29 @@ describe(PersonService.name, () => { }); }); - it('should defer non-core faces to end of queue', async () => { + it('should not queue face with no matches', async () => { const faces = [{ face: faceStub.noPerson1, distance: 0 }] as FaceSearchResult[]; + smartInfoMock.searchFaces.mockResolvedValue(faces); + personMock.getFaceByIdWithAssets.mockResolvedValue(faceStub.noPerson1); + personMock.create.mockResolvedValue(personStub.withName); + + await sut.handleRecognizeFaces({ id: faceStub.noPerson1.id }); + + expect(jobMock.queue).not.toHaveBeenCalled(); + expect(smartInfoMock.searchFaces).toHaveBeenCalledTimes(1); + expect(personMock.create).not.toHaveBeenCalled(); + expect(personMock.reassignFaces).not.toHaveBeenCalled(); + }); + + it('should defer non-core faces to end of queue', async () => { + const faces = [ + { face: faceStub.noPerson1, distance: 0 }, + { face: faceStub.noPerson2, distance: 0.4 }, + ] as FaceSearchResult[]; + configMock.load.mockResolvedValue([ - { key: SystemConfigKey.MACHINE_LEARNING_FACIAL_RECOGNITION_MIN_FACES, value: 2 }, + { key: SystemConfigKey.MACHINE_LEARNING_FACIAL_RECOGNITION_MIN_FACES, value: 3 }, ]); smartInfoMock.searchFaces.mockResolvedValue(faces); personMock.getFaceByIdWithAssets.mockResolvedValue(faceStub.noPerson1); @@ -887,11 +905,14 @@ describe(PersonService.name, () => { expect(personMock.reassignFaces).not.toHaveBeenCalled(); }); - it('should not assign person to non-core face with no matching person', async () => { - const faces = [{ face: faceStub.noPerson1, distance: 0 }] as FaceSearchResult[]; + it('should not assign person to deferred non-core face with no matching person', async () => { + const faces = [ + { face: faceStub.noPerson1, distance: 0 }, + { face: faceStub.noPerson2, distance: 0.4 }, + ] as FaceSearchResult[]; configMock.load.mockResolvedValue([ - { key: SystemConfigKey.MACHINE_LEARNING_FACIAL_RECOGNITION_MIN_FACES, value: 2 }, + { key: SystemConfigKey.MACHINE_LEARNING_FACIAL_RECOGNITION_MIN_FACES, value: 3 }, ]); smartInfoMock.searchFaces.mockResolvedValueOnce(faces).mockResolvedValueOnce([]); personMock.getFaceByIdWithAssets.mockResolvedValue(faceStub.noPerson1); diff --git a/server/src/domain/person/person.service.ts b/server/src/domain/person/person.service.ts index 576f94c491..63fc350002 100644 --- a/server/src/domain/person/person.service.ts +++ b/server/src/domain/person/person.service.ts @@ -417,7 +417,13 @@ export class PersonService { numResults: machineLearning.facialRecognition.minFaces, }); - this.logger.debug(`Face ${id} has ${matches.length} match${matches.length == 1 ? '' : 'es'}`); + // `matches` also includes the face itself + if (matches.length <= 1) { + this.logger.debug(`Face ${id} has no matches`); + return true; + } + + this.logger.debug(`Face ${id} has ${matches.length} matches`); const isCore = matches.length >= machineLearning.facialRecognition.minFaces; if (!isCore && !deferred) { @@ -426,7 +432,7 @@ export class PersonService { return true; } - let personId = matches.find((match) => match.face.personId)?.face.personId; // `matches` also includes the face itself + let personId = matches.find((match) => match.face.personId)?.face.personId; if (!personId) { const matchWithPerson = await this.smartInfoRepository.searchFaces({ userIds: [face.asset.ownerId],