1
0
mirror of https://github.com/xuthus83/pigallery2.git synced 2024-11-03 21:04:03 +08:00

improving faces sample photo selection

This commit is contained in:
Patrik J. Braun 2020-01-01 15:57:16 +01:00
parent 46aa9710ee
commit da6c77678f
6 changed files with 119 additions and 20 deletions

View File

@ -45,10 +45,10 @@ export class PersonMWs {
}
try {
const persons = (req.resultPipe as PersonWithPhoto[]);
for (let i = 0; i < persons.length; i++) {
persons[i].samplePhoto = await ObjectManagers.getInstance()
.PersonManager.getSamplePhoto(persons[i].name);
}
const photoMap = await ObjectManagers.getInstance()
.PersonManager.getSamplePhotos(persons.map(p => p.name));
persons.forEach(p => p.samplePhoto = photoMap[p.name]);
req.resultPipe = persons;
return next();

View File

@ -10,6 +10,7 @@ import {ThumbnailSourceType} from '../../model/threading/PhotoWorker';
import {MediaDTO} from '../../../common/entities/MediaDTO';
import {PersonWithPhoto} from '../PersonMWs';
import {PhotoProcessing} from '../../model/fileprocessing/PhotoProcessing';
import {PhotoDTO} from '../../../common/entities/PhotoDTO';
export class ThumbnailGeneratorMWs {
@ -75,12 +76,14 @@ export class ThumbnailGeneratorMWs {
if (!req.resultPipe) {
return next();
}
const photo: PhotoDTO = req.resultPipe;
try {
req.resultPipe = await PhotoProcessing.generatePersonThumbnail(req.resultPipe);
req.resultPipe = await PhotoProcessing.generatePersonThumbnail(photo);
return next();
} catch (error) {
console.error(error);
return next(new ErrorDTO(ErrorCodes.THUMBNAIL_GENERATION_ERROR,
'Error during generating face thumbnail: ' + req.resultPipe, error.toString()));
'Error during generating face thumbnail: ' + photo.name, error.toString()));
}
}

View File

@ -7,6 +7,8 @@ export interface IPersonManager {
getSamplePhoto(name: string): Promise<PhotoDTO>;
getSamplePhotos(names: string[]): Promise<{ [key: string]: PhotoDTO }>;
get(name: string): Promise<PersonEntry>;
saveAll(names: string[]): Promise<void>;

View File

@ -6,6 +6,7 @@ import {MediaEntity} from './enitites/MediaEntity';
import {FaceRegionEntry} from './enitites/FaceRegionEntry';
import {PersonDTO} from '../../../../common/entities/PersonDTO';
import {Utils} from '../../../../common/Utils';
import {SelectQueryBuilder} from 'typeorm';
const LOG_TAG = '[PersonManager]';
@ -35,28 +36,39 @@ export class PersonManager implements IPersonManager {
}
async getSamplePhoto(name: string): Promise<PhotoDTO> {
if (!this.samplePhotos[name]) {
return (await this.getSamplePhotos([name]))[name];
}
async getSamplePhotos(names: string[]): Promise<{ [key: string]: PhotoDTO }> {
const hasAll = names.reduce((prev, name) => prev && !!this.samplePhotos[name], true);
if (!hasAll) {
const connection = await SQLConnection.getConnection();
const rawAndEntities = await connection.getRepository(MediaEntity).createQueryBuilder('media')
.limit(1)
.leftJoinAndSelect('media.directory', 'directory')
const rawAndEntities = await (connection
.getRepository(MediaEntity)
.createQueryBuilder('media') as SelectQueryBuilder<MediaEntity>)
.select(['media.name', 'media.id', 'person.name', 'directory.name',
'directory.path', 'media.metadata.size.width', 'media.metadata.size.height'])
.leftJoin('media.directory', 'directory')
.leftJoinAndSelect('media.metadata.faces', 'faces')
.leftJoin('faces.person', 'person')
.where('person.name LIKE :name COLLATE utf8_general_ci', {name: name}).getRawAndEntities();
.groupBy('person.name')
.orWhere(`person.name IN (:...names) COLLATE utf8_general_ci`, {names: names}).getRawAndEntities();
if (rawAndEntities.entities.length === 0) {
return null;
for (let i = 0; i < rawAndEntities.raw.length; ++i) {
this.samplePhotos[rawAndEntities.raw[i].person_name] =
Utils.clone(rawAndEntities.entities.find(m => m.name === rawAndEntities.raw[i].media_name));
this.samplePhotos[rawAndEntities.raw[i].person_name].metadata.faces = [FaceRegionEntry.fromRawToDTO(rawAndEntities.raw[i])];
}
const media: PhotoDTO = Utils.clone(rawAndEntities.entities[0]);
media.metadata.faces = [FaceRegionEntry.fromRawToDTO(rawAndEntities.raw[0])];
this.samplePhotos[name] = media;
}
return this.samplePhotos[name];
const photoMap: { [key: string]: PhotoDTO } = {};
names.forEach(n => photoMap[n] = this.samplePhotos[n]);
return photoMap;
}
async loadAll(): Promise<void> {
const connection = await SQLConnection.getConnection();
const personRepository = connection.getRepository(PersonEntry);

View File

@ -6,6 +6,7 @@ import {PersonDTO} from '../../../../../common/entities/PersonDTO';
@Entity()
@Unique(['name'])
export class PersonEntry implements PersonDTO {
@Index()
@PrimaryGeneratedColumn({unsigned: true})
id: number;

View File

@ -1,4 +1,14 @@
import {expect} from 'chai';
import {PersonManager} from '../../../../../src/backend/model/database/sql/PersonManager';
import {SQLTestHelper} from '../../../SQLTestHelper';
import {TestHelper} from './TestHelper';
import {PhotoDTO} from '../../../../../src/common/entities/PhotoDTO';
import {PersonEntry} from '../../../../../src/backend/model/database/sql/enitites/PersonEntry';
import {FaceRegionEntry} from '../../../../../src/backend/model/database/sql/enitites/FaceRegionEntry';
import {SQLConnection} from '../../../../../src/backend/model/database/sql/SQLConnection';
import {PhotoEntity} from '../../../../../src/backend/model/database/sql/enitites/PhotoEntity';
import {DirectoryEntity} from '../../../../../src/backend/model/database/sql/enitites/DirectoryEntity';
import {VideoEntity} from '../../../../../src/backend/model/database/sql/enitites/VideoEntity';
// to help WebStorm to handle the test cases
@ -7,7 +17,78 @@ declare const after: any;
declare const it: any;
describe('PersonManager', () => {
describe = SQLTestHelper.describe;
describe('PersonManager', (sqlHelper: SQLTestHelper) => {
const dir = TestHelper.getDirectoryEntry();
const p = TestHelper.getPhotoEntry1(dir);
const p2 = TestHelper.getPhotoEntry2(dir);
const p_faceLess = TestHelper.getPhotoEntry2(dir);
delete p_faceLess.metadata.faces;
p_faceLess.name = 'fl';
const v = TestHelper.getVideoEntry1(dir);
const setUpSqlDB = async () => {
await sqlHelper.initDB();
const savePhoto = async (photo: PhotoDTO) => {
const savedPhoto = await pr.save(photo);
if (!photo.metadata.faces) {
return;
}
for (let i = 0; i < photo.metadata.faces.length; i++) {
const face = photo.metadata.faces[i];
const person = await conn.getRepository(PersonEntry).save({name: face.name});
await conn.getRepository(FaceRegionEntry).save({box: face.box, person: person, media: savedPhoto});
}
};
const conn = await SQLConnection.getConnection();
const pr = conn.getRepository(PhotoEntity);
await conn.getRepository(DirectoryEntity).save(p.directory);
await savePhoto(p);
await savePhoto(p2);
await savePhoto(p_faceLess);
await conn.getRepository(VideoEntity).save(v);
await SQLConnection.close();
};
beforeEach(async () => {
await setUpSqlDB();
});
after(async () => {
await sqlHelper.clearDB();
});
it('should get sample photos', async () => {
const pm = new PersonManager();
const map: { [key: string]: PhotoDTO } = {};
p.metadata.faces.forEach(face => {
map[face.name] = <any>{
id: p.id,
name: p.name,
directory: {
path: p.directory.path,
name: p.directory.name,
},
metadata: {
size: p.metadata.size,
faces: [p.metadata.faces.find(f => f.name === face.name)]
},
readyIcon: false,
readyThumbnails: []
};
});
expect(await pm.getSamplePhotos(p.metadata.faces.map(f => f.name))).to.deep.equal(map);
});
});