From 04623a665b6a5cac991037b4ed6e0bb364569a7c Mon Sep 17 00:00:00 2001 From: Pavel Bohdan Date: Sun, 23 May 2021 19:01:01 +0300 Subject: [PATCH 001/837] Add new component Face-displaying. --- .../face-displaying.component.ts | 76 +++++++++++++++++++ .../face-recognition-container.component.html | 1 - .../face-services/face-services.decorators.ts | 39 ++++++++++ .../face-services/face-services.helpers.ts | 2 +- .../face-services/face-services.module.ts | 2 + .../verification-result.component.html | 17 ++++- .../verification-result.component.ts | 23 +++++- .../app/store/face-verification/selectors.ts | 4 +- 8 files changed, 157 insertions(+), 7 deletions(-) create mode 100644 ui/src/app/features/face-services/face-displaying/face-displaying.component.ts create mode 100644 ui/src/app/features/face-services/face-services.decorators.ts diff --git a/ui/src/app/features/face-services/face-displaying/face-displaying.component.ts b/ui/src/app/features/face-services/face-displaying/face-displaying.component.ts new file mode 100644 index 0000000000..9e99e792d8 --- /dev/null +++ b/ui/src/app/features/face-services/face-displaying/face-displaying.component.ts @@ -0,0 +1,76 @@ +import { AfterViewInit, Component, ElementRef, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewChild } from '@angular/core'; + +import { LoadingPhotoService } from '../../../core/photo-loader/photo-loader.service'; +import { ImageSize } from '../../../data/interfaces/image'; +import { checkFile } from '../face-services.decorators'; +import { filter, map, takeUntil, tap } from 'rxjs/operators'; +import { Subject } from 'rxjs'; +import { recalculateFaceCoordinate } from '../face-services.helpers'; + +@Component({ + selector: 'app-face-displaying', + template: '', +}) +export class FaceDisplayingComponent implements OnInit, OnChanges, AfterViewInit, OnDestroy { + @Input() width: number; + @Input() file: any; + @Input() dataFrames: any[]; + + @ViewChild('canvasElement', { static: false }) canvasElement: ElementRef; + + private unsubscribes$: Subject = new Subject(); + private ctx: CanvasRenderingContext2D; + private sizeImage: ImageSize; + private sizeCanvas: ImageSize; + + private frames: Subject = new Subject(); + + private recal; + + constructor(private loadingPhotoService: LoadingPhotoService) {} + + ngOnChanges(changes: SimpleChanges): void { + this.loadImg(this.file); + this.frames.next(this.dataFrames); + } + + ngOnInit(): void { + this.frames + .pipe( + takeUntil(this.unsubscribes$), + filter(val => val), + map(val => val.map(value => ({ ...value, box: recalculateFaceCoordinate(value.box, this.sizeImage, this.sizeCanvas) }))) + ) + .subscribe(val => { + this.recal = val; + }); + } + + ngAfterViewInit(): void { + this.ctx = this.canvasElement.nativeElement.getContext('2d'); + } + + @checkFile + loadImg(file: File): void { + this.loadingPhotoService + .loader(file) + .pipe( + takeUntil(this.unsubscribes$), + tap(({ width, height }: ImageBitmap) => { + this.sizeImage = { width, height }; + this.sizeCanvas = { width: this.width, height: (height / width) * this.width }; + this.canvasElement.nativeElement.setAttribute('height', this.sizeCanvas.height); + }) + ) + .subscribe((img: ImageBitmap) => this.displayImg(img, this.sizeCanvas)); + } + + displayImg(img: ImageBitmap, size: ImageSize): void { + this.ctx.drawImage(img, 0, 0, size.width, size.height); + } + + ngOnDestroy(): void { + this.unsubscribes$.next(); + this.unsubscribes$.complete(); + } +} diff --git a/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.html b/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.html index d3861fed9b..a11b613093 100644 --- a/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.html +++ b/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.html @@ -14,7 +14,6 @@ ~ permissions and limitations under the License. --> -
diff --git a/ui/src/app/features/face-services/face-services.decorators.ts b/ui/src/app/features/face-services/face-services.decorators.ts new file mode 100644 index 0000000000..6317f3f153 --- /dev/null +++ b/ui/src/app/features/face-services/face-services.decorators.ts @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +import { SimpleChanges } from '@angular/core'; + +export function checkFile(targetClass, functionName: string, descriptor) { + const source = descriptor.value; + descriptor.value = function (changes: SimpleChanges) { + if (changes) return source.call(this, changes); + }; + return descriptor; +} + +export function recalculateCoordinate(inputs: string[]) { + return function (targetClass, functionName: string, descriptor) { + const source = descriptor.set; + + descriptor.set = function (changes: SimpleChanges) { + inputs.forEach(input => { + if (!!changes[input]) { + } + }); + return source.call(this, changes); + }; + return descriptor; + }; +} diff --git a/ui/src/app/features/face-services/face-services.helpers.ts b/ui/src/app/features/face-services/face-services.helpers.ts index d6f6dcecb1..1f65bc5d88 100644 --- a/ui/src/app/features/face-services/face-services.helpers.ts +++ b/ui/src/app/features/face-services/face-services.helpers.ts @@ -68,7 +68,7 @@ export const getFileExtension = (file: File): string => * @param sizeToCalc Canvas size. (design size). * @param yAxisPadding padding to ensure capacity for text area on image. */ -export const recalculateFaceCoordinate = (box: any, imageSize: ImageSize, sizeToCalc: ImageSize, yAxisPadding: number) => { +export const recalculateFaceCoordinate = (box: any, imageSize: ImageSize, sizeToCalc: ImageSize, yAxisPadding: number = 25) => { const divideWidth = imageSize.width / sizeToCalc.width; const divideHeight = imageSize.height / sizeToCalc.height; diff --git a/ui/src/app/features/face-services/face-services.module.ts b/ui/src/app/features/face-services/face-services.module.ts index 868db2542e..ed3aface2f 100644 --- a/ui/src/app/features/face-services/face-services.module.ts +++ b/ui/src/app/features/face-services/face-services.module.ts @@ -26,11 +26,13 @@ import { FaceVerificationContainerComponent } from './face-verification/face-ver import { RecognitionResultComponent } from './face-recognition/recognition-result/recognition-result.component'; import { VerificationResultComponent } from './face-verification/verification-result/verification-result.component'; import { MatCardModule } from '@angular/material/card'; +import { FaceDisplayingComponent } from './face-displaying/face-displaying.component'; @NgModule({ declarations: [ FaceRecognitionContainerComponent, FaceVerificationContainerComponent, + FaceDisplayingComponent, RecognitionResultComponent, VerificationResultComponent, ], diff --git a/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.html b/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.html index 0027dd1556..8cd199b2e1 100644 --- a/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.html +++ b/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.html @@ -22,6 +22,14 @@ viewComponentColumn > + + + +
@@ -32,6 +40,14 @@ viewComponentColumn > + + + + @@ -61,6 +77,5 @@ - diff --git a/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.ts b/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.ts index 454ba83ecd..c3eee44bb0 100644 --- a/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.ts +++ b/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.ts @@ -15,7 +15,7 @@ */ import { Component, ElementRef, Input, ViewChild, OnChanges, SimpleChanges, Output, EventEmitter } from '@angular/core'; -import { Observable } from 'rxjs'; +import { BehaviorSubject, Observable } from 'rxjs'; import { first, map, tap } from 'rxjs/operators'; import { recalculateFaceCoordinate, resultRecognitionFormatter, createDefaultImage } from '../../face-services.helpers'; import { RequestResultVerification } from '../../../../data/interfaces/response-result'; @@ -55,14 +55,31 @@ export class VerificationResultComponent implements OnChanges { private checkFileCanvasLink: any = null; private imgCanvas: ImageBitmap; + //New------------------------------------------------------------------------ + firstProcess: BehaviorSubject; + firstProcessFrames: BehaviorSubject; + + secondProcess: BehaviorSubject; + secondProcessFrames: BehaviorSubject; + //New------------------------------------------------------------------------ + constructor(private loadingPhotoService: LoadingPhotoService) {} ngOnChanges(changes: SimpleChanges) { if (!changes?.files?.currentValue) return; + //New------------------------------------------------------------------------ + if (changes?.printData?.currentValue) { + this.firstProcessFrames = new BehaviorSubject(changes?.printData?.currentValue[0].face_matches); + this.secondProcessFrames = new BehaviorSubject([changes?.printData?.currentValue[0].source_image_face]); + } + this.formattedResult = resultRecognitionFormatter(this.requestInfo.response); if (changes.files.currentValue.checkFile) { + //New------------------------------------------------------------------------ + if (!this.secondProcess) this.secondProcess = new BehaviorSubject(changes.files.currentValue.checkFile); + this.refreshCanvas( this.checkFileCanvasLink, this.checkFileCanvasSize, @@ -73,6 +90,9 @@ export class VerificationResultComponent implements OnChanges { } if (changes.files.currentValue.processFile) { + //New---------------------------------------------------------------------------------------------- + if (!this.firstProcess) this.firstProcess = new BehaviorSubject(changes.files.currentValue.processFile); + this.refreshCanvas( this.processFileCanvasLink, this.processFileCanvasSize, @@ -88,6 +108,7 @@ export class VerificationResultComponent implements OnChanges { tap((bitmap: ImageBitmap) => { canvasSize.height = (bitmap.height / bitmap.width) * canvasSize.width; canvas.nativeElement.setAttribute('height', canvasSize.height); + canvas.nativeElement.setAttribute('width', 500); this.imgCanvas = bitmap; }), map(imageSize => this.prepareForDraw(imageSize, data, canvasSize, key)), diff --git a/ui/src/app/store/face-verification/selectors.ts b/ui/src/app/store/face-verification/selectors.ts index b3bf0178f4..85f0d86d8b 100644 --- a/ui/src/app/store/face-verification/selectors.ts +++ b/ui/src/app/store/face-verification/selectors.ts @@ -21,9 +21,7 @@ export const selectTestEntityState = createFeatureSelector state.isPending); export const selectFaceData = createSelector(selectTestEntityState, state => (state.model ? state.model.result : null)); -export const selectFiles = createSelector(selectTestEntityState, state => { - return { processFile: state.processFile, checkFile: state.checkFile }; -}); +export const selectFiles = createSelector(selectTestEntityState, state => ({ processFile: state.processFile, checkFile: state.checkFile })); export const selectStateReady = createSelector(selectTestEntityState, state => !state.isPending && !!state?.model?.result[0]); export const selectRequest = createSelector(selectTestEntityState, state => ({ request: state.request, From 2834eb220680854534b7362e7307698e7170be9d Mon Sep 17 00:00:00 2001 From: Pavel Bohdan Date: Thu, 27 May 2021 10:39:02 +0300 Subject: [PATCH 002/837] Added dynamic frames. --- ui/src/app/data/interfaces/frame-size.ts | 22 +++ ui/src/app/data/interfaces/response-result.ts | 44 ++--- .../face-displaying.component.ts | 76 ------- .../face-recognition-container.component.html | 3 +- .../face-recognition-container.component.ts | 1 + .../recognition-result.component.html | 76 ++++--- .../recognition-result.component.scss | 10 +- .../recognition-result.component.ts | 135 +++++-------- .../face-services/face-services.decorators.ts | 39 ---- .../face-services/face-services.directive.ts | 86 ++++++++ .../face-services/face-services.helpers.ts | 4 +- .../face-services/face-services.module.ts | 4 +- ...face-verification-container.component.html | 3 +- ...face-verification-container.component.scss | 3 - .../face-verification-container.component.ts | 10 +- .../verification-result.component.html | 49 ++--- .../verification-result.component.scss | 4 +- .../verification-result.component.ts | 185 +++++++----------- .../app/store/face-verification/selectors.ts | 4 + ui/src/styles/frames.scss | 71 +++++++ 20 files changed, 420 insertions(+), 409 deletions(-) create mode 100644 ui/src/app/data/interfaces/frame-size.ts delete mode 100644 ui/src/app/features/face-services/face-displaying/face-displaying.component.ts delete mode 100644 ui/src/app/features/face-services/face-services.decorators.ts create mode 100644 ui/src/app/features/face-services/face-services.directive.ts create mode 100644 ui/src/styles/frames.scss diff --git a/ui/src/app/data/interfaces/frame-size.ts b/ui/src/app/data/interfaces/frame-size.ts new file mode 100644 index 0000000000..4ae333f9a7 --- /dev/null +++ b/ui/src/app/data/interfaces/frame-size.ts @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +export interface FrameSize { + top: number; + left: number; + width: number; + height: number; +} diff --git a/ui/src/app/data/interfaces/response-result.ts b/ui/src/app/data/interfaces/response-result.ts index c0179740cb..2232826a29 100644 --- a/ui/src/app/data/interfaces/response-result.ts +++ b/ui/src/app/data/interfaces/response-result.ts @@ -15,6 +15,14 @@ */ /* eslint-disable @typescript-eslint/naming-convention */ +export interface BoxSize { + probability: number; + x_max: number; + x_min: number; + y_max: number; + y_min: number; +} + export interface RequestResultRecognition { result: { box: { @@ -33,30 +41,16 @@ export interface RequestResultRecognition { }; } +export interface SourceImageFace { + box: BoxSize; +} + +export interface FaceMatches { + box: BoxSize; + similarity: number; +} + export interface RequestResultVerification { - result: [ - { - source_image_face: { - box: { - probability: number; - x_max: number; - y_max: number; - x_min: number; - y_min: number; - }; - }; - face_matches: [ - { - box: { - probability: number; - x_max: number; - y_max: number; - x_min: number; - y_min: number; - }; - similarity: number; - } - ]; - } - ]; + source_image_face: SourceImageFace; + face_matches: FaceMatches[]; } diff --git a/ui/src/app/features/face-services/face-displaying/face-displaying.component.ts b/ui/src/app/features/face-services/face-displaying/face-displaying.component.ts deleted file mode 100644 index 9e99e792d8..0000000000 --- a/ui/src/app/features/face-services/face-displaying/face-displaying.component.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { AfterViewInit, Component, ElementRef, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewChild } from '@angular/core'; - -import { LoadingPhotoService } from '../../../core/photo-loader/photo-loader.service'; -import { ImageSize } from '../../../data/interfaces/image'; -import { checkFile } from '../face-services.decorators'; -import { filter, map, takeUntil, tap } from 'rxjs/operators'; -import { Subject } from 'rxjs'; -import { recalculateFaceCoordinate } from '../face-services.helpers'; - -@Component({ - selector: 'app-face-displaying', - template: '', -}) -export class FaceDisplayingComponent implements OnInit, OnChanges, AfterViewInit, OnDestroy { - @Input() width: number; - @Input() file: any; - @Input() dataFrames: any[]; - - @ViewChild('canvasElement', { static: false }) canvasElement: ElementRef; - - private unsubscribes$: Subject = new Subject(); - private ctx: CanvasRenderingContext2D; - private sizeImage: ImageSize; - private sizeCanvas: ImageSize; - - private frames: Subject = new Subject(); - - private recal; - - constructor(private loadingPhotoService: LoadingPhotoService) {} - - ngOnChanges(changes: SimpleChanges): void { - this.loadImg(this.file); - this.frames.next(this.dataFrames); - } - - ngOnInit(): void { - this.frames - .pipe( - takeUntil(this.unsubscribes$), - filter(val => val), - map(val => val.map(value => ({ ...value, box: recalculateFaceCoordinate(value.box, this.sizeImage, this.sizeCanvas) }))) - ) - .subscribe(val => { - this.recal = val; - }); - } - - ngAfterViewInit(): void { - this.ctx = this.canvasElement.nativeElement.getContext('2d'); - } - - @checkFile - loadImg(file: File): void { - this.loadingPhotoService - .loader(file) - .pipe( - takeUntil(this.unsubscribes$), - tap(({ width, height }: ImageBitmap) => { - this.sizeImage = { width, height }; - this.sizeCanvas = { width: this.width, height: (height / width) * this.width }; - this.canvasElement.nativeElement.setAttribute('height', this.sizeCanvas.height); - }) - ) - .subscribe((img: ImageBitmap) => this.displayImg(img, this.sizeCanvas)); - } - - displayImg(img: ImageBitmap, size: ImageSize): void { - this.ctx.drawImage(img, 0, 0, size.width, size.height); - } - - ngOnDestroy(): void { - this.unsubscribes$.next(); - this.unsubscribes$.complete(); - } -} diff --git a/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.html b/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.html index a11b613093..f0b76da18a 100644 --- a/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.html +++ b/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.html @@ -14,8 +14,6 @@ ~ permissions and limitations under the License. --> - -
@@ -26,6 +24,7 @@ [requestInfo]="requestInfo$ | async" [printData]="data$ | async" [type]="type" + (selectFile)="recognizeFace($event)" > diff --git a/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.ts b/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.ts index b173dc78a3..f4b64d7617 100644 --- a/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.ts +++ b/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.ts @@ -72,6 +72,7 @@ export class FaceRecognitionContainerComponent implements OnInit, OnDestroy { } else if (file.size > MAX_IMAGE_SIZE) { this.snackBarService.openNotification({ messageText: 'face_recognition_container.file_size_error', type: 'error' }); } else { + console.log(file); this.store.dispatch(recognizeFace({ file })); } } diff --git a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.html b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.html index 60777622cd..fa6dc1d165 100644 --- a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.html +++ b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.html @@ -13,34 +13,56 @@ ~ or implied. See the License for the specific language governing ~ permissions and limitations under the License. --> - -
- - - -
- - - - {{ 'test_model.request' | translate }} - - -
-
{{ requestInfo.request }}
-
-
+
+
+ +
-
- - - - {{ 'test_model.response' | translate }} - - -
-
{{ formattedResult }}
+ + +
+
+ + +
+
+
+
{{ frame.similarity }}
+
+
+
- -
+
+ + + +
+ + + + {{ 'test_model.request' | translate }} + + +
+
{{ requestInfo.request }}
+
+
+
+
+ + + + {{ 'test_model.response' | translate }} + + +
+
{{ formattedResult }}
+
+
+
+
diff --git a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.scss b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.scss index 98c1df0b20..b519079164 100644 --- a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.scss +++ b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.scss @@ -13,8 +13,10 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ - @import "media.scss"; +@import "frames.scss"; + +@include frames(); .result { margin-top: 24px; @@ -22,7 +24,7 @@ grid-template-columns: 1fr 1fr; width: 100%; grid-gap: 20px; - grid-template-areas:"process-img process-img" "request response"; + grid-template-areas:"drag-n-drop drag-n-drop" "process-img process-img" "request response"; @include mobile-portrait { grid-template-columns: 1fr; @@ -34,6 +36,10 @@ grid-template-areas:"process-img" "request" "response"; } + .drag-n-drop { + grid-area: drag-n-drop; + } + .process-img { grid-area: process-img; text-align: center; diff --git a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.ts b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.ts index fe214a4469..d82d7b2d1b 100644 --- a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.ts +++ b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.ts @@ -13,130 +13,89 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ +import { Component, ElementRef, Input, ViewChild, OnChanges, SimpleChanges, Output, EventEmitter, OnDestroy } from '@angular/core'; -import { Component, ElementRef, Input, ViewChild, OnChanges, SimpleChanges } from '@angular/core'; -import { Observable } from 'rxjs'; -import { first, map, tap } from 'rxjs/operators'; +import { map, takeUntil, tap } from 'rxjs/operators'; +import { ReplaySubject, Subject } from 'rxjs'; -import { ServiceTypes } from '../../../../data/enums/service-types.enum'; -import { recalculateFaceCoordinate, resultRecognitionFormatter, createDefaultImage } from '../../face-services.helpers'; import { RequestResultRecognition } from '../../../../data/interfaces/response-result'; import { RequestInfo } from '../../../../data/interfaces/request-info'; import { LoadingPhotoService } from '../../../../core/photo-loader/photo-loader.service'; import { ImageSize } from '../../../../data/interfaces/image'; +import { recalculateFaceCoordinate, resultRecognitionFormatter } from '../../face-services.helpers'; @Component({ selector: 'app-recognition-result', templateUrl: './recognition-result.component.html', styleUrls: ['./recognition-result.component.scss'], }) -export class RecognitionResultComponent implements OnChanges { +export class RecognitionResultComponent implements OnChanges, OnDestroy { @Input() file: File; @Input() requestInfo: RequestInfo; @Input() printData: RequestResultRecognition; @Input() isLoaded: boolean; @Input() type: string; - @ViewChild('canvasElement') set canvasElement(canvas: ElementRef) { - if (canvas) { - this.myCanvas = canvas; + @Output() selectFile = new EventEmitter(); - if (this.printData && this.myCanvas) { - this.printResult(this.printData).pipe(first()).subscribe(); - } - } + @ViewChild('canvasElement') set canvasElement(canvas: ElementRef) { + this.myCanvas = canvas; } - canvasSize: ImageSize = { width: 500, height: null }; - myCanvas: ElementRef; - faceDescriptionHeight = 25; + private myCanvas: ElementRef; + private unsubscribe: Subject = new Subject(); + private sizes: ReplaySubject = new ReplaySubject(); + + filePrintData: any; + widthCanvas = 500; formattedResult: string; - private imgCanvas: ImageBitmap; constructor(private loadingPhotoService: LoadingPhotoService) {} ngOnChanges(changes: SimpleChanges) { - if (changes?.requestInfo?.currentValue) { - this.formattedResult = resultRecognitionFormatter(this.requestInfo.response); - } + if ('file' in changes) this.loadPhoto(this.file, this.myCanvas); + if ('printData' in changes) this.getFrames(this.printData); + if (!!this.requestInfo) this.formattedResult = resultRecognitionFormatter(this.requestInfo.response); } - printResult(result: any): Observable { - return this.loadingPhotoService.loader(this.file).pipe( - tap((bitmap: ImageBitmap) => { - this.canvasSize.height = (bitmap.height / bitmap.width) * this.canvasSize.width; - this.myCanvas.nativeElement.setAttribute('height', this.canvasSize.height); - this.imgCanvas = bitmap; - }), - map(imageSize => this.prepareForDraw(imageSize, result)), - map(preparedImageData => this.drawCanvas(preparedImageData)) - ); - } - - private prepareForDraw(size, rawData): Observable { - return rawData.map(value => ({ - box: recalculateFaceCoordinate(value.box, size, this.canvasSize, this.faceDescriptionHeight), - subjects: value.subjects, - })); - } + getFrames(printData: any): void { + if (!printData) return; - private createRecognitionImage(ctx, box, face) { - ctx = createDefaultImage(ctx, box); - ctx.fillStyle = 'green'; - ctx.fillRect(box.x_min, box.y_min - this.faceDescriptionHeight, box.x_max - box.x_min, this.faceDescriptionHeight); - ctx.fillRect(box.x_min, box.y_max, box.x_max - box.x_min, this.faceDescriptionHeight); - ctx.fillStyle = 'white'; - ctx.fillText(face.similarity, box.x_min + 10, box.y_max + 20); - ctx.fillText(face.subject, box.x_min + 10, box.y_min - 5); + this.sizes.pipe(takeUntil(this.unsubscribe)).subscribe(size => { + this.filePrintData = this.recalculateFrames(printData, size.img, size.sizeCanvas); + }); } - private createDetectionImage(ctx, box) { - ctx = createDefaultImage(ctx, box); - ctx.fillStyle = 'green'; - ctx.fillRect(box.x_min, box.y_max, box.x_max - box.x_min, this.faceDescriptionHeight); - ctx.fillStyle = 'white'; - ctx.fillText(box.probability.toFixed(4), box.x_min + 10, box.y_max + 20); + recalculateFrames(data: any[], img, sizeCanvas): any { + return data.map(val => ({ ...val, box: recalculateFaceCoordinate(val.box, img, sizeCanvas) })); } - /* - * Make canvas and draw face and info on image. - * - * @preparedData prepared box data and subjects. - */ - drawCanvas(preparedData) { - switch (this.type) { - case ServiceTypes.Recognition: - this.drawRecognitionCanvas(preparedData); - break; - - case ServiceTypes.Detection: - this.drawDetectionCanvas(preparedData); - break; - } + loadPhoto(file: File, canvas: ElementRef): void { + if (!file) return; + + this.loadingPhotoService + .loader(file) + .pipe( + takeUntil(this.unsubscribe), + map((img: ImageBitmap) => ({ + img, + sizeCanvas: { width: this.widthCanvas, height: (img.height / img.width) * this.widthCanvas }, + })), + tap(({ sizeCanvas }) => canvas.nativeElement.setAttribute('height', sizeCanvas.height)) + ) + .subscribe(value => { + this.displayPhoto(value.img, value.sizeCanvas, canvas); + this.sizes.next(value); + }); } - createImage(draw) { - const ctx: CanvasRenderingContext2D = this.myCanvas.nativeElement.getContext('2d'); - ctx.drawImage(this.imgCanvas, 0, 0, this.canvasSize.width, this.canvasSize.height); - draw(ctx); + displayPhoto(img: ImageBitmap, size: ImageSize, canvas: ElementRef): void { + const ctx = canvas.nativeElement.getContext('2d'); + ctx.drawImage(img, 0, 0, size.width, size.height); } - drawRecognitionCanvas(data) { - this.createImage(ctx => { - for (const value of data) { - // eslint-disable-next-line @typescript-eslint/naming-convention - const resultFace = value.subjects.length > 0 ? value.subjects[0] : { subject: undefined, similarity: 0 }; - this.createRecognitionImage(ctx, value.box, resultFace); - } - }); - } - - drawDetectionCanvas(data) { - this.createImage(ctx => { - for (const value of data) { - // eslint-disable-next-line @typescript-eslint/naming-convention - this.createDetectionImage(ctx, value.box); - } - }); + ngOnDestroy(): void { + this.unsubscribe.next(); + this.unsubscribe.complete(); } } diff --git a/ui/src/app/features/face-services/face-services.decorators.ts b/ui/src/app/features/face-services/face-services.decorators.ts deleted file mode 100644 index 6317f3f153..0000000000 --- a/ui/src/app/features/face-services/face-services.decorators.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2020 the original author or authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ -import { SimpleChanges } from '@angular/core'; - -export function checkFile(targetClass, functionName: string, descriptor) { - const source = descriptor.value; - descriptor.value = function (changes: SimpleChanges) { - if (changes) return source.call(this, changes); - }; - return descriptor; -} - -export function recalculateCoordinate(inputs: string[]) { - return function (targetClass, functionName: string, descriptor) { - const source = descriptor.set; - - descriptor.set = function (changes: SimpleChanges) { - inputs.forEach(input => { - if (!!changes[input]) { - } - }); - return source.call(this, changes); - }; - return descriptor; - }; -} diff --git a/ui/src/app/features/face-services/face-services.directive.ts b/ui/src/app/features/face-services/face-services.directive.ts new file mode 100644 index 0000000000..98bec8d148 --- /dev/null +++ b/ui/src/app/features/face-services/face-services.directive.ts @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +import { + AfterContentInit, + ContentChild, + Directive, + ElementRef, + HostListener, + Input, + OnChanges, + Renderer2, + SimpleChanges, +} from '@angular/core'; + +import { FrameSize } from '../../data/interfaces/frame-size'; +import { BoxSize } from '../../data/interfaces/response-result'; + +@Directive({ + selector: '[appFrameTooltip]', +}) +export class FaceServicesDirective implements OnChanges, AfterContentInit { + @Input('appFrameTooltip') dataFrames: any; + + @ContentChild('boxFace') boxFace: ElementRef; + @ContentChild('boxInfo') boxInfo: ElementRef; + + private borderColor = 'rgba(255, 255, 255, 0.5)'; + private borderColorActive = '#40BFEF'; + private size: FrameSize; + + constructor(private element: ElementRef, private renderer: Renderer2) {} + + ngOnChanges(changes: SimpleChanges): void { + // eslint-disable-next-line @typescript-eslint/naming-convention + const { x_max, x_min, y_max, y_min } = changes.dataFrames.currentValue as BoxSize; + this.size = { + top: y_min, + left: x_min, + width: x_max - x_min, + height: y_max - y_min, + }; + } + + ngAfterContentInit(): void { + if (this.boxInfo) this.renderer.setStyle(this.boxInfo.nativeElement, 'display', 'none'); + + this.addFrame(this.size); + this.renderer.setStyle(this.boxFace.nativeElement, 'borderColor', this.borderColor); + this.renderer.setStyle(this.element.nativeElement, 'zIndex', 1); + } + + @HostListener('mouseenter') onMouseEnter() { + if (!!this.boxInfo) { + this.renderer.setStyle(this.boxInfo.nativeElement, 'display', 'block'); + this.renderer.setStyle(this.boxFace.nativeElement, 'borderColor', this.borderColorActive); + } + + this.renderer.setStyle(this.element.nativeElement, 'zIndex', 2); + } + + @HostListener('mouseleave') onMouseLeave() { + this.renderer.setStyle(this.boxFace.nativeElement, 'borderColor', this.borderColor); + this.renderer.setStyle(this.element.nativeElement, 'zIndex', 1); + + if (!!this.boxInfo) { + this.renderer.setStyle(this.boxInfo.nativeElement, 'display', 'none'); + } + } + + addFrame(size: FrameSize): void { + Object.keys(size).forEach(key => this.renderer.setStyle(this.element.nativeElement, key, `${size[key]}px`)); + } +} diff --git a/ui/src/app/features/face-services/face-services.helpers.ts b/ui/src/app/features/face-services/face-services.helpers.ts index 1f65bc5d88..d9a34f2af2 100644 --- a/ui/src/app/features/face-services/face-services.helpers.ts +++ b/ui/src/app/features/face-services/face-services.helpers.ts @@ -77,8 +77,8 @@ export const recalculateFaceCoordinate = (box: any, imageSize: ImageSize, sizeTo /* eslint-disable @typescript-eslint/naming-convention */ x_max: box.x_max / divideWidth > sizeToCalc.width ? sizeToCalc.width : box.x_max / divideWidth, x_min: box.x_min / divideWidth, - y_max: box.y_max / divideHeight > sizeToCalc.height - yAxisPadding ? sizeToCalc.height - yAxisPadding : box.y_max / divideHeight, - y_min: box.y_min / divideHeight > yAxisPadding ? box.y_min / divideHeight : yAxisPadding, + y_max: box.y_max / divideHeight > sizeToCalc.height ? sizeToCalc.height : box.y_max / divideHeight, + y_min: box.y_min / divideHeight, /* eslint-enable @typescript-eslint/naming-convention */ }; }; diff --git a/ui/src/app/features/face-services/face-services.module.ts b/ui/src/app/features/face-services/face-services.module.ts index ed3aface2f..96e2d48b1b 100644 --- a/ui/src/app/features/face-services/face-services.module.ts +++ b/ui/src/app/features/face-services/face-services.module.ts @@ -26,15 +26,15 @@ import { FaceVerificationContainerComponent } from './face-verification/face-ver import { RecognitionResultComponent } from './face-recognition/recognition-result/recognition-result.component'; import { VerificationResultComponent } from './face-verification/verification-result/verification-result.component'; import { MatCardModule } from '@angular/material/card'; -import { FaceDisplayingComponent } from './face-displaying/face-displaying.component'; +import { FaceServicesDirective } from './face-services.directive'; @NgModule({ declarations: [ FaceRecognitionContainerComponent, FaceVerificationContainerComponent, - FaceDisplayingComponent, RecognitionResultComponent, VerificationResultComponent, + FaceServicesDirective, ], imports: [CommonModule, DragNDropModule, SpinnerModule, MatExpansionModule, TranslateModule, MatCardModule], providers: [FaceRecognitionService], diff --git a/ui/src/app/features/face-services/face-verification/face-verification-container.component.html b/ui/src/app/features/face-services/face-verification/face-verification-container.component.html index c5533790d2..3f03cd07d3 100644 --- a/ui/src/app/features/face-services/face-verification/face-verification-container.component.html +++ b/ui/src/app/features/face-services/face-verification/face-verification-container.component.html @@ -4,9 +4,10 @@
; + checkFile$: Observable; data$: Observable; - files$: Observable; requestInfo$: Observable; pending$: Observable; isLoaded$: Observable; @@ -45,8 +48,9 @@ export class FaceVerificationContainerComponent implements OnInit, OnDestroy { constructor(private store: Store, private snackBarService: SnackBarService) {} ngOnInit() { + this.processFile$ = this.store.select(selectProcessFile).pipe(map(obj => obj.processFile)); + this.checkFile$ = this.store.select(selectCheckFile).pipe(map(obj => obj.checkFile)); this.data$ = this.store.select(selectFaceData); - this.files$ = this.store.select(selectFiles); this.requestInfo$ = this.store.select(selectRequest); this.pending$ = this.store.select(selectTestIsPending); this.isLoaded$ = this.store.select(selectStateReady); diff --git a/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.html b/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.html index 8cd199b2e1..4323684ebc 100644 --- a/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.html +++ b/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.html @@ -17,38 +17,43 @@
- - - - - - + +
+
+ + +
+
+
+
+
+
- - - - - - + +
+
+ + +
+
+
+
{{ frame.similarity }}
+
+
+
+
+
diff --git a/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.scss b/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.scss index b2984bacca..cb36c1b02f 100644 --- a/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.scss +++ b/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.scss @@ -13,8 +13,10 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ - @import "media.scss"; +@import "frames.scss"; + +@include frames(); .result { display: grid; diff --git a/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.ts b/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.ts index c3eee44bb0..78dc82eeb3 100644 --- a/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.ts +++ b/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.ts @@ -13,158 +13,111 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ +import { Component, Input, OnChanges, SimpleChanges, Output, EventEmitter, ViewChild, ElementRef, OnDestroy } from '@angular/core'; -import { Component, ElementRef, Input, ViewChild, OnChanges, SimpleChanges, Output, EventEmitter } from '@angular/core'; -import { BehaviorSubject, Observable } from 'rxjs'; -import { first, map, tap } from 'rxjs/operators'; -import { recalculateFaceCoordinate, resultRecognitionFormatter, createDefaultImage } from '../../face-services.helpers'; -import { RequestResultVerification } from '../../../../data/interfaces/response-result'; +import { ReplaySubject, Subject } from 'rxjs'; +import { map, takeUntil, tap } from 'rxjs/operators'; + +import { FaceMatches, RequestResultVerification, SourceImageFace } from '../../../../data/interfaces/response-result'; import { RequestInfo } from '../../../../data/interfaces/request-info'; -import { VerificationServiceFields } from '../../../../data/enums/verification-service.enum'; import { LoadingPhotoService } from '../../../../core/photo-loader/photo-loader.service'; import { ImageSize } from '../../../../data/interfaces/image'; +import { recalculateFaceCoordinate, resultRecognitionFormatter } from '../../face-services.helpers'; + +enum PrintDataKeys { + SourceFace = 'source_image_face', + MatchesFace = 'face_matches', +} @Component({ selector: 'app-verification-result', templateUrl: './verification-result.component.html', styleUrls: ['./verification-result.component.scss'], }) -export class VerificationResultComponent implements OnChanges { +export class VerificationResultComponent implements OnChanges, OnDestroy { + @Input() processFile: File; + @Input() checkFile: File; + @Input() requestInfo: RequestInfo; - @Input() printData: RequestResultVerification; - @Input() files: any; + @Input() printData: RequestResultVerification[]; + @Input() isLoaded: boolean; @Input() pending: boolean; + @Output() selectProcessFile = new EventEmitter(); @Output() selectCheckFile = new EventEmitter(); @ViewChild('processFileCanvasElement') set processFileCanvasElement(canvas: ElementRef) { this.processFileCanvasLink = canvas; } - @ViewChild('checkFileCanvasElement') set checkFileCanvasElement(canvas: ElementRef) { this.checkFileCanvasLink = canvas; } - processFileCanvasSize: ImageSize = { width: 500, height: null }; - checkFileCanvasSize: ImageSize = { width: 500, height: null }; - faceDescriptionHeight = 25; - formattedResult: string; - - private processFileCanvasLink: any = null; - private checkFileCanvasLink: any = null; - private imgCanvas: ImageBitmap; + private processFileCanvasLink: ElementRef; + private checkFileCanvasLink: ElementRef; + private unsubscribe: Subject = new Subject(); + private sizes: ReplaySubject<{ key: PrintDataKeys; img: ImageBitmap; sizeCanvas: ImageSize }> = new ReplaySubject(); - //New------------------------------------------------------------------------ - firstProcess: BehaviorSubject; - firstProcessFrames: BehaviorSubject; - - secondProcess: BehaviorSubject; - secondProcessFrames: BehaviorSubject; - //New------------------------------------------------------------------------ + widthCanvas = 500; + processFilePrintData: FaceMatches[]; + checkFilePrintData: SourceImageFace[]; + formattedResult: string; constructor(private loadingPhotoService: LoadingPhotoService) {} - ngOnChanges(changes: SimpleChanges) { - if (!changes?.files?.currentValue) return; - - //New------------------------------------------------------------------------ - if (changes?.printData?.currentValue) { - this.firstProcessFrames = new BehaviorSubject(changes?.printData?.currentValue[0].face_matches); - this.secondProcessFrames = new BehaviorSubject([changes?.printData?.currentValue[0].source_image_face]); - } - - this.formattedResult = resultRecognitionFormatter(this.requestInfo.response); - - if (changes.files.currentValue.checkFile) { - //New------------------------------------------------------------------------ - if (!this.secondProcess) this.secondProcess = new BehaviorSubject(changes.files.currentValue.checkFile); - - this.refreshCanvas( - this.checkFileCanvasLink, - this.checkFileCanvasSize, - changes.files.currentValue.checkFile, - this.printData, - VerificationServiceFields.CheckFileData - ); - } - - if (changes.files.currentValue.processFile) { - //New---------------------------------------------------------------------------------------------- - if (!this.firstProcess) this.firstProcess = new BehaviorSubject(changes.files.currentValue.processFile); - - this.refreshCanvas( - this.processFileCanvasLink, - this.processFileCanvasSize, - changes.files.currentValue.processFile, - this.printData, - VerificationServiceFields.ProcessFileData - ); - } + ngOnChanges(changes: SimpleChanges): void { + if ('processFile' in changes) this.loadPhoto(this.processFile, this.processFileCanvasLink, PrintDataKeys.SourceFace); + if ('checkFile' in changes) this.loadPhoto(this.checkFile, this.checkFileCanvasLink, PrintDataKeys.MatchesFace); + if ('printData' in changes) this.getFrames(this.printData); + if (!!this.requestInfo) this.formattedResult = resultRecognitionFormatter(this.requestInfo.response); } - printResult(canvas: ElementRef, canvasSize: any, file: any, data, key: string): Observable { - return this.loadingPhotoService.loader(file).pipe( - tap((bitmap: ImageBitmap) => { - canvasSize.height = (bitmap.height / bitmap.width) * canvasSize.width; - canvas.nativeElement.setAttribute('height', canvasSize.height); - canvas.nativeElement.setAttribute('width', 500); - this.imgCanvas = bitmap; - }), - map(imageSize => this.prepareForDraw(imageSize, data, canvasSize, key)), - map(preparedImageData => this.drawCanvas(canvas, preparedImageData, file, canvasSize)) - ); - } - - private refreshCanvas(canvas, canvasSize, file, data, field) { - this.printResult(canvas, canvasSize, file, data, field).pipe(first()).subscribe(); - } - - private prepareForDraw(size, rawData, canvasSize, key): Observable { - return ( - rawData && - this.getBox(rawData, key).map(value => ({ - box: recalculateFaceCoordinate(value.box, size, canvasSize, this.faceDescriptionHeight), - similarity: value.similarity, - })) - ); + getFrames(printData: RequestResultVerification[]): void { + if (!printData) return; + + this.sizes.pipe(takeUntil(this.unsubscribe)).subscribe(size => { + switch (size.key) { + case PrintDataKeys.MatchesFace: + this.processFilePrintData = this.recalculateFrames(printData[0][size.key], size.img, size.sizeCanvas) as FaceMatches[]; + return; + case PrintDataKeys.SourceFace: + this.checkFilePrintData = this.recalculateFrames([printData[0][size.key]], size.img, size.sizeCanvas) as SourceImageFace[]; + return; + } + }); } - private getBox(rawData, key) { - return rawData.reduce((arr, value) => (value[key] instanceof Array ? [...arr, ...value[key]] : [...arr, value[key]]), []); + recalculateFrames(data: any[], img, sizeCanvas): SourceImageFace[] | FaceMatches[] { + return data.map(val => ({ ...val, box: recalculateFaceCoordinate(val.box, img, sizeCanvas) })); } - private createVerificationImage(ctx, box, face) { - const description = face.similarity ? this.faceDescriptionHeight : null; - - ctx = createDefaultImage(ctx, box); - ctx.fillStyle = 'green'; - ctx.fillRect(box.x_min, box.y_max, box.x_max - box.x_min, description); - - if (!!description) { - ctx.fillStyle = 'white'; - ctx.fillText(face.similarity, box.x_min + 10, box.y_max + 20); - } + loadPhoto(file: File, canvas: ElementRef, key: PrintDataKeys): void { + if (!file) return; + + this.loadingPhotoService + .loader(file) + .pipe( + takeUntil(this.unsubscribe), + map((img: ImageBitmap) => ({ + img, + sizeCanvas: { width: this.widthCanvas, height: (img.height / img.width) * this.widthCanvas }, + })), + tap(({ sizeCanvas }) => canvas.nativeElement.setAttribute('height', sizeCanvas.height)) + ) + .subscribe(value => { + this.displayPhoto(value.img, value.sizeCanvas, canvas); + this.sizes.next({ key, ...value }); + }); } - /* - * Make canvas and draw facanvasSizece and info on image. - * - * @preparedData prepared box data and faces. - */ - drawCanvas(canvas, data, file, canvasSize) { - this.createImage(canvas, file, canvasSize, ctx => { - if (!data) return; - for (const value of data) { - // eslint-disable-next-line @typescript-eslint/naming-convention - this.createVerificationImage(ctx, value.box, value); - } - }); + displayPhoto(img: ImageBitmap, size: ImageSize, canvas: ElementRef): void { + const ctx = canvas.nativeElement.getContext('2d'); + ctx.drawImage(img, 0, 0, size.width, size.height); } - createImage(canvas, file, canvasSize, draw) { - const ctx: CanvasRenderingContext2D = canvas.nativeElement.getContext('2d'); - ctx.drawImage(this.imgCanvas, 0, 0, canvasSize.width, canvasSize.height); - draw(ctx); + ngOnDestroy(): void { + this.unsubscribe.next(); + this.unsubscribe.complete(); } } diff --git a/ui/src/app/store/face-verification/selectors.ts b/ui/src/app/store/face-verification/selectors.ts index 85f0d86d8b..77f29a21ae 100644 --- a/ui/src/app/store/face-verification/selectors.ts +++ b/ui/src/app/store/face-verification/selectors.ts @@ -21,6 +21,10 @@ export const selectTestEntityState = createFeatureSelector state.isPending); export const selectFaceData = createSelector(selectTestEntityState, state => (state.model ? state.model.result : null)); + +export const selectProcessFile = createSelector(selectTestEntityState, state => ({ processFile: state.processFile })); +export const selectCheckFile = createSelector(selectTestEntityState, state => ({ checkFile: state.checkFile })); + export const selectFiles = createSelector(selectTestEntityState, state => ({ processFile: state.processFile, checkFile: state.checkFile })); export const selectStateReady = createSelector(selectTestEntityState, state => !state.isPending && !!state?.model?.result[0]); export const selectRequest = createSelector(selectTestEntityState, state => ({ diff --git a/ui/src/styles/frames.scss b/ui/src/styles/frames.scss new file mode 100644 index 0000000000..632d7e358c --- /dev/null +++ b/ui/src/styles/frames.scss @@ -0,0 +1,71 @@ +/*! +* Copyright (c) 2020 the original author or authors +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +* or implied. See the License for the specific language governing +* permissions and limitations under the License. +*/ +@import "colors.scss"; + +@mixin frames() { + + .face-displaying { + display: flex; + justify-content: center; + + &--wrapper { + position: relative; + + .face-box { + position: absolute; + + &--face { + width: 100%; + height: 100%; + box-sizing: border-box; + border: 4px solid; + border-radius: 12px; + } + + &--info { + position: relative; + margin-top: 18px; + padding: 8px; + max-width: 170px; + min-width: 70px; + max-height: 80px; + background-color: $light-blue; + border-radius: 6px; + color: $white; + left: 50%; + transform: translate(-50%, 0); + + &:before { + position: absolute; + content: ''; + top: 8px; + left: 50%; + width: 24px; + height: 24px; + background: #40BFEF; + transform: translate(-50%, -50%) rotate(45deg); + z-index: -1; + } + + &_similarity { + font-size: 14px; + line-height: 14px; + } + } + } + } + } +} From d52ac7cd8fd0afbdafd0738a2ff6b29cb30cfef9 Mon Sep 17 00:00:00 2001 From: Pavel Bohdan Date: Fri, 28 May 2021 11:37:55 +0300 Subject: [PATCH 003/837] Added dynamic frames (ver-2). --- ui/src/app/data/interfaces/image.ts | 15 +++++++ ui/src/app/data/interfaces/response-result.ts | 22 +++------ .../face-recognition-container.component.html | 7 ++- .../face-recognition-container.component.ts | 1 - .../recognition-result.component.html | 35 +++++++++++---- .../recognition-result.component.ts | 27 ++++++----- .../face-services/face-services.directive.ts | 45 ++++++++++++------- .../face-services/face-services.helpers.ts | 17 +------ .../verification-result.component.html | 4 +- ui/src/styles/frames.scss | 19 ++++++-- 10 files changed, 115 insertions(+), 77 deletions(-) diff --git a/ui/src/app/data/interfaces/image.ts b/ui/src/app/data/interfaces/image.ts index 2afbef6461..2f3cb2faa8 100644 --- a/ui/src/app/data/interfaces/image.ts +++ b/ui/src/app/data/interfaces/image.ts @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ export interface ImageSize { width: any; height: any; diff --git a/ui/src/app/data/interfaces/response-result.ts b/ui/src/app/data/interfaces/response-result.ts index 2232826a29..0f84995189 100644 --- a/ui/src/app/data/interfaces/response-result.ts +++ b/ui/src/app/data/interfaces/response-result.ts @@ -23,22 +23,14 @@ export interface BoxSize { y_min: number; } +export interface BoxSubjects { + subject: string; + similarity: number; +} + export interface RequestResultRecognition { - result: { - box: { - probability: number; - x_max: number; - x_min: number; - y_max: number; - y_min: number; - subjects: [ - { - subject: string; - similarity: number; - } - ]; - }; - }; + box: BoxSize; + subjects: BoxSubjects[]; } export interface SourceImageFace { diff --git a/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.html b/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.html index f0b76da18a..e310b0ba3b 100644 --- a/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.html +++ b/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.html @@ -14,10 +14,6 @@ ~ permissions and limitations under the License. --> -
- -
- +
+ +
diff --git a/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.ts b/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.ts index f4b64d7617..b173dc78a3 100644 --- a/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.ts +++ b/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.ts @@ -72,7 +72,6 @@ export class FaceRecognitionContainerComponent implements OnInit, OnDestroy { } else if (file.size > MAX_IMAGE_SIZE) { this.snackBarService.openNotification({ messageText: 'face_recognition_container.file_size_error', type: 'error' }); } else { - console.log(file); this.store.dispatch(recognizeFace({ file })); } } diff --git a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.html b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.html index fa6dc1d165..32124eb78e 100644 --- a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.html +++ b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.html @@ -15,30 +15,45 @@ -->
- - +
-
+ + -
+
+
+
-
{{ frame.similarity }}
+ + + +
{{ value.subject }}
+
{{ value.similarity }}
+
+ + +
Unknown
+
+ +
+ + +
{{ frame.box.probability }}
+
- + +
@@ -51,6 +66,7 @@
+
@@ -63,6 +79,7 @@
+
diff --git a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.ts b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.ts index d82d7b2d1b..26d20a5e18 100644 --- a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.ts +++ b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.ts @@ -22,6 +22,7 @@ import { RequestResultRecognition } from '../../../../data/interfaces/response-r import { RequestInfo } from '../../../../data/interfaces/request-info'; import { LoadingPhotoService } from '../../../../core/photo-loader/photo-loader.service'; import { ImageSize } from '../../../../data/interfaces/image'; +import { ServiceTypes } from '../../../../data/enums/service-types.enum'; import { recalculateFaceCoordinate, resultRecognitionFormatter } from '../../face-services.helpers'; @Component({ @@ -32,7 +33,7 @@ import { recalculateFaceCoordinate, resultRecognitionFormatter } from '../../fac export class RecognitionResultComponent implements OnChanges, OnDestroy { @Input() file: File; @Input() requestInfo: RequestInfo; - @Input() printData: RequestResultRecognition; + @Input() printData: RequestResultRecognition[]; @Input() isLoaded: boolean; @Input() type: string; @@ -44,11 +45,12 @@ export class RecognitionResultComponent implements OnChanges, OnDestroy { private myCanvas: ElementRef; private unsubscribe: Subject = new Subject(); - private sizes: ReplaySubject = new ReplaySubject(); + private sizes: ReplaySubject<{ img: ImageBitmap; sizeCanvas: ImageSize }> = new ReplaySubject(); - filePrintData: any; - widthCanvas = 500; formattedResult: string; + filePrintData: RequestResultRecognition[]; + widthCanvas = 500; + types = ServiceTypes; constructor(private loadingPhotoService: LoadingPhotoService) {} @@ -58,7 +60,7 @@ export class RecognitionResultComponent implements OnChanges, OnDestroy { if (!!this.requestInfo) this.formattedResult = resultRecognitionFormatter(this.requestInfo.response); } - getFrames(printData: any): void { + getFrames(printData: RequestResultRecognition[]): void { if (!printData) return; this.sizes.pipe(takeUntil(this.unsubscribe)).subscribe(size => { @@ -66,8 +68,8 @@ export class RecognitionResultComponent implements OnChanges, OnDestroy { }); } - recalculateFrames(data: any[], img, sizeCanvas): any { - return data.map(val => ({ ...val, box: recalculateFaceCoordinate(val.box, img, sizeCanvas) })); + recalculateFrames(data: Type, img, sizeCanvas): Type { + return data.map(val => ({ ...val, box: recalculateFaceCoordinate(val.box, img, sizeCanvas) })) as Type; } loadPhoto(file: File, canvas: ElementRef): void { @@ -77,10 +79,13 @@ export class RecognitionResultComponent implements OnChanges, OnDestroy { .loader(file) .pipe( takeUntil(this.unsubscribe), - map((img: ImageBitmap) => ({ - img, - sizeCanvas: { width: this.widthCanvas, height: (img.height / img.width) * this.widthCanvas }, - })), + map( + (img: ImageBitmap) => + ({ + img, + sizeCanvas: { width: this.widthCanvas, height: (img.height / img.width) * this.widthCanvas }, + } as { img: ImageBitmap; sizeCanvas: ImageSize }) + ), tap(({ sizeCanvas }) => canvas.nativeElement.setAttribute('height', sizeCanvas.height)) ) .subscribe(value => { diff --git a/ui/src/app/features/face-services/face-services.directive.ts b/ui/src/app/features/face-services/face-services.directive.ts index 98bec8d148..4d086cc644 100644 --- a/ui/src/app/features/face-services/face-services.directive.ts +++ b/ui/src/app/features/face-services/face-services.directive.ts @@ -32,7 +32,11 @@ import { BoxSize } from '../../data/interfaces/response-result'; selector: '[appFrameTooltip]', }) export class FaceServicesDirective implements OnChanges, AfterContentInit { - @Input('appFrameTooltip') dataFrames: any; + @Input() dataFrames: any; + @Input() + set framesQuantity(data: number) { + this.activeFrame = data > 1; + } @ContentChild('boxFace') boxFace: ElementRef; @ContentChild('boxInfo') boxInfo: ElementRef; @@ -40,6 +44,15 @@ export class FaceServicesDirective implements OnChanges, AfterContentInit { private borderColor = 'rgba(255, 255, 255, 0.5)'; private borderColorActive = '#40BFEF'; private size: FrameSize; + private activeFrame = false; + + @HostListener('mouseenter') onMouseEnter() { + if (this.activeFrame) this.styleActiveFrame(); + } + + @HostListener('mouseleave') onMouseLeave() { + if (this.activeFrame) this.styleNotActiveFrame(); + } constructor(private element: ElementRef, private renderer: Renderer2) {} @@ -55,32 +68,34 @@ export class FaceServicesDirective implements OnChanges, AfterContentInit { } ngAfterContentInit(): void { - if (this.boxInfo) this.renderer.setStyle(this.boxInfo.nativeElement, 'display', 'none'); - this.addFrame(this.size); - this.renderer.setStyle(this.boxFace.nativeElement, 'borderColor', this.borderColor); - this.renderer.setStyle(this.element.nativeElement, 'zIndex', 1); + + if (this.activeFrame) { + this.styleNotActiveFrame(); + } else { + this.styleActiveFrame(); + } } - @HostListener('mouseenter') onMouseEnter() { + addFrame(size: FrameSize): void { + Object.keys(size).forEach(key => this.renderer.setStyle(this.element.nativeElement, key, `${size[key]}px`)); + } + + styleActiveFrame(): void { + this.renderer.setStyle(this.element.nativeElement, 'zIndex', 2); + this.renderer.setStyle(this.boxFace.nativeElement, 'borderColor', this.borderColorActive); + if (!!this.boxInfo) { this.renderer.setStyle(this.boxInfo.nativeElement, 'display', 'block'); - this.renderer.setStyle(this.boxFace.nativeElement, 'borderColor', this.borderColorActive); } - - this.renderer.setStyle(this.element.nativeElement, 'zIndex', 2); } - @HostListener('mouseleave') onMouseLeave() { - this.renderer.setStyle(this.boxFace.nativeElement, 'borderColor', this.borderColor); + styleNotActiveFrame(): void { this.renderer.setStyle(this.element.nativeElement, 'zIndex', 1); + this.renderer.setStyle(this.boxFace.nativeElement, 'borderColor', this.borderColor); if (!!this.boxInfo) { this.renderer.setStyle(this.boxInfo.nativeElement, 'display', 'none'); } } - - addFrame(size: FrameSize): void { - Object.keys(size).forEach(key => this.renderer.setStyle(this.element.nativeElement, key, `${size[key]}px`)); - } } diff --git a/ui/src/app/features/face-services/face-services.helpers.ts b/ui/src/app/features/face-services/face-services.helpers.ts index d9a34f2af2..2f70da53b5 100644 --- a/ui/src/app/features/face-services/face-services.helpers.ts +++ b/ui/src/app/features/face-services/face-services.helpers.ts @@ -66,9 +66,8 @@ export const getFileExtension = (file: File): string => * @param box Face coordinates from BE. * @param imageSize Size of image. * @param sizeToCalc Canvas size. (design size). - * @param yAxisPadding padding to ensure capacity for text area on image. */ -export const recalculateFaceCoordinate = (box: any, imageSize: ImageSize, sizeToCalc: ImageSize, yAxisPadding: number = 25) => { +export const recalculateFaceCoordinate = (box: any, imageSize: ImageSize, sizeToCalc: ImageSize) => { const divideWidth = imageSize.width / sizeToCalc.width; const divideHeight = imageSize.height / sizeToCalc.height; @@ -82,17 +81,3 @@ export const recalculateFaceCoordinate = (box: any, imageSize: ImageSize, sizeTo /* eslint-enable @typescript-eslint/naming-convention */ }; }; - -export const createDefaultImage = (ctx, box) => { - ctx.beginPath(); - ctx.strokeStyle = 'green'; - ctx.moveTo(box.x_min, box.y_min); - ctx.lineTo(box.x_max, box.y_min); - ctx.lineTo(box.x_max, box.y_max); - ctx.lineTo(box.x_min, box.y_max); - ctx.lineTo(box.x_min, box.y_min); - ctx.stroke(); - ctx.font = '12pt Roboto Regular Helvetica Neue sans-serif'; - - return ctx; -}; diff --git a/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.html b/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.html index 4323684ebc..9745cab0ae 100644 --- a/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.html +++ b/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.html @@ -26,7 +26,7 @@
-
+
@@ -45,7 +45,7 @@
-
+
{{ frame.similarity }}
diff --git a/ui/src/styles/frames.scss b/ui/src/styles/frames.scss index 632d7e358c..b01c74f314 100644 --- a/ui/src/styles/frames.scss +++ b/ui/src/styles/frames.scss @@ -14,6 +14,7 @@ * permissions and limitations under the License. */ @import "colors.scss"; +@import "mixins.scss"; @mixin frames() { @@ -23,13 +24,16 @@ &--wrapper { position: relative; + line-height: 0; .face-box { position: absolute; + display: flex; + flex-direction: column; &--face { width: 100%; - height: 100%; + min-height: 100%; box-sizing: border-box; border: 4px solid; border-radius: 12px; @@ -38,10 +42,9 @@ &--info { position: relative; margin-top: 18px; - padding: 8px; + padding: 8px 20px; max-width: 170px; - min-width: 70px; - max-height: 80px; + width: fit-content; background-color: $light-blue; border-radius: 6px; color: $white; @@ -60,7 +63,15 @@ z-index: -1; } + &_name { + @include not-break-string; + font-weight: 600; + font-size: 16px; + line-height: 22px; + } + &_similarity { + @include not-break-string; font-size: 14px; line-height: 14px; } From b8d4bf2f91529700e4f5f76aba31ef846ceb5d7d Mon Sep 17 00:00:00 2001 From: Pavel Bohdan Date: Fri, 28 May 2021 13:05:42 +0300 Subject: [PATCH 004/837] Added dynamic frames (ver-2). --- .../face-services/face-services.directive.ts | 14 ++++---------- ui/src/styles/colors.scss | 1 + ui/src/styles/frames.scss | 7 ++++++- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/ui/src/app/features/face-services/face-services.directive.ts b/ui/src/app/features/face-services/face-services.directive.ts index 4d086cc644..75d5550553 100644 --- a/ui/src/app/features/face-services/face-services.directive.ts +++ b/ui/src/app/features/face-services/face-services.directive.ts @@ -41,8 +41,6 @@ export class FaceServicesDirective implements OnChanges, AfterContentInit { @ContentChild('boxFace') boxFace: ElementRef; @ContentChild('boxInfo') boxInfo: ElementRef; - private borderColor = 'rgba(255, 255, 255, 0.5)'; - private borderColorActive = '#40BFEF'; private size: FrameSize; private activeFrame = false; @@ -83,19 +81,15 @@ export class FaceServicesDirective implements OnChanges, AfterContentInit { styleActiveFrame(): void { this.renderer.setStyle(this.element.nativeElement, 'zIndex', 2); - this.renderer.setStyle(this.boxFace.nativeElement, 'borderColor', this.borderColorActive); + this.renderer.addClass(this.boxFace.nativeElement, 'active-frame'); - if (!!this.boxInfo) { - this.renderer.setStyle(this.boxInfo.nativeElement, 'display', 'block'); - } + if (!!this.boxInfo) this.renderer.setStyle(this.boxInfo.nativeElement, 'display', 'block'); } styleNotActiveFrame(): void { this.renderer.setStyle(this.element.nativeElement, 'zIndex', 1); - this.renderer.setStyle(this.boxFace.nativeElement, 'borderColor', this.borderColor); + this.renderer.removeClass(this.boxFace.nativeElement, 'active-frame'); - if (!!this.boxInfo) { - this.renderer.setStyle(this.boxInfo.nativeElement, 'display', 'none'); - } + if (!!this.boxInfo) this.renderer.setStyle(this.boxInfo.nativeElement, 'display', 'none'); } } diff --git a/ui/src/styles/colors.scss b/ui/src/styles/colors.scss index 0ecb23c773..0dbd906035 100644 --- a/ui/src/styles/colors.scss +++ b/ui/src/styles/colors.scss @@ -20,6 +20,7 @@ $white: #fff; $black: #222222; // titles, $gray: rgba(34, 34, 34, 0.25); // placeholder search $dark-gray: rgba(34, 34, 34, 0.8); // subtitles +$transparent-gray: rgba(255, 255, 255, 0.5); // border frames not active $light-gray: rgba(34, 34, 34, 0.45); // border drag-n-drop $super-light-gray: rgba(34, 34, 34, 0.1); // border drag-n-drop $pale-gray: #979899; // plain text labels, plain texts, plain form borders diff --git a/ui/src/styles/frames.scss b/ui/src/styles/frames.scss index b01c74f314..28a7941ae7 100644 --- a/ui/src/styles/frames.scss +++ b/ui/src/styles/frames.scss @@ -37,6 +37,11 @@ box-sizing: border-box; border: 4px solid; border-radius: 12px; + border-color: $transparent-gray; + + &.active-frame { + border-color: $light-blue; + } } &--info { @@ -58,7 +63,7 @@ left: 50%; width: 24px; height: 24px; - background: #40BFEF; + background: $light-blue; transform: translate(-50%, -50%) rotate(45deg); z-index: -1; } From 16a55c2ec20fcb17e2e7ea90bf8472e1bf88d941 Mon Sep 17 00:00:00 2001 From: spospielov Date: Sun, 30 May 2021 16:07:27 +0300 Subject: [PATCH 005/837] Updated documentation --- docs/How-to-Use-CompreFace.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/How-to-Use-CompreFace.md b/docs/How-to-Use-CompreFace.md index 13ab0e1d1d..ea93993497 100644 --- a/docs/How-to-Use-CompreFace.md +++ b/docs/How-to-Use-CompreFace.md @@ -13,11 +13,12 @@ their access roles or create new [Face Services](Face-services-and-plugins.md). Service, you will see it in the Services List with an appropriate name and API key. After this step, you can look at our [demos](#demos). **Step 6.** To add known subjects to your Face Collection of Face Recognition Service, you can use REST API. -Once you’ve uploaded all known faces, you can test the collection using REST API or the TEST page. +Once you’ve [uploaded all known faces](Rest-API-description.md#add-an-example-of-a-subject), +you can test the collection using [REST API](Rest-API-description.md#recognize-faces-from-a-given-image) or the TEST page. We recommend that you use an image size no higher than 5MB, as it could slow down the request process. The supported image formats include JPEG/PNG/JPG/ICO/BMP/GIF/TIF/TIFF. **Step 7.** Upload your photo and let our open-source face recognition system match the image against the Face Collection. If you use a -UI for face recognition, you will see the original picture with marks near every face. If you use REST API, you will receive a response in JSON format. +UI for face recognition, you will see the original picture with marks near every face. If you use [REST API](Rest-API-description.md#recognize-faces-from-a-given-image), you will receive a response in JSON format. JSON contains an array of objects that represent each recognized face. Each object has the following fields: From eb3f0d11f3ff66aa9706d3b883f71a91ac17c147 Mon Sep 17 00:00:00 2001 From: spospielov Date: Sun, 30 May 2021 16:25:41 +0300 Subject: [PATCH 006/837] Updated contributing documentation --- CONTRIBUTING.md | 53 +++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 32 ++++++++--------------------- 2 files changed, 61 insertions(+), 24 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..e650dd5867 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,53 @@ +# Contributing to CompreFace +Contributions are welcomed and greatly appreciated. We want to make contributing to this project as easy and transparent as possible, whether it's: +- Reporting a bug +- Proposing new features +- Discussing the current state of the code +- Submitting a fix or solutions +- Integrating CompreFace with other solutions +- Adding plugins to face recognition services + +## We develop with GitHub +We use GitHub to host code, to track issues and feature requests, as well as accept pull requests. + +## We use GitHub Flow +All code changes happen through Pull Requests. + +Pull requests are the best way to propose changes to the codebase (please follow this guid [Github Flow](https://guides.github.com/introduction/flow/index.html)). + +We actively welcome your pull requests: +1. Fork the repo and create your branch from `master`. +2. If you've added code that should be tested, add tests. +3. If you've changed APIs, update the documentation. +4. Ensure the test suite passes. +5. Make sure your code lints. +6. Issue that pull request! + +After creating your first contributing pull request, you will receive a request to sign our Contributor License Agreement by commenting your pull request with a special message. + +## Any contributions you make will be under the Apache License 2.0 License +In short, when you submit code changes, your submissions are understood to be under the same Apache License 2.0 that covers the project. Feel free to contact the maintainers if that's a concern. + +## Report Bugs + +Please report any bugs [here](https://github.com/exadel-inc/CompreFace/issues). + +If you are reporting a bug, please specify: + +- Your operating system name and version +- Any details about your local setup that might be helpful in troubleshooting +- Detailed steps to reproduce the bug + + +## Submit Feedback + +The best way to send us feedback is to file an issue at https://github.com/exadel-inc/CompreFace/issues. + +If you are proposing a feature, please: + +- Explain in detail how it should work. +- Keep the scope as narrow as possible to make it easier to implement. + +## Use a Consistent Coding Style + +For java just import dev/team_codestyle.xml file in your IntelliJ IDEA. diff --git a/README.md b/README.md index 58d9e8e039..1ed9667e12 100644 --- a/README.md +++ b/README.md @@ -138,32 +138,16 @@ More documentation is available [here](/docs) # Contributing We want to improve our open-source face recognition solution, so your contributions are welcome and greatly appreciated. -After creating your first contributing pull request, you will receive a request to sign our Contributor License Agreement by commenting your pull request with a special message. -### Formatting standards - -For java just import dev/team_codestyle.xml file in your IntelliJ IDEA. - -### Report Bugs - -Please report any bugs [here](https://github.com/exadel-inc/CompreFace/issues). - -If you are reporting a bug, please specify: - -- Your operating system name and version -- Any details about your local setup that might be helpful in troubleshooting -- Detailed steps to reproduce the bug - - -### Submit Feedback - -The best way to send us feedback is to file an issue at https://github.com/exadel-inc/CompreFace/issues. - -If you are proposing a feature, please: - -- Explain in detail how it should work. -- Keep the scope as narrow as possible to make it easier to implement. +* Just use CompreFace and [report](https://github.com/exadel-inc/CompreFace/issues) ideas and bugs on GitHub +* Share knowledge and experience via posting guides and articles, or just improve our [documentation](https://github.com/exadel-inc/CompreFace/tree/master/docs) +* Create [SDKs](https://github.com/topics/compreface-sdk) for favorite programming language, we will add it to our documentation +* Integrate CompreFace support to other platforms like [Home Assistant](https://www.home-assistant.io/) or [DreamFactory](https://www.dreamfactory.com/), we will add it to our documentation +* [Contribute](CONTRIBUTING.md) code +* Add [plugin](/docs/Face-services-and-plugins.md#face-plugins) to face services +* And last, but not least, you can just give a star to our free facial recognition system on GitHub +For more information, visit our [contributing](CONTRIBUTING.md) guide, or create a [discussion](https://github.com/exadel-inc/CompreFace/discussions). # License info From f88a8f74c7defe5e3bb62602fdc1e4f2de27065d Mon Sep 17 00:00:00 2001 From: Pospielov Serhii Date: Sun, 30 May 2021 16:31:32 +0300 Subject: [PATCH 007/837] Create CODE_OF_CONDUCT.md --- CODE_OF_CONDUCT.md | 128 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..ed21e24f1b --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +compreface.support@exadel.com. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. From 0fc8a3c03c179eb3758d685995dbb8afc297e8a6 Mon Sep 17 00:00:00 2001 From: spospielov Date: Sun, 30 May 2021 16:37:33 +0300 Subject: [PATCH 008/837] Updated version on UI --- ui/src/environments/environment.prod.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/environments/environment.prod.ts b/ui/src/environments/environment.prod.ts index 82969d2bb8..b1cb2267ba 100644 --- a/ui/src/environments/environment.prod.ts +++ b/ui/src/environments/environment.prod.ts @@ -21,5 +21,5 @@ export const environment: Environment = { basicToken: 'Basic Q29tbW9uQ2xpZW50SWQ6cGFzc3dvcmQ=', adminApiUrl: '/admin/', userApiUrl: '/api/v1/', - buildNumber: '0.5.1', + buildNumber: '0.6.0', }; From 4a82134a8bdb3f3628f6df9fd447448a0786a2f5 Mon Sep 17 00:00:00 2001 From: Ivan Kurnosau Date: Tue, 1 Jun 2021 18:52:38 +0300 Subject: [PATCH 009/837] face mask detector v1 --- embedding-calculator/.dockerignore | 2 +- embedding-calculator/.gitignore | 320 ++++++------- embedding-calculator/Dockerfile | 2 +- embedding-calculator/Makefile | 132 ++--- embedding-calculator/README.md | 450 +++++++++--------- embedding-calculator/__init__.py | 36 +- embedding-calculator/benchmark.sh | 52 +- embedding-calculator/gpu.Dockerfile | 158 +++--- embedding-calculator/pylama.ini | 28 +- embedding-calculator/pytest.ini | 26 +- embedding-calculator/requirements.txt | 58 +-- embedding-calculator/src/__init__.py | 26 +- embedding-calculator/src/_docs.py | 74 +-- embedding-calculator/src/_endpoints.py | 260 +++++----- embedding-calculator/src/conftest.py | 38 +- embedding-calculator/src/constants.py | 78 +-- embedding-calculator/src/docs/__init__.py | 34 +- .../src/services/dto/plugin_result.py | 6 + .../facescan/plugins/agegender/__init__.py | 2 +- .../facescan/plugins/agegender/agegender.py | 17 +- .../facescan/plugins/agegender/helpers.py | 50 +- .../src/services/facescan/plugins/base.py | 18 +- .../services/facescan/plugins/dependencies.py | 2 +- .../facescan/plugins/facenet/__init__.py | 3 +- .../plugins/facenet/facemask/__init__.py | 17 + .../plugins/facenet/facemask/facemask.py | 64 +++ .../facescan/plugins/facenet/facenet.py | 63 ++- .../plugins/insightface/insightface.py | 2 +- .../src/services/facescan/plugins/managers.py | 6 +- 29 files changed, 1080 insertions(+), 944 deletions(-) create mode 100644 embedding-calculator/src/services/facescan/plugins/facenet/facemask/__init__.py create mode 100644 embedding-calculator/src/services/facescan/plugins/facenet/facemask/facemask.py diff --git a/embedding-calculator/.dockerignore b/embedding-calculator/.dockerignore index 4e6eb0e652..4875b06569 100644 --- a/embedding-calculator/.dockerignore +++ b/embedding-calculator/.dockerignore @@ -1 +1 @@ -**/tmp/ +**/tmp/ diff --git a/embedding-calculator/.gitignore b/embedding-calculator/.gitignore index 4673ddccd2..e9341f6e8d 100644 --- a/embedding-calculator/.gitignore +++ b/embedding-calculator/.gitignore @@ -1,161 +1,161 @@ -tmp/ - -# Created by .ignore support plugin (hsz.mobi) -### Python template -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -.hypothesis/ -.pytest_cache/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# pyenv -.python-version - -# celery beat schedule file -celerybeat-schedule - -# SageMath parsed files -*.sage.py - -# Environments -shared_src/.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ - -### JetBrains template -# Covers JetBrains IDEs: IntelliJ, Ruby_mine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/**/dictionaries -.idea/**/shelf - -# Sensitive or high-churn files -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml - -# Gradle -.idea/**/gradle.xml -.idea/**/libraries - -# CMake -cmake-build-debug/ -cmake-build-release/ - -# Mongo Explorer plugin -.idea/**/mongoSettings.xml - -# File-based project format -*.iws - -# IntelliJ -out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Cursive Clojure plugin -.idea/replstate.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -# Editor-based Rest Client +tmp/ + +# Created by .ignore support plugin (hsz.mobi) +### Python template +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +shared_src/.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, Ruby_mine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/dictionaries +.idea/**/shelf + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# CMake +cmake-build-debug/ +cmake-build-release/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client .idea/httpRequests \ No newline at end of file diff --git a/embedding-calculator/Dockerfile b/embedding-calculator/Dockerfile index 35bd5ca978..2fb58e9bd3 100644 --- a/embedding-calculator/Dockerfile +++ b/embedding-calculator/Dockerfile @@ -28,7 +28,7 @@ ARG GPU_IDX=-1 ENV GPU_IDX=$GPU_IDX INTEL_OPTIMIZATION=$INTEL_OPTIMIZATION ARG FACE_DETECTION_PLUGIN="facenet.FaceDetector" ARG CALCULATION_PLUGIN="facenet.Calculator" -ARG EXTRA_PLUGINS="facenet.LandmarksDetector,agegender.AgeDetector,agegender.GenderDetector" +ARG EXTRA_PLUGINS="facenet.LandmarksDetector,agegender.AgeDetector,agegender.GenderDetector,facenet.facemask.MaskDetector" ENV FACE_DETECTION_PLUGIN=$FACE_DETECTION_PLUGIN CALCULATION_PLUGIN=$CALCULATION_PLUGIN \ EXTRA_PLUGINS=$EXTRA_PLUGINS COPY src src diff --git a/embedding-calculator/Makefile b/embedding-calculator/Makefile index 8f9b540bf0..c5f9b8649d 100755 --- a/embedding-calculator/Makefile +++ b/embedding-calculator/Makefile @@ -1,66 +1,66 @@ -SHELL := /bin/bash -.DEFAULT_GOAL := default -.PHONY := default up build-images build-cuda - -IMAGE := ${DOCKER_REGISTRY}compreface-core -CUDA_IMAGE = $(IMAGE)-base:base-cuda100-py37 - -MOBILENET_BUILD_ARGS := --build-arg FACE_DETECTION_PLUGIN=insightface.FaceDetector@retinaface_mnet025_v1 \ - --build-arg CALCULATION_PLUGIN=insightface.Calculator@arcface_mobilefacenet \ - --build-arg EXTRA_PLUGINS=insightface.LandmarksDetector,insightface.GenderDetector,insightface.AgeDetector - -ARCFACE_r100_BUILD_ARGS := --build-arg FACE_DETECTION_PLUGIN=insightface.FaceDetector@retinaface_r50_v1 \ - --build-arg CALCULATION_PLUGIN=insightface.Calculator@arcface-r100-msfdrop75 \ - --build-arg EXTRA_PLUGINS=insightface.LandmarksDetector,insightface.GenderDetector,insightface.AgeDetector - -FACENET_BUILD_ARGS := --build-arg FACE_DETECTION_PLUGIN=facenet.FaceDetector \ - --build-arg CALCULATION_PLUGIN=facenet.Calculator \ - --build-arg EXTRA_PLUGINS=facenet.LandmarksDetector,agegender.GenderDetector,agegender.AgeDetector - -define get_from_remote_tgz - mkdir -p $(2) - curl -o $(2)/tmp.tgz $(1) - tar zxvf $(2)/tmp.tgz -C $(2) - rm $(2)/tmp.tgz -endef - -define get_from_remote_zip - mkdir -p $(2) - curl -o $(2)/tmp.zip $(1) - unzip $(2)/tmp.zip -d $(2) - rm $(2)/tmp.zip -endef - -default: - pytest src tools - python -m pylama --options pylama.ini src tools - docker build . -t $(IMAGE):$(VERSION) - -build-cuda: - docker build . --file gpu.Dockerfile --tag $(CUDA_IMAGE) - -build-images: build-cuda - docker build . -t $(IMAGE):$(VERSION)-facenet - docker build . -t $(IMAGE):$(VERSION)-mobilenet $(MOBILENET_BUILD_ARGS) - docker build . -t $(IMAGE):$(VERSION)-mobilenet-gpu $(MOBILENET_BUILD_ARGS) --build-arg GPU_IDX=0 --build-arg BASE_IMAGE=$(CUDA_IMAGE) - docker build . -t $(IMAGE):$(VERSION)-arcface-r100 $(ARCFACE_r100_BUILD_ARGS) - docker build . -t $(IMAGE):$(VERSION)-arcface-r100-gpu $(ARCFACE_r100_BUILD_ARGS) --build-arg GPU_IDX=0 --build-arg BASE_IMAGE=$(CUDA_IMAGE) - -build-images-cpu: - docker build . -t $(IMAGE):$(VERSION)-facenet - docker build . -t $(IMAGE):$(VERSION)-mobilenet $(MOBILENET_BUILD_ARGS) - docker build . -t $(IMAGE):$(VERSION)-arcface-r100 $(ARCFACE_r100_BUILD_ARGS) - -build-images-gpu: build-cuda - docker build . -t $(IMAGE):$(VERSION)-mobilenet-gpu $(MOBILENET_BUILD_ARGS) --build-arg GPU_IDX=0 --build-arg BASE_IMAGE=$(CUDA_IMAGE) - docker build . -t $(IMAGE):$(VERSION)-arcface-r100-gpu $(ARCFACE_r100_BUILD_ARGS) --build-arg GPU_IDX=0 --build-arg BASE_IMAGE=$(CUDA_IMAGE) - -up: - docker run -p3000:3000 embedding-calculator - -tools/tmp: - $(call get_from_remote_zip,https://www.fontsquirrel.com/fonts/download/arimo,tools/tmp/arimo-font) - -tools/benchmark_detection/tmp: - $(call get_from_remote_tgz,http://tamaraberg.com/faceDataset/originalPics.tar.gz,tools/benchmark_detection/tmp/originalPics) - $(call get_from_remote_tgz,http://vis-www.cs.umass.edu/fddb/FDDB-folds.tgz,tools/benchmark_detection/tmp) +SHELL := /bin/bash +.DEFAULT_GOAL := default +.PHONY := default up build-images build-cuda + +IMAGE := ${DOCKER_REGISTRY}compreface-core +CUDA_IMAGE = $(IMAGE)-base:base-cuda100-py37 + +MOBILENET_BUILD_ARGS := --build-arg FACE_DETECTION_PLUGIN=insightface.FaceDetector@retinaface_mnet025_v1 \ + --build-arg CALCULATION_PLUGIN=insightface.Calculator@arcface_mobilefacenet \ + --build-arg EXTRA_PLUGINS=insightface.LandmarksDetector,insightface.GenderDetector,insightface.AgeDetector + +ARCFACE_r100_BUILD_ARGS := --build-arg FACE_DETECTION_PLUGIN=insightface.FaceDetector@retinaface_r50_v1 \ + --build-arg CALCULATION_PLUGIN=insightface.Calculator@arcface-r100-msfdrop75 \ + --build-arg EXTRA_PLUGINS=insightface.LandmarksDetector,insightface.GenderDetector,insightface.AgeDetector + +FACENET_BUILD_ARGS := --build-arg FACE_DETECTION_PLUGIN=facenet.FaceDetector \ + --build-arg CALCULATION_PLUGIN=facenet.Calculator \ + --build-arg EXTRA_PLUGINS=facenet.LandmarksDetector,agegender.GenderDetector,agegender.AgeDetector + +define get_from_remote_tgz + mkdir -p $(2) + curl -o $(2)/tmp.tgz $(1) + tar zxvf $(2)/tmp.tgz -C $(2) + rm $(2)/tmp.tgz +endef + +define get_from_remote_zip + mkdir -p $(2) + curl -o $(2)/tmp.zip $(1) + unzip $(2)/tmp.zip -d $(2) + rm $(2)/tmp.zip +endef + +default: + pytest src tools + python -m pylama --options pylama.ini src tools + docker build . -t $(IMAGE):$(VERSION) + +build-cuda: + docker build . --file gpu.Dockerfile --tag $(CUDA_IMAGE) + +build-images: build-cuda + docker build . -t $(IMAGE):$(VERSION)-facenet + docker build . -t $(IMAGE):$(VERSION)-mobilenet $(MOBILENET_BUILD_ARGS) + docker build . -t $(IMAGE):$(VERSION)-mobilenet-gpu $(MOBILENET_BUILD_ARGS) --build-arg GPU_IDX=0 --build-arg BASE_IMAGE=$(CUDA_IMAGE) + docker build . -t $(IMAGE):$(VERSION)-arcface-r100 $(ARCFACE_r100_BUILD_ARGS) + docker build . -t $(IMAGE):$(VERSION)-arcface-r100-gpu $(ARCFACE_r100_BUILD_ARGS) --build-arg GPU_IDX=0 --build-arg BASE_IMAGE=$(CUDA_IMAGE) + +build-images-cpu: + docker build . -t $(IMAGE):$(VERSION)-facenet + docker build . -t $(IMAGE):$(VERSION)-mobilenet $(MOBILENET_BUILD_ARGS) + docker build . -t $(IMAGE):$(VERSION)-arcface-r100 $(ARCFACE_r100_BUILD_ARGS) + +build-images-gpu: build-cuda + docker build . -t $(IMAGE):$(VERSION)-mobilenet-gpu $(MOBILENET_BUILD_ARGS) --build-arg GPU_IDX=0 --build-arg BASE_IMAGE=$(CUDA_IMAGE) + docker build . -t $(IMAGE):$(VERSION)-arcface-r100-gpu $(ARCFACE_r100_BUILD_ARGS) --build-arg GPU_IDX=0 --build-arg BASE_IMAGE=$(CUDA_IMAGE) + +up: + docker run -p3000:3000 embedding-calculator + +tools/tmp: + $(call get_from_remote_zip,https://www.fontsquirrel.com/fonts/download/arimo,tools/tmp/arimo-font) + +tools/benchmark_detection/tmp: + $(call get_from_remote_tgz,http://tamaraberg.com/faceDataset/originalPics.tar.gz,tools/benchmark_detection/tmp/originalPics) + $(call get_from_remote_tgz,http://vis-www.cs.umass.edu/fddb/FDDB-folds.tgz,tools/benchmark_detection/tmp) diff --git a/embedding-calculator/README.md b/embedding-calculator/README.md index f5bcee9b55..8e2195901e 100644 --- a/embedding-calculator/README.md +++ b/embedding-calculator/README.md @@ -1,225 +1,225 @@ -![Example output image](./sample_images/readme_example.png) - -# embedding-calculator -This is a component of CompreFace. CompreFace is a service for face recognition: upload images with faces of known people, then upload a new image, and the service will recognize faces in it. - -# Setup environment -Not needed if only running containers: -``` -$ python -m pip install -r requirements.txt -e srcext/insightface/python-package -$ imageio_download_bin freeimage -``` -Only needed if using tools: -``` -$ make tools/tmp -$ chmod +x tools/test_memory_constraints.sh -``` - -# Run service -### Locally -``` -$ export FLASK_ENV=development -$ python -m src.app -``` - -### Docker - -##### Images on DockerHub - -There are some pre-build images on https://hub.docker.com/r/exadel/compreface-core. To use it run: -``` -$ docker run -p 3000:3000 exadel/compreface-core:latest -``` - -###### DockerHub tags - -| Tag | Scanner | Build arguments | Comment | -|------------------------|-------------|----------------------------------------------------------------------------------------------------------|------------------------------------| -| :0.5.1 :latest | Facenet2018 | | | -| :0.5.1-insightface | InsightFace | FACE_DETECTION_PLUGIN=insightface.FaceDetector
CALCULATION_PLUGIN=insightface.Calculator | | -| :0.5.1-insightface-gpu | InsightFace | FACE_DETECTION_PLUGIN=insightface.FaceDetector
CALCULATION_PLUGIN=insightface.Calculator
GPU_IDX=0 | CORE_GPU_IDX - index of GPU-device | - - -##### Build -Builds container (also runs main tests during the build): -``` -$ docker build -t embedding-calculator -``` -To skip tests during build, use: -``` -$ docker build -t embedding-calculator --build-arg SKIP_TESTS=true . -``` - -##### Run -``` -$ docker run -p 3000:3000 embedding-calculator -``` - -### Run tests -Unit tests -``` -$ pytest -m "not integration and not performance" src tools -``` -Integration tests -``` -$ pytest -m integration src tools -``` -Performance tests -``` -$ pytest -m performance src tools -``` -Lint checks -``` -$ python -m pylama --options pylama.ini src tools -``` - -### Plugins - -If DockerHub images is not enough, build an image with only the necessary set of plugins. -For changing default plugins pass needed plugin names in build arguments and build your own image. - -##### Face detection and calculation plugins - -Set plugins by build arguments `FACE_DETECTION_PLUGIN` and `CALCULATION_PLUGIN` - -| Plugin name | Slug | Backend | Framework | GPU support | -|--------------------------|------------|-------------|------------|-------------| -| facenet.FaceDetector | detector | MTCNN | Tensorflow | | -| facenet.Calculator | calculator | Facenet | Tensorflow | | -| insightface.FaceDetector | detector | insightface | MXNet | + | -| insightface.Calculator | calculator | insightface | MXNet | + | - -##### Extra plugins - -Pass to `EXTRA_PLUGINS` comma-separated names of plugins. - -| Plugin name | Slug | Backend | Framework | GPU support | -|------------------------------------|----------------|-------------|------------|-------------| -| agegender.AgeDetector | age | agegender | Tensorflow | | -| agegender.GenderDetector | gender | agegender | Tensorflow | | -| insightface.AgeDetector | age | insightface | MXNet | + | -| insightface.GenderDetector | gender | insightface | MXNet | + | -| facenet.LandmarksDetector | landmarks | Facenet | Tensorflow | + | -| insightface.LandmarksDetector | landmarks | insightface | MXNet | + | -| insightface.Landmarks2d106Detector | landmarks2d106 | insightface | MXNet | + | - -Notes: -* `facenet.LandmarksDetector` and `insightface.LandmarksDetector` extract landmarks - from results of `FaceDetector` plugin without additional processing. Returns 5 points of eyes, nose and mouth. -* `insightface.Landmarks2d106Detector` detects 106 points of facial landmark. - [Points mark-up](https://github.com/deepinsight/insightface/tree/master/alignment/coordinateReg#visualization) - - -##### Default build arguments: -``` -FACE_DETECTION_PLUGIN=facenet.FaceDetector -CALCULATION_PLUGIN=facenet.Calculator -EXTRA_PLUGINS=agegender.AgeDetector,agegender.GenderDetector -``` - -#### Pre-trained models - -Some plugins have several pre-trained models. -To use an additional model pass a name of the model after a plugin name with a separator `@`. For example: -``` -FACE_DETECTION_PLUGIN=insightface.FaceDetector@retinaface_mnet025_v1 -``` - -List of pre-trained models: - -* facenet.Calculator - * 20180402-114759 (default) - * 20180408-102900 - -* insightface.FaceDetector - * retinaface_r50_v1 (default) - * retinaface_mnet025_v1 - * retinaface_mnet025_v2 - -* insightface.Calculator - * arcface_r100_v1 (default) - * arcface_resnet34 - * arcface_resnet50 - * arcface_mobilefacenet - * [arcface-r50-msfdrop75](https://github.com/deepinsight/insightface/tree/master/recognition/SubCenter-ArcFace) - * [arcface-r100-msfdrop75](https://github.com/deepinsight/insightface/tree/master/recognition/SubCenter-ArcFace) - - -#### Optimization - -There are two build arguments for optimization: -* `GPU_IDX` - id of NVIDIA GPU device, starts from `0` (empty or `-1` for disable) -* `INTEL_OPTIMIZATION` - enable Intel MKL optimization (true/false) - - -##### NVIDIA Runtime - -Install the nvidia-docker2 package and dependencies on the host machine: -``` -sudo apt-get update -sudo apt-get install -y nvidia-docker2 -sudo systemctl restart docker -``` - -Build and run with enabled gpu -``` -docker build . -t embedding-calculator-cuda -f gpu.Dockerfile -docker build . -t embedding-calculator-gpu --build-arg GPU_IDX=0 --build-arg BASE_IMAGE=embedding-calculator-cuda -docker run -p 3000:3000 --gpus all embedding-calculator-gpu -``` - -# Tools -Finds faces in a given image, puts bounding boxes and saves the resulting image. -``` -$ export IMG_NAMES=015_6.jpg -$ python -m tools.scan -``` - -Tests the accuracy of face detection. -``` -$ make tools/benchmark_detection/tmp -$ python -m tools.benchmark_detection -``` - -Tests whether service crashes with various parameters under given RAM constraints. -``` -$ docker build -t embedding-calculator . -$ tools/test_memory_constraints.sh $(pwd)/sample_images -``` - -Optimizes face detection library parameters with a given annotated image dataset. -``` -$ mkdir tmp -$ python -m tools.optimize_detection_params -``` - -# Benchmark - -Perform the following steps: -1. [Build and run](#build) `embedding-calculator` with the needed scanner backend and CPU/GPU supports -1. Run a benchmark: - 1. inside the container `docker exec embedding-calculator ./benchmark` - 1. or locally `cd .embedding-calculator && ./benchmark.sh` (require exposing API at localhost:3000) - -# Troubleshooting - -### Windows - -##### While building container, crashes with error `: invalid option` - -CRLF file endings may cause this. To fix, run `$ dos2unix *`. - -##### Installing packages `requirements.txt` in a local environment crashes - -Package *uWSGI* is not supported on Windows. Workaround is to temporarily delete the line with the package name from `requirements.txt` and install without it. - -# Misc -Check that the component is in valid state: run tests, build container, start it -``` -$ make -$ make up -``` -Get project line counts per file type -``` -$ which tokei >/dev/null || conda install -y -c conda-forge tokei && tokei --exclude srcext/ -``` +![Example output image](./sample_images/readme_example.png) + +# embedding-calculator +This is a component of CompreFace. CompreFace is a service for face recognition: upload images with faces of known people, then upload a new image, and the service will recognize faces in it. + +# Setup environment +Not needed if only running containers: +``` +$ python -m pip install -r requirements.txt -e srcext/insightface/python-package +$ imageio_download_bin freeimage +``` +Only needed if using tools: +``` +$ make tools/tmp +$ chmod +x tools/test_memory_constraints.sh +``` + +# Run service +### Locally +``` +$ export FLASK_ENV=development +$ python -m src.app +``` + +### Docker + +##### Images on DockerHub + +There are some pre-build images on https://hub.docker.com/r/exadel/compreface-core. To use it run: +``` +$ docker run -p 3000:3000 exadel/compreface-core:latest +``` + +###### DockerHub tags + +| Tag | Scanner | Build arguments | Comment | +|------------------------|-------------|----------------------------------------------------------------------------------------------------------|------------------------------------| +| :0.5.0 :latest | Facenet2018 | | | +| :0.5.0-insightface | InsightFace | FACE_DETECTION_PLUGIN=insightface.FaceDetector
CALCULATION_PLUGIN=insightface.Calculator | | +| :0.5.0-insightface-gpu | InsightFace | FACE_DETECTION_PLUGIN=insightface.FaceDetector
CALCULATION_PLUGIN=insightface.Calculator
GPU_IDX=0 | CORE_GPU_IDX - index of GPU-device | + + +##### Build +Builds container (also runs main tests during the build): +``` +$ docker build -t embedding-calculator +``` +To skip tests during build, use: +``` +$ docker build -t embedding-calculator --build-arg SKIP_TESTS=true . +``` + +##### Run +``` +$ docker run -p 3000:3000 embedding-calculator +``` + +### Run tests +Unit tests +``` +$ pytest -m "not integration and not performance" src tools +``` +Integration tests +``` +$ pytest -m integration src tools +``` +Performance tests +``` +$ pytest -m performance src tools +``` +Lint checks +``` +$ python -m pylama --options pylama.ini src tools +``` + +### Plugins + +If DockerHub images is not enough, build an image with only the necessary set of plugins. +For changing default plugins pass needed plugin names in build arguments and build your own image. + +##### Face detection and calculation plugins + +Set plugins by build arguments `FACE_DETECTION_PLUGIN` and `CALCULATION_PLUGIN` + +| Plugin name | Slug | Backend | Framework | GPU support | +|--------------------------|------------|-------------|------------|-------------| +| facenet.FaceDetector | detector | MTCNN | Tensorflow | | +| facenet.Calculator | calculator | Facenet | Tensorflow | | +| insightface.FaceDetector | detector | insightface | MXNet | + | +| insightface.Calculator | calculator | insightface | MXNet | + | + +##### Extra plugins + +Pass to `EXTRA_PLUGINS` comma-separated names of plugins. + +| Plugin name | Slug | Backend | Framework | GPU support | +|------------------------------------|----------------|-------------|------------|-------------| +| agegender.AgeDetector | age | agegender | Tensorflow | | +| agegender.GenderDetector | gender | agegender | Tensorflow | | +| insightface.AgeDetector | age | insightface | MXNet | + | +| insightface.GenderDetector | gender | insightface | MXNet | + | +| facenet.LandmarksDetector | landmarks | Facenet | Tensorflow | + | +| insightface.LandmarksDetector | landmarks | insightface | MXNet | + | +| insightface.Landmarks2d106Detector | landmarks2d106 | insightface | MXNet | + | + +Notes: +* `facenet.LandmarksDetector` and `insightface.LandmarksDetector` extract landmarks + from results of `FaceDetector` plugin without additional processing. Returns 5 points of eyes, nose and mouth. +* `insightface.Landmarks2d106Detector` detects 106 points of facial landmark. + [Points mark-up](https://github.com/deepinsight/insightface/tree/master/alignment/coordinateReg#visualization) + + +##### Default build arguments: +``` +FACE_DETECTION_PLUGIN=facenet.FaceDetector +CALCULATION_PLUGIN=facenet.Calculator +EXTRA_PLUGINS=agegender.AgeDetector,agegender.GenderDetector +``` + +#### Pre-trained models + +Some plugins have several pre-trained models. +To use an additional model pass a name of the model after a plugin name with a separator `@`. For example: +``` +FACE_DETECTION_PLUGIN=insightface.FaceDetector@retinaface_mnet025_v1 +``` + +List of pre-trained models: + +* facenet.Calculator + * 20180402-114759 (default) + * 20180408-102900 + +* insightface.FaceDetector + * retinaface_r50_v1 (default) + * retinaface_mnet025_v1 + * retinaface_mnet025_v2 + +* insightface.Calculator + * arcface_r100_v1 (default) + * arcface_resnet34 + * arcface_resnet50 + * arcface_mobilefacenet + * [arcface-r50-msfdrop75](https://github.com/deepinsight/insightface/tree/master/recognition/SubCenter-ArcFace) + * [arcface-r100-msfdrop75](https://github.com/deepinsight/insightface/tree/master/recognition/SubCenter-ArcFace) + + +#### Optimization + +There are two build arguments for optimization: +* `GPU_IDX` - id of NVIDIA GPU device, starts from `0` (empty or `-1` for disable) +* `INTEL_OPTIMIZATION` - enable Intel MKL optimization (true/false) + + +##### NVIDIA Runtime + +Install the nvidia-docker2 package and dependencies on the host machine: +``` +sudo apt-get update +sudo apt-get install -y nvidia-docker2 +sudo systemctl restart docker +``` + +Build and run with enabled gpu +``` +docker build . -t embedding-calculator-cuda -f gpu.Dockerfile +docker build . -t embedding-calculator-gpu --build-arg GPU_IDX=0 --build-arg BASE_IMAGE=embedding-calculator-cuda +docker run -p 3000:3000 --gpus all embedding-calculator-gpu +``` + +# Tools +Finds faces in a given image, puts bounding boxes and saves the resulting image. +``` +$ export IMG_NAMES=015_6.jpg +$ python -m tools.scan +``` + +Tests the accuracy of face detection. +``` +$ make tools/benchmark_detection/tmp +$ python -m tools.benchmark_detection +``` + +Tests whether service crashes with various parameters under given RAM constraints. +``` +$ docker build -t embedding-calculator . +$ tools/test_memory_constraints.sh $(pwd)/sample_images +``` + +Optimizes face detection library parameters with a given annotated image dataset. +``` +$ mkdir tmp +$ python -m tools.optimize_detection_params +``` + +# Benchmark + +Perform the following steps: +1. [Build and run](#build) `embedding-calculator` with the needed scanner backend and CPU/GPU supports +1. Run a benchmark: + 1. inside the container `docker exec embedding-calculator ./benchmark` + 1. or locally `cd .embedding-calculator && ./benchmark.sh` (require exposing API at localhost:3000) + +# Troubleshooting + +### Windows + +##### While building container, crashes with error `: invalid option` + +CRLF file endings may cause this. To fix, run `$ dos2unix *`. + +##### Installing packages `requirements.txt` in a local environment crashes + +Package *uWSGI* is not supported on Windows. Workaround is to temporarily delete the line with the package name from `requirements.txt` and install without it. + +# Misc +Check that the component is in valid state: run tests, build container, start it +``` +$ make +$ make up +``` +Get project line counts per file type +``` +$ which tokei >/dev/null || conda install -y -c conda-forge tokei && tokei --exclude srcext/ +``` diff --git a/embedding-calculator/__init__.py b/embedding-calculator/__init__.py index ef9a5c1a9e..9d3375ecf6 100644 --- a/embedding-calculator/__init__.py +++ b/embedding-calculator/__init__.py @@ -1,18 +1,18 @@ -# Copyright (c) 2020 the original author or authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express -# or implied. See the License for the specific language governing -# permissions and limitations under the License. - -import os -import sys - -sys.path.insert(0, os.path.dirname(__file__)) +# Copyright (c) 2020 the original author or authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing +# permissions and limitations under the License. + +import os +import sys + +sys.path.insert(0, os.path.dirname(__file__)) diff --git a/embedding-calculator/benchmark.sh b/embedding-calculator/benchmark.sh index 5d2a67bb6a..1f2f29ca7a 100644 --- a/embedding-calculator/benchmark.sh +++ b/embedding-calculator/benchmark.sh @@ -1,27 +1,27 @@ -#!/bin/bash -CONCURRENCY=${1:-1} -ENDPOINT=${2:-http://localhost:3000/scan_faces} -declare -a IMAGES=("008_B.jpg" "001_A.jpg") - -# install apache benchmark -ab -V > /dev/null || (apt-get update && apt-get install -y apache2-utils) - -# run benchmark -for IMAGE in "${IMAGES[@]}" -do - IMAGE="./sample_images/$IMAGE" - [ ! -f "$IMAGE" ] && echo "Image ${IMAGE} not found" && exit - FILESIZE=$(stat -c%s "$IMAGE") - IMAGE_B64=$(base64 $IMAGE) - echo -e "--image_file\r\nContent-Disposition: form-data; name=\"file\"; filename=\"image.jpg\"\r\nContent-Transfer-Encoding: base64\r\n\r\n${IMAGE_B64}\r\n--image_file--" > post.data - echo "--- Run benchmark with $IMAGE ($FILESIZE bytes) ---" - - RESPONSE=$(ab -n 1 -c 1 -v 4 -p post.data -T "multipart/form-data; boundary=image_file" $ENDPOINT | grep embedding) - [[ -z $RESPONSE ]] && echo "Error: No embedding in response" && exit 1 - - ab -n 20 -c $CONCURRENCY -p post.data -T "multipart/form-data; boundary=image_file" $ENDPOINT \ - | grep 'per request\|Concurrency' - echo -done - +#!/bin/bash +CONCURRENCY=${1:-1} +ENDPOINT=${2:-http://localhost:3000/scan_faces} +declare -a IMAGES=("008_B.jpg" "001_A.jpg") + +# install apache benchmark +ab -V > /dev/null || (apt-get update && apt-get install -y apache2-utils) + +# run benchmark +for IMAGE in "${IMAGES[@]}" +do + IMAGE="./sample_images/$IMAGE" + [ ! -f "$IMAGE" ] && echo "Image ${IMAGE} not found" && exit + FILESIZE=$(stat -c%s "$IMAGE") + IMAGE_B64=$(base64 $IMAGE) + echo -e "--image_file\r\nContent-Disposition: form-data; name=\"file\"; filename=\"image.jpg\"\r\nContent-Transfer-Encoding: base64\r\n\r\n${IMAGE_B64}\r\n--image_file--" > post.data + echo "--- Run benchmark with $IMAGE ($FILESIZE bytes) ---" + + RESPONSE=$(ab -n 1 -c 1 -v 4 -p post.data -T "multipart/form-data; boundary=image_file" $ENDPOINT | grep embedding) + [[ -z $RESPONSE ]] && echo "Error: No embedding in response" && exit 1 + + ab -n 20 -c $CONCURRENCY -p post.data -T "multipart/form-data; boundary=image_file" $ENDPOINT \ + | grep 'per request\|Concurrency' + echo +done + rm post.data \ No newline at end of file diff --git a/embedding-calculator/gpu.Dockerfile b/embedding-calculator/gpu.Dockerfile index b43511f5a4..0e27288742 100644 --- a/embedding-calculator/gpu.Dockerfile +++ b/embedding-calculator/gpu.Dockerfile @@ -1,80 +1,80 @@ -ARG UBUNTU_VERSION=18.04 - -ARG ARCH= -ARG CUDA=10.0 -FROM nvidia/cuda${ARCH:+-$ARCH}:${CUDA}-base-ubuntu${UBUNTU_VERSION} as base -# ARCH and CUDA are specified again because the FROM directive resets ARGs -# (but their default value is retained if set previously) -ARG ARCH -ARG CUDA -ARG CUDNN=7.6.4.38-1 -ARG CUDNN_MAJOR_VERSION=7 -ARG LIB_DIR_PREFIX=x86_64 -ARG LIBNVINFER=6.0.1-1 -ARG LIBNVINFER_MAJOR_VERSION=6 -ENV CUDA=$CUDA - -# Needed for string substitution -SHELL ["/bin/bash", "-c"] -# Pick up some TF dependencies -RUN apt-get update && apt-get install -y --no-install-recommends \ - build-essential \ - cuda-command-line-tools-${CUDA/./-} \ - # There appears to be a regression in libcublas10=10.2.2.89-1 which - # prevents cublas from initializing in TF. See - # https://github.com/tensorflow/tensorflow/issues/9489#issuecomment-562394257 - libcublas10=10.2.1.243-1 \ - cuda-nvrtc-${CUDA/./-} \ - cuda-cufft-${CUDA/./-} \ - cuda-curand-${CUDA/./-} \ - cuda-cusolver-${CUDA/./-} \ - cuda-cusparse-${CUDA/./-} \ - curl \ - libcudnn7=${CUDNN}+cuda${CUDA} \ - libfreetype6-dev \ - libhdf5-serial-dev \ - libzmq3-dev \ - pkg-config \ - software-properties-common \ - unzip - -# Install TensorRT if not building for PowerPC -RUN [[ "${ARCH}" = "ppc64le" ]] || { apt-get update && \ - apt-get install -y --no-install-recommends libnvinfer${LIBNVINFER_MAJOR_VERSION}=${LIBNVINFER}+cuda${CUDA} \ - libnvinfer-plugin${LIBNVINFER_MAJOR_VERSION}=${LIBNVINFER}+cuda${CUDA} \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/*; } - -# For CUDA profiling, TensorFlow requires CUPTI. -ENV LD_LIBRARY_PATH /usr/local/cuda/extras/CUPTI/lib64:/usr/local/cuda/lib64:$LD_LIBRARY_PATH - -# Link the libcuda stub to the location where tensorflow is searching for it and reconfigure -# dynamic linker run-time bindings -RUN ln -s /usr/local/cuda/lib64/stubs/libcuda.so /usr/local/cuda/lib64/stubs/libcuda.so.1 \ - && echo "/usr/local/cuda/lib64/stubs" > /etc/ld.so.conf.d/z-cuda-stubs.conf \ - && ldconfig - -# See http://bugs.python.org/issue19846 -ENV LANG C.UTF-8 - -ARG PYTHON=python3.7 -RUN add-apt-repository ppa:deadsnakes/ppa && apt-get update && apt-get install -y ${PYTHON} libpython3.7-dev libgl1-mesa-glx -RUN curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py && ${PYTHON} get-pip.py -RUN ${PYTHON} -m pip --no-cache-dir install --upgrade pip setuptools - -# Some TF tools expect a "python" binary -RUN ln -s $(which $PYTHON) /usr/local/bin/python - - -# Variables for Tensorflow -ENV TF_FORCE_GPU_ALLOW_GROWTH=true - -# Variables for MXNET -ENV MXNET_CPU_WORKER_NTHREADS=24 -ENV MXNET_ENGINE_TYPE=ThreadedEnginePerDevice MXNET_CUDNN_AUTOTUNE_DEFAULT=0 - -# No access to GPU devices in the build stage, so skip tests -ENV SKIP_TESTS=1 -# The number of processes depends on GPU memory. -# Keep in mind that one uwsgi process with InsightFace consumes about 2.5GB memory +ARG UBUNTU_VERSION=18.04 + +ARG ARCH= +ARG CUDA=10.0 +FROM nvidia/cuda${ARCH:+-$ARCH}:${CUDA}-base-ubuntu${UBUNTU_VERSION} as base +# ARCH and CUDA are specified again because the FROM directive resets ARGs +# (but their default value is retained if set previously) +ARG ARCH +ARG CUDA +ARG CUDNN=7.6.4.38-1 +ARG CUDNN_MAJOR_VERSION=7 +ARG LIB_DIR_PREFIX=x86_64 +ARG LIBNVINFER=6.0.1-1 +ARG LIBNVINFER_MAJOR_VERSION=6 +ENV CUDA=$CUDA + +# Needed for string substitution +SHELL ["/bin/bash", "-c"] +# Pick up some TF dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + cuda-command-line-tools-${CUDA/./-} \ + # There appears to be a regression in libcublas10=10.2.2.89-1 which + # prevents cublas from initializing in TF. See + # https://github.com/tensorflow/tensorflow/issues/9489#issuecomment-562394257 + libcublas10=10.2.1.243-1 \ + cuda-nvrtc-${CUDA/./-} \ + cuda-cufft-${CUDA/./-} \ + cuda-curand-${CUDA/./-} \ + cuda-cusolver-${CUDA/./-} \ + cuda-cusparse-${CUDA/./-} \ + curl \ + libcudnn7=${CUDNN}+cuda${CUDA} \ + libfreetype6-dev \ + libhdf5-serial-dev \ + libzmq3-dev \ + pkg-config \ + software-properties-common \ + unzip + +# Install TensorRT if not building for PowerPC +RUN [[ "${ARCH}" = "ppc64le" ]] || { apt-get update && \ + apt-get install -y --no-install-recommends libnvinfer${LIBNVINFER_MAJOR_VERSION}=${LIBNVINFER}+cuda${CUDA} \ + libnvinfer-plugin${LIBNVINFER_MAJOR_VERSION}=${LIBNVINFER}+cuda${CUDA} \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/*; } + +# For CUDA profiling, TensorFlow requires CUPTI. +ENV LD_LIBRARY_PATH /usr/local/cuda/extras/CUPTI/lib64:/usr/local/cuda/lib64:$LD_LIBRARY_PATH + +# Link the libcuda stub to the location where tensorflow is searching for it and reconfigure +# dynamic linker run-time bindings +RUN ln -s /usr/local/cuda/lib64/stubs/libcuda.so /usr/local/cuda/lib64/stubs/libcuda.so.1 \ + && echo "/usr/local/cuda/lib64/stubs" > /etc/ld.so.conf.d/z-cuda-stubs.conf \ + && ldconfig + +# See http://bugs.python.org/issue19846 +ENV LANG C.UTF-8 + +ARG PYTHON=python3.7 +RUN add-apt-repository ppa:deadsnakes/ppa && apt-get update && apt-get install -y ${PYTHON} libpython3.7-dev libgl1-mesa-glx +RUN curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py && ${PYTHON} get-pip.py +RUN ${PYTHON} -m pip --no-cache-dir install --upgrade pip setuptools + +# Some TF tools expect a "python" binary +RUN ln -s $(which $PYTHON) /usr/local/bin/python + + +# Variables for Tensorflow +ENV TF_FORCE_GPU_ALLOW_GROWTH=true + +# Variables for MXNET +ENV MXNET_CPU_WORKER_NTHREADS=24 +ENV MXNET_ENGINE_TYPE=ThreadedEnginePerDevice MXNET_CUDNN_AUTOTUNE_DEFAULT=0 + +# No access to GPU devices in the build stage, so skip tests +ENV SKIP_TESTS=1 +# The number of processes depends on GPU memory. +# Keep in mind that one uwsgi process with InsightFace consumes about 2.5GB memory ENV UWSGI_PROCESSES=1 \ No newline at end of file diff --git a/embedding-calculator/pylama.ini b/embedding-calculator/pylama.ini index 8f16a64dd8..5a8e64211e 100644 --- a/embedding-calculator/pylama.ini +++ b/embedding-calculator/pylama.ini @@ -1,14 +1,14 @@ -[pylama:pycodestyle] -max_line_length = 120 - -[pylama] -ignore = W291,E302 - -[pylama:*/__init__.py] -ignore = W0611 - -[pylama:*/_endpoints.py] -ignore = C901 - -[pylama:*/_save_img.py] -ignore = C901 +[pylama:pycodestyle] +max_line_length = 120 + +[pylama] +ignore = W291,E302 + +[pylama:*/__init__.py] +ignore = W0611 + +[pylama:*/_endpoints.py] +ignore = C901 + +[pylama:*/_save_img.py] +ignore = C901 diff --git a/embedding-calculator/pytest.ini b/embedding-calculator/pytest.ini index 5359c7e415..8f456b3cbe 100644 --- a/embedding-calculator/pytest.ini +++ b/embedding-calculator/pytest.ini @@ -1,13 +1,13 @@ -[pytest] -filterwarnings = - ignore:.*U.*is deprecated:DeprecationWarning -markers = - integration - performance -mock_use_standalone_module = true -addopts = - --strict-markers - --doctest-modules - -ra - --verbose - --ignore-glob=*/tmp/* +[pytest] +filterwarnings = + ignore:.*U.*is deprecated:DeprecationWarning +markers = + integration + performance +mock_use_standalone_module = true +addopts = + --strict-markers + --doctest-modules + -ra + --verbose + --ignore-glob=*/tmp/* diff --git a/embedding-calculator/requirements.txt b/embedding-calculator/requirements.txt index 553853de94..fdad3e3671 100644 --- a/embedding-calculator/requirements.txt +++ b/embedding-calculator/requirements.txt @@ -1,27 +1,31 @@ -attrs==20.2.0 -cached-property==1.5.2 -colour==0.1.5 -flasgger==0.9.5 -Flask==1.1.2 -gdown~=3.12 -Werkzeug==1.0.1 - -# tests -mock~=4.0.2 -pytest~=6.1.2 -pytest-mock~=3.3.1 -requests~=2.24.0 -pylama~=7.7.1 - -# dependencies for both scanner backends -Pillow~=8.0.1 -imagecodecs~=2020.5.30 -numpy~=1.18.0 -scipy~=1.5.4 -opencv-python~=4.4.0 -scikit-learn~=0.23.2 -scikit-image~=0.17.2 -joblib~=0.17.0 - -# web server -uWSGI==2.0.19 \ No newline at end of file +attrs==20.2.0 +cached-property==1.5.2 +colour==0.1.5 +flasgger==0.9.5 +Flask==1.1.2 +gdown~=3.12 +Werkzeug==1.0.1 + +# tests +mock~=4.0.2 +pytest~=6.1.2 +pytest-mock~=3.3.1 +requests~=2.24.0 +pylama~=7.7.1 + +# dependencies for both scanner backends +Pillow~=8.0.1 +imagecodecs~=2020.5.30 +#numpy~=1.18.0 +numpy~=1.19.5 +scipy~=1.5.4 +opencv-python~=4.4.0 +scikit-learn~=0.23.2 +scikit-image~=0.17.2 +joblib~=0.17.0 + +# web server +uWSGI==2.0.19 + +# for successful tensorflow import +mtcnn~=0.1.0 \ No newline at end of file diff --git a/embedding-calculator/src/__init__.py b/embedding-calculator/src/__init__.py index 9642041973..617138f790 100644 --- a/embedding-calculator/src/__init__.py +++ b/embedding-calculator/src/__init__.py @@ -1,13 +1,13 @@ -# Copyright (c) 2020 the original author or authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express -# or implied. See the License for the specific language governing -# permissions and limitations under the License. +# Copyright (c) 2020 the original author or authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing +# permissions and limitations under the License. diff --git a/embedding-calculator/src/_docs.py b/embedding-calculator/src/_docs.py index 11d634d351..49bb4b80cd 100644 --- a/embedding-calculator/src/_docs.py +++ b/embedding-calculator/src/_docs.py @@ -1,37 +1,37 @@ -# Copyright (c) 2020 the original author or authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express -# or implied. See the License for the specific language governing -# permissions and limitations under the License. - -from flasgger import Swagger - -from src.docs import DOCS_DIR - - -def add_docs(app): - app.config['SWAGGER'] = { - "title": "Swagger UI", - "doc_dir": str(DOCS_DIR), - "specs": [ - { - "endpoint": "frs-core-api", - "route": "/frs-core-api.json", - "rule_filter": lambda rule: True, # all in - "model_filter": lambda tag: True, # all in - } - ], - "static_url_path": "/apidocs", - "swagger_ui": True, - "specs_route": "/apidocs", - "endpoint": 'flasgger' - } - Swagger(app, template_file=str(DOCS_DIR / 'template.yml')) +# Copyright (c) 2020 the original author or authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing +# permissions and limitations under the License. + +from flasgger import Swagger + +from src.docs import DOCS_DIR + + +def add_docs(app): + app.config['SWAGGER'] = { + "title": "Swagger UI", + "doc_dir": str(DOCS_DIR), + "specs": [ + { + "endpoint": "frs-core-api", + "route": "/frs-core-api.json", + "rule_filter": lambda rule: True, # all in + "model_filter": lambda tag: True, # all in + } + ], + "static_url_path": "/apidocs", + "swagger_ui": True, + "specs_route": "/apidocs", + "endpoint": 'flasgger' + } + Swagger(app, template_file=str(DOCS_DIR / 'template.yml')) diff --git a/embedding-calculator/src/_endpoints.py b/embedding-calculator/src/_endpoints.py index 53685293d1..59de89769f 100644 --- a/embedding-calculator/src/_endpoints.py +++ b/embedding-calculator/src/_endpoints.py @@ -1,130 +1,130 @@ -# Copyright (c) 2020 the original author or authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express -# or implied. See the License for the specific language governing -# permissions and limitations under the License. -from typing import List, Optional - -from flask import request -from flask.json import jsonify -from werkzeug.exceptions import BadRequest - -from src.constants import ENV -from src.exceptions import NoFaceFoundError -from src.services.facescan.plugins import base, managers -from src.services.facescan.scanner.facescanners import scanner -from src.services.flask_.constants import ARG -from src.services.flask_.needs_attached_file import needs_attached_file -from src.services.imgtools.read_img import read_img -from src.services.utils.pyutils import Constants -import base64 - - -def endpoints(app): - @app.route('/status') - def status_get(): - available_plugins = {p.slug: str(p) - for p in managers.plugin_manager.plugins} - calculator = managers.plugin_manager.calculator - return jsonify( - status='OK', build_version=ENV.BUILD_VERSION, - calculator_version=str(calculator), - similarity_coefficients=calculator.ml_model.similarity_coefficients, - available_plugins=available_plugins - ) - - @app.route('/find_faces_base64', methods=['POST']) - def find_faces_base64_post(): - detector = managers.plugin_manager.detector - face_plugins = managers.plugin_manager.filter_face_plugins( - _get_face_plugin_names() - ) - - rawfile = base64.b64decode(request.get_json()["file"]) - - faces = detector( - img=read_img(rawfile), - det_prob_threshold=_get_det_prob_threshold(), - face_plugins=face_plugins - ) - plugins_versions = {p.slug: str(p) for p in [detector] + face_plugins} - faces = _limit(faces, request.values.get(ARG.LIMIT)) - return jsonify(plugins_versions=plugins_versions, result=faces) - - @app.route('/find_faces', methods=['POST']) - @needs_attached_file - def find_faces_post(): - detector = managers.plugin_manager.detector - face_plugins = managers.plugin_manager.filter_face_plugins( - _get_face_plugin_names() - ) - faces = detector( - img=read_img(request.files['file']), - det_prob_threshold=_get_det_prob_threshold(), - face_plugins=face_plugins - ) - plugins_versions = {p.slug: str(p) for p in [detector] + face_plugins} - faces = _limit(faces, request.values.get(ARG.LIMIT)) - return jsonify(plugins_versions=plugins_versions, result=faces) - - @app.route('/scan_faces', methods=['POST']) - @needs_attached_file - def scan_faces_post(): - faces = scanner.scan( - img=read_img(request.files['file']), - det_prob_threshold=_get_det_prob_threshold() - ) - faces = _limit(faces, request.values.get(ARG.LIMIT)) - return jsonify(calculator_version=scanner.ID, result=faces) - - -def _get_det_prob_threshold(): - det_prob_threshold_val = request.values.get(ARG.DET_PROB_THRESHOLD) - if det_prob_threshold_val is None: - return None - det_prob_threshold = float(det_prob_threshold_val) - if not (0 <= det_prob_threshold <= 1): - raise BadRequest('Detection threshold incorrect (0 <= det_prob_threshold <= 1)') - return det_prob_threshold - - -def _get_face_plugin_names() -> Optional[List[str]]: - if ARG.FACE_PLUGINS not in request.values: - return [] - return [ - name for name in Constants.split(request.values[ARG.FACE_PLUGINS]) - ] - - -def _limit(faces: List, limit: str = None) -> List: - """ - >>> _limit([1, 2, 3], None) - [1, 2, 3] - >>> _limit([1, 2, 3], '') - [1, 2, 3] - >>> _limit([1, 2, 3], 0) - [1, 2, 3] - >>> _limit([1, 2, 3], 1) - [1] - >>> _limit([1, 2, 3], 2) - [1, 2] - """ - if len(faces) == 0: - raise NoFaceFoundError - - try: - limit = int(limit or 0) - except ValueError as e: - raise BadRequest('Limit format is invalid (limit >= 0)') from e - if not (limit >= 0): - raise BadRequest('Limit value is invalid (limit >= 0)') - - return faces[:limit] if limit else faces +# Copyright (c) 2020 the original author or authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing +# permissions and limitations under the License. +from typing import List, Optional + +from flask import request +from flask.json import jsonify +from werkzeug.exceptions import BadRequest + +from src.constants import ENV +from src.exceptions import NoFaceFoundError +from src.services.facescan.plugins import base, managers +from src.services.facescan.scanner.facescanners import scanner +from src.services.flask_.constants import ARG +from src.services.flask_.needs_attached_file import needs_attached_file +from src.services.imgtools.read_img import read_img +from src.services.utils.pyutils import Constants +import base64 + + +def endpoints(app): + @app.route('/status') + def status_get(): + available_plugins = {p.slug: str(p) + for p in managers.plugin_manager.plugins} + calculator = managers.plugin_manager.calculator + return jsonify( + status='OK', build_version=ENV.BUILD_VERSION, + calculator_version=str(calculator), + similarity_coefficients=calculator.ml_model.similarity_coefficients, + available_plugins=available_plugins + ) + + @app.route('/find_faces_base64', methods=['POST']) + def find_faces_base64_post(): + detector = managers.plugin_manager.detector + face_plugins = managers.plugin_manager.filter_face_plugins( + _get_face_plugin_names() + ) + + rawfile = base64.b64decode(request.get_json()["file"]) + + faces = detector( + img=read_img(rawfile), + det_prob_threshold=_get_det_prob_threshold(), + face_plugins=face_plugins + ) + plugins_versions = {p.slug: str(p) for p in [detector] + face_plugins} + faces = _limit(faces, request.values.get(ARG.LIMIT)) + return jsonify(plugins_versions=plugins_versions, result=faces) + + @app.route('/find_faces', methods=['POST']) + @needs_attached_file + def find_faces_post(): + detector = managers.plugin_manager.detector + face_plugins = managers.plugin_manager.filter_face_plugins( + _get_face_plugin_names() + ) + faces = detector( + img=read_img(request.files['file']), + det_prob_threshold=_get_det_prob_threshold(), + face_plugins=face_plugins + ) + plugins_versions = {p.slug: str(p) for p in [detector] + face_plugins} + faces = _limit(faces, request.values.get(ARG.LIMIT)) + return jsonify(plugins_versions=plugins_versions, result=faces) + + @app.route('/scan_faces', methods=['POST']) + @needs_attached_file + def scan_faces_post(): + faces = scanner.scan( + img=read_img(request.files['file']), + det_prob_threshold=_get_det_prob_threshold() + ) + faces = _limit(faces, request.values.get(ARG.LIMIT)) + return jsonify(calculator_version=scanner.ID, result=faces) + + +def _get_det_prob_threshold(): + det_prob_threshold_val = request.values.get(ARG.DET_PROB_THRESHOLD) + if det_prob_threshold_val is None: + return None + det_prob_threshold = float(det_prob_threshold_val) + if not (0 <= det_prob_threshold <= 1): + raise BadRequest('Detection threshold incorrect (0 <= det_prob_threshold <= 1)') + return det_prob_threshold + + +def _get_face_plugin_names() -> Optional[List[str]]: + if ARG.FACE_PLUGINS not in request.values: + return [] + return [ + name for name in Constants.split(request.values[ARG.FACE_PLUGINS]) + ] + + +def _limit(faces: List, limit: str = None) -> List: + """ + >>> _limit([1, 2, 3], None) + [1, 2, 3] + >>> _limit([1, 2, 3], '') + [1, 2, 3] + >>> _limit([1, 2, 3], 0) + [1, 2, 3] + >>> _limit([1, 2, 3], 1) + [1] + >>> _limit([1, 2, 3], 2) + [1, 2] + """ + if len(faces) == 0: + raise NoFaceFoundError + + try: + limit = int(limit or 0) + except ValueError as e: + raise BadRequest('Limit format is invalid (limit >= 0)') from e + if not (limit >= 0): + raise BadRequest('Limit value is invalid (limit >= 0)') + + return faces[:limit] if limit else faces diff --git a/embedding-calculator/src/conftest.py b/embedding-calculator/src/conftest.py index 77ecef1430..4fd50fd4fb 100644 --- a/embedding-calculator/src/conftest.py +++ b/embedding-calculator/src/conftest.py @@ -1,19 +1,19 @@ -# Copyright (c) 2020 the original author or authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express -# or implied. See the License for the specific language governing -# permissions and limitations under the License. - -import logging - -from src.init_runtime import init_runtime - -init_runtime(logging.DEBUG) +# Copyright (c) 2020 the original author or authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing +# permissions and limitations under the License. + +import logging + +from src.init_runtime import init_runtime + +init_runtime(logging.DEBUG) diff --git a/embedding-calculator/src/constants.py b/embedding-calculator/src/constants.py index 492013ffc9..50ce605cbd 100644 --- a/embedding-calculator/src/constants.py +++ b/embedding-calculator/src/constants.py @@ -1,39 +1,39 @@ -# Copyright (c) 2020 the original author or authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express -# or implied. See the License for the specific language governing -# permissions and limitations under the License. - -import logging - -from src.services.utils.pyutils import get_env, get_env_split, get_env_bool, Constants - -_DEFAULT_SCANNER = 'Facenet2018' - - -class ENV(Constants): - ML_PORT = int(get_env('ML_PORT', '3000')) - IMG_LENGTH_LIMIT = int(get_env('IMG_LENGTH_LIMIT', '640')) - - FACE_DETECTION_PLUGIN = get_env('FACE_DETECTION_PLUGIN', 'facenet.FaceDetector') - CALCULATION_PLUGIN = get_env('CALCULATION_PLUGIN', 'facenet.Calculator') - EXTRA_PLUGINS = get_env_split('EXTRA_PLUGINS', '') - - LOGGING_LEVEL_NAME = get_env('LOGGING_LEVEL_NAME', 'debug').upper() - IS_DEV_ENV = get_env('FLASK_ENV', 'production') == 'development' - BUILD_VERSION = get_env('APP_VERSION_STRING', 'dev') - - GPU_IDX = int(get_env('GPU_IDX', '-1')) - INTEL_OPTIMIZATION = get_env_bool('INTEL_OPTIMIZATION') - - -LOGGING_LEVEL = logging._nameToLevel[ENV.LOGGING_LEVEL_NAME] -ENV_MAIN = ENV +# Copyright (c) 2020 the original author or authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing +# permissions and limitations under the License. + +import logging + +from src.services.utils.pyutils import get_env, get_env_split, get_env_bool, Constants + +_DEFAULT_SCANNER = 'Facenet2018' + + +class ENV(Constants): + ML_PORT = int(get_env('ML_PORT', '3000')) + IMG_LENGTH_LIMIT = int(get_env('IMG_LENGTH_LIMIT', '640')) + + FACE_DETECTION_PLUGIN = get_env('FACE_DETECTION_PLUGIN', 'facenet.FaceDetector') + CALCULATION_PLUGIN = get_env('CALCULATION_PLUGIN', 'facenet.Calculator') + EXTRA_PLUGINS = get_env_split('EXTRA_PLUGINS', 'facenet.LandmarksDetector,agegender.AgeDetector,agegender.GenderDetector,facenet.facemask.MaskDetector') + + LOGGING_LEVEL_NAME = get_env('LOGGING_LEVEL_NAME', 'debug').upper() + IS_DEV_ENV = get_env('FLASK_ENV', 'production') == 'development' + BUILD_VERSION = get_env('APP_VERSION_STRING', 'dev') + + GPU_IDX = int(get_env('GPU_IDX', '-1')) + INTEL_OPTIMIZATION = get_env_bool('INTEL_OPTIMIZATION') + + +LOGGING_LEVEL = logging._nameToLevel[ENV.LOGGING_LEVEL_NAME] +ENV_MAIN = ENV diff --git a/embedding-calculator/src/docs/__init__.py b/embedding-calculator/src/docs/__init__.py index f3d196aba3..fd583aefaa 100644 --- a/embedding-calculator/src/docs/__init__.py +++ b/embedding-calculator/src/docs/__init__.py @@ -1,17 +1,17 @@ -# Copyright (c) 2020 the original author or authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express -# or implied. See the License for the specific language governing -# permissions and limitations under the License. - -from src.services.utils.pyutils import get_current_dir - -DOCS_DIR = get_current_dir(__file__) +# Copyright (c) 2020 the original author or authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing +# permissions and limitations under the License. + +from src.services.utils.pyutils import get_current_dir + +DOCS_DIR = get_current_dir(__file__) diff --git a/embedding-calculator/src/services/dto/plugin_result.py b/embedding-calculator/src/services/dto/plugin_result.py index 49b32fef49..6a94b7f5bc 100644 --- a/embedding-calculator/src/services/dto/plugin_result.py +++ b/embedding-calculator/src/services/dto/plugin_result.py @@ -23,6 +23,12 @@ class AgeDTO(JSONEncodable): age_probability: float = attr.ib(converter=float, default=1) +@attr.s(auto_attribs=True, frozen=True) +class MaskDTO(JSONEncodable): + mask: str + mask_probability: float = attr.ib(converter=float, default=1) + + @attr.s(auto_attribs=True, frozen=True) class LandmarksDTO(JSONEncodable): """ 5-points facial landmarks: eyes, nose, mouth """ diff --git a/embedding-calculator/src/services/facescan/plugins/agegender/__init__.py b/embedding-calculator/src/services/facescan/plugins/agegender/__init__.py index 32d2256d3d..94673c403f 100644 --- a/embedding-calculator/src/services/facescan/plugins/agegender/__init__.py +++ b/embedding-calculator/src/services/facescan/plugins/agegender/__init__.py @@ -12,4 +12,4 @@ # or implied. See the License for the specific language governing # permissions and limitations under the License. -requirements = ('tensorflow~=1.15.4',) \ No newline at end of file +requirements = ('tensorflow~=2.5.0','tf-slim~=1.1.0') \ No newline at end of file diff --git a/embedding-calculator/src/services/facescan/plugins/agegender/agegender.py b/embedding-calculator/src/services/facescan/plugins/agegender/agegender.py index 0130af1a2a..4a2c19cb99 100644 --- a/embedding-calculator/src/services/facescan/plugins/agegender/agegender.py +++ b/embedding-calculator/src/services/facescan/plugins/agegender/agegender.py @@ -15,7 +15,7 @@ from typing import Tuple, Union import numpy as np -import tensorflow as tf +import tensorflow.compat.v1 as tf1 from cached_property import cached_property from src.services.imgtools.types import Array3D @@ -33,18 +33,18 @@ def _model(self): model_dir = self.ml_model.path IMAGE_SIZE = managers.plugin_manager.detector.IMAGE_SIZE - g = tf.Graph() + g = tf1.Graph() with g.as_default(): - sess = tf.Session(config=tf.ConfigProto(allow_soft_placement=True)) + sess = tf1.Session(config=tf1.ConfigProto(allow_soft_placement=True)) - images = tf.placeholder(tf.float32, [None, IMAGE_SIZE, IMAGE_SIZE, 3]) + images = tf1.placeholder(tf1.float32, [None, IMAGE_SIZE, IMAGE_SIZE, 3]) logits = helpers.inception_v3(len(labels), images) - tf.global_variables_initializer() + tf1.global_variables_initializer() - checkpoint = tf.train.get_checkpoint_state(model_dir) - saver = tf.train.Saver() + checkpoint = tf1.train.get_checkpoint_state(model_dir) + saver = tf1.train.Saver() saver.restore(sess, checkpoint.model_checkpoint_path) - softmax_output = tf.nn.softmax(logits) + softmax_output = tf1.nn.softmax(logits) def get_value(img: Array3D) -> Tuple[Union[str, Tuple], float]: img = np.expand_dims(helpers.prewhiten(img), 0) @@ -76,3 +76,4 @@ class GenderDetector(BaseAgeGender): def __call__(self, face: plugin_result.FaceDTO): value, probability = self._model(face._face_img) return plugin_result.GenderDTO(gender=value, gender_probability=probability) + diff --git a/embedding-calculator/src/services/facescan/plugins/agegender/helpers.py b/embedding-calculator/src/services/facescan/plugins/agegender/helpers.py index 79ee3b8877..84ee94a2da 100644 --- a/embedding-calculator/src/services/facescan/plugins/agegender/helpers.py +++ b/embedding-calculator/src/services/facescan/plugins/agegender/helpers.py @@ -13,11 +13,10 @@ # permissions and limitations under the License. import numpy as np -import os -import tensorflow as tf +import tensorflow.compat.v1 as tf1 import re -from tensorflow.contrib import layers, slim -from tensorflow.contrib.slim.python.slim.nets.inception_v3 import inception_v3_base +import tf_slim +from tf_slim.nets.inception_v3 import inception_v3_base def prewhiten(img): @@ -42,38 +41,39 @@ def inception_v3(nlabels, images): } weight_decay = 0.00004 stddev = 0.1 - weights_regularizer = layers.l2_regularizer(weight_decay) + weights_regularizer = tf_slim.l2_regularizer(weight_decay) args_for_scope = ( - dict(list_ops_or_scope=[slim.conv2d, slim.fully_connected], + dict(list_ops_or_scope=[tf_slim.layers.conv2d, tf_slim.layers.fully_connected], weights_regularizer=weights_regularizer, trainable=True), - dict(list_ops_or_scope=[slim.conv2d], - weights_initializer=tf.truncated_normal_initializer(stddev=stddev), - activation_fn=tf.nn.relu, - normalizer_fn=layers.batch_norm, + dict(list_ops_or_scope=[tf_slim.layers.conv2d], + weights_initializer=tf1.truncated_normal_initializer(stddev=stddev), + activation_fn=tf1.nn.relu, + normalizer_fn=tf_slim.layers.batch_norm, normalizer_params=batch_norm_params), ) - with tf.variable_scope("InceptionV3", "InceptionV3", [images]) as scope, \ - slim.arg_scope(**args_for_scope[0]), \ - slim.arg_scope(**args_for_scope[1]): + with tf1.variable_scope("InceptionV3", "InceptionV3", [images]) as scope, \ + tf_slim.arg_scope(**args_for_scope[0]), \ + tf_slim.arg_scope(**args_for_scope[1]): net, end_points = inception_v3_base(images, scope=scope) - with tf.variable_scope("logits"): + with tf1.variable_scope("logits"): shape = net.get_shape() - net = layers.avg_pool2d(net, shape[1:3], padding="VALID", + net = tf_slim.layers.avg_pool2d(net, shape[1:3], padding="VALID", scope="pool") - net = tf.nn.dropout(net, 1, name='droplast') - net = layers.flatten(net, scope="flatten") + net = tf1.nn.dropout(net, 1, name='droplast') + net = tf_slim.layers.flatten(net, scope="flatten") - with tf.variable_scope('output') as scope: - weights = tf.Variable( - tf.truncated_normal([2048, nlabels], mean=0.0, stddev=0.01), + with tf1.variable_scope('output') as scope: + weights = tf1.Variable( + tf1.truncated_normal([2048, nlabels], mean=0.0, stddev=0.01), name='weights') - biases = tf.Variable( - tf.constant(0.0, shape=[nlabels], dtype=tf.float32), name='biases') - output = tf.add(tf.matmul(net, weights), biases, name=scope.name) + biases = tf1.Variable( + tf1.constant(0.0, shape=[nlabels], dtype=tf1.float32), name='biases') + output = tf1.add(tf1.matmul(net, weights), biases, name=scope.name) tensor_name = re.sub('tower_[0-9]*/', '', output.op.name) - tf.summary.histogram(tensor_name + '/activations', output) - tf.summary.scalar(tensor_name + '/sparsity', tf.nn.zero_fraction(output)) + tf1.summary.histogram(tensor_name + '/activations', output) + tf1.summary.scalar(tensor_name + '/sparsity', tf1.nn.zero_fraction(output)) return output + diff --git a/embedding-calculator/src/services/facescan/plugins/base.py b/embedding-calculator/src/services/facescan/plugins/base.py index ac9a09009f..96b19dffc8 100644 --- a/embedding-calculator/src/services/facescan/plugins/base.py +++ b/embedding-calculator/src/services/facescan/plugins/base.py @@ -71,11 +71,19 @@ def _download(cls, url: str, output): def _extract(self, filename: str): os.makedirs(self.path, exist_ok=True) with ZipFile(filename, 'r') as zf: - for info in zf.infolist(): - if info.is_dir(): - continue - file_path = Path(self.path) / Path(info.filename).name - file_path.write_bytes(zf.read(info)) + if str(self.path)[-18:] == 'face_mask_detector': + for info in zf.infolist(): + if info.is_dir(): + os.makedirs(Path(self.path) / Path(info.filename)) + continue + file_path = Path(self.path) / Path(info.filename) + file_path.write_bytes(zf.read(info)) + else: + for info in zf.infolist(): + if info.is_dir(): + continue + file_path = Path(self.path) / Path(info.filename).name + file_path.write_bytes(zf.read(info)) @attr.s(auto_attribs=True) diff --git a/embedding-calculator/src/services/facescan/plugins/dependencies.py b/embedding-calculator/src/services/facescan/plugins/dependencies.py index dbf7e79699..d0310bf6a0 100644 --- a/embedding-calculator/src/services/facescan/plugins/dependencies.py +++ b/embedding-calculator/src/services/facescan/plugins/dependencies.py @@ -18,7 +18,7 @@ from src.services.utils.pyutils import get_env -def get_tensorflow(version='1.15.4') -> Tuple[str, ...]: +def get_tensorflow(version='2.5.0') -> Tuple[str, ...]: libs = [f'tensorflow=={version}'] cuda_version = get_env('CUDA', '').replace('.', '') if ENV.GPU_IDX > -1 and cuda_version: diff --git a/embedding-calculator/src/services/facescan/plugins/facenet/__init__.py b/embedding-calculator/src/services/facescan/plugins/facenet/__init__.py index 793c8ffb04..43a483dc42 100644 --- a/embedding-calculator/src/services/facescan/plugins/facenet/__init__.py +++ b/embedding-calculator/src/services/facescan/plugins/facenet/__init__.py @@ -14,5 +14,4 @@ from src.services.facescan.plugins.dependencies import get_tensorflow - -requirements = get_tensorflow() + ('facenet~=1.0.5',) +requirements = get_tensorflow() # + ('mtcnn~=0.1.0',) diff --git a/embedding-calculator/src/services/facescan/plugins/facenet/facemask/__init__.py b/embedding-calculator/src/services/facescan/plugins/facenet/facemask/__init__.py new file mode 100644 index 0000000000..28a3ccefaf --- /dev/null +++ b/embedding-calculator/src/services/facescan/plugins/facenet/facemask/__init__.py @@ -0,0 +1,17 @@ +# Copyright (c) 2020 the original author or authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing +# permissions and limitations under the License. + +from src.services.facescan.plugins.dependencies import get_tensorflow + +requirements = get_tensorflow() \ No newline at end of file diff --git a/embedding-calculator/src/services/facescan/plugins/facenet/facemask/facemask.py b/embedding-calculator/src/services/facescan/plugins/facenet/facemask/facemask.py new file mode 100644 index 0000000000..a07cde4f6e --- /dev/null +++ b/embedding-calculator/src/services/facescan/plugins/facenet/facemask/facemask.py @@ -0,0 +1,64 @@ +# Copyright (c) 2020 the original author or authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing +# permissions and limitations under the License. + +from typing import Tuple, Union + +import numpy as np +import tensorflow as tf2 +from tensorflow.keras.models import load_model +from cached_property import cached_property + +from src.services.imgtools.types import Array3D +from src.services.facescan.plugins import base +from src.services.dto import plugin_result + +import cv2 + + +class MaskDetector(base.BasePlugin): + slug = 'mask' + LABELS = ('without_mask', 'with_mask', 'mask_weared_incorrect') + ml_models = ( + ('face_mask_detector', '1AeSYb_E_3cqZM67qXnJ__wJDgw6yqTDV'), + ) + INPUT_IMAGE_SIZE = 100 + category2label = {0: 'without_mask', 1: 'with_mask', 2: 'mask_weared_incorrect'} + + @cached_property + def _model(self): + model = tf2.keras.models.load_model( + self.ml_model.path, + options=tf2.saved_model.LoadOptions( + experimental_io_device='/job:localhost' + ) + ) + + def get_value(img: Array3D) -> Tuple[Union[str, Tuple], float]: + img = cv2.resize(img, dsize=(self.INPUT_IMAGE_SIZE, self.INPUT_IMAGE_SIZE), + interpolation=cv2.INTER_CUBIC) + img = img[:, :, [2, 1, 0]] + img = np.expand_dims(img, 0) + + scores = model.predict(img) + print('Predictions: ' + str(scores)) + val = self.LABELS[int(np.argmax(scores, axis=1)[0])] + prob = scores[0][int(np.argmax(scores, axis=1)[0])] + return val, prob + return get_value + + def __call__(self, face: plugin_result.FaceDTO): + value, probability = self._model(face._face_img) + return plugin_result.MaskDTO(mask=value, mask_probability=probability) + + diff --git a/embedding-calculator/src/services/facescan/plugins/facenet/facenet.py b/embedding-calculator/src/services/facescan/plugins/facenet/facenet.py index edc0b5bc41..64ba6cd668 100644 --- a/embedding-calculator/src/services/facescan/plugins/facenet/facenet.py +++ b/embedding-calculator/src/services/facescan/plugins/facenet/facenet.py @@ -18,11 +18,10 @@ from typing import List import numpy as np -import tensorflow as tf +import tensorflow.compat.v1 as tf1 from tensorflow.python.platform import gfile from cached_property import cached_property -from facenet.src.align import detect_face -from facenet.src.facenet import prewhiten +from mtcnn import MTCNN from src.constants import ENV from src.services.dto.bounding_box import BoundingBoxDTO @@ -41,6 +40,15 @@ _FaceDetectionNets = namedtuple('_FaceDetectionNets', 'pnet rnet onet') +def prewhiten(img): + """ Normalize image.""" + mean = np.mean(img) + std = np.std(img) + std_adj = np.maximum(std, 1.0 / np.sqrt(img.size)) + y = np.multiply(np.subtract(img, mean), 1 / std_adj) + return y + + class FaceDetector(mixins.FaceDetectorMixin, base.BasePlugin): FACE_MIN_SIZE = 20 SCALE_FACTOR = 0.709 @@ -54,11 +62,19 @@ class FaceDetector(mixins.FaceDetectorMixin, base.BasePlugin): det_threshold_b = 0.7059968943 det_threshold_c = 0.5506904359 - @cached_property + ''' @cached_property def _face_detection_nets(self): - with tf.Graph().as_default(): - sess = tf.Session() - return _FaceDetectionNets(*detect_face.create_mtcnn(sess, None)) + with tf1.Graph().as_default(): + sess = tf1.Session() + return _FaceDetectionNets(*detect_face.create_mtcnn(sess, None))''' + + @cached_property + def _face_detection_net(self): + return MTCNN( + min_face_size=self.FACE_MIN_SIZE, + scale_factor=self.SCALE_FACTOR, + steps_threshold=[self.det_threshold_a, self.det_threshold_b, self.det_threshold_c] + ) def crop_face(self, img: Array3D, box: BoundingBoxDTO) -> Array3D: return squish_img(crop_img(img, box), (self.IMAGE_SIZE, self.IMAGE_SIZE)) @@ -70,15 +86,16 @@ def find_faces(self, img: Array3D, det_prob_threshold: float = None) -> List[Bou scaler = ImgScaler(self.IMG_LENGTH_LIMIT) img = scaler.downscale_img(img) - fdn = self._face_detection_nets - detect_face_result = detect_face.detect_face( + fdn = self._face_detection_net + '''detect_face_result = detect_face.detect_face( img, self.FACE_MIN_SIZE, fdn.pnet, fdn.rnet, fdn.onet, [self.det_threshold_a, self.det_threshold_b, self.det_threshold_c], - self.SCALE_FACTOR) + self.SCALE_FACTOR)''' + detect_face_result = fdn.detect_faces(img) img_size = np.asarray(img.shape)[0:2] bounding_boxes = [] - detect_face_result = list( + '''detect_face_result = list( zip(detect_face_result[0], detect_face_result[1].transpose())) for result_item, landmarks in detect_face_result: result_item = np.squeeze(result_item) @@ -92,6 +109,22 @@ def find_faces(self, img: Array3D, det_prob_threshold: float = None) -> List[Bou probability=result_item[4] ) logger.debug(f"Found: {box}") + bounding_boxes.append(box)''' + + for face in detect_face_result: + #margin = self.BOX_MARGIN / 2 + x, y, w, h = face['box'] + margin_x = w / 8 + margin_y = h / 8 + box = BoundingBoxDTO( + x_min=int(np.maximum(x - margin_x, 0)), + y_min=int(np.maximum(y - margin_y, 0)), + x_max=int(np.minimum(x + w + margin_x, img_size[1])), + y_max=int(np.minimum(y + h + margin_y, img_size[0])), + np_landmarks=np.array([list(value) for value in face['keypoints'].values()]), + probability=face['confidence'] + ) + logger.debug(f"Found: {box}") bounding_boxes.append(box) filtered_bounding_boxes = [] @@ -122,13 +155,13 @@ def calc_embedding(self, face_img: Array3D) -> Array3D: @cached_property def _embedding_calculator(self): - with tf.Graph().as_default() as graph: - graph_def = tf.GraphDef() + with tf1.Graph().as_default() as graph: + graph_def = tf1.GraphDef() with gfile.FastGFile(self.ml_model_file, 'rb') as f: model = f.read() graph_def.ParseFromString(model) - tf.import_graph_def(graph_def, name='') - return _EmbeddingCalculator(graph=graph, sess=tf.Session(graph=graph)) + tf1.import_graph_def(graph_def, name='') + return _EmbeddingCalculator(graph=graph, sess=tf1.Session(graph=graph)) def _calculate_embeddings(self, cropped_images): """Run forward pass to calculate embeddings""" diff --git a/embedding-calculator/src/services/facescan/plugins/insightface/insightface.py b/embedding-calculator/src/services/facescan/plugins/insightface/insightface.py index 8ab1a5cfea..2c8cfa2919 100644 --- a/embedding-calculator/src/services/facescan/plugins/insightface/insightface.py +++ b/embedding-calculator/src/services/facescan/plugins/insightface/insightface.py @@ -61,7 +61,7 @@ class FaceDetector(InsightFaceMixin, mixins.FaceDetectorMixin, base.BasePlugin): ml_models = ( ('retinaface_mnet025_v1', '1ggNFFqpe0abWz6V1A82rnxD6fyxB8W2c'), ('retinaface_mnet025_v2', '1EYTMxgcNdlvoL1fSC8N1zkaWrX75ZoNL'), - ('retinaface_r50_v1', '1LZ5h9f_YC5EdbIZAqVba9TKHipi90JBj'), + ('retinaface_r50_v1', '1hvEv4xZP-_50cO7IYkH6sDUb_SC92wut'), ) IMG_LENGTH_LIMIT = ENV.IMG_LENGTH_LIMIT diff --git a/embedding-calculator/src/services/facescan/plugins/managers.py b/embedding-calculator/src/services/facescan/plugins/managers.py index 79b87c64f9..27b6873f7b 100644 --- a/embedding-calculator/src/services/facescan/plugins/managers.py +++ b/embedding-calculator/src/services/facescan/plugins/managers.py @@ -36,7 +36,11 @@ class PluginManager: def __init__(self): self.plugins_modules = defaultdict(list) for plugin_name in self.get_plugins_names(): - module = import_module(f'{__package__}.{plugin_name.split(".")[0]}') + # this don't work's + # module = import_module(f'{__package__}.{plugin_name.split(".")[0]}') + # this how i fix it + module = import_module(f'{__package__}.{plugin_name.rsplit(".", 1)[0]}') + plugin_name = plugin_name.split('.')[-2] + '.' + plugin_name.split('.')[-1] self.plugins_modules[module].append(plugin_name) @property From c7a643283b1617005a6afdc300fe7dd990642e30 Mon Sep 17 00:00:00 2001 From: Pavel Bohdan Date: Tue, 1 Jun 2021 23:26:49 +0300 Subject: [PATCH 010/837] Added new frames for Recognition and Detection services. --- .../app-change-photo.component.html | 30 ++++++ .../app-change-photo.component.scss | 51 +++++++++ .../app-change-photo.component.ts | 34 ++++++ .../app-change-photo.module.ts | 27 +++++ .../face-recognition-container.component.html | 6 +- .../face-recognition-container.component.ts | 9 +- .../recognition-result.component.html | 48 +++++---- .../recognition-result.component.scss | 10 +- .../recognition-result.component.ts | 101 +++++++++--------- .../face-services/face-services.module.ts | 5 +- ui/src/app/store/face-recognition/action.ts | 1 - ui/src/app/store/face-recognition/effects.ts | 1 + ui/src/app/store/face-recognition/reducers.ts | 2 +- ui/src/app/store/face-verification/action.ts | 1 + ui/src/app/store/face-verification/effects.ts | 1 + ui/src/styles/colors.scss | 2 + ui/src/styles/photo-spiner.scss | 31 ++++++ 17 files changed, 272 insertions(+), 88 deletions(-) create mode 100644 ui/src/app/features/app-change-photo/app-change-photo.component.html create mode 100644 ui/src/app/features/app-change-photo/app-change-photo.component.scss create mode 100644 ui/src/app/features/app-change-photo/app-change-photo.component.ts create mode 100644 ui/src/app/features/app-change-photo/app-change-photo.module.ts create mode 100644 ui/src/styles/photo-spiner.scss diff --git a/ui/src/app/features/app-change-photo/app-change-photo.component.html b/ui/src/app/features/app-change-photo/app-change-photo.component.html new file mode 100644 index 0000000000..ad24d0a545 --- /dev/null +++ b/ui/src/app/features/app-change-photo/app-change-photo.component.html @@ -0,0 +1,30 @@ + +
+ + + + + + +
diff --git a/ui/src/app/features/app-change-photo/app-change-photo.component.scss b/ui/src/app/features/app-change-photo/app-change-photo.component.scss new file mode 100644 index 0000000000..7d0c296bd4 --- /dev/null +++ b/ui/src/app/features/app-change-photo/app-change-photo.component.scss @@ -0,0 +1,51 @@ +/*! + * Copyright (c) 2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +@import "mixins.scss"; +@import "colors.scss"; + +.change-photo { + position: absolute; + padding: 28px; + line-height: 0; + z-index: 4; + + .btn-input { + opacity: 0; + position: absolute; + height: 100%; + width: 100%; + top: 0; + left: 0; + cursor: pointer; + } + + &--btn { + padding: 11px; + margin-right: 8px; + border-radius: 8px; + line-height: normal; + min-width: auto; + background: $transparent-black; + + &:last-child { + margin-right: 0; + } + + &_icon { + @include lg-icon-size; + } + } +} diff --git a/ui/src/app/features/app-change-photo/app-change-photo.component.ts b/ui/src/app/features/app-change-photo/app-change-photo.component.ts new file mode 100644 index 0000000000..14f2fc973b --- /dev/null +++ b/ui/src/app/features/app-change-photo/app-change-photo.component.ts @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +import { ChangeDetectionStrategy, Component, Output, EventEmitter, ViewChild, ElementRef } from '@angular/core'; + +@Component({ + selector: 'app-change-photo', + templateUrl: './app-change-photo.component.html', + styleUrls: ['./app-change-photo.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AppChangePhotoComponent { + @Output() changePhoto = new EventEmitter(); + @Output() resetPhoto = new EventEmitter(); + @Output() addLandmark = new EventEmitter(); + + @ViewChild('uploadFile') fileDropEl: ElementRef; + + addFile(event) { + console.log(event); + } +} diff --git a/ui/src/app/features/app-change-photo/app-change-photo.module.ts b/ui/src/app/features/app-change-photo/app-change-photo.module.ts new file mode 100644 index 0000000000..62a44f76b9 --- /dev/null +++ b/ui/src/app/features/app-change-photo/app-change-photo.module.ts @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +import { NgModule } from '@angular/core'; +import { MatIconModule } from '@angular/material/icon'; +import { MatButtonModule } from '@angular/material/button'; + +import { AppChangePhotoComponent } from './app-change-photo.component'; + +@NgModule({ + declarations: [AppChangePhotoComponent], + exports: [AppChangePhotoComponent], + imports: [MatIconModule, MatButtonModule], +}) +export class AppChangePhotoModule {} diff --git a/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.html b/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.html index e310b0ba3b..7db0d3534c 100644 --- a/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.html +++ b/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.html @@ -13,7 +13,6 @@ ~ or implied. See the License for the specific language governing ~ permissions and limitations under the License. --> - - -
- -
diff --git a/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.ts b/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.ts index b173dc78a3..7cd5c5d650 100644 --- a/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.ts +++ b/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.ts @@ -29,6 +29,7 @@ import { } from '../../../store/face-recognition/selectors'; import { getFileExtension } from '../face-services.helpers'; import { SnackBarService } from '../../snackbar/snackbar.service'; +import { LoadingPhotoService } from '../../../core/photo-loader/photo-loader.service'; @Component({ selector: 'app-face-recognition-container', @@ -48,7 +49,7 @@ export class FaceRecognitionContainerComponent implements OnInit, OnDestroy { @Input() type: string; - constructor(private store: Store, private snackBarService: SnackBarService) {} + constructor(private store: Store, private snackBarService: SnackBarService, private loadingPhoto: LoadingPhotoService) {} ngOnInit() { this.data$ = this.store.select(selectFaceData); @@ -58,7 +59,7 @@ export class FaceRecognitionContainerComponent implements OnInit, OnDestroy { this.isLoaded$ = this.store.select(selectStateReady); } - ngOnDestroy() { + resetFace(): void { this.store.dispatch(recognizeFaceReset()); } @@ -75,4 +76,8 @@ export class FaceRecognitionContainerComponent implements OnInit, OnDestroy { this.store.dispatch(recognizeFace({ file })); } } + + ngOnDestroy() { + this.resetFace(); + } } diff --git a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.html b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.html index 32124eb78e..2493c2b76f 100644 --- a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.html +++ b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.html @@ -14,40 +14,46 @@ ~ permissions and limitations under the License. -->
+
+ +
- + + - -
- -
- -
- +
+
+
- -
{{ value.subject }}
-
{{ value.similarity }}
+ + +
{{ value.subject }}
+
{{ value.similarity }}
+
+ +
Unknown
+
- -
Unknown
-
- -
- - -
{{ frame.box.probability }}
-
+ +
{{ frame.box.probability }}
+
+
-
+ +
+ +
diff --git a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.scss b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.scss index b519079164..8d02bd9369 100644 --- a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.scss +++ b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.scss @@ -15,6 +15,8 @@ */ @import "media.scss"; @import "frames.scss"; +@import "mixins.scss"; +@import "photo-spiner.scss"; @include frames(); @@ -43,6 +45,10 @@ .process-img { grid-area: process-img; text-align: center; + + &--spinner { + @include photo-spinner(); + } } .request { @@ -54,8 +60,4 @@ grid-area: response; min-width: 100%; } - - canvas { - max-width: 100%; - } } diff --git a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.ts b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.ts index 26d20a5e18..f30481da83 100644 --- a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.ts +++ b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.ts @@ -13,16 +13,16 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ -import { Component, ElementRef, Input, ViewChild, OnChanges, SimpleChanges, Output, EventEmitter, OnDestroy } from '@angular/core'; +import { Component, ElementRef, Input, ViewChild, OnChanges, SimpleChanges, Output, EventEmitter, AfterViewInit } from '@angular/core'; -import { map, takeUntil, tap } from 'rxjs/operators'; -import { ReplaySubject, Subject } from 'rxjs'; +import { filter, map, switchMap, tap } from 'rxjs/operators'; +import { BehaviorSubject, Observable } from 'rxjs'; import { RequestResultRecognition } from '../../../../data/interfaces/response-result'; import { RequestInfo } from '../../../../data/interfaces/request-info'; import { LoadingPhotoService } from '../../../../core/photo-loader/photo-loader.service'; -import { ImageSize } from '../../../../data/interfaces/image'; import { ServiceTypes } from '../../../../data/enums/service-types.enum'; +import { ImageSize } from '../../../../data/interfaces/image'; import { recalculateFaceCoordinate, resultRecognitionFormatter } from '../../face-services.helpers'; @Component({ @@ -30,77 +30,74 @@ import { recalculateFaceCoordinate, resultRecognitionFormatter } from '../../fac templateUrl: './recognition-result.component.html', styleUrls: ['./recognition-result.component.scss'], }) -export class RecognitionResultComponent implements OnChanges, OnDestroy { +export class RecognitionResultComponent implements OnChanges, AfterViewInit { @Input() file: File; @Input() requestInfo: RequestInfo; - @Input() printData: RequestResultRecognition[]; @Input() isLoaded: boolean; @Input() type: string; + @Input() printData: RequestResultRecognition[]; @Output() selectFile = new EventEmitter(); + @Output() resetFace = new EventEmitter(); - @ViewChild('canvasElement') set canvasElement(canvas: ElementRef) { - this.myCanvas = canvas; - } + @ViewChild('canvasElement', { static: true }) canvas: ElementRef; - private myCanvas: ElementRef; - private unsubscribe: Subject = new Subject(); - private sizes: ReplaySubject<{ img: ImageBitmap; sizeCanvas: ImageSize }> = new ReplaySubject(); + private ctx: CanvasRenderingContext2D; + private dataPrint$: BehaviorSubject = new BehaviorSubject(null); + private dataPhoto$: BehaviorSubject = new BehaviorSubject(null); formattedResult: string; - filePrintData: RequestResultRecognition[]; widthCanvas = 500; types = ServiceTypes; + recalculatePrint$: Observable; + + constructor(private loadingPhotoService: LoadingPhotoService) { + this.recalculatePrint$ = this.dataPhoto$.pipe( + filter(data => !!data), + switchMap(data => this.displayPhoto(data)), + switchMap(sizes => this.dataPrint$.pipe(switchMap(printData => this.displayFrames(printData, sizes.imageBitmap, sizes.sizeCanvas)))) + ); + } - constructor(private loadingPhotoService: LoadingPhotoService) {} + ngAfterViewInit(): void { + this.ctx = this.canvas.nativeElement.getContext('2d'); + } ngOnChanges(changes: SimpleChanges) { - if ('file' in changes) this.loadPhoto(this.file, this.myCanvas); - if ('printData' in changes) this.getFrames(this.printData); + this.dataPhoto$.next(this.file); + this.dataPrint$.next(this.printData); if (!!this.requestInfo) this.formattedResult = resultRecognitionFormatter(this.requestInfo.response); } - getFrames(printData: RequestResultRecognition[]): void { - if (!printData) return; - - this.sizes.pipe(takeUntil(this.unsubscribe)).subscribe(size => { - this.filePrintData = this.recalculateFrames(printData, size.img, size.sizeCanvas); - }); + displayPhoto(file: File): Observable { + return this.loadingPhotoService.loader(file).pipe( + map(img => ({ + imageBitmap: img, + sizeCanvas: { width: this.widthCanvas, height: (img.height / img.width) * this.widthCanvas }, + })), + tap(({ sizeCanvas }) => this.canvas.nativeElement.setAttribute('height', String(sizeCanvas.height))), + tap(({ imageBitmap, sizeCanvas }) => this.ctx.drawImage(imageBitmap, 0, 0, sizeCanvas.width, sizeCanvas.height)) + ); } - recalculateFrames(data: Type, img, sizeCanvas): Type { - return data.map(val => ({ ...val, box: recalculateFaceCoordinate(val.box, img, sizeCanvas) })) as Type; + displayFrames(data: Type, sizeImage: ImageSize, sizeCanvas: ImageSize): Observable { + return new Observable(observer => { + if (!!data) { + const recalculate = data.map(val => ({ ...val, box: recalculateFaceCoordinate(val.box, sizeImage, sizeCanvas) })) as Type; + observer.next(recalculate); + } else { + observer.next(null); + } + observer.complete(); + }); } - loadPhoto(file: File, canvas: ElementRef): void { - if (!file) return; + onResetFile(event?: File): void { + const { offsetHeight, offsetWidth } = this.ctx.canvas; + this.ctx.clearRect(0, 0, offsetWidth, offsetHeight); - this.loadingPhotoService - .loader(file) - .pipe( - takeUntil(this.unsubscribe), - map( - (img: ImageBitmap) => - ({ - img, - sizeCanvas: { width: this.widthCanvas, height: (img.height / img.width) * this.widthCanvas }, - } as { img: ImageBitmap; sizeCanvas: ImageSize }) - ), - tap(({ sizeCanvas }) => canvas.nativeElement.setAttribute('height', sizeCanvas.height)) - ) - .subscribe(value => { - this.displayPhoto(value.img, value.sizeCanvas, canvas); - this.sizes.next(value); - }); - } - - displayPhoto(img: ImageBitmap, size: ImageSize, canvas: ElementRef): void { - const ctx = canvas.nativeElement.getContext('2d'); - ctx.drawImage(img, 0, 0, size.width, size.height); - } + this.resetFace.emit(); - ngOnDestroy(): void { - this.unsubscribe.next(); - this.unsubscribe.complete(); + if (event) this.selectFile.emit(event); } } diff --git a/ui/src/app/features/face-services/face-services.module.ts b/ui/src/app/features/face-services/face-services.module.ts index 96e2d48b1b..a6b9f1362f 100644 --- a/ui/src/app/features/face-services/face-services.module.ts +++ b/ui/src/app/features/face-services/face-services.module.ts @@ -16,6 +16,7 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { MatExpansionModule } from '@angular/material/expansion'; +import { MatCardModule } from '@angular/material/card'; import { TranslateModule } from '@ngx-translate/core'; import { FaceRecognitionService } from '../../core/face-recognition/face-recognition.service'; @@ -25,8 +26,8 @@ import { FaceRecognitionContainerComponent } from './face-recognition/face-recog import { FaceVerificationContainerComponent } from './face-verification/face-verification-container.component'; import { RecognitionResultComponent } from './face-recognition/recognition-result/recognition-result.component'; import { VerificationResultComponent } from './face-verification/verification-result/verification-result.component'; -import { MatCardModule } from '@angular/material/card'; import { FaceServicesDirective } from './face-services.directive'; +import { AppChangePhotoModule } from '../app-change-photo/app-change-photo.module'; @NgModule({ declarations: [ @@ -36,7 +37,7 @@ import { FaceServicesDirective } from './face-services.directive'; VerificationResultComponent, FaceServicesDirective, ], - imports: [CommonModule, DragNDropModule, SpinnerModule, MatExpansionModule, TranslateModule, MatCardModule], + imports: [CommonModule, DragNDropModule, SpinnerModule, MatExpansionModule, TranslateModule, MatCardModule, AppChangePhotoModule], providers: [FaceRecognitionService], exports: [FaceRecognitionContainerComponent, FaceVerificationContainerComponent], }) diff --git a/ui/src/app/store/face-recognition/action.ts b/ui/src/app/store/face-recognition/action.ts index a3b2c0d9cf..a4fb6710c8 100644 --- a/ui/src/app/store/face-recognition/action.ts +++ b/ui/src/app/store/face-recognition/action.ts @@ -13,7 +13,6 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ - import { createAction, props } from '@ngrx/store'; import { Model } from 'src/app/data/interfaces/model'; diff --git a/ui/src/app/store/face-recognition/effects.ts b/ui/src/app/store/face-recognition/effects.ts index e39d7b592b..8f4fe7286b 100644 --- a/ui/src/app/store/face-recognition/effects.ts +++ b/ui/src/app/store/face-recognition/effects.ts @@ -25,6 +25,7 @@ import { selectDemoApiKey } from '../demo/selectors'; import { selectCurrentModel } from '../model/selectors'; import { addFace, addFaceFail, addFaceSuccess, recognizeFace, recognizeFaceFail, recognizeFaceSuccess } from './action'; import { ServiceTypes } from '../../data/enums/service-types.enum'; + @Injectable() export class FaceRecognitionEffects { constructor( diff --git a/ui/src/app/store/face-recognition/reducers.ts b/ui/src/app/store/face-recognition/reducers.ts index 478666cfa6..b9969e1ee7 100644 --- a/ui/src/app/store/face-recognition/reducers.ts +++ b/ui/src/app/store/face-recognition/reducers.ts @@ -33,7 +33,7 @@ const initialState: FaceRecognitionEntityState = { const reducer: ActionReducer = createReducer( initialState, - on(recognizeFace, state => ({ ...state, isPending: true })), + on(recognizeFace, (state, action) => ({ ...state, ...action, isPending: true })), on(recognizeFaceSuccess, (state, action) => ({ ...state, ...action, isPending: false })), on(recognizeFaceReset, () => ({ ...initialState })), on(recognizeFaceFail, state => ({ ...state, isPending: false })) diff --git a/ui/src/app/store/face-verification/action.ts b/ui/src/app/store/face-verification/action.ts index 7cc3412d7b..3ac2d3e376 100644 --- a/ui/src/app/store/face-verification/action.ts +++ b/ui/src/app/store/face-verification/action.ts @@ -14,6 +14,7 @@ * permissions and limitations under the License. */ import { createAction, props } from '@ngrx/store'; + export const verifyFace = createAction('[Model] Face Verification Save', props<{ processFile?: any; checkFile?: any }>()); export const verifyFaceSuccess = createAction('[Model] Face Verification Success', props<{ model: any; request: any }>()); export const verifyFaceFail = createAction('[Model] Face Verification Fail', props<{ error: any }>()); diff --git a/ui/src/app/store/face-verification/effects.ts b/ui/src/app/store/face-verification/effects.ts index e5f4ebe676..efa0621559 100644 --- a/ui/src/app/store/face-verification/effects.ts +++ b/ui/src/app/store/face-verification/effects.ts @@ -25,6 +25,7 @@ import { selectDemoApiKey } from '../demo/selectors'; import { selectCurrentModel } from '../model/selectors'; import { selectFiles } from './selectors'; import { verifyFace, verifyFaceSuccess, verifyFaceFail } from './action'; + @Injectable() export class FaceRecognitionEffects { constructor( diff --git a/ui/src/styles/colors.scss b/ui/src/styles/colors.scss index 0dbd906035..db4e6ec863 100644 --- a/ui/src/styles/colors.scss +++ b/ui/src/styles/colors.scss @@ -18,6 +18,7 @@ $red: #FF6B6B; // icons, accent labels, accent text labels, accent borders, acce $green: #27C224; // accent buttons $white: #fff; $black: #222222; // titles, +$transparent-black: rgba(255, 255, 255, 0.75); // background for the photo spinner $gray: rgba(34, 34, 34, 0.25); // placeholder search $dark-gray: rgba(34, 34, 34, 0.8); // subtitles $transparent-gray: rgba(255, 255, 255, 0.5); // border frames not active @@ -38,3 +39,4 @@ $special-gray: rgba(229, 229, 229, 1); // scrollbar Handle $special-dark-gray: rgb(173, 173, 173); // scrollbar Handle on hover $special-light-blue: rgba(0, 130, 202, 0.02); // drag-n-drop hover $special-blue: rgba(0, 130, 202, 0.5); // drag-n-drop border hover + diff --git a/ui/src/styles/photo-spiner.scss b/ui/src/styles/photo-spiner.scss new file mode 100644 index 0000000000..c4059529b6 --- /dev/null +++ b/ui/src/styles/photo-spiner.scss @@ -0,0 +1,31 @@ +/*! + * Copyright (c) 2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +@import "mixins.scss"; +@import "colors.scss"; + +@mixin photo-spinner() { + position: absolute; + background-color: $light-gray; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 5; + + app-spinner { + @include wrapper-center(); + } +} From b4ed528846331776ba7fd84d12c3ec8bff29ba94 Mon Sep 17 00:00:00 2001 From: spospielov Date: Wed, 2 Jun 2021 10:20:19 +0300 Subject: [PATCH 011/837] reverted LF to CRLF --- embedding-calculator/.dockerignore | 2 +- embedding-calculator/.gitignore | 320 ++++++------- embedding-calculator/Makefile | 132 ++--- embedding-calculator/README.md | 450 +++++++++--------- embedding-calculator/__init__.py | 36 +- embedding-calculator/benchmark.sh | 52 +- embedding-calculator/gpu.Dockerfile | 158 +++--- embedding-calculator/pylama.ini | 28 +- embedding-calculator/pytest.ini | 26 +- embedding-calculator/requirements.txt | 60 +-- embedding-calculator/src/__init__.py | 26 +- embedding-calculator/src/_docs.py | 74 +-- embedding-calculator/src/_endpoints.py | 260 +++++----- embedding-calculator/src/conftest.py | 38 +- embedding-calculator/src/constants.py | 78 +-- embedding-calculator/src/docs/__init__.py | 34 +- .../plugins/facenet/facemask/__init__.py | 32 +- .../plugins/facenet/facemask/facemask.py | 128 ++--- 18 files changed, 967 insertions(+), 967 deletions(-) diff --git a/embedding-calculator/.dockerignore b/embedding-calculator/.dockerignore index 4875b06569..4e6eb0e652 100644 --- a/embedding-calculator/.dockerignore +++ b/embedding-calculator/.dockerignore @@ -1 +1 @@ -**/tmp/ +**/tmp/ diff --git a/embedding-calculator/.gitignore b/embedding-calculator/.gitignore index e9341f6e8d..4673ddccd2 100644 --- a/embedding-calculator/.gitignore +++ b/embedding-calculator/.gitignore @@ -1,161 +1,161 @@ -tmp/ - -# Created by .ignore support plugin (hsz.mobi) -### Python template -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -.hypothesis/ -.pytest_cache/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# pyenv -.python-version - -# celery beat schedule file -celerybeat-schedule - -# SageMath parsed files -*.sage.py - -# Environments -shared_src/.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ - -### JetBrains template -# Covers JetBrains IDEs: IntelliJ, Ruby_mine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/**/dictionaries -.idea/**/shelf - -# Sensitive or high-churn files -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml - -# Gradle -.idea/**/gradle.xml -.idea/**/libraries - -# CMake -cmake-build-debug/ -cmake-build-release/ - -# Mongo Explorer plugin -.idea/**/mongoSettings.xml - -# File-based project format -*.iws - -# IntelliJ -out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Cursive Clojure plugin -.idea/replstate.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -# Editor-based Rest Client +tmp/ + +# Created by .ignore support plugin (hsz.mobi) +### Python template +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +shared_src/.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, Ruby_mine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/dictionaries +.idea/**/shelf + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# CMake +cmake-build-debug/ +cmake-build-release/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client .idea/httpRequests \ No newline at end of file diff --git a/embedding-calculator/Makefile b/embedding-calculator/Makefile index c5f9b8649d..8f9b540bf0 100755 --- a/embedding-calculator/Makefile +++ b/embedding-calculator/Makefile @@ -1,66 +1,66 @@ -SHELL := /bin/bash -.DEFAULT_GOAL := default -.PHONY := default up build-images build-cuda - -IMAGE := ${DOCKER_REGISTRY}compreface-core -CUDA_IMAGE = $(IMAGE)-base:base-cuda100-py37 - -MOBILENET_BUILD_ARGS := --build-arg FACE_DETECTION_PLUGIN=insightface.FaceDetector@retinaface_mnet025_v1 \ - --build-arg CALCULATION_PLUGIN=insightface.Calculator@arcface_mobilefacenet \ - --build-arg EXTRA_PLUGINS=insightface.LandmarksDetector,insightface.GenderDetector,insightface.AgeDetector - -ARCFACE_r100_BUILD_ARGS := --build-arg FACE_DETECTION_PLUGIN=insightface.FaceDetector@retinaface_r50_v1 \ - --build-arg CALCULATION_PLUGIN=insightface.Calculator@arcface-r100-msfdrop75 \ - --build-arg EXTRA_PLUGINS=insightface.LandmarksDetector,insightface.GenderDetector,insightface.AgeDetector - -FACENET_BUILD_ARGS := --build-arg FACE_DETECTION_PLUGIN=facenet.FaceDetector \ - --build-arg CALCULATION_PLUGIN=facenet.Calculator \ - --build-arg EXTRA_PLUGINS=facenet.LandmarksDetector,agegender.GenderDetector,agegender.AgeDetector - -define get_from_remote_tgz - mkdir -p $(2) - curl -o $(2)/tmp.tgz $(1) - tar zxvf $(2)/tmp.tgz -C $(2) - rm $(2)/tmp.tgz -endef - -define get_from_remote_zip - mkdir -p $(2) - curl -o $(2)/tmp.zip $(1) - unzip $(2)/tmp.zip -d $(2) - rm $(2)/tmp.zip -endef - -default: - pytest src tools - python -m pylama --options pylama.ini src tools - docker build . -t $(IMAGE):$(VERSION) - -build-cuda: - docker build . --file gpu.Dockerfile --tag $(CUDA_IMAGE) - -build-images: build-cuda - docker build . -t $(IMAGE):$(VERSION)-facenet - docker build . -t $(IMAGE):$(VERSION)-mobilenet $(MOBILENET_BUILD_ARGS) - docker build . -t $(IMAGE):$(VERSION)-mobilenet-gpu $(MOBILENET_BUILD_ARGS) --build-arg GPU_IDX=0 --build-arg BASE_IMAGE=$(CUDA_IMAGE) - docker build . -t $(IMAGE):$(VERSION)-arcface-r100 $(ARCFACE_r100_BUILD_ARGS) - docker build . -t $(IMAGE):$(VERSION)-arcface-r100-gpu $(ARCFACE_r100_BUILD_ARGS) --build-arg GPU_IDX=0 --build-arg BASE_IMAGE=$(CUDA_IMAGE) - -build-images-cpu: - docker build . -t $(IMAGE):$(VERSION)-facenet - docker build . -t $(IMAGE):$(VERSION)-mobilenet $(MOBILENET_BUILD_ARGS) - docker build . -t $(IMAGE):$(VERSION)-arcface-r100 $(ARCFACE_r100_BUILD_ARGS) - -build-images-gpu: build-cuda - docker build . -t $(IMAGE):$(VERSION)-mobilenet-gpu $(MOBILENET_BUILD_ARGS) --build-arg GPU_IDX=0 --build-arg BASE_IMAGE=$(CUDA_IMAGE) - docker build . -t $(IMAGE):$(VERSION)-arcface-r100-gpu $(ARCFACE_r100_BUILD_ARGS) --build-arg GPU_IDX=0 --build-arg BASE_IMAGE=$(CUDA_IMAGE) - -up: - docker run -p3000:3000 embedding-calculator - -tools/tmp: - $(call get_from_remote_zip,https://www.fontsquirrel.com/fonts/download/arimo,tools/tmp/arimo-font) - -tools/benchmark_detection/tmp: - $(call get_from_remote_tgz,http://tamaraberg.com/faceDataset/originalPics.tar.gz,tools/benchmark_detection/tmp/originalPics) - $(call get_from_remote_tgz,http://vis-www.cs.umass.edu/fddb/FDDB-folds.tgz,tools/benchmark_detection/tmp) +SHELL := /bin/bash +.DEFAULT_GOAL := default +.PHONY := default up build-images build-cuda + +IMAGE := ${DOCKER_REGISTRY}compreface-core +CUDA_IMAGE = $(IMAGE)-base:base-cuda100-py37 + +MOBILENET_BUILD_ARGS := --build-arg FACE_DETECTION_PLUGIN=insightface.FaceDetector@retinaface_mnet025_v1 \ + --build-arg CALCULATION_PLUGIN=insightface.Calculator@arcface_mobilefacenet \ + --build-arg EXTRA_PLUGINS=insightface.LandmarksDetector,insightface.GenderDetector,insightface.AgeDetector + +ARCFACE_r100_BUILD_ARGS := --build-arg FACE_DETECTION_PLUGIN=insightface.FaceDetector@retinaface_r50_v1 \ + --build-arg CALCULATION_PLUGIN=insightface.Calculator@arcface-r100-msfdrop75 \ + --build-arg EXTRA_PLUGINS=insightface.LandmarksDetector,insightface.GenderDetector,insightface.AgeDetector + +FACENET_BUILD_ARGS := --build-arg FACE_DETECTION_PLUGIN=facenet.FaceDetector \ + --build-arg CALCULATION_PLUGIN=facenet.Calculator \ + --build-arg EXTRA_PLUGINS=facenet.LandmarksDetector,agegender.GenderDetector,agegender.AgeDetector + +define get_from_remote_tgz + mkdir -p $(2) + curl -o $(2)/tmp.tgz $(1) + tar zxvf $(2)/tmp.tgz -C $(2) + rm $(2)/tmp.tgz +endef + +define get_from_remote_zip + mkdir -p $(2) + curl -o $(2)/tmp.zip $(1) + unzip $(2)/tmp.zip -d $(2) + rm $(2)/tmp.zip +endef + +default: + pytest src tools + python -m pylama --options pylama.ini src tools + docker build . -t $(IMAGE):$(VERSION) + +build-cuda: + docker build . --file gpu.Dockerfile --tag $(CUDA_IMAGE) + +build-images: build-cuda + docker build . -t $(IMAGE):$(VERSION)-facenet + docker build . -t $(IMAGE):$(VERSION)-mobilenet $(MOBILENET_BUILD_ARGS) + docker build . -t $(IMAGE):$(VERSION)-mobilenet-gpu $(MOBILENET_BUILD_ARGS) --build-arg GPU_IDX=0 --build-arg BASE_IMAGE=$(CUDA_IMAGE) + docker build . -t $(IMAGE):$(VERSION)-arcface-r100 $(ARCFACE_r100_BUILD_ARGS) + docker build . -t $(IMAGE):$(VERSION)-arcface-r100-gpu $(ARCFACE_r100_BUILD_ARGS) --build-arg GPU_IDX=0 --build-arg BASE_IMAGE=$(CUDA_IMAGE) + +build-images-cpu: + docker build . -t $(IMAGE):$(VERSION)-facenet + docker build . -t $(IMAGE):$(VERSION)-mobilenet $(MOBILENET_BUILD_ARGS) + docker build . -t $(IMAGE):$(VERSION)-arcface-r100 $(ARCFACE_r100_BUILD_ARGS) + +build-images-gpu: build-cuda + docker build . -t $(IMAGE):$(VERSION)-mobilenet-gpu $(MOBILENET_BUILD_ARGS) --build-arg GPU_IDX=0 --build-arg BASE_IMAGE=$(CUDA_IMAGE) + docker build . -t $(IMAGE):$(VERSION)-arcface-r100-gpu $(ARCFACE_r100_BUILD_ARGS) --build-arg GPU_IDX=0 --build-arg BASE_IMAGE=$(CUDA_IMAGE) + +up: + docker run -p3000:3000 embedding-calculator + +tools/tmp: + $(call get_from_remote_zip,https://www.fontsquirrel.com/fonts/download/arimo,tools/tmp/arimo-font) + +tools/benchmark_detection/tmp: + $(call get_from_remote_tgz,http://tamaraberg.com/faceDataset/originalPics.tar.gz,tools/benchmark_detection/tmp/originalPics) + $(call get_from_remote_tgz,http://vis-www.cs.umass.edu/fddb/FDDB-folds.tgz,tools/benchmark_detection/tmp) diff --git a/embedding-calculator/README.md b/embedding-calculator/README.md index 8e2195901e..20a232b913 100644 --- a/embedding-calculator/README.md +++ b/embedding-calculator/README.md @@ -1,225 +1,225 @@ -![Example output image](./sample_images/readme_example.png) - -# embedding-calculator -This is a component of CompreFace. CompreFace is a service for face recognition: upload images with faces of known people, then upload a new image, and the service will recognize faces in it. - -# Setup environment -Not needed if only running containers: -``` -$ python -m pip install -r requirements.txt -e srcext/insightface/python-package -$ imageio_download_bin freeimage -``` -Only needed if using tools: -``` -$ make tools/tmp -$ chmod +x tools/test_memory_constraints.sh -``` - -# Run service -### Locally -``` -$ export FLASK_ENV=development -$ python -m src.app -``` - -### Docker - -##### Images on DockerHub - -There are some pre-build images on https://hub.docker.com/r/exadel/compreface-core. To use it run: -``` -$ docker run -p 3000:3000 exadel/compreface-core:latest -``` - -###### DockerHub tags - -| Tag | Scanner | Build arguments | Comment | -|------------------------|-------------|----------------------------------------------------------------------------------------------------------|------------------------------------| -| :0.5.0 :latest | Facenet2018 | | | -| :0.5.0-insightface | InsightFace | FACE_DETECTION_PLUGIN=insightface.FaceDetector
CALCULATION_PLUGIN=insightface.Calculator | | -| :0.5.0-insightface-gpu | InsightFace | FACE_DETECTION_PLUGIN=insightface.FaceDetector
CALCULATION_PLUGIN=insightface.Calculator
GPU_IDX=0 | CORE_GPU_IDX - index of GPU-device | - - -##### Build -Builds container (also runs main tests during the build): -``` -$ docker build -t embedding-calculator -``` -To skip tests during build, use: -``` -$ docker build -t embedding-calculator --build-arg SKIP_TESTS=true . -``` - -##### Run -``` -$ docker run -p 3000:3000 embedding-calculator -``` - -### Run tests -Unit tests -``` -$ pytest -m "not integration and not performance" src tools -``` -Integration tests -``` -$ pytest -m integration src tools -``` -Performance tests -``` -$ pytest -m performance src tools -``` -Lint checks -``` -$ python -m pylama --options pylama.ini src tools -``` - -### Plugins - -If DockerHub images is not enough, build an image with only the necessary set of plugins. -For changing default plugins pass needed plugin names in build arguments and build your own image. - -##### Face detection and calculation plugins - -Set plugins by build arguments `FACE_DETECTION_PLUGIN` and `CALCULATION_PLUGIN` - -| Plugin name | Slug | Backend | Framework | GPU support | -|--------------------------|------------|-------------|------------|-------------| -| facenet.FaceDetector | detector | MTCNN | Tensorflow | | -| facenet.Calculator | calculator | Facenet | Tensorflow | | -| insightface.FaceDetector | detector | insightface | MXNet | + | -| insightface.Calculator | calculator | insightface | MXNet | + | - -##### Extra plugins - -Pass to `EXTRA_PLUGINS` comma-separated names of plugins. - -| Plugin name | Slug | Backend | Framework | GPU support | -|------------------------------------|----------------|-------------|------------|-------------| -| agegender.AgeDetector | age | agegender | Tensorflow | | -| agegender.GenderDetector | gender | agegender | Tensorflow | | -| insightface.AgeDetector | age | insightface | MXNet | + | -| insightface.GenderDetector | gender | insightface | MXNet | + | -| facenet.LandmarksDetector | landmarks | Facenet | Tensorflow | + | -| insightface.LandmarksDetector | landmarks | insightface | MXNet | + | -| insightface.Landmarks2d106Detector | landmarks2d106 | insightface | MXNet | + | - -Notes: -* `facenet.LandmarksDetector` and `insightface.LandmarksDetector` extract landmarks - from results of `FaceDetector` plugin without additional processing. Returns 5 points of eyes, nose and mouth. -* `insightface.Landmarks2d106Detector` detects 106 points of facial landmark. - [Points mark-up](https://github.com/deepinsight/insightface/tree/master/alignment/coordinateReg#visualization) - - -##### Default build arguments: -``` -FACE_DETECTION_PLUGIN=facenet.FaceDetector -CALCULATION_PLUGIN=facenet.Calculator -EXTRA_PLUGINS=agegender.AgeDetector,agegender.GenderDetector -``` - -#### Pre-trained models - -Some plugins have several pre-trained models. -To use an additional model pass a name of the model after a plugin name with a separator `@`. For example: -``` -FACE_DETECTION_PLUGIN=insightface.FaceDetector@retinaface_mnet025_v1 -``` - -List of pre-trained models: - -* facenet.Calculator - * 20180402-114759 (default) - * 20180408-102900 - -* insightface.FaceDetector - * retinaface_r50_v1 (default) - * retinaface_mnet025_v1 - * retinaface_mnet025_v2 - -* insightface.Calculator - * arcface_r100_v1 (default) - * arcface_resnet34 - * arcface_resnet50 - * arcface_mobilefacenet - * [arcface-r50-msfdrop75](https://github.com/deepinsight/insightface/tree/master/recognition/SubCenter-ArcFace) - * [arcface-r100-msfdrop75](https://github.com/deepinsight/insightface/tree/master/recognition/SubCenter-ArcFace) - - -#### Optimization - -There are two build arguments for optimization: -* `GPU_IDX` - id of NVIDIA GPU device, starts from `0` (empty or `-1` for disable) -* `INTEL_OPTIMIZATION` - enable Intel MKL optimization (true/false) - - -##### NVIDIA Runtime - -Install the nvidia-docker2 package and dependencies on the host machine: -``` -sudo apt-get update -sudo apt-get install -y nvidia-docker2 -sudo systemctl restart docker -``` - -Build and run with enabled gpu -``` -docker build . -t embedding-calculator-cuda -f gpu.Dockerfile -docker build . -t embedding-calculator-gpu --build-arg GPU_IDX=0 --build-arg BASE_IMAGE=embedding-calculator-cuda -docker run -p 3000:3000 --gpus all embedding-calculator-gpu -``` - -# Tools -Finds faces in a given image, puts bounding boxes and saves the resulting image. -``` -$ export IMG_NAMES=015_6.jpg -$ python -m tools.scan -``` - -Tests the accuracy of face detection. -``` -$ make tools/benchmark_detection/tmp -$ python -m tools.benchmark_detection -``` - -Tests whether service crashes with various parameters under given RAM constraints. -``` -$ docker build -t embedding-calculator . -$ tools/test_memory_constraints.sh $(pwd)/sample_images -``` - -Optimizes face detection library parameters with a given annotated image dataset. -``` -$ mkdir tmp -$ python -m tools.optimize_detection_params -``` - -# Benchmark - -Perform the following steps: -1. [Build and run](#build) `embedding-calculator` with the needed scanner backend and CPU/GPU supports -1. Run a benchmark: - 1. inside the container `docker exec embedding-calculator ./benchmark` - 1. or locally `cd .embedding-calculator && ./benchmark.sh` (require exposing API at localhost:3000) - -# Troubleshooting - -### Windows - -##### While building container, crashes with error `: invalid option` - -CRLF file endings may cause this. To fix, run `$ dos2unix *`. - -##### Installing packages `requirements.txt` in a local environment crashes - -Package *uWSGI* is not supported on Windows. Workaround is to temporarily delete the line with the package name from `requirements.txt` and install without it. - -# Misc -Check that the component is in valid state: run tests, build container, start it -``` -$ make -$ make up -``` -Get project line counts per file type -``` -$ which tokei >/dev/null || conda install -y -c conda-forge tokei && tokei --exclude srcext/ -``` +![Example output image](./sample_images/readme_example.png) + +# embedding-calculator +This is a component of CompreFace. CompreFace is a service for face recognition: upload images with faces of known people, then upload a new image, and the service will recognize faces in it. + +# Setup environment +Not needed if only running containers: +``` +$ python -m pip install -r requirements.txt -e srcext/insightface/python-package +$ imageio_download_bin freeimage +``` +Only needed if using tools: +``` +$ make tools/tmp +$ chmod +x tools/test_memory_constraints.sh +``` + +# Run service +### Locally +``` +$ export FLASK_ENV=development +$ python -m src.app +``` + +### Docker + +##### Images on DockerHub + +There are some pre-build images on https://hub.docker.com/r/exadel/compreface-core. To use it run: +``` +$ docker run -p 3000:3000 exadel/compreface-core:latest +``` + +###### DockerHub tags + +| Tag | Scanner | Build arguments | Comment | +|------------------------|-------------|----------------------------------------------------------------------------------------------------------|------------------------------------| +| :0.5.0 :latest | Facenet2018 | | | +| :0.5.0-insightface | InsightFace | FACE_DETECTION_PLUGIN=insightface.FaceDetector
CALCULATION_PLUGIN=insightface.Calculator | | +| :0.5.0-insightface-gpu | InsightFace | FACE_DETECTION_PLUGIN=insightface.FaceDetector
CALCULATION_PLUGIN=insightface.Calculator
GPU_IDX=0 | CORE_GPU_IDX - index of GPU-device | + + +##### Build +Builds container (also runs main tests during the build): +``` +$ docker build -t embedding-calculator +``` +To skip tests during build, use: +``` +$ docker build -t embedding-calculator --build-arg SKIP_TESTS=true . +``` + +##### Run +``` +$ docker run -p 3000:3000 embedding-calculator +``` + +### Run tests +Unit tests +``` +$ pytest -m "not integration and not performance" src tools +``` +Integration tests +``` +$ pytest -m integration src tools +``` +Performance tests +``` +$ pytest -m performance src tools +``` +Lint checks +``` +$ python -m pylama --options pylama.ini src tools +``` + +### Plugins + +If DockerHub images is not enough, build an image with only the necessary set of plugins. +For changing default plugins pass needed plugin names in build arguments and build your own image. + +##### Face detection and calculation plugins + +Set plugins by build arguments `FACE_DETECTION_PLUGIN` and `CALCULATION_PLUGIN` + +| Plugin name | Slug | Backend | Framework | GPU support | +|--------------------------|------------|-------------|------------|-------------| +| facenet.FaceDetector | detector | MTCNN | Tensorflow | | +| facenet.Calculator | calculator | Facenet | Tensorflow | | +| insightface.FaceDetector | detector | insightface | MXNet | + | +| insightface.Calculator | calculator | insightface | MXNet | + | + +##### Extra plugins + +Pass to `EXTRA_PLUGINS` comma-separated names of plugins. + +| Plugin name | Slug | Backend | Framework | GPU support | +|------------------------------------|----------------|-------------|------------|-------------| +| agegender.AgeDetector | age | agegender | Tensorflow | | +| agegender.GenderDetector | gender | agegender | Tensorflow | | +| insightface.AgeDetector | age | insightface | MXNet | + | +| insightface.GenderDetector | gender | insightface | MXNet | + | +| facenet.LandmarksDetector | landmarks | Facenet | Tensorflow | + | +| insightface.LandmarksDetector | landmarks | insightface | MXNet | + | +| insightface.Landmarks2d106Detector | landmarks2d106 | insightface | MXNet | + | + +Notes: +* `facenet.LandmarksDetector` and `insightface.LandmarksDetector` extract landmarks + from results of `FaceDetector` plugin without additional processing. Returns 5 points of eyes, nose and mouth. +* `insightface.Landmarks2d106Detector` detects 106 points of facial landmark. + [Points mark-up](https://github.com/deepinsight/insightface/tree/master/alignment/coordinateReg#visualization) + + +##### Default build arguments: +``` +FACE_DETECTION_PLUGIN=facenet.FaceDetector +CALCULATION_PLUGIN=facenet.Calculator +EXTRA_PLUGINS=agegender.AgeDetector,agegender.GenderDetector +``` + +#### Pre-trained models + +Some plugins have several pre-trained models. +To use an additional model pass a name of the model after a plugin name with a separator `@`. For example: +``` +FACE_DETECTION_PLUGIN=insightface.FaceDetector@retinaface_mnet025_v1 +``` + +List of pre-trained models: + +* facenet.Calculator + * 20180402-114759 (default) + * 20180408-102900 + +* insightface.FaceDetector + * retinaface_r50_v1 (default) + * retinaface_mnet025_v1 + * retinaface_mnet025_v2 + +* insightface.Calculator + * arcface_r100_v1 (default) + * arcface_resnet34 + * arcface_resnet50 + * arcface_mobilefacenet + * [arcface-r50-msfdrop75](https://github.com/deepinsight/insightface/tree/master/recognition/SubCenter-ArcFace) + * [arcface-r100-msfdrop75](https://github.com/deepinsight/insightface/tree/master/recognition/SubCenter-ArcFace) + + +#### Optimization + +There are two build arguments for optimization: +* `GPU_IDX` - id of NVIDIA GPU device, starts from `0` (empty or `-1` for disable) +* `INTEL_OPTIMIZATION` - enable Intel MKL optimization (true/false) + + +##### NVIDIA Runtime + +Install the nvidia-docker2 package and dependencies on the host machine: +``` +sudo apt-get update +sudo apt-get install -y nvidia-docker2 +sudo systemctl restart docker +``` + +Build and run with enabled gpu +``` +docker build . -t embedding-calculator-cuda -f gpu.Dockerfile +docker build . -t embedding-calculator-gpu --build-arg GPU_IDX=0 --build-arg BASE_IMAGE=embedding-calculator-cuda +docker run -p 3000:3000 --gpus all embedding-calculator-gpu +``` + +# Tools +Finds faces in a given image, puts bounding boxes and saves the resulting image. +``` +$ export IMG_NAMES=015_6.jpg +$ python -m tools.scan +``` + +Tests the accuracy of face detection. +``` +$ make tools/benchmark_detection/tmp +$ python -m tools.benchmark_detection +``` + +Tests whether service crashes with various parameters under given RAM constraints. +``` +$ docker build -t embedding-calculator . +$ tools/test_memory_constraints.sh $(pwd)/sample_images +``` + +Optimizes face detection library parameters with a given annotated image dataset. +``` +$ mkdir tmp +$ python -m tools.optimize_detection_params +``` + +# Benchmark + +Perform the following steps: +1. [Build and run](#build) `embedding-calculator` with the needed scanner backend and CPU/GPU supports +1. Run a benchmark: + 1. inside the container `docker exec embedding-calculator ./benchmark` + 1. or locally `cd .embedding-calculator && ./benchmark.sh` (require exposing API at localhost:3000) + +# Troubleshooting + +### Windows + +##### While building container, crashes with error `: invalid option` + +CRLF file endings may cause this. To fix, run `$ dos2unix *`. + +##### Installing packages `requirements.txt` in a local environment crashes + +Package *uWSGI* is not supported on Windows. Workaround is to temporarily delete the line with the package name from `requirements.txt` and install without it. + +# Misc +Check that the component is in valid state: run tests, build container, start it +``` +$ make +$ make up +``` +Get project line counts per file type +``` +$ which tokei >/dev/null || conda install -y -c conda-forge tokei && tokei --exclude srcext/ +``` diff --git a/embedding-calculator/__init__.py b/embedding-calculator/__init__.py index 9d3375ecf6..ef9a5c1a9e 100644 --- a/embedding-calculator/__init__.py +++ b/embedding-calculator/__init__.py @@ -1,18 +1,18 @@ -# Copyright (c) 2020 the original author or authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express -# or implied. See the License for the specific language governing -# permissions and limitations under the License. - -import os -import sys - -sys.path.insert(0, os.path.dirname(__file__)) +# Copyright (c) 2020 the original author or authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing +# permissions and limitations under the License. + +import os +import sys + +sys.path.insert(0, os.path.dirname(__file__)) diff --git a/embedding-calculator/benchmark.sh b/embedding-calculator/benchmark.sh index 1f2f29ca7a..5d2a67bb6a 100644 --- a/embedding-calculator/benchmark.sh +++ b/embedding-calculator/benchmark.sh @@ -1,27 +1,27 @@ -#!/bin/bash -CONCURRENCY=${1:-1} -ENDPOINT=${2:-http://localhost:3000/scan_faces} -declare -a IMAGES=("008_B.jpg" "001_A.jpg") - -# install apache benchmark -ab -V > /dev/null || (apt-get update && apt-get install -y apache2-utils) - -# run benchmark -for IMAGE in "${IMAGES[@]}" -do - IMAGE="./sample_images/$IMAGE" - [ ! -f "$IMAGE" ] && echo "Image ${IMAGE} not found" && exit - FILESIZE=$(stat -c%s "$IMAGE") - IMAGE_B64=$(base64 $IMAGE) - echo -e "--image_file\r\nContent-Disposition: form-data; name=\"file\"; filename=\"image.jpg\"\r\nContent-Transfer-Encoding: base64\r\n\r\n${IMAGE_B64}\r\n--image_file--" > post.data - echo "--- Run benchmark with $IMAGE ($FILESIZE bytes) ---" - - RESPONSE=$(ab -n 1 -c 1 -v 4 -p post.data -T "multipart/form-data; boundary=image_file" $ENDPOINT | grep embedding) - [[ -z $RESPONSE ]] && echo "Error: No embedding in response" && exit 1 - - ab -n 20 -c $CONCURRENCY -p post.data -T "multipart/form-data; boundary=image_file" $ENDPOINT \ - | grep 'per request\|Concurrency' - echo -done - +#!/bin/bash +CONCURRENCY=${1:-1} +ENDPOINT=${2:-http://localhost:3000/scan_faces} +declare -a IMAGES=("008_B.jpg" "001_A.jpg") + +# install apache benchmark +ab -V > /dev/null || (apt-get update && apt-get install -y apache2-utils) + +# run benchmark +for IMAGE in "${IMAGES[@]}" +do + IMAGE="./sample_images/$IMAGE" + [ ! -f "$IMAGE" ] && echo "Image ${IMAGE} not found" && exit + FILESIZE=$(stat -c%s "$IMAGE") + IMAGE_B64=$(base64 $IMAGE) + echo -e "--image_file\r\nContent-Disposition: form-data; name=\"file\"; filename=\"image.jpg\"\r\nContent-Transfer-Encoding: base64\r\n\r\n${IMAGE_B64}\r\n--image_file--" > post.data + echo "--- Run benchmark with $IMAGE ($FILESIZE bytes) ---" + + RESPONSE=$(ab -n 1 -c 1 -v 4 -p post.data -T "multipart/form-data; boundary=image_file" $ENDPOINT | grep embedding) + [[ -z $RESPONSE ]] && echo "Error: No embedding in response" && exit 1 + + ab -n 20 -c $CONCURRENCY -p post.data -T "multipart/form-data; boundary=image_file" $ENDPOINT \ + | grep 'per request\|Concurrency' + echo +done + rm post.data \ No newline at end of file diff --git a/embedding-calculator/gpu.Dockerfile b/embedding-calculator/gpu.Dockerfile index 0e27288742..b43511f5a4 100644 --- a/embedding-calculator/gpu.Dockerfile +++ b/embedding-calculator/gpu.Dockerfile @@ -1,80 +1,80 @@ -ARG UBUNTU_VERSION=18.04 - -ARG ARCH= -ARG CUDA=10.0 -FROM nvidia/cuda${ARCH:+-$ARCH}:${CUDA}-base-ubuntu${UBUNTU_VERSION} as base -# ARCH and CUDA are specified again because the FROM directive resets ARGs -# (but their default value is retained if set previously) -ARG ARCH -ARG CUDA -ARG CUDNN=7.6.4.38-1 -ARG CUDNN_MAJOR_VERSION=7 -ARG LIB_DIR_PREFIX=x86_64 -ARG LIBNVINFER=6.0.1-1 -ARG LIBNVINFER_MAJOR_VERSION=6 -ENV CUDA=$CUDA - -# Needed for string substitution -SHELL ["/bin/bash", "-c"] -# Pick up some TF dependencies -RUN apt-get update && apt-get install -y --no-install-recommends \ - build-essential \ - cuda-command-line-tools-${CUDA/./-} \ - # There appears to be a regression in libcublas10=10.2.2.89-1 which - # prevents cublas from initializing in TF. See - # https://github.com/tensorflow/tensorflow/issues/9489#issuecomment-562394257 - libcublas10=10.2.1.243-1 \ - cuda-nvrtc-${CUDA/./-} \ - cuda-cufft-${CUDA/./-} \ - cuda-curand-${CUDA/./-} \ - cuda-cusolver-${CUDA/./-} \ - cuda-cusparse-${CUDA/./-} \ - curl \ - libcudnn7=${CUDNN}+cuda${CUDA} \ - libfreetype6-dev \ - libhdf5-serial-dev \ - libzmq3-dev \ - pkg-config \ - software-properties-common \ - unzip - -# Install TensorRT if not building for PowerPC -RUN [[ "${ARCH}" = "ppc64le" ]] || { apt-get update && \ - apt-get install -y --no-install-recommends libnvinfer${LIBNVINFER_MAJOR_VERSION}=${LIBNVINFER}+cuda${CUDA} \ - libnvinfer-plugin${LIBNVINFER_MAJOR_VERSION}=${LIBNVINFER}+cuda${CUDA} \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/*; } - -# For CUDA profiling, TensorFlow requires CUPTI. -ENV LD_LIBRARY_PATH /usr/local/cuda/extras/CUPTI/lib64:/usr/local/cuda/lib64:$LD_LIBRARY_PATH - -# Link the libcuda stub to the location where tensorflow is searching for it and reconfigure -# dynamic linker run-time bindings -RUN ln -s /usr/local/cuda/lib64/stubs/libcuda.so /usr/local/cuda/lib64/stubs/libcuda.so.1 \ - && echo "/usr/local/cuda/lib64/stubs" > /etc/ld.so.conf.d/z-cuda-stubs.conf \ - && ldconfig - -# See http://bugs.python.org/issue19846 -ENV LANG C.UTF-8 - -ARG PYTHON=python3.7 -RUN add-apt-repository ppa:deadsnakes/ppa && apt-get update && apt-get install -y ${PYTHON} libpython3.7-dev libgl1-mesa-glx -RUN curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py && ${PYTHON} get-pip.py -RUN ${PYTHON} -m pip --no-cache-dir install --upgrade pip setuptools - -# Some TF tools expect a "python" binary -RUN ln -s $(which $PYTHON) /usr/local/bin/python - - -# Variables for Tensorflow -ENV TF_FORCE_GPU_ALLOW_GROWTH=true - -# Variables for MXNET -ENV MXNET_CPU_WORKER_NTHREADS=24 -ENV MXNET_ENGINE_TYPE=ThreadedEnginePerDevice MXNET_CUDNN_AUTOTUNE_DEFAULT=0 - -# No access to GPU devices in the build stage, so skip tests -ENV SKIP_TESTS=1 -# The number of processes depends on GPU memory. -# Keep in mind that one uwsgi process with InsightFace consumes about 2.5GB memory +ARG UBUNTU_VERSION=18.04 + +ARG ARCH= +ARG CUDA=10.0 +FROM nvidia/cuda${ARCH:+-$ARCH}:${CUDA}-base-ubuntu${UBUNTU_VERSION} as base +# ARCH and CUDA are specified again because the FROM directive resets ARGs +# (but their default value is retained if set previously) +ARG ARCH +ARG CUDA +ARG CUDNN=7.6.4.38-1 +ARG CUDNN_MAJOR_VERSION=7 +ARG LIB_DIR_PREFIX=x86_64 +ARG LIBNVINFER=6.0.1-1 +ARG LIBNVINFER_MAJOR_VERSION=6 +ENV CUDA=$CUDA + +# Needed for string substitution +SHELL ["/bin/bash", "-c"] +# Pick up some TF dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + cuda-command-line-tools-${CUDA/./-} \ + # There appears to be a regression in libcublas10=10.2.2.89-1 which + # prevents cublas from initializing in TF. See + # https://github.com/tensorflow/tensorflow/issues/9489#issuecomment-562394257 + libcublas10=10.2.1.243-1 \ + cuda-nvrtc-${CUDA/./-} \ + cuda-cufft-${CUDA/./-} \ + cuda-curand-${CUDA/./-} \ + cuda-cusolver-${CUDA/./-} \ + cuda-cusparse-${CUDA/./-} \ + curl \ + libcudnn7=${CUDNN}+cuda${CUDA} \ + libfreetype6-dev \ + libhdf5-serial-dev \ + libzmq3-dev \ + pkg-config \ + software-properties-common \ + unzip + +# Install TensorRT if not building for PowerPC +RUN [[ "${ARCH}" = "ppc64le" ]] || { apt-get update && \ + apt-get install -y --no-install-recommends libnvinfer${LIBNVINFER_MAJOR_VERSION}=${LIBNVINFER}+cuda${CUDA} \ + libnvinfer-plugin${LIBNVINFER_MAJOR_VERSION}=${LIBNVINFER}+cuda${CUDA} \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/*; } + +# For CUDA profiling, TensorFlow requires CUPTI. +ENV LD_LIBRARY_PATH /usr/local/cuda/extras/CUPTI/lib64:/usr/local/cuda/lib64:$LD_LIBRARY_PATH + +# Link the libcuda stub to the location where tensorflow is searching for it and reconfigure +# dynamic linker run-time bindings +RUN ln -s /usr/local/cuda/lib64/stubs/libcuda.so /usr/local/cuda/lib64/stubs/libcuda.so.1 \ + && echo "/usr/local/cuda/lib64/stubs" > /etc/ld.so.conf.d/z-cuda-stubs.conf \ + && ldconfig + +# See http://bugs.python.org/issue19846 +ENV LANG C.UTF-8 + +ARG PYTHON=python3.7 +RUN add-apt-repository ppa:deadsnakes/ppa && apt-get update && apt-get install -y ${PYTHON} libpython3.7-dev libgl1-mesa-glx +RUN curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py && ${PYTHON} get-pip.py +RUN ${PYTHON} -m pip --no-cache-dir install --upgrade pip setuptools + +# Some TF tools expect a "python" binary +RUN ln -s $(which $PYTHON) /usr/local/bin/python + + +# Variables for Tensorflow +ENV TF_FORCE_GPU_ALLOW_GROWTH=true + +# Variables for MXNET +ENV MXNET_CPU_WORKER_NTHREADS=24 +ENV MXNET_ENGINE_TYPE=ThreadedEnginePerDevice MXNET_CUDNN_AUTOTUNE_DEFAULT=0 + +# No access to GPU devices in the build stage, so skip tests +ENV SKIP_TESTS=1 +# The number of processes depends on GPU memory. +# Keep in mind that one uwsgi process with InsightFace consumes about 2.5GB memory ENV UWSGI_PROCESSES=1 \ No newline at end of file diff --git a/embedding-calculator/pylama.ini b/embedding-calculator/pylama.ini index 5a8e64211e..8f16a64dd8 100644 --- a/embedding-calculator/pylama.ini +++ b/embedding-calculator/pylama.ini @@ -1,14 +1,14 @@ -[pylama:pycodestyle] -max_line_length = 120 - -[pylama] -ignore = W291,E302 - -[pylama:*/__init__.py] -ignore = W0611 - -[pylama:*/_endpoints.py] -ignore = C901 - -[pylama:*/_save_img.py] -ignore = C901 +[pylama:pycodestyle] +max_line_length = 120 + +[pylama] +ignore = W291,E302 + +[pylama:*/__init__.py] +ignore = W0611 + +[pylama:*/_endpoints.py] +ignore = C901 + +[pylama:*/_save_img.py] +ignore = C901 diff --git a/embedding-calculator/pytest.ini b/embedding-calculator/pytest.ini index 8f456b3cbe..5359c7e415 100644 --- a/embedding-calculator/pytest.ini +++ b/embedding-calculator/pytest.ini @@ -1,13 +1,13 @@ -[pytest] -filterwarnings = - ignore:.*U.*is deprecated:DeprecationWarning -markers = - integration - performance -mock_use_standalone_module = true -addopts = - --strict-markers - --doctest-modules - -ra - --verbose - --ignore-glob=*/tmp/* +[pytest] +filterwarnings = + ignore:.*U.*is deprecated:DeprecationWarning +markers = + integration + performance +mock_use_standalone_module = true +addopts = + --strict-markers + --doctest-modules + -ra + --verbose + --ignore-glob=*/tmp/* diff --git a/embedding-calculator/requirements.txt b/embedding-calculator/requirements.txt index fdad3e3671..e5cd11317d 100644 --- a/embedding-calculator/requirements.txt +++ b/embedding-calculator/requirements.txt @@ -1,31 +1,31 @@ -attrs==20.2.0 -cached-property==1.5.2 -colour==0.1.5 -flasgger==0.9.5 -Flask==1.1.2 -gdown~=3.12 -Werkzeug==1.0.1 - -# tests -mock~=4.0.2 -pytest~=6.1.2 -pytest-mock~=3.3.1 -requests~=2.24.0 -pylama~=7.7.1 - -# dependencies for both scanner backends -Pillow~=8.0.1 -imagecodecs~=2020.5.30 -#numpy~=1.18.0 -numpy~=1.19.5 -scipy~=1.5.4 -opencv-python~=4.4.0 -scikit-learn~=0.23.2 -scikit-image~=0.17.2 -joblib~=0.17.0 - -# web server -uWSGI==2.0.19 - -# for successful tensorflow import +attrs==20.2.0 +cached-property==1.5.2 +colour==0.1.5 +flasgger==0.9.5 +Flask==1.1.2 +gdown~=3.12 +Werkzeug==1.0.1 + +# tests +mock~=4.0.2 +pytest~=6.1.2 +pytest-mock~=3.3.1 +requests~=2.24.0 +pylama~=7.7.1 + +# dependencies for both scanner backends +Pillow~=8.0.1 +imagecodecs~=2020.5.30 +#numpy~=1.18.0 +numpy~=1.19.5 +scipy~=1.5.4 +opencv-python~=4.4.0 +scikit-learn~=0.23.2 +scikit-image~=0.17.2 +joblib~=0.17.0 + +# web server +uWSGI==2.0.19 + +# for successful tensorflow import mtcnn~=0.1.0 \ No newline at end of file diff --git a/embedding-calculator/src/__init__.py b/embedding-calculator/src/__init__.py index 617138f790..9642041973 100644 --- a/embedding-calculator/src/__init__.py +++ b/embedding-calculator/src/__init__.py @@ -1,13 +1,13 @@ -# Copyright (c) 2020 the original author or authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express -# or implied. See the License for the specific language governing -# permissions and limitations under the License. +# Copyright (c) 2020 the original author or authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing +# permissions and limitations under the License. diff --git a/embedding-calculator/src/_docs.py b/embedding-calculator/src/_docs.py index 49bb4b80cd..11d634d351 100644 --- a/embedding-calculator/src/_docs.py +++ b/embedding-calculator/src/_docs.py @@ -1,37 +1,37 @@ -# Copyright (c) 2020 the original author or authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express -# or implied. See the License for the specific language governing -# permissions and limitations under the License. - -from flasgger import Swagger - -from src.docs import DOCS_DIR - - -def add_docs(app): - app.config['SWAGGER'] = { - "title": "Swagger UI", - "doc_dir": str(DOCS_DIR), - "specs": [ - { - "endpoint": "frs-core-api", - "route": "/frs-core-api.json", - "rule_filter": lambda rule: True, # all in - "model_filter": lambda tag: True, # all in - } - ], - "static_url_path": "/apidocs", - "swagger_ui": True, - "specs_route": "/apidocs", - "endpoint": 'flasgger' - } - Swagger(app, template_file=str(DOCS_DIR / 'template.yml')) +# Copyright (c) 2020 the original author or authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing +# permissions and limitations under the License. + +from flasgger import Swagger + +from src.docs import DOCS_DIR + + +def add_docs(app): + app.config['SWAGGER'] = { + "title": "Swagger UI", + "doc_dir": str(DOCS_DIR), + "specs": [ + { + "endpoint": "frs-core-api", + "route": "/frs-core-api.json", + "rule_filter": lambda rule: True, # all in + "model_filter": lambda tag: True, # all in + } + ], + "static_url_path": "/apidocs", + "swagger_ui": True, + "specs_route": "/apidocs", + "endpoint": 'flasgger' + } + Swagger(app, template_file=str(DOCS_DIR / 'template.yml')) diff --git a/embedding-calculator/src/_endpoints.py b/embedding-calculator/src/_endpoints.py index 59de89769f..53685293d1 100644 --- a/embedding-calculator/src/_endpoints.py +++ b/embedding-calculator/src/_endpoints.py @@ -1,130 +1,130 @@ -# Copyright (c) 2020 the original author or authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express -# or implied. See the License for the specific language governing -# permissions and limitations under the License. -from typing import List, Optional - -from flask import request -from flask.json import jsonify -from werkzeug.exceptions import BadRequest - -from src.constants import ENV -from src.exceptions import NoFaceFoundError -from src.services.facescan.plugins import base, managers -from src.services.facescan.scanner.facescanners import scanner -from src.services.flask_.constants import ARG -from src.services.flask_.needs_attached_file import needs_attached_file -from src.services.imgtools.read_img import read_img -from src.services.utils.pyutils import Constants -import base64 - - -def endpoints(app): - @app.route('/status') - def status_get(): - available_plugins = {p.slug: str(p) - for p in managers.plugin_manager.plugins} - calculator = managers.plugin_manager.calculator - return jsonify( - status='OK', build_version=ENV.BUILD_VERSION, - calculator_version=str(calculator), - similarity_coefficients=calculator.ml_model.similarity_coefficients, - available_plugins=available_plugins - ) - - @app.route('/find_faces_base64', methods=['POST']) - def find_faces_base64_post(): - detector = managers.plugin_manager.detector - face_plugins = managers.plugin_manager.filter_face_plugins( - _get_face_plugin_names() - ) - - rawfile = base64.b64decode(request.get_json()["file"]) - - faces = detector( - img=read_img(rawfile), - det_prob_threshold=_get_det_prob_threshold(), - face_plugins=face_plugins - ) - plugins_versions = {p.slug: str(p) for p in [detector] + face_plugins} - faces = _limit(faces, request.values.get(ARG.LIMIT)) - return jsonify(plugins_versions=plugins_versions, result=faces) - - @app.route('/find_faces', methods=['POST']) - @needs_attached_file - def find_faces_post(): - detector = managers.plugin_manager.detector - face_plugins = managers.plugin_manager.filter_face_plugins( - _get_face_plugin_names() - ) - faces = detector( - img=read_img(request.files['file']), - det_prob_threshold=_get_det_prob_threshold(), - face_plugins=face_plugins - ) - plugins_versions = {p.slug: str(p) for p in [detector] + face_plugins} - faces = _limit(faces, request.values.get(ARG.LIMIT)) - return jsonify(plugins_versions=plugins_versions, result=faces) - - @app.route('/scan_faces', methods=['POST']) - @needs_attached_file - def scan_faces_post(): - faces = scanner.scan( - img=read_img(request.files['file']), - det_prob_threshold=_get_det_prob_threshold() - ) - faces = _limit(faces, request.values.get(ARG.LIMIT)) - return jsonify(calculator_version=scanner.ID, result=faces) - - -def _get_det_prob_threshold(): - det_prob_threshold_val = request.values.get(ARG.DET_PROB_THRESHOLD) - if det_prob_threshold_val is None: - return None - det_prob_threshold = float(det_prob_threshold_val) - if not (0 <= det_prob_threshold <= 1): - raise BadRequest('Detection threshold incorrect (0 <= det_prob_threshold <= 1)') - return det_prob_threshold - - -def _get_face_plugin_names() -> Optional[List[str]]: - if ARG.FACE_PLUGINS not in request.values: - return [] - return [ - name for name in Constants.split(request.values[ARG.FACE_PLUGINS]) - ] - - -def _limit(faces: List, limit: str = None) -> List: - """ - >>> _limit([1, 2, 3], None) - [1, 2, 3] - >>> _limit([1, 2, 3], '') - [1, 2, 3] - >>> _limit([1, 2, 3], 0) - [1, 2, 3] - >>> _limit([1, 2, 3], 1) - [1] - >>> _limit([1, 2, 3], 2) - [1, 2] - """ - if len(faces) == 0: - raise NoFaceFoundError - - try: - limit = int(limit or 0) - except ValueError as e: - raise BadRequest('Limit format is invalid (limit >= 0)') from e - if not (limit >= 0): - raise BadRequest('Limit value is invalid (limit >= 0)') - - return faces[:limit] if limit else faces +# Copyright (c) 2020 the original author or authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing +# permissions and limitations under the License. +from typing import List, Optional + +from flask import request +from flask.json import jsonify +from werkzeug.exceptions import BadRequest + +from src.constants import ENV +from src.exceptions import NoFaceFoundError +from src.services.facescan.plugins import base, managers +from src.services.facescan.scanner.facescanners import scanner +from src.services.flask_.constants import ARG +from src.services.flask_.needs_attached_file import needs_attached_file +from src.services.imgtools.read_img import read_img +from src.services.utils.pyutils import Constants +import base64 + + +def endpoints(app): + @app.route('/status') + def status_get(): + available_plugins = {p.slug: str(p) + for p in managers.plugin_manager.plugins} + calculator = managers.plugin_manager.calculator + return jsonify( + status='OK', build_version=ENV.BUILD_VERSION, + calculator_version=str(calculator), + similarity_coefficients=calculator.ml_model.similarity_coefficients, + available_plugins=available_plugins + ) + + @app.route('/find_faces_base64', methods=['POST']) + def find_faces_base64_post(): + detector = managers.plugin_manager.detector + face_plugins = managers.plugin_manager.filter_face_plugins( + _get_face_plugin_names() + ) + + rawfile = base64.b64decode(request.get_json()["file"]) + + faces = detector( + img=read_img(rawfile), + det_prob_threshold=_get_det_prob_threshold(), + face_plugins=face_plugins + ) + plugins_versions = {p.slug: str(p) for p in [detector] + face_plugins} + faces = _limit(faces, request.values.get(ARG.LIMIT)) + return jsonify(plugins_versions=plugins_versions, result=faces) + + @app.route('/find_faces', methods=['POST']) + @needs_attached_file + def find_faces_post(): + detector = managers.plugin_manager.detector + face_plugins = managers.plugin_manager.filter_face_plugins( + _get_face_plugin_names() + ) + faces = detector( + img=read_img(request.files['file']), + det_prob_threshold=_get_det_prob_threshold(), + face_plugins=face_plugins + ) + plugins_versions = {p.slug: str(p) for p in [detector] + face_plugins} + faces = _limit(faces, request.values.get(ARG.LIMIT)) + return jsonify(plugins_versions=plugins_versions, result=faces) + + @app.route('/scan_faces', methods=['POST']) + @needs_attached_file + def scan_faces_post(): + faces = scanner.scan( + img=read_img(request.files['file']), + det_prob_threshold=_get_det_prob_threshold() + ) + faces = _limit(faces, request.values.get(ARG.LIMIT)) + return jsonify(calculator_version=scanner.ID, result=faces) + + +def _get_det_prob_threshold(): + det_prob_threshold_val = request.values.get(ARG.DET_PROB_THRESHOLD) + if det_prob_threshold_val is None: + return None + det_prob_threshold = float(det_prob_threshold_val) + if not (0 <= det_prob_threshold <= 1): + raise BadRequest('Detection threshold incorrect (0 <= det_prob_threshold <= 1)') + return det_prob_threshold + + +def _get_face_plugin_names() -> Optional[List[str]]: + if ARG.FACE_PLUGINS not in request.values: + return [] + return [ + name for name in Constants.split(request.values[ARG.FACE_PLUGINS]) + ] + + +def _limit(faces: List, limit: str = None) -> List: + """ + >>> _limit([1, 2, 3], None) + [1, 2, 3] + >>> _limit([1, 2, 3], '') + [1, 2, 3] + >>> _limit([1, 2, 3], 0) + [1, 2, 3] + >>> _limit([1, 2, 3], 1) + [1] + >>> _limit([1, 2, 3], 2) + [1, 2] + """ + if len(faces) == 0: + raise NoFaceFoundError + + try: + limit = int(limit or 0) + except ValueError as e: + raise BadRequest('Limit format is invalid (limit >= 0)') from e + if not (limit >= 0): + raise BadRequest('Limit value is invalid (limit >= 0)') + + return faces[:limit] if limit else faces diff --git a/embedding-calculator/src/conftest.py b/embedding-calculator/src/conftest.py index 4fd50fd4fb..77ecef1430 100644 --- a/embedding-calculator/src/conftest.py +++ b/embedding-calculator/src/conftest.py @@ -1,19 +1,19 @@ -# Copyright (c) 2020 the original author or authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express -# or implied. See the License for the specific language governing -# permissions and limitations under the License. - -import logging - -from src.init_runtime import init_runtime - -init_runtime(logging.DEBUG) +# Copyright (c) 2020 the original author or authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing +# permissions and limitations under the License. + +import logging + +from src.init_runtime import init_runtime + +init_runtime(logging.DEBUG) diff --git a/embedding-calculator/src/constants.py b/embedding-calculator/src/constants.py index 50ce605cbd..ad4f5dfd38 100644 --- a/embedding-calculator/src/constants.py +++ b/embedding-calculator/src/constants.py @@ -1,39 +1,39 @@ -# Copyright (c) 2020 the original author or authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express -# or implied. See the License for the specific language governing -# permissions and limitations under the License. - -import logging - -from src.services.utils.pyutils import get_env, get_env_split, get_env_bool, Constants - -_DEFAULT_SCANNER = 'Facenet2018' - - -class ENV(Constants): - ML_PORT = int(get_env('ML_PORT', '3000')) - IMG_LENGTH_LIMIT = int(get_env('IMG_LENGTH_LIMIT', '640')) - - FACE_DETECTION_PLUGIN = get_env('FACE_DETECTION_PLUGIN', 'facenet.FaceDetector') - CALCULATION_PLUGIN = get_env('CALCULATION_PLUGIN', 'facenet.Calculator') - EXTRA_PLUGINS = get_env_split('EXTRA_PLUGINS', 'facenet.LandmarksDetector,agegender.AgeDetector,agegender.GenderDetector,facenet.facemask.MaskDetector') - - LOGGING_LEVEL_NAME = get_env('LOGGING_LEVEL_NAME', 'debug').upper() - IS_DEV_ENV = get_env('FLASK_ENV', 'production') == 'development' - BUILD_VERSION = get_env('APP_VERSION_STRING', 'dev') - - GPU_IDX = int(get_env('GPU_IDX', '-1')) - INTEL_OPTIMIZATION = get_env_bool('INTEL_OPTIMIZATION') - - -LOGGING_LEVEL = logging._nameToLevel[ENV.LOGGING_LEVEL_NAME] -ENV_MAIN = ENV +# Copyright (c) 2020 the original author or authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing +# permissions and limitations under the License. + +import logging + +from src.services.utils.pyutils import get_env, get_env_split, get_env_bool, Constants + +_DEFAULT_SCANNER = 'Facenet2018' + + +class ENV(Constants): + ML_PORT = int(get_env('ML_PORT', '3000')) + IMG_LENGTH_LIMIT = int(get_env('IMG_LENGTH_LIMIT', '640')) + + FACE_DETECTION_PLUGIN = get_env('FACE_DETECTION_PLUGIN', 'facenet.FaceDetector') + CALCULATION_PLUGIN = get_env('CALCULATION_PLUGIN', 'facenet.Calculator') + EXTRA_PLUGINS = get_env_split('EXTRA_PLUGINS', 'facenet.LandmarksDetector,agegender.AgeDetector,agegender.GenderDetector,facenet.facemask.MaskDetector') + + LOGGING_LEVEL_NAME = get_env('LOGGING_LEVEL_NAME', 'debug').upper() + IS_DEV_ENV = get_env('FLASK_ENV', 'production') == 'development' + BUILD_VERSION = get_env('APP_VERSION_STRING', 'dev') + + GPU_IDX = int(get_env('GPU_IDX', '-1')) + INTEL_OPTIMIZATION = get_env_bool('INTEL_OPTIMIZATION') + + +LOGGING_LEVEL = logging._nameToLevel[ENV.LOGGING_LEVEL_NAME] +ENV_MAIN = ENV diff --git a/embedding-calculator/src/docs/__init__.py b/embedding-calculator/src/docs/__init__.py index fd583aefaa..f3d196aba3 100644 --- a/embedding-calculator/src/docs/__init__.py +++ b/embedding-calculator/src/docs/__init__.py @@ -1,17 +1,17 @@ -# Copyright (c) 2020 the original author or authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express -# or implied. See the License for the specific language governing -# permissions and limitations under the License. - -from src.services.utils.pyutils import get_current_dir - -DOCS_DIR = get_current_dir(__file__) +# Copyright (c) 2020 the original author or authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing +# permissions and limitations under the License. + +from src.services.utils.pyutils import get_current_dir + +DOCS_DIR = get_current_dir(__file__) diff --git a/embedding-calculator/src/services/facescan/plugins/facenet/facemask/__init__.py b/embedding-calculator/src/services/facescan/plugins/facenet/facemask/__init__.py index 28a3ccefaf..600be10b5d 100644 --- a/embedding-calculator/src/services/facescan/plugins/facenet/facemask/__init__.py +++ b/embedding-calculator/src/services/facescan/plugins/facenet/facemask/__init__.py @@ -1,17 +1,17 @@ -# Copyright (c) 2020 the original author or authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express -# or implied. See the License for the specific language governing -# permissions and limitations under the License. - -from src.services.facescan.plugins.dependencies import get_tensorflow - +# Copyright (c) 2020 the original author or authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing +# permissions and limitations under the License. + +from src.services.facescan.plugins.dependencies import get_tensorflow + requirements = get_tensorflow() \ No newline at end of file diff --git a/embedding-calculator/src/services/facescan/plugins/facenet/facemask/facemask.py b/embedding-calculator/src/services/facescan/plugins/facenet/facemask/facemask.py index a07cde4f6e..cf17ab20a0 100644 --- a/embedding-calculator/src/services/facescan/plugins/facenet/facemask/facemask.py +++ b/embedding-calculator/src/services/facescan/plugins/facenet/facemask/facemask.py @@ -1,64 +1,64 @@ -# Copyright (c) 2020 the original author or authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express -# or implied. See the License for the specific language governing -# permissions and limitations under the License. - -from typing import Tuple, Union - -import numpy as np -import tensorflow as tf2 -from tensorflow.keras.models import load_model -from cached_property import cached_property - -from src.services.imgtools.types import Array3D -from src.services.facescan.plugins import base -from src.services.dto import plugin_result - -import cv2 - - -class MaskDetector(base.BasePlugin): - slug = 'mask' - LABELS = ('without_mask', 'with_mask', 'mask_weared_incorrect') - ml_models = ( - ('face_mask_detector', '1AeSYb_E_3cqZM67qXnJ__wJDgw6yqTDV'), - ) - INPUT_IMAGE_SIZE = 100 - category2label = {0: 'without_mask', 1: 'with_mask', 2: 'mask_weared_incorrect'} - - @cached_property - def _model(self): - model = tf2.keras.models.load_model( - self.ml_model.path, - options=tf2.saved_model.LoadOptions( - experimental_io_device='/job:localhost' - ) - ) - - def get_value(img: Array3D) -> Tuple[Union[str, Tuple], float]: - img = cv2.resize(img, dsize=(self.INPUT_IMAGE_SIZE, self.INPUT_IMAGE_SIZE), - interpolation=cv2.INTER_CUBIC) - img = img[:, :, [2, 1, 0]] - img = np.expand_dims(img, 0) - - scores = model.predict(img) - print('Predictions: ' + str(scores)) - val = self.LABELS[int(np.argmax(scores, axis=1)[0])] - prob = scores[0][int(np.argmax(scores, axis=1)[0])] - return val, prob - return get_value - - def __call__(self, face: plugin_result.FaceDTO): - value, probability = self._model(face._face_img) - return plugin_result.MaskDTO(mask=value, mask_probability=probability) - - +# Copyright (c) 2020 the original author or authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing +# permissions and limitations under the License. + +from typing import Tuple, Union + +import numpy as np +import tensorflow as tf2 +from tensorflow.keras.models import load_model +from cached_property import cached_property + +from src.services.imgtools.types import Array3D +from src.services.facescan.plugins import base +from src.services.dto import plugin_result + +import cv2 + + +class MaskDetector(base.BasePlugin): + slug = 'mask' + LABELS = ('without_mask', 'with_mask', 'mask_weared_incorrect') + ml_models = ( + ('face_mask_detector', '1AeSYb_E_3cqZM67qXnJ__wJDgw6yqTDV'), + ) + INPUT_IMAGE_SIZE = 100 + category2label = {0: 'without_mask', 1: 'with_mask', 2: 'mask_weared_incorrect'} + + @cached_property + def _model(self): + model = tf2.keras.models.load_model( + self.ml_model.path, + options=tf2.saved_model.LoadOptions( + experimental_io_device='/job:localhost' + ) + ) + + def get_value(img: Array3D) -> Tuple[Union[str, Tuple], float]: + img = cv2.resize(img, dsize=(self.INPUT_IMAGE_SIZE, self.INPUT_IMAGE_SIZE), + interpolation=cv2.INTER_CUBIC) + img = img[:, :, [2, 1, 0]] + img = np.expand_dims(img, 0) + + scores = model.predict(img) + print('Predictions: ' + str(scores)) + val = self.LABELS[int(np.argmax(scores, axis=1)[0])] + prob = scores[0][int(np.argmax(scores, axis=1)[0])] + return val, prob + return get_value + + def __call__(self, face: plugin_result.FaceDTO): + value, probability = self._model(face._face_img) + return plugin_result.MaskDTO(mask=value, mask_probability=probability) + + From 706e9be1cdf30a9460d0f2d6cecc792680bf3329 Mon Sep 17 00:00:00 2001 From: Ivan Kurnosau Date: Wed, 2 Jun 2021 20:46:56 +0300 Subject: [PATCH 012/837] fix first pull request mistakes --- embedding-calculator/README.md | 6 ++--- embedding-calculator/requirements.txt | 3 +-- .../src/services/facescan/plugins/base.py | 6 ++++- .../plugins/facenet/facemask/facemask.py | 15 +++++------ .../facescan/plugins/facenet/facenet.py | 27 ------------------- .../plugins/insightface/insightface.py | 2 +- .../src/services/facescan/plugins/managers.py | 3 --- 7 files changed, 16 insertions(+), 46 deletions(-) diff --git a/embedding-calculator/README.md b/embedding-calculator/README.md index 20a232b913..f5bcee9b55 100644 --- a/embedding-calculator/README.md +++ b/embedding-calculator/README.md @@ -35,9 +35,9 @@ $ docker run -p 3000:3000 exadel/compreface-core:latest | Tag | Scanner | Build arguments | Comment | |------------------------|-------------|----------------------------------------------------------------------------------------------------------|------------------------------------| -| :0.5.0 :latest | Facenet2018 | | | -| :0.5.0-insightface | InsightFace | FACE_DETECTION_PLUGIN=insightface.FaceDetector
CALCULATION_PLUGIN=insightface.Calculator | | -| :0.5.0-insightface-gpu | InsightFace | FACE_DETECTION_PLUGIN=insightface.FaceDetector
CALCULATION_PLUGIN=insightface.Calculator
GPU_IDX=0 | CORE_GPU_IDX - index of GPU-device | +| :0.5.1 :latest | Facenet2018 | | | +| :0.5.1-insightface | InsightFace | FACE_DETECTION_PLUGIN=insightface.FaceDetector
CALCULATION_PLUGIN=insightface.Calculator | | +| :0.5.1-insightface-gpu | InsightFace | FACE_DETECTION_PLUGIN=insightface.FaceDetector
CALCULATION_PLUGIN=insightface.Calculator
GPU_IDX=0 | CORE_GPU_IDX - index of GPU-device | ##### Build diff --git a/embedding-calculator/requirements.txt b/embedding-calculator/requirements.txt index e5cd11317d..c3f6fb4c9c 100644 --- a/embedding-calculator/requirements.txt +++ b/embedding-calculator/requirements.txt @@ -16,7 +16,6 @@ pylama~=7.7.1 # dependencies for both scanner backends Pillow~=8.0.1 imagecodecs~=2020.5.30 -#numpy~=1.18.0 numpy~=1.19.5 scipy~=1.5.4 opencv-python~=4.4.0 @@ -27,5 +26,5 @@ joblib~=0.17.0 # web server uWSGI==2.0.19 -# for successful tensorflow import +# for successful import with tensorflow mtcnn~=0.1.0 \ No newline at end of file diff --git a/embedding-calculator/src/services/facescan/plugins/base.py b/embedding-calculator/src/services/facescan/plugins/base.py index 96b19dffc8..65dbf79b87 100644 --- a/embedding-calculator/src/services/facescan/plugins/base.py +++ b/embedding-calculator/src/services/facescan/plugins/base.py @@ -71,7 +71,7 @@ def _download(cls, url: str, output): def _extract(self, filename: str): os.makedirs(self.path, exist_ok=True) with ZipFile(filename, 'r') as zf: - if str(self.path)[-18:] == 'face_mask_detector': + if self.plugin.unzip_with_untouched_structure: for info in zf.infolist(): if info.is_dir(): os.makedirs(Path(self.path) / Path(info.filename)) @@ -132,6 +132,10 @@ def backend(self) -> str: def name(self) -> str: return f'{self.backend}.{self.__class__.__name__}' + @property + def unzip_with_untouched_structure(self) -> bool: + return False + def __str__(self): if self.ml_model and self.ml_model_name: return f'{self.name}@{self.ml_model_name}' diff --git a/embedding-calculator/src/services/facescan/plugins/facenet/facemask/facemask.py b/embedding-calculator/src/services/facescan/plugins/facenet/facemask/facemask.py index cf17ab20a0..e6aded4d61 100644 --- a/embedding-calculator/src/services/facescan/plugins/facenet/facemask/facemask.py +++ b/embedding-calculator/src/services/facescan/plugins/facenet/facemask/facemask.py @@ -30,19 +30,17 @@ class MaskDetector(base.BasePlugin): slug = 'mask' LABELS = ('without_mask', 'with_mask', 'mask_weared_incorrect') ml_models = ( - ('face_mask_detector', '1AeSYb_E_3cqZM67qXnJ__wJDgw6yqTDV'), + ('inception_v3_on_mafa_kaggle123', '1AeSYb_E_3cqZM67qXnJ__wJDgw6yqTDV'), ) INPUT_IMAGE_SIZE = 100 - category2label = {0: 'without_mask', 1: 'with_mask', 2: 'mask_weared_incorrect'} + + @property + def unzip_with_untouched_structure(self) -> bool: + return True @cached_property def _model(self): - model = tf2.keras.models.load_model( - self.ml_model.path, - options=tf2.saved_model.LoadOptions( - experimental_io_device='/job:localhost' - ) - ) + model = tf2.keras.models.load_model(self.ml_model.path) def get_value(img: Array3D) -> Tuple[Union[str, Tuple], float]: img = cv2.resize(img, dsize=(self.INPUT_IMAGE_SIZE, self.INPUT_IMAGE_SIZE), @@ -51,7 +49,6 @@ def get_value(img: Array3D) -> Tuple[Union[str, Tuple], float]: img = np.expand_dims(img, 0) scores = model.predict(img) - print('Predictions: ' + str(scores)) val = self.LABELS[int(np.argmax(scores, axis=1)[0])] prob = scores[0][int(np.argmax(scores, axis=1)[0])] return val, prob diff --git a/embedding-calculator/src/services/facescan/plugins/facenet/facenet.py b/embedding-calculator/src/services/facescan/plugins/facenet/facenet.py index 64ba6cd668..adffe73aea 100644 --- a/embedding-calculator/src/services/facescan/plugins/facenet/facenet.py +++ b/embedding-calculator/src/services/facescan/plugins/facenet/facenet.py @@ -62,12 +62,6 @@ class FaceDetector(mixins.FaceDetectorMixin, base.BasePlugin): det_threshold_b = 0.7059968943 det_threshold_c = 0.5506904359 - ''' @cached_property - def _face_detection_nets(self): - with tf1.Graph().as_default(): - sess = tf1.Session() - return _FaceDetectionNets(*detect_face.create_mtcnn(sess, None))''' - @cached_property def _face_detection_net(self): return MTCNN( @@ -87,32 +81,11 @@ def find_faces(self, img: Array3D, det_prob_threshold: float = None) -> List[Bou img = scaler.downscale_img(img) fdn = self._face_detection_net - '''detect_face_result = detect_face.detect_face( - img, self.FACE_MIN_SIZE, fdn.pnet, fdn.rnet, fdn.onet, - [self.det_threshold_a, self.det_threshold_b, self.det_threshold_c], - self.SCALE_FACTOR)''' detect_face_result = fdn.detect_faces(img) img_size = np.asarray(img.shape)[0:2] bounding_boxes = [] - '''detect_face_result = list( - zip(detect_face_result[0], detect_face_result[1].transpose())) - for result_item, landmarks in detect_face_result: - result_item = np.squeeze(result_item) - margin = self.BOX_MARGIN / 2 - box = BoundingBoxDTO( - x_min=int(np.maximum(result_item[0] - margin, 0)), - y_min=int(np.maximum(result_item[1] - margin, 0)), - x_max=int(np.minimum(result_item[2] + margin, img_size[1])), - y_max=int(np.minimum(result_item[3] + margin, img_size[0])), - np_landmarks=landmarks.reshape(2, 5).transpose(), - probability=result_item[4] - ) - logger.debug(f"Found: {box}") - bounding_boxes.append(box)''' - for face in detect_face_result: - #margin = self.BOX_MARGIN / 2 x, y, w, h = face['box'] margin_x = w / 8 margin_y = h / 8 diff --git a/embedding-calculator/src/services/facescan/plugins/insightface/insightface.py b/embedding-calculator/src/services/facescan/plugins/insightface/insightface.py index 2c8cfa2919..8ab1a5cfea 100644 --- a/embedding-calculator/src/services/facescan/plugins/insightface/insightface.py +++ b/embedding-calculator/src/services/facescan/plugins/insightface/insightface.py @@ -61,7 +61,7 @@ class FaceDetector(InsightFaceMixin, mixins.FaceDetectorMixin, base.BasePlugin): ml_models = ( ('retinaface_mnet025_v1', '1ggNFFqpe0abWz6V1A82rnxD6fyxB8W2c'), ('retinaface_mnet025_v2', '1EYTMxgcNdlvoL1fSC8N1zkaWrX75ZoNL'), - ('retinaface_r50_v1', '1hvEv4xZP-_50cO7IYkH6sDUb_SC92wut'), + ('retinaface_r50_v1', '1LZ5h9f_YC5EdbIZAqVba9TKHipi90JBj'), ) IMG_LENGTH_LIMIT = ENV.IMG_LENGTH_LIMIT diff --git a/embedding-calculator/src/services/facescan/plugins/managers.py b/embedding-calculator/src/services/facescan/plugins/managers.py index 27b6873f7b..3c73292368 100644 --- a/embedding-calculator/src/services/facescan/plugins/managers.py +++ b/embedding-calculator/src/services/facescan/plugins/managers.py @@ -36,9 +36,6 @@ class PluginManager: def __init__(self): self.plugins_modules = defaultdict(list) for plugin_name in self.get_plugins_names(): - # this don't work's - # module = import_module(f'{__package__}.{plugin_name.split(".")[0]}') - # this how i fix it module = import_module(f'{__package__}.{plugin_name.rsplit(".", 1)[0]}') plugin_name = plugin_name.split('.')[-2] + '.' + plugin_name.split('.')[-1] self.plugins_modules[module].append(plugin_name) From aedbc31c1e35ab6b64e6f46e6ee86c11dc597a78 Mon Sep 17 00:00:00 2001 From: Pavel Bohdan Date: Thu, 3 Jun 2021 16:14:35 +0300 Subject: [PATCH 013/837] Added new frames for Verification services. --- .../app-change-photo.component.html | 8 +- .../app-change-photo.component.ts | 3 +- .../application-user-list.component.ts | 1 - .../face-recognition-container.component.ts | 10 +- .../recognition-result.component.html | 18 +- .../recognition-result.component.ts | 45 +++-- ...face-verification-container.component.html | 6 +- .../face-verification-container.component.ts | 24 ++- .../verification-result.component.html | 35 +++- .../verification-result.component.scss | 5 +- .../verification-result.component.ts | 172 +++++++++++------- ui/src/app/store/face-recognition/reducers.ts | 1 - ui/src/app/store/face-verification/action.ts | 5 +- ui/src/app/store/face-verification/effects.ts | 30 +-- .../app/store/face-verification/reducers.ts | 19 +- 15 files changed, 259 insertions(+), 123 deletions(-) diff --git a/ui/src/app/features/app-change-photo/app-change-photo.component.html b/ui/src/app/features/app-change-photo/app-change-photo.component.html index ad24d0a545..7c8229e84f 100644 --- a/ui/src/app/features/app-change-photo/app-change-photo.component.html +++ b/ui/src/app/features/app-change-photo/app-change-photo.component.html @@ -15,16 +15,16 @@ -->
- - -
diff --git a/ui/src/app/features/app-change-photo/app-change-photo.component.ts b/ui/src/app/features/app-change-photo/app-change-photo.component.ts index 14f2fc973b..b7de3e51d7 100644 --- a/ui/src/app/features/app-change-photo/app-change-photo.component.ts +++ b/ui/src/app/features/app-change-photo/app-change-photo.component.ts @@ -13,7 +13,7 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ -import { ChangeDetectionStrategy, Component, Output, EventEmitter, ViewChild, ElementRef } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Output, EventEmitter, ViewChild, ElementRef, Input } from '@angular/core'; @Component({ selector: 'app-change-photo', @@ -22,6 +22,7 @@ import { ChangeDetectionStrategy, Component, Output, EventEmitter, ViewChild, El changeDetection: ChangeDetectionStrategy.OnPush, }) export class AppChangePhotoComponent { + @Input() disabledButtons: boolean; @Output() changePhoto = new EventEmitter(); @Output() resetPhoto = new EventEmitter(); @Output() addLandmark = new EventEmitter(); diff --git a/ui/src/app/features/app-user-list/application-user-list.component.ts b/ui/src/app/features/app-user-list/application-user-list.component.ts index 62eb3cede3..9664cbb77b 100644 --- a/ui/src/app/features/app-user-list/application-user-list.component.ts +++ b/ui/src/app/features/app-user-list/application-user-list.component.ts @@ -82,7 +82,6 @@ export class ApplicationUserListComponent implements OnInit, OnDestroy { } onSearch(value: string) { - console.log('jlkjl'); this.search = value; } diff --git a/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.ts b/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.ts index 7cd5c5d650..3f151d3a0b 100644 --- a/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.ts +++ b/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.ts @@ -29,7 +29,6 @@ import { } from '../../../store/face-recognition/selectors'; import { getFileExtension } from '../face-services.helpers'; import { SnackBarService } from '../../snackbar/snackbar.service'; -import { LoadingPhotoService } from '../../../core/photo-loader/photo-loader.service'; @Component({ selector: 'app-face-recognition-container', @@ -43,13 +42,10 @@ export class FaceRecognitionContainerComponent implements OnInit, OnDestroy { pending$: Observable; isLoaded$: Observable; - @Input() - title: string; + @Input() title: string; + @Input() type: string; - @Input() - type: string; - - constructor(private store: Store, private snackBarService: SnackBarService, private loadingPhoto: LoadingPhotoService) {} + constructor(private store: Store, private snackBarService: SnackBarService) {} ngOnInit() { this.data$ = this.store.select(selectFaceData); diff --git a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.html b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.html index 2493c2b76f..d5ce48999f 100644 --- a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.html +++ b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.html @@ -20,42 +20,56 @@
+ +
+ - - + + +
+
{{ value.subject }}
{{ value.similarity }}
+
Unknown
+
{{ frame.box.probability }}
+
+
+
+
+
+
diff --git a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.ts b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.ts index f30481da83..46db3aa596 100644 --- a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.ts +++ b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.ts @@ -13,10 +13,22 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ -import { Component, ElementRef, Input, ViewChild, OnChanges, SimpleChanges, Output, EventEmitter, AfterViewInit } from '@angular/core'; +import { + Component, + ElementRef, + Input, + ViewChild, + OnChanges, + SimpleChanges, + Output, + EventEmitter, + AfterViewInit, + OnInit, + OnDestroy, +} from '@angular/core'; -import { filter, map, switchMap, tap } from 'rxjs/operators'; -import { BehaviorSubject, Observable } from 'rxjs'; +import { filter, map, switchMap, takeUntil, tap } from 'rxjs/operators'; +import { BehaviorSubject, Observable, Subject } from 'rxjs'; import { RequestResultRecognition } from '../../../../data/interfaces/response-result'; import { RequestInfo } from '../../../../data/interfaces/request-info'; @@ -30,7 +42,7 @@ import { recalculateFaceCoordinate, resultRecognitionFormatter } from '../../fac templateUrl: './recognition-result.component.html', styleUrls: ['./recognition-result.component.scss'], }) -export class RecognitionResultComponent implements OnChanges, AfterViewInit { +export class RecognitionResultComponent implements OnInit, OnChanges, OnDestroy, AfterViewInit { @Input() file: File; @Input() requestInfo: RequestInfo; @Input() isLoaded: boolean; @@ -45,18 +57,24 @@ export class RecognitionResultComponent implements OnChanges, AfterViewInit { private ctx: CanvasRenderingContext2D; private dataPrint$: BehaviorSubject = new BehaviorSubject(null); private dataPhoto$: BehaviorSubject = new BehaviorSubject(null); + private unsubscribe$: Subject = new Subject(); formattedResult: string; widthCanvas = 500; types = ServiceTypes; - recalculatePrint$: Observable; + recalculatePrint: RequestResultRecognition[]; - constructor(private loadingPhotoService: LoadingPhotoService) { - this.recalculatePrint$ = this.dataPhoto$.pipe( - filter(data => !!data), - switchMap(data => this.displayPhoto(data)), - switchMap(sizes => this.dataPrint$.pipe(switchMap(printData => this.displayFrames(printData, sizes.imageBitmap, sizes.sizeCanvas)))) - ); + constructor(private loadingPhotoService: LoadingPhotoService) {} + + ngOnInit(): void { + this.dataPhoto$ + .pipe( + takeUntil(this.unsubscribe$), + filter(data => !!data), + switchMap(data => this.displayPhoto(data)), + switchMap(sizes => this.dataPrint$.pipe(switchMap(printData => this.displayFrames(printData, sizes.imageBitmap, sizes.sizeCanvas)))) + ) + .subscribe(value => (this.recalculatePrint = value)); } ngAfterViewInit(): void { @@ -100,4 +118,9 @@ export class RecognitionResultComponent implements OnChanges, AfterViewInit { if (event) this.selectFile.emit(event); } + + ngOnDestroy(): void { + this.unsubscribe$.next(); + this.unsubscribe$.complete(); + } } diff --git a/ui/src/app/features/face-services/face-verification/face-verification-container.component.html b/ui/src/app/features/face-services/face-verification/face-verification-container.component.html index 3f03cd07d3..add8737bee 100644 --- a/ui/src/app/features/face-services/face-verification/face-verification-container.component.html +++ b/ui/src/app/features/face-services/face-verification/face-verification-container.component.html @@ -1,8 +1,4 @@
-
- -
-
diff --git a/ui/src/app/features/face-services/face-verification/face-verification-container.component.ts b/ui/src/app/features/face-services/face-verification/face-verification-container.component.ts index 3474b8741f..ce7bbd430c 100644 --- a/ui/src/app/features/face-services/face-verification/face-verification-container.component.ts +++ b/ui/src/app/features/face-services/face-verification/face-verification-container.component.ts @@ -19,7 +19,13 @@ import { Observable } from 'rxjs'; import { AVAILABLE_IMAGE_EXTENSIONS, MAX_IMAGE_SIZE } from 'src/app/core/constants'; import { AppState } from '../../../store'; -import { verifyFaceReset, verifyFace } from '../../../store/face-verification/action'; +import { + verifyFaceReset, + verifyFace, + verifyFaceAddFile, + verifyFaceProcessFileReset, + verifyFaceCheckFileReset, +} from '../../../store/face-verification/action'; import { selectCheckFile, selectFaceData, @@ -62,16 +68,28 @@ export class FaceVerificationContainerComponent implements OnInit, OnDestroy { processFileUpload(file) { if (this.validateImage(file)) { - this.store.dispatch(verifyFace({ processFile: file })); + this.store.dispatch(verifyFaceAddFile({ processFile: file })); } } + processFileReset(event?: File) { + this.store.dispatch(verifyFaceProcessFileReset()); + + if (!!event) this.processFileUpload(event); + } + checkFileUpload(file) { if (this.validateImage(file)) { - this.store.dispatch(verifyFace({ checkFile: file })); + this.store.dispatch(verifyFaceAddFile({ checkFile: file })); } } + checkFileReset(event: File) { + this.store.dispatch(verifyFaceCheckFileReset()); + + if (!!event) this.checkFileUpload(event); + } + validateImage(file) { if (!AVAILABLE_IMAGE_EXTENSIONS.includes(getFileExtension(file))) { this.snackBarService.openNotification({ diff --git a/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.html b/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.html index 9745cab0ae..6556cde22c 100644 --- a/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.html +++ b/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.html @@ -13,7 +13,6 @@ ~ or implied. See the License for the specific language governing ~ permissions and limitations under the License. --> -
+ +
- -
-
+ +
+
+
+ +
@@ -41,17 +49,26 @@ viewComponentColumn > + +
- -
-
-
-
{{ frame.similarity }}
+ +
+
+
+
{{ frame.similarity }}
+
+ +
diff --git a/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.scss b/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.scss index cb36c1b02f..139ca6bfb2 100644 --- a/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.scss +++ b/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.scss @@ -15,6 +15,7 @@ */ @import "media.scss"; @import "frames.scss"; +@import "photo-spiner"; @include frames(); @@ -55,7 +56,7 @@ min-width: 100%; } - canvas { - max-width: 100%; + .spinner-img { + @include photo-spinner(); } } diff --git a/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.ts b/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.ts index 78dc82eeb3..3edf3030bf 100644 --- a/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.ts +++ b/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.ts @@ -13,111 +13,161 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ -import { Component, Input, OnChanges, SimpleChanges, Output, EventEmitter, ViewChild, ElementRef, OnDestroy } from '@angular/core'; - -import { ReplaySubject, Subject } from 'rxjs'; -import { map, takeUntil, tap } from 'rxjs/operators'; +import { + Component, + Input, + OnChanges, + SimpleChanges, + Output, + EventEmitter, + ViewChild, + ElementRef, + AfterViewInit, + OnInit, + OnDestroy, +} from '@angular/core'; + +import { BehaviorSubject, Observable, Subject } from 'rxjs'; +import { filter, map, switchMap, takeUntil, tap } from 'rxjs/operators'; import { FaceMatches, RequestResultVerification, SourceImageFace } from '../../../../data/interfaces/response-result'; import { RequestInfo } from '../../../../data/interfaces/request-info'; import { LoadingPhotoService } from '../../../../core/photo-loader/photo-loader.service'; import { ImageSize } from '../../../../data/interfaces/image'; import { recalculateFaceCoordinate, resultRecognitionFormatter } from '../../face-services.helpers'; - -enum PrintDataKeys { - SourceFace = 'source_image_face', - MatchesFace = 'face_matches', -} +import { VerificationServiceFields } from '../../../../data/enums/verification-service.enum'; @Component({ selector: 'app-verification-result', templateUrl: './verification-result.component.html', styleUrls: ['./verification-result.component.scss'], }) -export class VerificationResultComponent implements OnChanges, OnDestroy { +export class VerificationResultComponent implements OnInit, OnChanges, AfterViewInit, OnDestroy { @Input() processFile: File; @Input() checkFile: File; - @Input() requestInfo: RequestInfo; @Input() printData: RequestResultVerification[]; - @Input() isLoaded: boolean; @Input() pending: boolean; @Output() selectProcessFile = new EventEmitter(); @Output() selectCheckFile = new EventEmitter(); + @Output() resetProcessFile = new EventEmitter(); + @Output() resetCheckFile = new EventEmitter(); - @ViewChild('processFileCanvasElement') set processFileCanvasElement(canvas: ElementRef) { - this.processFileCanvasLink = canvas; - } - @ViewChild('checkFileCanvasElement') set checkFileCanvasElement(canvas: ElementRef) { - this.checkFileCanvasLink = canvas; - } + @ViewChild('processFileCanvasElement', { static: true }) canvasProcessFile: ElementRef; + @ViewChild('checkFileCanvasElement', { static: true }) canvasCheckFile: ElementRef; - private processFileCanvasLink: ElementRef; - private checkFileCanvasLink: ElementRef; - private unsubscribe: Subject = new Subject(); - private sizes: ReplaySubject<{ key: PrintDataKeys; img: ImageBitmap; sizeCanvas: ImageSize }> = new ReplaySubject(); + private ctxPhotoProcess: CanvasRenderingContext2D; + private photoProcess$: BehaviorSubject = new BehaviorSubject(null); + + private ctxPhotoCheck: CanvasRenderingContext2D; + private photoCheck$: BehaviorSubject = new BehaviorSubject(null); + + private unsubscribe$: Subject = new Subject(); + private printData$: BehaviorSubject = new BehaviorSubject(null); widthCanvas = 500; - processFilePrintData: FaceMatches[]; - checkFilePrintData: SourceImageFace[]; formattedResult: string; + recalculateProcessFile: SourceImageFace[]; + recalculateCheckFile: FaceMatches[]; constructor(private loadingPhotoService: LoadingPhotoService) {} + ngAfterViewInit(): void { + this.ctxPhotoProcess = this.canvasProcessFile.nativeElement.getContext('2d'); + this.ctxPhotoCheck = this.canvasCheckFile.nativeElement.getContext('2d'); + } + + ngOnInit(): void { + this.photoProcess$ + .pipe( + takeUntil(this.unsubscribe$), + filter(data => !!data), + switchMap(data => this.displayPhoto(data, this.canvasProcessFile, this.ctxPhotoProcess)), + switchMap(sizes => this.displayFrames(VerificationServiceFields.ProcessFileData, sizes)), + map(data => data as SourceImageFace[]) + ) + .subscribe(value => (this.recalculateProcessFile = value)); + + this.photoCheck$ + .pipe( + takeUntil(this.unsubscribe$), + filter(data => !!data), + switchMap(data => this.displayPhoto(data, this.canvasCheckFile, this.ctxPhotoCheck)), + switchMap(sizes => this.displayFrames(VerificationServiceFields.CheckFileData, sizes)), + map(data => data as FaceMatches[]) + ) + .subscribe(value => (this.recalculateCheckFile = value)); + } + ngOnChanges(changes: SimpleChanges): void { - if ('processFile' in changes) this.loadPhoto(this.processFile, this.processFileCanvasLink, PrintDataKeys.SourceFace); - if ('checkFile' in changes) this.loadPhoto(this.checkFile, this.checkFileCanvasLink, PrintDataKeys.MatchesFace); - if ('printData' in changes) this.getFrames(this.printData); + this.photoProcess$.next(this.processFile); + this.photoCheck$.next(this.checkFile); + this.printData$.next(this.printData); + if (!!this.requestInfo) this.formattedResult = resultRecognitionFormatter(this.requestInfo.response); } - getFrames(printData: RequestResultVerification[]): void { - if (!printData) return; - - this.sizes.pipe(takeUntil(this.unsubscribe)).subscribe(size => { - switch (size.key) { - case PrintDataKeys.MatchesFace: - this.processFilePrintData = this.recalculateFrames(printData[0][size.key], size.img, size.sizeCanvas) as FaceMatches[]; - return; - case PrintDataKeys.SourceFace: - this.checkFilePrintData = this.recalculateFrames([printData[0][size.key]], size.img, size.sizeCanvas) as SourceImageFace[]; - return; + recalculateFrames(data: Type, sizeImage: ImageSize, sizeCanvas: ImageSize): Observable { + return new Observable(observer => { + if (!!data) { + const recalculate = data.map(val => ({ ...val, box: recalculateFaceCoordinate(val.box, sizeImage, sizeCanvas) })) as Type; + observer.next(recalculate); + } else { + observer.next(null); } + observer.complete(); }); } - recalculateFrames(data: any[], img, sizeCanvas): SourceImageFace[] | FaceMatches[] { - return data.map(val => ({ ...val, box: recalculateFaceCoordinate(val.box, img, sizeCanvas) })); + getDataFrames(type: VerificationServiceFields, result: RequestResultVerification[]): SourceImageFace[] | FaceMatches[] { + switch (type) { + case VerificationServiceFields.CheckFileData: + return result[0][VerificationServiceFields.CheckFileData] as SourceImageFace[]; + case VerificationServiceFields.ProcessFileData: + return [result[0][VerificationServiceFields.ProcessFileData]] as FaceMatches[]; + } } - loadPhoto(file: File, canvas: ElementRef, key: PrintDataKeys): void { - if (!file) return; + displayFrames(type: VerificationServiceFields, sizes): Observable { + return this.printData$.pipe( + map(data => (!!data ? this.getDataFrames(type, data) : null)), + switchMap(printData => this.recalculateFrames(printData, sizes.imageBitmap, sizes.sizeCanvas)) + ); + } - this.loadingPhotoService - .loader(file) - .pipe( - takeUntil(this.unsubscribe), - map((img: ImageBitmap) => ({ - img, - sizeCanvas: { width: this.widthCanvas, height: (img.height / img.width) * this.widthCanvas }, - })), - tap(({ sizeCanvas }) => canvas.nativeElement.setAttribute('height', sizeCanvas.height)) - ) - .subscribe(value => { - this.displayPhoto(value.img, value.sizeCanvas, canvas); - this.sizes.next({ key, ...value }); - }); + displayPhoto(file: File, el: ElementRef, ctx: CanvasRenderingContext2D): Observable { + return this.loadingPhotoService.loader(file).pipe( + map(img => ({ + imageBitmap: img, + sizeCanvas: { width: this.widthCanvas, height: (img.height / img.width) * this.widthCanvas }, + })), + tap(({ sizeCanvas }) => el.nativeElement.setAttribute('height', String(sizeCanvas.height))), + tap(({ imageBitmap, sizeCanvas }) => ctx.drawImage(imageBitmap, 0, 0, sizeCanvas.width, sizeCanvas.height)) + ); } - displayPhoto(img: ImageBitmap, size: ImageSize, canvas: ElementRef): void { - const ctx = canvas.nativeElement.getContext('2d'); - ctx.drawImage(img, 0, 0, size.width, size.height); + onResetProcessFile(event?: File): void { + const { offsetHeight, offsetWidth } = this.ctxPhotoProcess.canvas; + this.ctxPhotoProcess.clearRect(0, 0, offsetWidth, offsetHeight); + + this.resetProcessFile.emit(); + + if (event) this.resetProcessFile.emit(event); + } + + onResetCheckFile(event?: File): void { + const { offsetHeight, offsetWidth } = this.ctxPhotoCheck.canvas; + this.ctxPhotoCheck.clearRect(0, 0, offsetWidth, offsetHeight); + + this.resetCheckFile.emit(); + + if (event) this.resetCheckFile.emit(event); } ngOnDestroy(): void { - this.unsubscribe.next(); - this.unsubscribe.complete(); + this.unsubscribe$.next(); + this.unsubscribe$.complete(); } } diff --git a/ui/src/app/store/face-recognition/reducers.ts b/ui/src/app/store/face-recognition/reducers.ts index b9969e1ee7..e7a59a29ca 100644 --- a/ui/src/app/store/face-recognition/reducers.ts +++ b/ui/src/app/store/face-recognition/reducers.ts @@ -14,7 +14,6 @@ * permissions and limitations under the License. */ import { Action, ActionReducer, createReducer, on } from '@ngrx/store'; - import { recognizeFace, recognizeFaceFail, recognizeFaceReset, recognizeFaceSuccess } from './action'; export interface FaceRecognitionEntityState { diff --git a/ui/src/app/store/face-verification/action.ts b/ui/src/app/store/face-verification/action.ts index 3ac2d3e376..d082e70eca 100644 --- a/ui/src/app/store/face-verification/action.ts +++ b/ui/src/app/store/face-verification/action.ts @@ -15,7 +15,10 @@ */ import { createAction, props } from '@ngrx/store'; -export const verifyFace = createAction('[Model] Face Verification Save', props<{ processFile?: any; checkFile?: any }>()); +export const verifyFace = createAction('[Model] Face Verification Save'); +export const verifyFaceAddFile = createAction('[Model] Face Verification Add File', props<{ processFile?: any; checkFile?: any }>()); export const verifyFaceSuccess = createAction('[Model] Face Verification Success', props<{ model: any; request: any }>()); export const verifyFaceFail = createAction('[Model] Face Verification Fail', props<{ error: any }>()); export const verifyFaceReset = createAction('[Model] Face Verification Reset'); +export const verifyFaceProcessFileReset = createAction('[Model] Face Verification Reset Process File'); +export const verifyFaceCheckFileReset = createAction('[Model] Face Verification Reset Check File'); diff --git a/ui/src/app/store/face-verification/effects.ts b/ui/src/app/store/face-verification/effects.ts index efa0621559..790a5984aa 100644 --- a/ui/src/app/store/face-verification/effects.ts +++ b/ui/src/app/store/face-verification/effects.ts @@ -16,15 +16,16 @@ import { Injectable } from '@angular/core'; import { Actions, Effect, ofType } from '@ngrx/effects'; import { Action, Store } from '@ngrx/store'; + import { iif, Observable, of } from 'rxjs'; import { catchError, map, switchMap, tap, withLatestFrom } from 'rxjs/operators'; -import { SnackBarService } from 'src/app/features/snackbar/snackbar.service'; +import { SnackBarService } from 'src/app/features/snackbar/snackbar.service'; import { FaceRecognitionService } from '../../core/face-recognition/face-recognition.service'; import { selectDemoApiKey } from '../demo/selectors'; import { selectCurrentModel } from '../model/selectors'; import { selectFiles } from './selectors'; -import { verifyFace, verifyFaceSuccess, verifyFaceFail } from './action'; +import { verifyFace, verifyFaceSuccess, verifyFaceFail, verifyFaceAddFile } from './action'; @Injectable() export class FaceRecognitionEffects { @@ -35,21 +36,24 @@ export class FaceRecognitionEffects { private snackBarService: SnackBarService ) {} + @Effect() + verifyFaceAddFile$ = this.actions.pipe( + ofType(verifyFaceAddFile), + withLatestFrom(this.store.select(selectFiles)), + switchMap(([action, files]) => (files.processFile && files.checkFile ? [verifyFace()] : [])) + ); + @Effect() verifyFaceSaveToStore$ = this.actions.pipe( ofType(verifyFace), withLatestFrom(this.store.select(selectCurrentModel), this.store.select(selectDemoApiKey), this.store.select(selectFiles)), - switchMap(([action, model, demoApiKey, files]) => { - if (files.processFile && files.checkFile) { - return iif( - () => !!model, - this.verificationFace(files.processFile, files.checkFile, model?.apiKey), - this.verificationFace(files.processFile, files.checkFile, demoApiKey) - ); - } else { - return []; - } - }) + switchMap(([action, model, demoApiKey, files]) => + iif( + () => !!model, + this.verificationFace(files.processFile, files.checkFile, model?.apiKey), + this.verificationFace(files.processFile, files.checkFile, demoApiKey) + ) + ) ); @Effect({ dispatch: false }) diff --git a/ui/src/app/store/face-verification/reducers.ts b/ui/src/app/store/face-verification/reducers.ts index dcba2faab0..522a558807 100644 --- a/ui/src/app/store/face-verification/reducers.ts +++ b/ui/src/app/store/face-verification/reducers.ts @@ -15,7 +15,15 @@ */ import { Action, ActionReducer, createReducer, on } from '@ngrx/store'; -import { verifyFaceFail, verifyFaceReset, verifyFace, verifyFaceSuccess } from './action'; +import { + verifyFaceFail, + verifyFaceReset, + verifyFace, + verifyFaceSuccess, + verifyFaceAddFile, + verifyFaceProcessFileReset, + verifyFaceCheckFileReset, +} from './action'; export interface FaceVerificationEntityState { isPending: boolean; model: any; @@ -34,8 +42,13 @@ const initialStateVerification: FaceVerificationEntityState = { const reducerVerification: ActionReducer = createReducer( initialStateVerification, - on(verifyFace, verifyFaceSuccess, (state, action) => ({ ...state, ...action, isPending: false })), - on(verifyFaceReset, verifyFaceFail, () => ({ ...initialStateVerification })) + on(verifyFaceAddFile, (state, action) => ({ ...state, ...action })), + on(verifyFace, (state, action) => ({ ...state, ...action, isPending: true })), + on(verifyFaceSuccess, (state, action) => ({ ...state, ...action, isPending: false })), + on(verifyFaceFail, state => ({ ...state, isPending: false })), + on(verifyFaceProcessFileReset, verifyFaceFail, state => ({ ...state, processFile: null, request: null, model: null })), + on(verifyFaceCheckFileReset, state => ({ ...state, checkFile: null, request: null, model: null })), + on(verifyFaceReset, () => ({ ...initialStateVerification })) ); export const faceVerificationReducer = (verificationState: FaceVerificationEntityState, action: Action) => From 0f6b83b483c238b5f404245988dbc99a3c6b9de4 Mon Sep 17 00:00:00 2001 From: Pavel Bohdan Date: Fri, 4 Jun 2021 13:39:22 +0300 Subject: [PATCH 014/837] Added landmarks. --- .../face-recognition.service.ts | 4 +++ ui/src/app/data/interfaces/response-result.ts | 3 ++ .../face-landmarks.component.ts | 31 +++++++++++++++++++ .../recognition-result.component.html | 6 ++++ .../recognition-result.component.ts | 11 +++++-- .../face-services/face-services.helpers.ts | 12 +++++++ .../face-services/face-services.module.ts | 2 ++ ui/src/assets/img/icons/trash_not_active.svg | 3 -- 8 files changed, 66 insertions(+), 6 deletions(-) create mode 100644 ui/src/app/features/face-services/face-landmarks/face-landmarks.component.ts delete mode 100644 ui/src/assets/img/icons/trash_not_active.svg diff --git a/ui/src/app/core/face-recognition/face-recognition.service.ts b/ui/src/app/core/face-recognition/face-recognition.service.ts index c8f4ed9d02..7e2f0c3f3a 100644 --- a/ui/src/app/core/face-recognition/face-recognition.service.ts +++ b/ui/src/app/core/face-recognition/face-recognition.service.ts @@ -50,6 +50,8 @@ export class FaceRecognitionService { return this.http .post(url, formData, { headers: { 'x-api-key': apiKey }, + // eslint-disable-next-line @typescript-eslint/naming-convention + params: { face_plugins: ['landmarks', 'gender', 'age'] }, }) .pipe( map(data => ({ @@ -67,6 +69,8 @@ export class FaceRecognitionService { return this.http .post(url, formData, { headers: { 'x-api-key': apiKey }, + // eslint-disable-next-line @typescript-eslint/naming-convention + params: { face_plugins: ['landmarks', 'gender', 'age'] }, }) .pipe( map(data => ({ diff --git a/ui/src/app/data/interfaces/response-result.ts b/ui/src/app/data/interfaces/response-result.ts index 0f84995189..60a58e3173 100644 --- a/ui/src/app/data/interfaces/response-result.ts +++ b/ui/src/app/data/interfaces/response-result.ts @@ -29,8 +29,11 @@ export interface BoxSubjects { } export interface RequestResultRecognition { + age: number[]; + gender: 'male' | 'female'; box: BoxSize; subjects: BoxSubjects[]; + landmarks: [number[]]; } export interface SourceImageFace { diff --git a/ui/src/app/features/face-services/face-landmarks/face-landmarks.component.ts b/ui/src/app/features/face-services/face-landmarks/face-landmarks.component.ts new file mode 100644 index 0000000000..0a63086751 --- /dev/null +++ b/ui/src/app/features/face-services/face-landmarks/face-landmarks.component.ts @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +import { Component, ElementRef, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { recalculateLandmarks } from '../face-services.helpers'; + +@Component({ + selector: 'app-face-landmarks', + template: '', + styles: ['canvas { position: absolute; top: 0; left: 0 }'], +}) +export class FaceLandmarksComponent { + @Input() sizes: HTMLCanvasElement; + @Input() landmarks: [number[]]; + + private recalculateLandmarks: [number[]]; + + constructor() {} +} diff --git a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.html b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.html index d5ce48999f..3e66972b34 100644 --- a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.html +++ b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.html @@ -34,6 +34,12 @@ + + +
diff --git a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.ts b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.ts index 46db3aa596..b41462ae56 100644 --- a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.ts +++ b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.ts @@ -35,7 +35,7 @@ import { RequestInfo } from '../../../../data/interfaces/request-info'; import { LoadingPhotoService } from '../../../../core/photo-loader/photo-loader.service'; import { ServiceTypes } from '../../../../data/enums/service-types.enum'; import { ImageSize } from '../../../../data/interfaces/image'; -import { recalculateFaceCoordinate, resultRecognitionFormatter } from '../../face-services.helpers'; +import { recalculateFaceCoordinate, recalculateLandmarks, resultRecognitionFormatter } from '../../face-services.helpers'; @Component({ selector: 'app-recognition-result', @@ -81,7 +81,8 @@ export class RecognitionResultComponent implements OnInit, OnChanges, OnDestroy, this.ctx = this.canvas.nativeElement.getContext('2d'); } - ngOnChanges(changes: SimpleChanges) { + ngOnChanges(changes: SimpleChanges): void { + console.log(this.printData); this.dataPhoto$.next(this.file); this.dataPrint$.next(this.printData); if (!!this.requestInfo) this.formattedResult = resultRecognitionFormatter(this.requestInfo.response); @@ -101,7 +102,11 @@ export class RecognitionResultComponent implements OnInit, OnChanges, OnDestroy, displayFrames(data: Type, sizeImage: ImageSize, sizeCanvas: ImageSize): Observable { return new Observable(observer => { if (!!data) { - const recalculate = data.map(val => ({ ...val, box: recalculateFaceCoordinate(val.box, sizeImage, sizeCanvas) })) as Type; + const recalculate = data.map(val => ({ + ...val, + box: recalculateFaceCoordinate(val.box, sizeImage, sizeCanvas), + landmarks: recalculateLandmarks(val.landmarks, sizeImage, sizeCanvas), + })) as Type; observer.next(recalculate); } else { observer.next(null); diff --git a/ui/src/app/features/face-services/face-services.helpers.ts b/ui/src/app/features/face-services/face-services.helpers.ts index 2f70da53b5..b6131c3bab 100644 --- a/ui/src/app/features/face-services/face-services.helpers.ts +++ b/ui/src/app/features/face-services/face-services.helpers.ts @@ -81,3 +81,15 @@ export const recalculateFaceCoordinate = (box: any, imageSize: ImageSize, sizeTo /* eslint-enable @typescript-eslint/naming-convention */ }; }; + +/** + * Recalculate face coordinates according to canvas size (design). + * + * @param landmarks Face coordinates from BE. + * @param imageSize Size of image. + * @param sizeToCalc Size of image. + */ +export const recalculateLandmarks = (landmarks: [number[]], imageSize: ImageSize, sizeToCalc: ImageSize) => { + console.log(landmarks); + return landmarks; +}; diff --git a/ui/src/app/features/face-services/face-services.module.ts b/ui/src/app/features/face-services/face-services.module.ts index a6b9f1362f..05ce22d7ff 100644 --- a/ui/src/app/features/face-services/face-services.module.ts +++ b/ui/src/app/features/face-services/face-services.module.ts @@ -28,6 +28,7 @@ import { RecognitionResultComponent } from './face-recognition/recognition-resul import { VerificationResultComponent } from './face-verification/verification-result/verification-result.component'; import { FaceServicesDirective } from './face-services.directive'; import { AppChangePhotoModule } from '../app-change-photo/app-change-photo.module'; +import { FaceLandmarksComponent } from './face-landmarks/face-landmarks.component'; @NgModule({ declarations: [ @@ -35,6 +36,7 @@ import { AppChangePhotoModule } from '../app-change-photo/app-change-photo.modul FaceVerificationContainerComponent, RecognitionResultComponent, VerificationResultComponent, + FaceLandmarksComponent, FaceServicesDirective, ], imports: [CommonModule, DragNDropModule, SpinnerModule, MatExpansionModule, TranslateModule, MatCardModule, AppChangePhotoModule], diff --git a/ui/src/assets/img/icons/trash_not_active.svg b/ui/src/assets/img/icons/trash_not_active.svg deleted file mode 100644 index 5e76435fd7..0000000000 --- a/ui/src/assets/img/icons/trash_not_active.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - From 5c1839347095a98df09817486254f19a748a2ad7 Mon Sep 17 00:00:00 2001 From: Ivan Kurnosau Date: Fri, 4 Jun 2021 15:18:57 +0300 Subject: [PATCH 015/837] Add mobilenet_v2 --- .../src/services/dto/plugin_result.py | 24 ++++++++++++------- .../plugins/facenet/facemask/facemask.py | 11 +++++++-- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/embedding-calculator/src/services/dto/plugin_result.py b/embedding-calculator/src/services/dto/plugin_result.py index 6a94b7f5bc..5df18528bc 100644 --- a/embedding-calculator/src/services/dto/plugin_result.py +++ b/embedding-calculator/src/services/dto/plugin_result.py @@ -11,22 +11,28 @@ class EmbeddingDTO(JSONEncodable): embedding: Array1D -@attr.s(auto_attribs=True, frozen=True) class GenderDTO(JSONEncodable): - gender: str - gender_probability: float = attr.ib(converter=float, default=1) + def __init__(self, gender, gender_probability): + self.gender = { + 'value': gender, + 'probability': float(gender_probability) + } -@attr.s(auto_attribs=True, frozen=True) class AgeDTO(JSONEncodable): - age: Tuple[int, int] - age_probability: float = attr.ib(converter=float, default=1) + def __init__(self, age, age_probability): + self.age = { + 'low': age[0], + 'high': age[1], + 'probability': float(age_probability)} -@attr.s(auto_attribs=True, frozen=True) class MaskDTO(JSONEncodable): - mask: str - mask_probability: float = attr.ib(converter=float, default=1) + def __init__(self, mask, mask_probability): + self.mask = { + 'value': mask, + 'probability': float(mask_probability) + } @attr.s(auto_attribs=True, frozen=True) diff --git a/embedding-calculator/src/services/facescan/plugins/facenet/facemask/facemask.py b/embedding-calculator/src/services/facescan/plugins/facenet/facemask/facemask.py index e6aded4d61..ea53499b9f 100644 --- a/embedding-calculator/src/services/facescan/plugins/facenet/facemask/facemask.py +++ b/embedding-calculator/src/services/facescan/plugins/facenet/facemask/facemask.py @@ -31,8 +31,15 @@ class MaskDetector(base.BasePlugin): LABELS = ('without_mask', 'with_mask', 'mask_weared_incorrect') ml_models = ( ('inception_v3_on_mafa_kaggle123', '1AeSYb_E_3cqZM67qXnJ__wJDgw6yqTDV'), + ('mobilenet_v2_on_mafa_kaggle123', '1-eqivfTVaXC_9Z5INbYeFVEwBzzqIzm3') ) - INPUT_IMAGE_SIZE = 100 + + @property + def input_image_size(self) -> Tuple[int, int]: + if self.ml_model_name == self.ml_models[1][0]: + return 128, 128 + else: + return 100, 100 @property def unzip_with_untouched_structure(self) -> bool: @@ -43,7 +50,7 @@ def _model(self): model = tf2.keras.models.load_model(self.ml_model.path) def get_value(img: Array3D) -> Tuple[Union[str, Tuple], float]: - img = cv2.resize(img, dsize=(self.INPUT_IMAGE_SIZE, self.INPUT_IMAGE_SIZE), + img = cv2.resize(img, dsize=self.input_image_size, interpolation=cv2.INTER_CUBIC) img = img[:, :, [2, 1, 0]] img = np.expand_dims(img, 0) From c9d35f96c018ed00b627291509abe53d7d228fa1 Mon Sep 17 00:00:00 2001 From: Pavel Bohdan Date: Mon, 7 Jun 2021 17:33:13 +0300 Subject: [PATCH 016/837] Added landmarks, refactor code. --- ui/src/app/core/custom-icons/custom-icons.ts | 1 - .../face-recognition.service.ts | 2 + .../data/interfaces/image-convert.ts} | 20 +-- ui/src/app/data/interfaces/response-result.ts | 6 + .../app-change-photo.component.html | 21 ++- .../app-change-photo.component.scss | 6 + .../app-change-photo.component.ts | 5 +- .../face-landmarks.component.ts | 48 +++++- .../face-picture/face-picture.component.html | 58 +++++++ .../face-picture/face-picture.component.scss | 113 +++++++++++++ .../face-picture/face-picture.component.ts | 104 ++++++++++++ .../face-services.directive.ts | 4 +- .../face-recognition-container.component.ts | 3 +- .../recognition-result.component.html | 61 ++----- .../recognition-result.component.scss | 8 - .../recognition-result.component.ts | 101 +++++------- .../face-services/face-services.helpers.ts | 13 +- .../face-services/face-services.module.ts | 6 +- ...face-verification-container.component.html | 16 ++ .../face-verification-container.component.ts | 5 +- .../verification-result.component.html | 67 ++++---- .../verification-result.component.scss | 8 - .../verification-result.component.ts | 152 +++++++----------- .../user-table/user-table.component.html | 2 +- .../test-model/test-model.component.html | 2 +- .../pages/test-model/test-model.component.ts | 2 +- .../pages/test-model/test-model.service.ts | 3 +- ui/src/app/store/face-recognition/reducers.ts | 4 +- ui/src/assets/dnd/tmp-upload-file.svg | 7 - ui/src/styles/custom-theme.scss | 39 ++++- ui/src/styles/frames.scss | 87 ---------- 31 files changed, 578 insertions(+), 396 deletions(-) rename ui/src/{styles/photo-spiner.scss => app/data/interfaces/image-convert.ts} (71%) create mode 100644 ui/src/app/features/face-services/face-picture/face-picture.component.html create mode 100644 ui/src/app/features/face-services/face-picture/face-picture.component.scss create mode 100644 ui/src/app/features/face-services/face-picture/face-picture.component.ts rename ui/src/app/features/face-services/{ => face-picture}/face-services.directive.ts (95%) delete mode 100644 ui/src/assets/dnd/tmp-upload-file.svg delete mode 100644 ui/src/styles/frames.scss diff --git a/ui/src/app/core/custom-icons/custom-icons.ts b/ui/src/app/core/custom-icons/custom-icons.ts index 8bd55dd0eb..4f2761890c 100644 --- a/ui/src/app/core/custom-icons/custom-icons.ts +++ b/ui/src/app/core/custom-icons/custom-icons.ts @@ -27,7 +27,6 @@ export enum Icons { Profile = 'profile', Search = 'search', Trash = 'trash', - TrashNotActive = 'trash_not_active', Upload = 'upload', Warning = 'warning', } diff --git a/ui/src/app/core/face-recognition/face-recognition.service.ts b/ui/src/app/core/face-recognition/face-recognition.service.ts index 7e2f0c3f3a..30fa94fe48 100644 --- a/ui/src/app/core/face-recognition/face-recognition.service.ts +++ b/ui/src/app/core/face-recognition/face-recognition.service.ts @@ -94,6 +94,8 @@ export class FaceRecognitionService { return this.http .post(url, formData, { headers: { 'x-api-key': apiKey }, + // eslint-disable-next-line @typescript-eslint/naming-convention + params: { face_plugins: ['landmarks', 'gender', 'age'] }, }) .pipe( map(data => data as { result: RequestResultVerification }), diff --git a/ui/src/styles/photo-spiner.scss b/ui/src/app/data/interfaces/image-convert.ts similarity index 71% rename from ui/src/styles/photo-spiner.scss rename to ui/src/app/data/interfaces/image-convert.ts index c4059529b6..2d67240fcc 100644 --- a/ui/src/styles/photo-spiner.scss +++ b/ui/src/app/data/interfaces/image-convert.ts @@ -1,4 +1,4 @@ -/*! +/* * Copyright (c) 2020 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,19 +13,9 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ -@import "mixins.scss"; -@import "colors.scss"; +import { ImageSize } from './image'; -@mixin photo-spinner() { - position: absolute; - background-color: $light-gray; - top: 0; - left: 0; - right: 0; - bottom: 0; - z-index: 5; - - app-spinner { - @include wrapper-center(); - } +export interface ImageConvert { + imageBitmap: ImageBitmap; + sizeCanvas: ImageSize; } diff --git a/ui/src/app/data/interfaces/response-result.ts b/ui/src/app/data/interfaces/response-result.ts index 60a58e3173..ef66a02558 100644 --- a/ui/src/app/data/interfaces/response-result.ts +++ b/ui/src/app/data/interfaces/response-result.ts @@ -37,12 +37,18 @@ export interface RequestResultRecognition { } export interface SourceImageFace { + age: number[]; + gender: 'male' | 'female'; box: BoxSize; + landmarks: [number[]]; } export interface FaceMatches { + age: number[]; + gender: 'male' | 'female'; box: BoxSize; similarity: number; + landmarks: [number[]]; } export interface RequestResultVerification { diff --git a/ui/src/app/features/app-change-photo/app-change-photo.component.html b/ui/src/app/features/app-change-photo/app-change-photo.component.html index 7c8229e84f..4d5ccbf77d 100644 --- a/ui/src/app/features/app-change-photo/app-change-photo.component.html +++ b/ui/src/app/features/app-change-photo/app-change-photo.component.html @@ -15,16 +15,31 @@ -->
- - -
diff --git a/ui/src/app/features/app-change-photo/app-change-photo.component.scss b/ui/src/app/features/app-change-photo/app-change-photo.component.scss index 7d0c296bd4..0bb019e06b 100644 --- a/ui/src/app/features/app-change-photo/app-change-photo.component.scss +++ b/ui/src/app/features/app-change-photo/app-change-photo.component.scss @@ -40,6 +40,12 @@ min-width: auto; background: $transparent-black; + &.landmarks-active { + svg path { + fill: $special-dark-gray !important; + } + } + &:last-child { margin-right: 0; } diff --git a/ui/src/app/features/app-change-photo/app-change-photo.component.ts b/ui/src/app/features/app-change-photo/app-change-photo.component.ts index b7de3e51d7..86cdc54423 100644 --- a/ui/src/app/features/app-change-photo/app-change-photo.component.ts +++ b/ui/src/app/features/app-change-photo/app-change-photo.component.ts @@ -23,13 +23,12 @@ import { ChangeDetectionStrategy, Component, Output, EventEmitter, ViewChild, El }) export class AppChangePhotoComponent { @Input() disabledButtons: boolean; + @Output() changePhoto = new EventEmitter(); @Output() resetPhoto = new EventEmitter(); @Output() addLandmark = new EventEmitter(); @ViewChild('uploadFile') fileDropEl: ElementRef; - addFile(event) { - console.log(event); - } + showLandmarks = false; } diff --git a/ui/src/app/features/face-services/face-landmarks/face-landmarks.component.ts b/ui/src/app/features/face-services/face-landmarks/face-landmarks.component.ts index 0a63086751..b06185b58e 100644 --- a/ui/src/app/features/face-services/face-landmarks/face-landmarks.component.ts +++ b/ui/src/app/features/face-services/face-landmarks/face-landmarks.component.ts @@ -13,19 +13,51 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ -import { Component, ElementRef, Input, OnChanges, SimpleChanges } from '@angular/core'; -import { recalculateLandmarks } from '../face-services.helpers'; +import { AfterViewInit, Component, ElementRef, Input, ViewChild } from '@angular/core'; + +import { FaceMatches, RequestResultRecognition, SourceImageFace } from '../../../data/interfaces/response-result'; @Component({ selector: 'app-face-landmarks', - template: '', + template: '', styles: ['canvas { position: absolute; top: 0; left: 0 }'], }) -export class FaceLandmarksComponent { - @Input() sizes: HTMLCanvasElement; - @Input() landmarks: [number[]]; +export class FaceLandmarksComponent implements AfterViewInit { + @Input() parentCanvas: HTMLCanvasElement; + @Input() printData: RequestResultRecognition[] | FaceMatches[] | SourceImageFace[]; + + @ViewChild('canvasElement', { static: false }) canvas: ElementRef; + + private ctx: CanvasRenderingContext2D; + private colorLandmarks = '#27C224'; + + ngAfterViewInit(): void { + this.ctx = this.canvas.nativeElement.getContext('2d'); + this.ctx.fillStyle = this.colorLandmarks; + this.getLandmarks(this.printData); + } + + getLandmarks(data: RequestResultRecognition[] | FaceMatches[] | SourceImageFace[]): void { + if (!data) return; + + data.forEach(val => this.displayLandmarks(val)); + } + + displayLandmarks(data: RequestResultRecognition | FaceMatches | SourceImageFace): void { + // eslint-disable-next-line @typescript-eslint/naming-convention + const { x_max, x_min, y_max, y_min } = data.box; + const frameArea: number = Math.round(x_max - x_min) * Math.round(y_max - y_min); + const px = frameArea / 3000 / 10 > 1.4 || data.landmarks.length >= 108 ? frameArea / 3000 / 10 : 1.4; - private recalculateLandmarks: [number[]]; + data.landmarks.forEach(val => this.drawLandmarks(val, this.ctx, this.colorLandmarks, px)); + } - constructor() {} + drawLandmarks(box: number[], ctx: CanvasRenderingContext2D, color: string, sizePoint: number) { + ctx.beginPath(); + ctx.strokeStyle = color; + ctx.arc(box[0], box[1], sizePoint, 0, Math.PI * 2, false); + ctx.fill(); + ctx.closePath(); + ctx.stroke(); + } } diff --git a/ui/src/app/features/face-services/face-picture/face-picture.component.html b/ui/src/app/features/face-services/face-picture/face-picture.component.html new file mode 100644 index 0000000000..1e9435b49f --- /dev/null +++ b/ui/src/app/features/face-services/face-picture/face-picture.component.html @@ -0,0 +1,58 @@ + +
+
+ +
+ +
+ + + + + + +
+
+ +
+ + +
{{ value.subject }}
+
Similarity: {{ value.similarity | number : '1.0-2' }}
+
+ + +
Unknown
+
+
+ + +
{{ face.box.probability | number : '1.0-2' }}
+
+ + +
Similarity: {{ face.similarity | number : '1.0-2' }}
+
+ +
{{ face.gender | titlecase }}, Age: {{ face.age[0] + '-' + face.age[1] }}
+
+
+
+
+ +
+
diff --git a/ui/src/app/features/face-services/face-picture/face-picture.component.scss b/ui/src/app/features/face-services/face-picture/face-picture.component.scss new file mode 100644 index 0000000000..aea7b75543 --- /dev/null +++ b/ui/src/app/features/face-services/face-picture/face-picture.component.scss @@ -0,0 +1,113 @@ +/*! + * Copyright (c) 2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +@import "colors.scss"; +@import "mixins.scss"; + +.face-displaying { + display: flex; + justify-content: center; + + &--wrapper { + position: relative; + line-height: 0; + + .spinner { + position: absolute; + background-color: $light-gray; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 5; + + app-spinner { + @include wrapper-center(); + } + } + + .canvas-landmarks { + position: absolute; + left: 0; + } + + .face-box { + position: absolute; + display: flex; + flex-direction: column; + + &--face { + width: 100%; + min-height: 100%; + box-sizing: border-box; + border: 4px solid; + border-radius: 12px; + border-color: $transparent-gray; + + &.active-frame { + border-color: $light-blue; + } + } + + &--info { + position: relative; + margin-top: 18px; + padding: 8px 20px; + max-width: 170px; + width: fit-content; + background-color: $light-blue; + border-radius: 6px; + color: $white; + left: 50%; + transform: translate(-50%, 0); + line-height: 21px; + + &:before { + position: absolute; + content: ''; + top: 8px; + left: 50%; + width: 24px; + height: 24px; + background: $light-blue; + transform: translate(-50%, -50%) rotate(45deg); + z-index: -1; + } + + &_name { + @include not-break-string; + margin-bottom: 4px; + font-weight: 600; + font-size: 16px; + line-height: 22px; + } + + &_similarity { + @include not-break-string; + margin-bottom: 6px; + font-weight: normal; + font-size: 14px; + line-height: 14px; + } + + &_gender { + @include not-break-string; + font-weight: 500; + font-size: 14px; + } + } + } + } +} diff --git a/ui/src/app/features/face-services/face-picture/face-picture.component.ts b/ui/src/app/features/face-services/face-picture/face-picture.component.ts new file mode 100644 index 0000000000..0d8af15cf4 --- /dev/null +++ b/ui/src/app/features/face-services/face-picture/face-picture.component.ts @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +import { ChangeDetectionStrategy, Component, ElementRef, Input, OnChanges, OnInit, ViewChild } from '@angular/core'; +import { ServiceTypes } from '../../../data/enums/service-types.enum'; +import { FaceMatches, RequestResultRecognition, SourceImageFace } from '../../../data/interfaces/response-result'; +import { ImageConvert } from '../../../data/interfaces/image-convert'; + +@Component({ + selector: 'app-face-picture', + templateUrl: './face-picture.component.html', + styleUrls: ['./face-picture.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class FacePictureComponent implements OnChanges, OnInit { + @Input() picture: ImageConvert; + @Input() printData: RequestResultRecognition[] | FaceMatches[] | SourceImageFace[]; + @Input() isLoaded: boolean; + @Input() type: ServiceTypes; + @Input() + set showLandmarks(value: boolean) { + this.disableLandmarks = value; + } + + @ViewChild('canvasPicture', { static: true }) canvasPicture: ElementRef; + @ViewChild('canvasLandmarks', { static: true }) canvasLandmarks: ElementRef; + + types = ServiceTypes; + disableLandmarks = false; + + private currentPicture: () => any; + private colorLandmarks = '#27C224'; + + ngOnInit(): void { + this.initCanvasSize(); + this.currentPicture = this.loadPicture(this.canvasPicture, this.picture, this.currentPicture); + } + + ngOnChanges(): void { + this.initCanvasSize(); + if (!!this.currentPicture) this.currentPicture = this.loadPicture(this.canvasPicture, this.picture, this.currentPicture); + this.getLandmarks(this.canvasLandmarks, this.printData, this.colorLandmarks); + } + + initCanvasSize(): void { + this.canvasPicture.nativeElement.setAttribute('height', String(this.picture.sizeCanvas.height)); + this.canvasPicture.nativeElement.setAttribute('width', String(this.picture.sizeCanvas.width)); + + this.canvasLandmarks.nativeElement.setAttribute('height', String(this.picture.sizeCanvas.height)); + this.canvasLandmarks.nativeElement.setAttribute('width', String(this.picture.sizeCanvas.width)); + } + + loadPicture(canvasEl: ElementRef, picture, currentPicture): () => any { + if (!!currentPicture) currentPicture(); + + const { imageBitmap, sizeCanvas } = picture; + const ctx: CanvasRenderingContext2D = canvasEl.nativeElement.getContext('2d'); + + ctx.drawImage(imageBitmap, 0, 0, sizeCanvas.width, sizeCanvas.height); + + return () => ctx.clearRect(0, 0, sizeCanvas.width, sizeCanvas.height); + } + + getLandmarks( + canvasEl: ElementRef, + data: RequestResultRecognition[] | FaceMatches[] | SourceImageFace[], + color: string + ): void { + if (!data) return; + + const ctx: CanvasRenderingContext2D = canvasEl.nativeElement.getContext('2d'); + ctx.fillStyle = color; + + data.forEach(val => this.displayLandmarks(ctx, val, color)); + } + + displayLandmarks(ctx: CanvasRenderingContext2D, data: RequestResultRecognition | FaceMatches | SourceImageFace, color: string): void { + // eslint-disable-next-line @typescript-eslint/naming-convention + const { x_max, x_min, y_max, y_min } = data.box; + const frameArea: number = Math.round(x_max - x_min) * Math.round(y_max - y_min); + const sizePoint = frameArea / 3000 / 10 > 1.2 || data.landmarks.length >= 108 ? frameArea / 3000 / 10 : 1.2; + + data.landmarks.forEach(landmark => { + ctx.beginPath(); + ctx.strokeStyle = color; + ctx.arc(landmark[0], landmark[1], sizePoint, 0, Math.PI * 2, false); + ctx.fill(); + ctx.closePath(); + ctx.stroke(); + }); + } +} diff --git a/ui/src/app/features/face-services/face-services.directive.ts b/ui/src/app/features/face-services/face-picture/face-services.directive.ts similarity index 95% rename from ui/src/app/features/face-services/face-services.directive.ts rename to ui/src/app/features/face-services/face-picture/face-services.directive.ts index 75d5550553..cbbd0bca4c 100644 --- a/ui/src/app/features/face-services/face-services.directive.ts +++ b/ui/src/app/features/face-services/face-picture/face-services.directive.ts @@ -25,8 +25,8 @@ import { SimpleChanges, } from '@angular/core'; -import { FrameSize } from '../../data/interfaces/frame-size'; -import { BoxSize } from '../../data/interfaces/response-result'; +import { FrameSize } from '../../../data/interfaces/frame-size'; +import { BoxSize } from '../../../data/interfaces/response-result'; @Directive({ selector: '[appFrameTooltip]', diff --git a/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.ts b/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.ts index 3f151d3a0b..8c41a63594 100644 --- a/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.ts +++ b/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.ts @@ -29,6 +29,7 @@ import { } from '../../../store/face-recognition/selectors'; import { getFileExtension } from '../face-services.helpers'; import { SnackBarService } from '../../snackbar/snackbar.service'; +import { ServiceTypes } from '../../../data/enums/service-types.enum'; @Component({ selector: 'app-face-recognition-container', @@ -43,7 +44,7 @@ export class FaceRecognitionContainerComponent implements OnInit, OnDestroy { isLoaded$: Observable; @Input() title: string; - @Input() type: string; + @Input() type: ServiceTypes; constructor(private store: Store, private snackBarService: SnackBarService) {} diff --git a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.html b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.html index 3e66972b34..a100ff0a34 100644 --- a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.html +++ b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.html @@ -19,62 +19,25 @@
- + -
-
- - - - - - - - - -
-
-
- - - - -
{{ value.subject }}
-
{{ value.similarity }}
-
- - -
Unknown
-
- -
- - -
{{ frame.box.probability }}
-
- -
-
- -
- -
- -
- -
- -
-
+ +
diff --git a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.scss b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.scss index 8d02bd9369..ad84f3a499 100644 --- a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.scss +++ b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.scss @@ -14,11 +14,7 @@ * permissions and limitations under the License. */ @import "media.scss"; -@import "frames.scss"; @import "mixins.scss"; -@import "photo-spiner.scss"; - -@include frames(); .result { margin-top: 24px; @@ -45,10 +41,6 @@ .process-img { grid-area: process-img; text-align: center; - - &--spinner { - @include photo-spinner(); - } } .request { diff --git a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.ts b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.ts index b41462ae56..a9f0305aaf 100644 --- a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.ts +++ b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.ts @@ -13,22 +13,10 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ -import { - Component, - ElementRef, - Input, - ViewChild, - OnChanges, - SimpleChanges, - Output, - EventEmitter, - AfterViewInit, - OnInit, - OnDestroy, -} from '@angular/core'; +import { Component, Input, OnChanges, SimpleChanges, Output, EventEmitter, OnInit, ChangeDetectionStrategy } from '@angular/core'; -import { filter, map, switchMap, takeUntil, tap } from 'rxjs/operators'; -import { BehaviorSubject, Observable, Subject } from 'rxjs'; +import { filter, map, switchMap, tap } from 'rxjs/operators'; +import { BehaviorSubject, defer, Observable, of } from 'rxjs'; import { RequestResultRecognition } from '../../../../data/interfaces/response-result'; import { RequestInfo } from '../../../../data/interfaces/request-info'; @@ -36,96 +24,85 @@ import { LoadingPhotoService } from '../../../../core/photo-loader/photo-loader. import { ServiceTypes } from '../../../../data/enums/service-types.enum'; import { ImageSize } from '../../../../data/interfaces/image'; import { recalculateFaceCoordinate, recalculateLandmarks, resultRecognitionFormatter } from '../../face-services.helpers'; +import { ImageConvert } from '../../../../data/interfaces/image-convert'; @Component({ selector: 'app-recognition-result', templateUrl: './recognition-result.component.html', styleUrls: ['./recognition-result.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, }) -export class RecognitionResultComponent implements OnInit, OnChanges, OnDestroy, AfterViewInit { +export class RecognitionResultComponent implements OnInit, OnChanges { @Input() file: File; @Input() requestInfo: RequestInfo; @Input() isLoaded: boolean; - @Input() type: string; + @Input() type: ServiceTypes; @Input() printData: RequestResultRecognition[]; @Output() selectFile = new EventEmitter(); @Output() resetFace = new EventEmitter(); - @ViewChild('canvasElement', { static: true }) canvas: ElementRef; + dataPrintRecalculate$: BehaviorSubject = new BehaviorSubject(null); + convertFile$: BehaviorSubject = new BehaviorSubject(null); - private ctx: CanvasRenderingContext2D; - private dataPrint$: BehaviorSubject = new BehaviorSubject(null); - private dataPhoto$: BehaviorSubject = new BehaviorSubject(null); - private unsubscribe$: Subject = new Subject(); + picture$: Observable; + printData$: Observable; formattedResult: string; widthCanvas = 500; - types = ServiceTypes; - recalculatePrint: RequestResultRecognition[]; constructor(private loadingPhotoService: LoadingPhotoService) {} ngOnInit(): void { - this.dataPhoto$ - .pipe( - takeUntil(this.unsubscribe$), - filter(data => !!data), - switchMap(data => this.displayPhoto(data)), - switchMap(sizes => this.dataPrint$.pipe(switchMap(printData => this.displayFrames(printData, sizes.imageBitmap, sizes.sizeCanvas)))) - ) - .subscribe(value => (this.recalculatePrint = value)); - } + this.picture$ = this.convertFile$.pipe( + filter(file => !!file), + switchMap(file => this.displayPhotoConvert(file)) + ); - ngAfterViewInit(): void { - this.ctx = this.canvas.nativeElement.getContext('2d'); + this.printData$ = this.dataPrintRecalculate$.pipe( + switchMap(printData => + defer(() => + !!printData + ? this.picture$.pipe( + tap(data => console.log(data)), + switchMap(sizes => this.printDataRecalculate(printData, sizes.imageBitmap, sizes.sizeCanvas)) + ) + : of(null) + ) + ) + ); } ngOnChanges(changes: SimpleChanges): void { - console.log(this.printData); - this.dataPhoto$.next(this.file); - this.dataPrint$.next(this.printData); + if (changes.hasOwnProperty('file')) this.convertFile$.next(this.file); + if (changes.hasOwnProperty('printData')) this.dataPrintRecalculate$.next(this.printData); + if (!!this.requestInfo) this.formattedResult = resultRecognitionFormatter(this.requestInfo.response); } - displayPhoto(file: File): Observable { + displayPhotoConvert(file: File): Observable { return this.loadingPhotoService.loader(file).pipe( map(img => ({ imageBitmap: img, sizeCanvas: { width: this.widthCanvas, height: (img.height / img.width) * this.widthCanvas }, - })), - tap(({ sizeCanvas }) => this.canvas.nativeElement.setAttribute('height', String(sizeCanvas.height))), - tap(({ imageBitmap, sizeCanvas }) => this.ctx.drawImage(imageBitmap, 0, 0, sizeCanvas.width, sizeCanvas.height)) + })) ); } - displayFrames(data: Type, sizeImage: ImageSize, sizeCanvas: ImageSize): Observable { + printDataRecalculate(data: Type, sizeImage: ImageSize, sizeCanvas: ImageSize): Observable { return new Observable(observer => { - if (!!data) { - const recalculate = data.map(val => ({ - ...val, - box: recalculateFaceCoordinate(val.box, sizeImage, sizeCanvas), - landmarks: recalculateLandmarks(val.landmarks, sizeImage, sizeCanvas), - })) as Type; - observer.next(recalculate); - } else { - observer.next(null); - } + const recalculate = data.map(val => ({ + ...val, + box: recalculateFaceCoordinate(val.box, sizeImage, sizeCanvas), + landmarks: recalculateLandmarks(val.landmarks, sizeImage, sizeCanvas), + })) as Type; + observer.next(recalculate); observer.complete(); }); } onResetFile(event?: File): void { - const { offsetHeight, offsetWidth } = this.ctx.canvas; - this.ctx.clearRect(0, 0, offsetWidth, offsetHeight); - this.resetFace.emit(); - if (event) this.selectFile.emit(event); } - - ngOnDestroy(): void { - this.unsubscribe$.next(); - this.unsubscribe$.complete(); - } } diff --git a/ui/src/app/features/face-services/face-services.helpers.ts b/ui/src/app/features/face-services/face-services.helpers.ts index b6131c3bab..4554a3fe7e 100644 --- a/ui/src/app/features/face-services/face-services.helpers.ts +++ b/ui/src/app/features/face-services/face-services.helpers.ts @@ -83,13 +83,18 @@ export const recalculateFaceCoordinate = (box: any, imageSize: ImageSize, sizeTo }; /** - * Recalculate face coordinates according to canvas size (design). + * Recalculate face coordinates landmarks to canvas size (design). * - * @param landmarks Face coordinates from BE. + * @param landmarks Landmarks coordinates from BE. * @param imageSize Size of image. * @param sizeToCalc Size of image. */ export const recalculateLandmarks = (landmarks: [number[]], imageSize: ImageSize, sizeToCalc: ImageSize) => { - console.log(landmarks); - return landmarks; + const divideWidth = imageSize.width / sizeToCalc.width; + const divideHeight = imageSize.height / sizeToCalc.height; + + return landmarks.map(val => [ + val[0] / divideWidth > sizeToCalc.width ? sizeToCalc.width : val[0] / divideWidth, + val[1] / divideHeight > sizeToCalc.height ? sizeToCalc.height : val[1] / divideHeight, + ]); }; diff --git a/ui/src/app/features/face-services/face-services.module.ts b/ui/src/app/features/face-services/face-services.module.ts index 05ce22d7ff..dcb4c2e17a 100644 --- a/ui/src/app/features/face-services/face-services.module.ts +++ b/ui/src/app/features/face-services/face-services.module.ts @@ -26,9 +26,9 @@ import { FaceRecognitionContainerComponent } from './face-recognition/face-recog import { FaceVerificationContainerComponent } from './face-verification/face-verification-container.component'; import { RecognitionResultComponent } from './face-recognition/recognition-result/recognition-result.component'; import { VerificationResultComponent } from './face-verification/verification-result/verification-result.component'; -import { FaceServicesDirective } from './face-services.directive'; +import { FaceServicesDirective } from './face-picture/face-services.directive'; import { AppChangePhotoModule } from '../app-change-photo/app-change-photo.module'; -import { FaceLandmarksComponent } from './face-landmarks/face-landmarks.component'; +import { FacePictureComponent } from './face-picture/face-picture.component'; @NgModule({ declarations: [ @@ -36,7 +36,7 @@ import { FaceLandmarksComponent } from './face-landmarks/face-landmarks.componen FaceVerificationContainerComponent, RecognitionResultComponent, VerificationResultComponent, - FaceLandmarksComponent, + FacePictureComponent, FaceServicesDirective, ], imports: [CommonModule, DragNDropModule, SpinnerModule, MatExpansionModule, TranslateModule, MatCardModule, AppChangePhotoModule], diff --git a/ui/src/app/features/face-services/face-verification/face-verification-container.component.html b/ui/src/app/features/face-services/face-verification/face-verification-container.component.html index add8737bee..570ed019e3 100644 --- a/ui/src/app/features/face-services/face-verification/face-verification-container.component.html +++ b/ui/src/app/features/face-services/face-verification/face-verification-container.component.html @@ -1,3 +1,18 @@ +
; isLoaded$: Observable; + @Input() type: ServiceTypes; + constructor(private store: Store, private snackBarService: SnackBarService) {} ngOnInit() { diff --git a/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.html b/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.html index 6556cde22c..d899d4db8d 100644 --- a/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.html +++ b/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.html @@ -15,64 +15,69 @@ -->
+ - + + + -
-
- - -
-
-
-
-
- -
-
-
+ + + +
+
+ - + + + -
-
- - -
-
-
-
{{ frame.similarity }}
-
-
-
-
- -
-
-
+ + + +
+
+
diff --git a/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.scss b/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.scss index 139ca6bfb2..7f86395e95 100644 --- a/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.scss +++ b/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.scss @@ -14,10 +14,6 @@ * permissions and limitations under the License. */ @import "media.scss"; -@import "frames.scss"; -@import "photo-spiner"; - -@include frames(); .result { display: grid; @@ -55,8 +51,4 @@ grid-area: response; min-width: 100%; } - - .spinner-img { - @include photo-spinner(); - } } diff --git a/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.ts b/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.ts index 3edf3030bf..a37194a4d1 100644 --- a/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.ts +++ b/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.ts @@ -13,161 +13,125 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ -import { - Component, - Input, - OnChanges, - SimpleChanges, - Output, - EventEmitter, - ViewChild, - ElementRef, - AfterViewInit, - OnInit, - OnDestroy, -} from '@angular/core'; - -import { BehaviorSubject, Observable, Subject } from 'rxjs'; -import { filter, map, switchMap, takeUntil, tap } from 'rxjs/operators'; +import { Component, Input, OnChanges, Output, EventEmitter, OnInit, SimpleChanges } from '@angular/core'; + +import { BehaviorSubject, defer, Observable, of } from 'rxjs'; +import { filter, map, switchMap } from 'rxjs/operators'; import { FaceMatches, RequestResultVerification, SourceImageFace } from '../../../../data/interfaces/response-result'; import { RequestInfo } from '../../../../data/interfaces/request-info'; import { LoadingPhotoService } from '../../../../core/photo-loader/photo-loader.service'; import { ImageSize } from '../../../../data/interfaces/image'; -import { recalculateFaceCoordinate, resultRecognitionFormatter } from '../../face-services.helpers'; +import { recalculateFaceCoordinate, recalculateLandmarks, resultRecognitionFormatter } from '../../face-services.helpers'; import { VerificationServiceFields } from '../../../../data/enums/verification-service.enum'; +import { ImageConvert } from '../../../../data/interfaces/image-convert'; +import { ServiceTypes } from '../../../../data/enums/service-types.enum'; @Component({ selector: 'app-verification-result', templateUrl: './verification-result.component.html', styleUrls: ['./verification-result.component.scss'], }) -export class VerificationResultComponent implements OnInit, OnChanges, AfterViewInit, OnDestroy { +export class VerificationResultComponent implements OnChanges, OnInit { @Input() processFile: File; @Input() checkFile: File; @Input() requestInfo: RequestInfo; @Input() printData: RequestResultVerification[]; @Input() isLoaded: boolean; @Input() pending: boolean; + @Input() type: ServiceTypes; @Output() selectProcessFile = new EventEmitter(); @Output() selectCheckFile = new EventEmitter(); @Output() resetProcessFile = new EventEmitter(); @Output() resetCheckFile = new EventEmitter(); - @ViewChild('processFileCanvasElement', { static: true }) canvasProcessFile: ElementRef; - @ViewChild('checkFileCanvasElement', { static: true }) canvasCheckFile: ElementRef; - - private ctxPhotoProcess: CanvasRenderingContext2D; - private photoProcess$: BehaviorSubject = new BehaviorSubject(null); - - private ctxPhotoCheck: CanvasRenderingContext2D; - private photoCheck$: BehaviorSubject = new BehaviorSubject(null); - - private unsubscribe$: Subject = new Subject(); - private printData$: BehaviorSubject = new BehaviorSubject(null); + private processFileConvert$: BehaviorSubject = new BehaviorSubject(null); + private checkFileConvert$: BehaviorSubject = new BehaviorSubject(null); + private dataPrintRecalculate$: BehaviorSubject = new BehaviorSubject(null); + private verificationServiceFields = VerificationServiceFields; + processPicture$: Observable; + checkPicture$: Observable; + processFileData$: Observable; + checkFileData$: Observable; widthCanvas = 500; formattedResult: string; - recalculateProcessFile: SourceImageFace[]; - recalculateCheckFile: FaceMatches[]; constructor(private loadingPhotoService: LoadingPhotoService) {} - ngAfterViewInit(): void { - this.ctxPhotoProcess = this.canvasProcessFile.nativeElement.getContext('2d'); - this.ctxPhotoCheck = this.canvasCheckFile.nativeElement.getContext('2d'); - } - ngOnInit(): void { - this.photoProcess$ - .pipe( - takeUntil(this.unsubscribe$), - filter(data => !!data), - switchMap(data => this.displayPhoto(data, this.canvasProcessFile, this.ctxPhotoProcess)), - switchMap(sizes => this.displayFrames(VerificationServiceFields.ProcessFileData, sizes)), - map(data => data as SourceImageFace[]) + this.processPicture$ = this.processFileConvert$.pipe( + filter(file => !!file), + switchMap(file => this.displayPhotoConvert(file)) + ); + + this.processFileData$ = this.dataPrintRecalculate$.pipe( + map(data => (data ? [data[0][this.verificationServiceFields.ProcessFileData]] : null)), + switchMap(printData => + defer(() => + !!printData + ? this.processPicture$.pipe(switchMap(sizes => this.printDataRecalculate(printData, sizes.imageBitmap, sizes.sizeCanvas))) + : of(null) + ) ) - .subscribe(value => (this.recalculateProcessFile = value)); - - this.photoCheck$ - .pipe( - takeUntil(this.unsubscribe$), - filter(data => !!data), - switchMap(data => this.displayPhoto(data, this.canvasCheckFile, this.ctxPhotoCheck)), - switchMap(sizes => this.displayFrames(VerificationServiceFields.CheckFileData, sizes)), - map(data => data as FaceMatches[]) + ); + + this.checkPicture$ = this.checkFileConvert$.pipe( + filter(file => !!file), + switchMap(file => this.displayPhotoConvert(file)) + ); + + this.checkFileData$ = this.dataPrintRecalculate$.pipe( + map(data => (data ? data[0][this.verificationServiceFields.CheckFileData] : null)), + switchMap(printData => + defer(() => + !!printData + ? this.checkPicture$.pipe(switchMap(sizes => this.printDataRecalculate(printData, sizes.imageBitmap, sizes.sizeCanvas))) + : of(null) + ) ) - .subscribe(value => (this.recalculateCheckFile = value)); + ); } ngOnChanges(changes: SimpleChanges): void { - this.photoProcess$.next(this.processFile); - this.photoCheck$.next(this.checkFile); - this.printData$.next(this.printData); + if (changes.hasOwnProperty('processFile')) this.processFileConvert$.next(this.processFile); + if (changes.hasOwnProperty('checkFile')) this.checkFileConvert$.next(this.checkFile); + if (changes.hasOwnProperty('printData')) this.dataPrintRecalculate$.next(this.printData); if (!!this.requestInfo) this.formattedResult = resultRecognitionFormatter(this.requestInfo.response); } - recalculateFrames(data: Type, sizeImage: ImageSize, sizeCanvas: ImageSize): Observable { + printDataRecalculate(data: Type, sizeImage: ImageSize, sizeCanvas: ImageSize): Observable { return new Observable(observer => { - if (!!data) { - const recalculate = data.map(val => ({ ...val, box: recalculateFaceCoordinate(val.box, sizeImage, sizeCanvas) })) as Type; - observer.next(recalculate); - } else { - observer.next(null); - } + const recalculate = data.map(val => ({ + ...val, + box: recalculateFaceCoordinate(val.box, sizeImage, sizeCanvas), + landmarks: recalculateLandmarks(val.landmarks, sizeImage, sizeCanvas), + })) as Type; + observer.next(recalculate); observer.complete(); }); } - getDataFrames(type: VerificationServiceFields, result: RequestResultVerification[]): SourceImageFace[] | FaceMatches[] { - switch (type) { - case VerificationServiceFields.CheckFileData: - return result[0][VerificationServiceFields.CheckFileData] as SourceImageFace[]; - case VerificationServiceFields.ProcessFileData: - return [result[0][VerificationServiceFields.ProcessFileData]] as FaceMatches[]; - } - } - - displayFrames(type: VerificationServiceFields, sizes): Observable { - return this.printData$.pipe( - map(data => (!!data ? this.getDataFrames(type, data) : null)), - switchMap(printData => this.recalculateFrames(printData, sizes.imageBitmap, sizes.sizeCanvas)) - ); - } - - displayPhoto(file: File, el: ElementRef, ctx: CanvasRenderingContext2D): Observable { + displayPhotoConvert(file: File): Observable { return this.loadingPhotoService.loader(file).pipe( map(img => ({ imageBitmap: img, sizeCanvas: { width: this.widthCanvas, height: (img.height / img.width) * this.widthCanvas }, - })), - tap(({ sizeCanvas }) => el.nativeElement.setAttribute('height', String(sizeCanvas.height))), - tap(({ imageBitmap, sizeCanvas }) => ctx.drawImage(imageBitmap, 0, 0, sizeCanvas.width, sizeCanvas.height)) + })) ); } onResetProcessFile(event?: File): void { - const { offsetHeight, offsetWidth } = this.ctxPhotoProcess.canvas; - this.ctxPhotoProcess.clearRect(0, 0, offsetWidth, offsetHeight); - this.resetProcessFile.emit(); if (event) this.resetProcessFile.emit(event); } onResetCheckFile(event?: File): void { - const { offsetHeight, offsetWidth } = this.ctxPhotoCheck.canvas; - this.ctxPhotoCheck.clearRect(0, 0, offsetWidth, offsetHeight); - this.resetCheckFile.emit(); if (event) this.resetCheckFile.emit(event); } - - ngOnDestroy(): void { - this.unsubscribe$.next(); - this.unsubscribe$.complete(); - } } diff --git a/ui/src/app/features/user-table/user-table.component.html b/ui/src/app/features/user-table/user-table.component.html index 604653bc11..ca019bc202 100644 --- a/ui/src/app/features/user-table/user-table.component.html +++ b/ui/src/app/features/user-table/user-table.component.html @@ -47,7 +47,7 @@ diff --git a/ui/src/app/pages/test-model/test-model.component.html b/ui/src/app/pages/test-model/test-model.component.html index 78d830ce14..879123cdbb 100644 --- a/ui/src/app/pages/test-model/test-model.component.html +++ b/ui/src/app/pages/test-model/test-model.component.html @@ -18,7 +18,7 @@
- +
diff --git a/ui/src/app/pages/test-model/test-model.component.ts b/ui/src/app/pages/test-model/test-model.component.ts index dd27d6b10d..d345cb0569 100644 --- a/ui/src/app/pages/test-model/test-model.component.ts +++ b/ui/src/app/pages/test-model/test-model.component.ts @@ -28,7 +28,7 @@ import { ServiceTypes } from '../../data/enums/service-types.enum'; }) export class TestModelComponent implements OnInit, OnDestroy { modelLoading$: Observable; - type: string; + type: ServiceTypes; verification: string = ServiceTypes.Verification; constructor(private modelService: TestModelPageService, private store: Store) {} diff --git a/ui/src/app/pages/test-model/test-model.service.ts b/ui/src/app/pages/test-model/test-model.service.ts index 5bd20365e9..374a243055 100644 --- a/ui/src/app/pages/test-model/test-model.service.ts +++ b/ui/src/app/pages/test-model/test-model.service.ts @@ -25,6 +25,7 @@ import { loadApplications, setSelectedAppIdEntityAction } from '../../store/appl import { loadModels, setSelectedModelIdEntityAction } from '../../store/model/action'; import { selectModels } from '../../store/model/selectors'; import { getUserInfo } from '../../store/userInfo/action'; +import { ServiceTypes } from '../../data/enums/service-types.enum'; @Injectable() export class TestModelPageService { @@ -32,7 +33,7 @@ export class TestModelPageService { private modelSub: Subscription; private appId: string; private modelId: string; - private type: string; + private type: ServiceTypes; constructor(private router: Router, private route: ActivatedRoute, private store: Store) {} diff --git a/ui/src/app/store/face-recognition/reducers.ts b/ui/src/app/store/face-recognition/reducers.ts index e7a59a29ca..d453fcddd8 100644 --- a/ui/src/app/store/face-recognition/reducers.ts +++ b/ui/src/app/store/face-recognition/reducers.ts @@ -34,8 +34,8 @@ const reducer: ActionReducer = createReducer( initialState, on(recognizeFace, (state, action) => ({ ...state, ...action, isPending: true })), on(recognizeFaceSuccess, (state, action) => ({ ...state, ...action, isPending: false })), - on(recognizeFaceReset, () => ({ ...initialState })), - on(recognizeFaceFail, state => ({ ...state, isPending: false })) + on(recognizeFaceFail, state => ({ ...state, isPending: false })), + on(recognizeFaceReset, () => ({ ...initialState })) ); export const faceRecognitionReducer = (recognitionState: FaceRecognitionEntityState, action: Action) => reducer(recognitionState, action); diff --git a/ui/src/assets/dnd/tmp-upload-file.svg b/ui/src/assets/dnd/tmp-upload-file.svg deleted file mode 100644 index 31a6ef4216..0000000000 --- a/ui/src/assets/dnd/tmp-upload-file.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/ui/src/styles/custom-theme.scss b/ui/src/styles/custom-theme.scss index c16777e92e..a174f52cae 100644 --- a/ui/src/styles/custom-theme.scss +++ b/ui/src/styles/custom-theme.scss @@ -1,6 +1,21 @@ -@import '~@angular/material/theming'; -@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600&display=swap'); -@import 'colors.scss'; +/*! + * Copyright (c) 2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +@import "~@angular/material/theming"; +@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600&display=swap"); +@import "colors.scss"; // Include the common styles for Angular Material. We include this here so that you only // have to load a single css file for Angular Material in your app. @@ -351,6 +366,24 @@ $custom-theme: mat-light-theme($custom-theme-primary, $custom-theme-accent, $cus .mat-button-focus-overlay { background-color: transparent; } + + .mat-flat-button, + .mat-icon-button { + + &.mat-button-disabled { + background-color: transparent; + + svg path { + fill: $special-dark-gray !important; + } + } + + &.landmarks-active { + svg path:not([fill-rule]) { + fill: $green; + } + } + } } @include angular-material-typography($typography); diff --git a/ui/src/styles/frames.scss b/ui/src/styles/frames.scss deleted file mode 100644 index 28a7941ae7..0000000000 --- a/ui/src/styles/frames.scss +++ /dev/null @@ -1,87 +0,0 @@ -/*! -* Copyright (c) 2020 the original author or authors -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* https://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express -* or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ -@import "colors.scss"; -@import "mixins.scss"; - -@mixin frames() { - - .face-displaying { - display: flex; - justify-content: center; - - &--wrapper { - position: relative; - line-height: 0; - - .face-box { - position: absolute; - display: flex; - flex-direction: column; - - &--face { - width: 100%; - min-height: 100%; - box-sizing: border-box; - border: 4px solid; - border-radius: 12px; - border-color: $transparent-gray; - - &.active-frame { - border-color: $light-blue; - } - } - - &--info { - position: relative; - margin-top: 18px; - padding: 8px 20px; - max-width: 170px; - width: fit-content; - background-color: $light-blue; - border-radius: 6px; - color: $white; - left: 50%; - transform: translate(-50%, 0); - - &:before { - position: absolute; - content: ''; - top: 8px; - left: 50%; - width: 24px; - height: 24px; - background: $light-blue; - transform: translate(-50%, -50%) rotate(45deg); - z-index: -1; - } - - &_name { - @include not-break-string; - font-weight: 600; - font-size: 16px; - line-height: 22px; - } - - &_similarity { - @include not-break-string; - font-size: 14px; - line-height: 14px; - } - } - } - } - } -} From ec867fbdabfaac32c3133cdced3b57ecf0d63606 Mon Sep 17 00:00:00 2001 From: Pavel Bohdan Date: Mon, 7 Jun 2021 17:37:46 +0300 Subject: [PATCH 017/837] Added landmarks, refactor code. --- .../face-landmarks.component.ts | 63 ------------------- 1 file changed, 63 deletions(-) delete mode 100644 ui/src/app/features/face-services/face-landmarks/face-landmarks.component.ts diff --git a/ui/src/app/features/face-services/face-landmarks/face-landmarks.component.ts b/ui/src/app/features/face-services/face-landmarks/face-landmarks.component.ts deleted file mode 100644 index b06185b58e..0000000000 --- a/ui/src/app/features/face-services/face-landmarks/face-landmarks.component.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2020 the original author or authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ -import { AfterViewInit, Component, ElementRef, Input, ViewChild } from '@angular/core'; - -import { FaceMatches, RequestResultRecognition, SourceImageFace } from '../../../data/interfaces/response-result'; - -@Component({ - selector: 'app-face-landmarks', - template: '', - styles: ['canvas { position: absolute; top: 0; left: 0 }'], -}) -export class FaceLandmarksComponent implements AfterViewInit { - @Input() parentCanvas: HTMLCanvasElement; - @Input() printData: RequestResultRecognition[] | FaceMatches[] | SourceImageFace[]; - - @ViewChild('canvasElement', { static: false }) canvas: ElementRef; - - private ctx: CanvasRenderingContext2D; - private colorLandmarks = '#27C224'; - - ngAfterViewInit(): void { - this.ctx = this.canvas.nativeElement.getContext('2d'); - this.ctx.fillStyle = this.colorLandmarks; - this.getLandmarks(this.printData); - } - - getLandmarks(data: RequestResultRecognition[] | FaceMatches[] | SourceImageFace[]): void { - if (!data) return; - - data.forEach(val => this.displayLandmarks(val)); - } - - displayLandmarks(data: RequestResultRecognition | FaceMatches | SourceImageFace): void { - // eslint-disable-next-line @typescript-eslint/naming-convention - const { x_max, x_min, y_max, y_min } = data.box; - const frameArea: number = Math.round(x_max - x_min) * Math.round(y_max - y_min); - const px = frameArea / 3000 / 10 > 1.4 || data.landmarks.length >= 108 ? frameArea / 3000 / 10 : 1.4; - - data.landmarks.forEach(val => this.drawLandmarks(val, this.ctx, this.colorLandmarks, px)); - } - - drawLandmarks(box: number[], ctx: CanvasRenderingContext2D, color: string, sizePoint: number) { - ctx.beginPath(); - ctx.strokeStyle = color; - ctx.arc(box[0], box[1], sizePoint, 0, Math.PI * 2, false); - ctx.fill(); - ctx.closePath(); - ctx.stroke(); - } -} From 3b505bb50146ddb13d53673f2f2cd6be03d92e6d Mon Sep 17 00:00:00 2001 From: Pavel Bohdan Date: Tue, 8 Jun 2021 15:00:25 +0300 Subject: [PATCH 018/837] Reduce number of supported image formats on UI --- ui/angular.json | 1 - ui/package.json | 5 +-- ui/src/app/core/constants.ts | 2 +- .../photo-loader/photo-loader.service.spec.ts | 14 ------ .../core/photo-loader/photo-loader.service.ts | 44 ++----------------- ui/src/assets/i18n/en.json | 2 +- 6 files changed, 6 insertions(+), 62 deletions(-) diff --git a/ui/angular.json b/ui/angular.json index d1df1226ff..24c7958543 100644 --- a/ui/angular.json +++ b/ui/angular.json @@ -17,7 +17,6 @@ "build": { "builder": "@angular-devkit/build-angular:browser", "options": { - "allowedCommonJsDependencies": ["utif"], "outputPath": "dist/compreface", "index": "src/index.html", "main": "src/main.ts", diff --git a/ui/package.json b/ui/package.json index 40ba83227d..8fa7079b1c 100644 --- a/ui/package.json +++ b/ui/package.json @@ -37,7 +37,6 @@ "@ngx-translate/http-loader": "^6.0.0", "jasmine-marbles": "^0.6.0", "rxjs": "~6.6.3", - "tiff": "^4.3.0", "tslib": "^2.0.0", "zone.js": "~0.10.2" }, @@ -54,7 +53,6 @@ "@types/jasmine": "~3.6.0", "@types/jasminewd2": "~2.0.3", "@types/node": "^12.11.1", - "@types/utif": "^3.0.1", "@typescript-eslint/eslint-plugin": "4.3.0", "@typescript-eslint/parser": "4.3.0", "body-parser": "latest", @@ -78,7 +76,6 @@ "prettier-eslint": "^12.0.0", "protractor": "~7.0.0", "ts-node": "~7.0.0", - "typescript": "~4.0.3", - "utif": "^3.1.0" + "typescript": "~4.0.3" } } diff --git a/ui/src/app/core/constants.ts b/ui/src/app/core/constants.ts index c3b88bda84..3eb93742c8 100644 --- a/ui/src/app/core/constants.ts +++ b/ui/src/app/core/constants.ts @@ -16,4 +16,4 @@ export const EMAIL_REGEXP_PATTERN = '^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$'; export const MAX_IMAGE_SIZE = 5000000; -export const AVAILABLE_IMAGE_EXTENSIONS: string[] = ['jpeg', 'jpg', 'ico', 'png', 'bmp', 'gif', 'tif', 'tiff', 'webp']; +export const AVAILABLE_IMAGE_EXTENSIONS: string[] = ['jpeg', 'jpg', 'png', 'webp']; diff --git a/ui/src/app/core/photo-loader/photo-loader.service.spec.ts b/ui/src/app/core/photo-loader/photo-loader.service.spec.ts index aebbde8196..405be319de 100644 --- a/ui/src/app/core/photo-loader/photo-loader.service.spec.ts +++ b/ui/src/app/core/photo-loader/photo-loader.service.spec.ts @@ -36,23 +36,9 @@ describe('LoadingPhotoService', () => { expect(service).toBeTruthy(); }); - it('should be types check', () => { - service.imageType.forEach((value: string) => { - const file = new File([''], 'image', { type: value }); - - expect(service.loader(file)).toBeTruthy(); - }); - }); - it('should be type check that is not included in list for example: image/x-jg', () => { const file = new File([''], 'image', { type: 'image/x-jg' }); expect(service.loader(file)).toBeUndefined(); }); - - it('should be type check that is not included in list for example: text/html', () => { - const file = new File([''], 'text', { type: 'text/html' }); - - expect(service.loader(file)).toBeUndefined(); - }); }); diff --git a/ui/src/app/core/photo-loader/photo-loader.service.ts b/ui/src/app/core/photo-loader/photo-loader.service.ts index 32f04b447a..792f225639 100644 --- a/ui/src/app/core/photo-loader/photo-loader.service.ts +++ b/ui/src/app/core/photo-loader/photo-loader.service.ts @@ -16,51 +16,17 @@ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; -import { map, switchMap } from 'rxjs/operators'; +import { switchMap } from 'rxjs/operators'; import { Observable } from 'rxjs'; -import { decode, decodeImage, IFD, toRGBA8 } from 'utif'; @Injectable({ providedIn: 'root', }) export class LoadingPhotoService { - private type: string[] = [ - 'image/bmp', - 'image/gif', - 'image/jpeg', - 'image/png', - 'image/tiff', - 'image/vnd.wap.wbmp', - 'image/webp', - 'image/x-icon', - 'image/x-jng', - ]; - - get imageType(): string[] { - return this.type; - } constructor(private http: HttpClient) {} - tiffConvertor(url: string): Observable { - return this.http.get(url, { responseType: 'arraybuffer' }).pipe( - map((array: ArrayBuffer) => { - const ifd: IFD = decode(array)[0]; - - decodeImage(array, ifd); - - const rgba: Uint8Array = toRGBA8(ifd); - const imageData: ImageData = new ImageData(ifd.width, ifd.height); - - rgba.forEach((value: number, index: number) => (imageData.data[index] = value)); - - return imageData as ImageData; - }), - switchMap(async (imageData: ImageData) => (await createImageBitmap(imageData)) as ImageBitmap) - ); - } - createImage(url: string): Observable { return this.http .get(url, { responseType: 'blob' }) @@ -68,13 +34,9 @@ export class LoadingPhotoService { } loader(file: File): Observable { - const checkImageType: boolean = this.imageType.includes(file.type); - - if (!checkImageType) return; - + console.log(file); const url: string = URL.createObjectURL(file); - const type = 'image/tiff'; - return file.type === type ? this.tiffConvertor(url) : this.createImage(url); + return this.createImage(url); } } diff --git a/ui/src/assets/i18n/en.json b/ui/src/assets/i18n/en.json index 2ae322d7ca..a242538782 100644 --- a/ui/src/assets/i18n/en.json +++ b/ui/src/assets/i18n/en.json @@ -168,7 +168,7 @@ }, "dnd": { "title": "Click here or Drag and drop your image here", - "label": "Available images extensions: jpeg, jpg, ico, png, bmp, gif, tif, tiff, webp \nMax images size = 5Mb (5242880bytes)", + "label": "Available images extensions: jpeg, jpg, png, webp. \nMax images size = 5Mb (5242880bytes).", "test": "Test model", "add": "Add to model", "train": "Train model", From d360f51e441ebe5c8842203e1fdfba26687c393e Mon Sep 17 00:00:00 2001 From: Lenovo Date: Tue, 8 Jun 2021 23:33:23 +0400 Subject: [PATCH 019/837] fixed deleted user issue --- .../app/features/tool-bar/tool-bar.component.ts | 8 ++++++-- .../features/user-table/user-table.component.ts | 16 +++++++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/ui/src/app/features/tool-bar/tool-bar.component.ts b/ui/src/app/features/tool-bar/tool-bar.component.ts index 360d9530f1..58eaf4f0b9 100644 --- a/ui/src/app/features/tool-bar/tool-bar.component.ts +++ b/ui/src/app/features/tool-bar/tool-bar.component.ts @@ -13,7 +13,7 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ -import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; +import {ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { TranslateService } from '@ngx-translate/core'; import { filter, first } from 'rxjs/operators'; @@ -26,7 +26,7 @@ import { EditUserInfoDialogComponent } from '../edit-user-info-dialog/edit-user- styleUrls: ['./tool-bar.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ToolBarComponent { +export class ToolBarComponent implements OnInit{ @Input() userAvatarInfo: string; @Input() userName: string; @Input() isUserInfoAvailable: boolean; @@ -39,6 +39,10 @@ export class ToolBarComponent { constructor(private dialog: MatDialog, private translate: TranslateService) {} + ngOnInit(): void { + console.log(this.userName, 'sana'); + } + changeArrowIcon(): void { this.openMenu = !this.openMenu; } diff --git a/ui/src/app/features/user-table/user-table.component.ts b/ui/src/app/features/user-table/user-table.component.ts index 50ca98fd1e..e91d438f5c 100644 --- a/ui/src/app/features/user-table/user-table.component.ts +++ b/ui/src/app/features/user-table/user-table.component.ts @@ -23,6 +23,7 @@ import { TranslateService } from '@ngx-translate/core'; import { RoleEditDialogComponent } from '../role-edit-dialog/role-edit-dialog.component'; import { MatDialog } from '@angular/material/dialog'; import { UserRole } from '../../data/interfaces/user-role'; +import {ActivatedRoute, Router} from "@angular/router"; @Component({ selector: 'app-user-table', @@ -42,13 +43,16 @@ export class UserTableComponent extends TableComponent implements OnInit, OnChan @Input() searchText: string; @Output() deleteUser = new EventEmitter(); - constructor(private dialog: MatDialog, private translate: TranslateService) { + constructor(private dialog: MatDialog, private translate: TranslateService, + private router: Router, private activatedRoute: ActivatedRoute) { super(); + this.refreshPage(); } ngOnInit() { this.message = this.createMessage; this.noResultMessage = this.translate.instant('users.search.no_results'); + } ngOnChanges(): void { @@ -98,4 +102,14 @@ export class UserTableComponent extends TableComponent implements OnInit, OnChan this.message = this.createMessage; } } + + refreshPage(): void { + if (!localStorage.getItem('foo')) { + localStorage.setItem('foo', 'no reload') + location.reload() + } else { + localStorage.removeItem('foo') + } + } + } From 39a1fd52fa50a57f0cf75493035c095280e0ddf3 Mon Sep 17 00:00:00 2001 From: Lenovo Date: Tue, 8 Jun 2021 23:36:35 +0400 Subject: [PATCH 020/837] fixed deleted user issue final version --- ui/src/app/features/user-table/user-table.component.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ui/src/app/features/user-table/user-table.component.ts b/ui/src/app/features/user-table/user-table.component.ts index e91d438f5c..a26e010a01 100644 --- a/ui/src/app/features/user-table/user-table.component.ts +++ b/ui/src/app/features/user-table/user-table.component.ts @@ -43,8 +43,7 @@ export class UserTableComponent extends TableComponent implements OnInit, OnChan @Input() searchText: string; @Output() deleteUser = new EventEmitter(); - constructor(private dialog: MatDialog, private translate: TranslateService, - private router: Router, private activatedRoute: ActivatedRoute) { + constructor(private dialog: MatDialog, private translate: TranslateService,) { super(); this.refreshPage(); } From 9ce44b845de053fc3e8dc1a2fbffbb0a99769473 Mon Sep 17 00:00:00 2001 From: Pavel Bohdan Date: Wed, 9 Jun 2021 00:13:10 +0300 Subject: [PATCH 021/837] Code Refactoring --- .../face-recognition-container.component.html | 2 +- .../face-recognition-container.component.ts | 17 ++- .../recognition-result.component.html | 9 +- .../recognition-result.component.ts | 86 +++++-------- ...face-verification-container.component.html | 7 +- .../face-verification-container.component.ts | 57 ++++++--- .../verification-result.component.html | 18 ++- .../verification-result.component.ts | 119 ++++++------------ ui/src/app/store/face-verification/action.ts | 3 +- ui/src/app/store/face-verification/effects.ts | 8 +- .../app/store/face-verification/reducers.ts | 8 +- .../app/store/face-verification/selectors.ts | 3 - 12 files changed, 144 insertions(+), 193 deletions(-) diff --git a/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.html b/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.html index 7db0d3534c..64bb8b6640 100644 --- a/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.html +++ b/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.html @@ -15,7 +15,7 @@ --> ; - file$: Observable; + photo$: Observable; requestInfo$: Observable; pending$: Observable; isLoaded$: Observable; @@ -46,14 +51,16 @@ export class FaceRecognitionContainerComponent implements OnInit, OnDestroy { @Input() title: string; @Input() type: ServiceTypes; - constructor(private store: Store, private snackBarService: SnackBarService) {} + constructor(private store: Store, private snackBarService: SnackBarService, private loadingPhotoService: LoadingPhotoService) {} ngOnInit() { this.data$ = this.store.select(selectFaceData); - this.file$ = this.store.select(selectFile); this.requestInfo$ = this.store.select(selectRequest); this.pending$ = this.store.select(selectTestIsPending); this.isLoaded$ = this.store.select(selectStateReady); + this.photo$ = this.store + .select(selectFile) + .pipe(switchMap(file => defer(() => (!!file ? this.loadingPhotoService.loader(file) : of(null))))); } resetFace(): void { diff --git a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.html b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.html index a100ff0a34..8877d8748e 100644 --- a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.html +++ b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.html @@ -16,10 +16,10 @@
- +
- + = new BehaviorSubject(null); - convertFile$: BehaviorSubject = new BehaviorSubject(null); - - picture$: Observable; - printData$: Observable; - - formattedResult: string; widthCanvas = 500; - - constructor(private loadingPhotoService: LoadingPhotoService) {} - - ngOnInit(): void { - this.picture$ = this.convertFile$.pipe( - filter(file => !!file), - switchMap(file => this.displayPhotoConvert(file)) - ); - - this.printData$ = this.dataPrintRecalculate$.pipe( - switchMap(printData => - defer(() => - !!printData - ? this.picture$.pipe( - tap(data => console.log(data)), - switchMap(sizes => this.printDataRecalculate(printData, sizes.imageBitmap, sizes.sizeCanvas)) - ) - : of(null) - ) - ) - ); - } + formattedResult: string; + resizePhoto: ImageConvert; + recalculatePrintData: RequestResultRecognition[]; ngOnChanges(changes: SimpleChanges): void { - if (changes.hasOwnProperty('file')) this.convertFile$.next(this.file); - if (changes.hasOwnProperty('printData')) this.dataPrintRecalculate$.next(this.printData); - if (!!this.requestInfo) this.formattedResult = resultRecognitionFormatter(this.requestInfo.response); } - displayPhotoConvert(file: File): Observable { - return this.loadingPhotoService.loader(file).pipe( - map(img => ({ - imageBitmap: img, - sizeCanvas: { width: this.widthCanvas, height: (img.height / img.width) * this.widthCanvas }, - })) - ); + resize(image: ImageBitmap): ImageConvert { + return !!image + ? ({ + imageBitmap: image, + sizeCanvas: { width: this.widthCanvas, height: (image.height / image.width) * this.widthCanvas }, + } as ImageConvert) + : null; } - printDataRecalculate(data: Type, sizeImage: ImageSize, sizeCanvas: ImageSize): Observable { - return new Observable(observer => { - const recalculate = data.map(val => ({ - ...val, - box: recalculateFaceCoordinate(val.box, sizeImage, sizeCanvas), - landmarks: recalculateLandmarks(val.landmarks, sizeImage, sizeCanvas), - })) as Type; - observer.next(recalculate); - observer.complete(); - }); + recalculate(data: RequestResultRecognition[], image: ImageConvert): RequestResultRecognition[] { + return !!data + ? (data.map(val => ({ + ...val, + box: recalculateFaceCoordinate(val.box, image.imageBitmap, image.sizeCanvas), + landmarks: recalculateLandmarks(val.landmarks, image.imageBitmap, image.sizeCanvas), + })) as RequestResultRecognition[]) + : null; } onResetFile(event?: File): void { diff --git a/ui/src/app/features/face-services/face-verification/face-verification-container.component.html b/ui/src/app/features/face-services/face-verification/face-verification-container.component.html index 570ed019e3..9a0a21e51d 100644 --- a/ui/src/app/features/face-services/face-verification/face-verification-container.component.html +++ b/ui/src/app/features/face-services/face-verification/face-verification-container.component.html @@ -15,12 +15,13 @@ -->
; - checkFile$: Observable; - data$: Observable; + processPhoto$: Observable; + dataProcessPhoto$: Observable; + checkPhoto$: Observable; + dataCheckPhoto$: Observable; requestInfo$: Observable; pending$: Observable; isLoaded$: Observable; @Input() type: ServiceTypes; - constructor(private store: Store, private snackBarService: SnackBarService) {} + fields = VerificationServiceFields; + + constructor(private store: Store, private snackBarService: SnackBarService, private loadingPhotoService: LoadingPhotoService) {} ngOnInit() { - this.processFile$ = this.store.select(selectProcessFile).pipe(map(obj => obj.processFile)); - this.checkFile$ = this.store.select(selectCheckFile).pipe(map(obj => obj.checkFile)); - this.data$ = this.store.select(selectFaceData); + this.dataProcessPhoto$ = this.store.select(selectFaceData).pipe(map(data => (data ? [data?.[0][this.fields.ProcessFileData]] : null))); + this.dataCheckPhoto$ = this.store.select(selectFaceData).pipe(map(data => (data ? data?.[0][this.fields.CheckFileData] : null))); this.requestInfo$ = this.store.select(selectRequest); this.pending$ = this.store.select(selectTestIsPending); this.isLoaded$ = this.store.select(selectStateReady); + this.processPhoto$ = this.store.select(selectFiles).pipe( + map(obj => obj.processFile), + switchMap(file => defer(() => (!!file ? this.loadingPhotoService.loader(file) : of(null)))) + ); + this.checkPhoto$ = this.store.select(selectFiles).pipe( + map(obj => obj.checkFile), + switchMap(file => defer(() => (!!file ? this.loadingPhotoService.loader(file) : of(null)))) + ); } ngOnDestroy() { @@ -71,26 +86,28 @@ export class FaceVerificationContainerComponent implements OnInit, OnDestroy { processFileUpload(file) { if (this.validateImage(file)) { - this.store.dispatch(verifyFaceAddFile({ processFile: file })); + this.store.dispatch(verifyFaceAddProcessFile({ processFile: file })); } } processFileReset(event?: File) { this.store.dispatch(verifyFaceProcessFileReset()); - - if (!!event) this.processFileUpload(event); + if (!!event) { + this.processFileUpload(event); + } } checkFileUpload(file) { if (this.validateImage(file)) { - this.store.dispatch(verifyFaceAddFile({ checkFile: file })); + this.store.dispatch(verifyFaceAddCheckFileFile({ checkFile: file })); } } checkFileReset(event: File) { this.store.dispatch(verifyFaceCheckFileReset()); - - if (!!event) this.checkFileUpload(event); + if (!!event) { + this.checkFileUpload(event); + } } validateImage(file) { diff --git a/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.html b/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.html index d899d4db8d..d574d0003a 100644 --- a/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.html +++ b/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.html @@ -17,12 +17,12 @@
- + @@ -48,12 +47,12 @@
- + = new BehaviorSubject(null); - private checkFileConvert$: BehaviorSubject = new BehaviorSubject(null); - private dataPrintRecalculate$: BehaviorSubject = new BehaviorSubject(null); - private verificationServiceFields = VerificationServiceFields; - - processPicture$: Observable; - checkPicture$: Observable; - processFileData$: Observable; - checkFileData$: Observable; widthCanvas = 500; formattedResult: string; - constructor(private loadingPhotoService: LoadingPhotoService) {} - - ngOnInit(): void { - this.processPicture$ = this.processFileConvert$.pipe( - filter(file => !!file), - switchMap(file => this.displayPhotoConvert(file)) - ); + resizeProcessPhoto: ImageConvert; + resizeCheckPhoto: ImageConvert; - this.processFileData$ = this.dataPrintRecalculate$.pipe( - map(data => (data ? [data[0][this.verificationServiceFields.ProcessFileData]] : null)), - switchMap(printData => - defer(() => - !!printData - ? this.processPicture$.pipe(switchMap(sizes => this.printDataRecalculate(printData, sizes.imageBitmap, sizes.sizeCanvas))) - : of(null) - ) - ) - ); - - this.checkPicture$ = this.checkFileConvert$.pipe( - filter(file => !!file), - switchMap(file => this.displayPhotoConvert(file)) - ); - - this.checkFileData$ = this.dataPrintRecalculate$.pipe( - map(data => (data ? data[0][this.verificationServiceFields.CheckFileData] : null)), - switchMap(printData => - defer(() => - !!printData - ? this.checkPicture$.pipe(switchMap(sizes => this.printDataRecalculate(printData, sizes.imageBitmap, sizes.sizeCanvas))) - : of(null) - ) - ) - ); - } + printDataProcessPhoto: SourceImageFace[]; + printDataCheckPhoto: FaceMatches[]; ngOnChanges(changes: SimpleChanges): void { - if (changes.hasOwnProperty('processFile')) this.processFileConvert$.next(this.processFile); - if (changes.hasOwnProperty('checkFile')) this.checkFileConvert$.next(this.checkFile); - if (changes.hasOwnProperty('printData')) this.dataPrintRecalculate$.next(this.printData); - if (!!this.requestInfo) this.formattedResult = resultRecognitionFormatter(this.requestInfo.response); } - printDataRecalculate(data: Type, sizeImage: ImageSize, sizeCanvas: ImageSize): Observable { - return new Observable(observer => { - const recalculate = data.map(val => ({ - ...val, - box: recalculateFaceCoordinate(val.box, sizeImage, sizeCanvas), - landmarks: recalculateLandmarks(val.landmarks, sizeImage, sizeCanvas), - })) as Type; - observer.next(recalculate); - observer.complete(); - }); + resize(image: ImageBitmap): ImageConvert { + return !!image + ? ({ + imageBitmap: image, + sizeCanvas: { width: this.widthCanvas, height: (image.height / image.width) * this.widthCanvas }, + } as ImageConvert) + : null; } - displayPhotoConvert(file: File): Observable { - return this.loadingPhotoService.loader(file).pipe( - map(img => ({ - imageBitmap: img, - sizeCanvas: { width: this.widthCanvas, height: (img.height / img.width) * this.widthCanvas }, - })) - ); + recalculate(data: any[], image: ImageConvert): any[] { + return !!data + ? (data.map(val => ({ + ...val, + box: recalculateFaceCoordinate(val.box, image.imageBitmap, image.sizeCanvas), + landmarks: recalculateLandmarks(val.landmarks, image.imageBitmap, image.sizeCanvas), + })) as RequestResultRecognition[]) + : null; } onResetProcessFile(event?: File): void { this.resetProcessFile.emit(); - if (event) this.resetProcessFile.emit(event); } onResetCheckFile(event?: File): void { this.resetCheckFile.emit(); - if (event) this.resetCheckFile.emit(event); } } diff --git a/ui/src/app/store/face-verification/action.ts b/ui/src/app/store/face-verification/action.ts index d082e70eca..779e8cea26 100644 --- a/ui/src/app/store/face-verification/action.ts +++ b/ui/src/app/store/face-verification/action.ts @@ -16,7 +16,8 @@ import { createAction, props } from '@ngrx/store'; export const verifyFace = createAction('[Model] Face Verification Save'); -export const verifyFaceAddFile = createAction('[Model] Face Verification Add File', props<{ processFile?: any; checkFile?: any }>()); +export const verifyFaceAddProcessFile = createAction('[Model] Face Verification Add Process File', props<{ processFile: any }>()); +export const verifyFaceAddCheckFileFile = createAction('[Model] Face Verification Add Check File', props<{ checkFile: any }>()); export const verifyFaceSuccess = createAction('[Model] Face Verification Success', props<{ model: any; request: any }>()); export const verifyFaceFail = createAction('[Model] Face Verification Fail', props<{ error: any }>()); export const verifyFaceReset = createAction('[Model] Face Verification Reset'); diff --git a/ui/src/app/store/face-verification/effects.ts b/ui/src/app/store/face-verification/effects.ts index 790a5984aa..83d07a4d00 100644 --- a/ui/src/app/store/face-verification/effects.ts +++ b/ui/src/app/store/face-verification/effects.ts @@ -25,7 +25,7 @@ import { FaceRecognitionService } from '../../core/face-recognition/face-recogni import { selectDemoApiKey } from '../demo/selectors'; import { selectCurrentModel } from '../model/selectors'; import { selectFiles } from './selectors'; -import { verifyFace, verifyFaceSuccess, verifyFaceFail, verifyFaceAddFile } from './action'; +import { verifyFace, verifyFaceSuccess, verifyFaceFail, verifyFaceAddProcessFile, verifyFaceAddCheckFileFile } from './action'; @Injectable() export class FaceRecognitionEffects { @@ -38,16 +38,16 @@ export class FaceRecognitionEffects { @Effect() verifyFaceAddFile$ = this.actions.pipe( - ofType(verifyFaceAddFile), + ofType(verifyFaceAddProcessFile, verifyFaceAddCheckFileFile), withLatestFrom(this.store.select(selectFiles)), - switchMap(([action, files]) => (files.processFile && files.checkFile ? [verifyFace()] : [])) + switchMap(([, files]) => (files.processFile && files.checkFile ? [verifyFace()] : [])) ); @Effect() verifyFaceSaveToStore$ = this.actions.pipe( ofType(verifyFace), withLatestFrom(this.store.select(selectCurrentModel), this.store.select(selectDemoApiKey), this.store.select(selectFiles)), - switchMap(([action, model, demoApiKey, files]) => + switchMap(([, model, demoApiKey, files]) => iif( () => !!model, this.verificationFace(files.processFile, files.checkFile, model?.apiKey), diff --git a/ui/src/app/store/face-verification/reducers.ts b/ui/src/app/store/face-verification/reducers.ts index 522a558807..bbe416cfd4 100644 --- a/ui/src/app/store/face-verification/reducers.ts +++ b/ui/src/app/store/face-verification/reducers.ts @@ -20,9 +20,10 @@ import { verifyFaceReset, verifyFace, verifyFaceSuccess, - verifyFaceAddFile, verifyFaceProcessFileReset, verifyFaceCheckFileReset, + verifyFaceAddProcessFile, + verifyFaceAddCheckFileFile, } from './action'; export interface FaceVerificationEntityState { isPending: boolean; @@ -42,8 +43,9 @@ const initialStateVerification: FaceVerificationEntityState = { const reducerVerification: ActionReducer = createReducer( initialStateVerification, - on(verifyFaceAddFile, (state, action) => ({ ...state, ...action })), - on(verifyFace, (state, action) => ({ ...state, ...action, isPending: true })), + on(verifyFaceAddProcessFile, (state, action) => ({ ...state, ...action })), + on(verifyFaceAddCheckFileFile, (state, action) => ({ ...state, ...action })), + on(verifyFace, state => ({ ...state, isPending: true })), on(verifyFaceSuccess, (state, action) => ({ ...state, ...action, isPending: false })), on(verifyFaceFail, state => ({ ...state, isPending: false })), on(verifyFaceProcessFileReset, verifyFaceFail, state => ({ ...state, processFile: null, request: null, model: null })), diff --git a/ui/src/app/store/face-verification/selectors.ts b/ui/src/app/store/face-verification/selectors.ts index 77f29a21ae..4a68870d57 100644 --- a/ui/src/app/store/face-verification/selectors.ts +++ b/ui/src/app/store/face-verification/selectors.ts @@ -22,9 +22,6 @@ export const selectTestIsPending = createSelector(selectTestEntityState, state = export const selectFaceData = createSelector(selectTestEntityState, state => (state.model ? state.model.result : null)); -export const selectProcessFile = createSelector(selectTestEntityState, state => ({ processFile: state.processFile })); -export const selectCheckFile = createSelector(selectTestEntityState, state => ({ checkFile: state.checkFile })); - export const selectFiles = createSelector(selectTestEntityState, state => ({ processFile: state.processFile, checkFile: state.checkFile })); export const selectStateReady = createSelector(selectTestEntityState, state => !state.isPending && !!state?.model?.result[0]); export const selectRequest = createSelector(selectTestEntityState, state => ({ From 34fd98b533346edf826770974ce1fd136f62c779 Mon Sep 17 00:00:00 2001 From: Pavel Bohdan Date: Wed, 9 Jun 2021 10:52:02 +0300 Subject: [PATCH 022/837] Changed drag-n-drop.component --- ui/src/app/features/drag-n-drop/drag-n-drop.component.html | 1 + ui/src/app/features/drag-n-drop/drag-n-drop.component.scss | 2 +- ui/src/app/features/drag-n-drop/drag-n-drop.component.ts | 1 + .../verification-result/verification-result.component.html | 2 ++ ui/src/assets/i18n/en.json | 4 +++- 5 files changed, 8 insertions(+), 2 deletions(-) diff --git a/ui/src/app/features/drag-n-drop/drag-n-drop.component.html b/ui/src/app/features/drag-n-drop/drag-n-drop.component.html index 622aac92e6..dd45f6445c 100644 --- a/ui/src/app/features/drag-n-drop/drag-n-drop.component.html +++ b/ui/src/app/features/drag-n-drop/drag-n-drop.component.html @@ -20,6 +20,7 @@
+
{{ type }}
{{ title }}
{{ label }}
diff --git a/ui/src/app/features/drag-n-drop/drag-n-drop.component.scss b/ui/src/app/features/drag-n-drop/drag-n-drop.component.scss index ff6b4c1365..e17d02b8d6 100644 --- a/ui/src/app/features/drag-n-drop/drag-n-drop.component.scss +++ b/ui/src/app/features/drag-n-drop/drag-n-drop.component.scss @@ -61,7 +61,7 @@ $border-radius: 12px; @include content(400px, 16px); .icon { - margin-bottom: 48px; + margin-bottom: 26px; } } diff --git a/ui/src/app/features/drag-n-drop/drag-n-drop.component.ts b/ui/src/app/features/drag-n-drop/drag-n-drop.component.ts index adc82f69cc..6a84ac7343 100644 --- a/ui/src/app/features/drag-n-drop/drag-n-drop.component.ts +++ b/ui/src/app/features/drag-n-drop/drag-n-drop.component.ts @@ -23,6 +23,7 @@ import { TranslateService } from '@ngx-translate/core'; }) export class DragNDropComponent implements OnInit, AfterViewInit { @ViewChild('fileDropRef') fileDropEl: ElementRef; + @Input() type: string; @Input() title: string; @Input() label: string; @Input() model: any; diff --git a/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.html b/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.html index d574d0003a..a73194ec63 100644 --- a/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.html +++ b/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.html @@ -19,6 +19,7 @@ @@ -49,6 +50,7 @@ diff --git a/ui/src/assets/i18n/en.json b/ui/src/assets/i18n/en.json index 2ae322d7ca..37ea5f5384 100644 --- a/ui/src/assets/i18n/en.json +++ b/ui/src/assets/i18n/en.json @@ -172,7 +172,9 @@ "test": "Test model", "add": "Add to model", "train": "Train model", - "recognize": "Recognize Face" + "recognize": "Recognize Face", + "comparable": "Comparable", + "source": "Source" }, "test_model": { "title": "Test Model", From 4f485efbae9314cf3aff71cf948ad78ee589d3dd Mon Sep 17 00:00:00 2001 From: Pavel Bohdan Date: Wed, 9 Jun 2021 17:48:02 +0300 Subject: [PATCH 023/837] Changed drag-n-drop title. --- .../face-recognition/face-recognition.service.ts | 4 ++-- .../drag-n-drop/drag-n-drop.component.html | 1 - .../drag-n-drop/drag-n-drop.component.scss | 2 +- .../features/drag-n-drop/drag-n-drop.component.ts | 14 +++----------- .../face-picture/face-picture.component.html | 2 +- .../face-picture/face-picture.component.scss | 2 -- .../face-picture/face-picture.component.ts | 2 +- .../recognition-result.component.html | 8 +++++++- .../verification-result.component.html | 6 ++++-- ui/src/assets/i18n/en.json | 7 ++++--- 10 files changed, 23 insertions(+), 25 deletions(-) diff --git a/ui/src/app/core/face-recognition/face-recognition.service.ts b/ui/src/app/core/face-recognition/face-recognition.service.ts index 30fa94fe48..aa1b38de7e 100644 --- a/ui/src/app/core/face-recognition/face-recognition.service.ts +++ b/ui/src/app/core/face-recognition/face-recognition.service.ts @@ -128,7 +128,7 @@ export class FaceRecognitionService { apiKey, file: { name: fname }, } = options; - return `curl -X POST "${window.location.origin}${url}" \\\n-H "Content-Type: multipart/form-data" \\\n-H "x-api-key: ${apiKey}" \\\n-F "file=@${fname}"`; + return `curl -X POST "${window.location.origin}${url}" \\\n-H "Content-Type: multipart/form-data" \\\n-H "x-api-key: ${apiKey}" \\\n-H "face_plugins: ['landmarks', 'gender', 'age']" \\\n-F "file=@${fname}"`; } private createUIDoubleFileRequest(url: string, options = {} as UIDoubleFileRequestOptions, params = {}): string { @@ -137,6 +137,6 @@ export class FaceRecognitionService { sourceImage: { name: ffname }, targetImage: { name: sfname }, } = options; - return `curl -X POST "${window.location.origin}${url}" \\\n-H "Content-Type: multipart/form-data" \\\n-H "x-api-key: ${apiKey}" \\\n-F "source_image=@${ffname}" \\\n-F "target_image=@${sfname}"`; + return `curl -X POST "${window.location.origin}${url}" \\\n-H "Content-Type: multipart/form-data" \\\n-H "x-api-key: ${apiKey}" \\\n-H "face_plugins: ['landmarks', 'gender', 'age']" \\\n-F "source_image=@${ffname}" \\\n-F "target_image=@${sfname}"`; } } diff --git a/ui/src/app/features/drag-n-drop/drag-n-drop.component.html b/ui/src/app/features/drag-n-drop/drag-n-drop.component.html index dd45f6445c..622aac92e6 100644 --- a/ui/src/app/features/drag-n-drop/drag-n-drop.component.html +++ b/ui/src/app/features/drag-n-drop/drag-n-drop.component.html @@ -20,7 +20,6 @@
-
{{ type }}
{{ title }}
{{ label }}
diff --git a/ui/src/app/features/drag-n-drop/drag-n-drop.component.scss b/ui/src/app/features/drag-n-drop/drag-n-drop.component.scss index e17d02b8d6..ff6b4c1365 100644 --- a/ui/src/app/features/drag-n-drop/drag-n-drop.component.scss +++ b/ui/src/app/features/drag-n-drop/drag-n-drop.component.scss @@ -61,7 +61,7 @@ $border-radius: 12px; @include content(400px, 16px); .icon { - margin-bottom: 26px; + margin-bottom: 48px; } } diff --git a/ui/src/app/features/drag-n-drop/drag-n-drop.component.ts b/ui/src/app/features/drag-n-drop/drag-n-drop.component.ts index 6a84ac7343..9df810c9f0 100644 --- a/ui/src/app/features/drag-n-drop/drag-n-drop.component.ts +++ b/ui/src/app/features/drag-n-drop/drag-n-drop.component.ts @@ -13,17 +13,15 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ -import { AfterViewInit, Component, ElementRef, EventEmitter, Input, OnInit, Output, Renderer2, ViewChild } from '@angular/core'; -import { TranslateService } from '@ngx-translate/core'; +import { AfterViewInit, Component, ElementRef, EventEmitter, Input, Output, Renderer2, ViewChild } from '@angular/core'; @Component({ selector: 'app-drag-n-drop', templateUrl: './drag-n-drop.component.html', styleUrls: ['./drag-n-drop.component.scss'], }) -export class DragNDropComponent implements OnInit, AfterViewInit { +export class DragNDropComponent implements AfterViewInit { @ViewChild('fileDropRef') fileDropEl: ElementRef; - @Input() type: string; @Input() title: string; @Input() label: string; @Input() model: any; @@ -38,13 +36,7 @@ export class DragNDropComponent implements OnInit, AfterViewInit { viewColumn = false; - constructor(private translate: TranslateService, private renderer: Renderer2, private elementRef: ElementRef) {} - - ngOnInit(): void { - // Set the default title and label. But leave possibility to set another title and label. - this.title = this.translate.instant('dnd.title'); - this.label = this.translate.instant('dnd.label'); - } + constructor(private renderer: Renderer2, private elementRef: ElementRef) {} ngAfterViewInit(): void { const nativeElement: ChildNode = this.elementRef.nativeElement.firstChild.firstChild; diff --git a/ui/src/app/features/face-services/face-picture/face-picture.component.html b/ui/src/app/features/face-services/face-picture/face-picture.component.html index 1e9435b49f..669359dbfe 100644 --- a/ui/src/app/features/face-services/face-picture/face-picture.component.html +++ b/ui/src/app/features/face-services/face-picture/face-picture.component.html @@ -41,7 +41,7 @@ -
{{ face.box.probability | number : '1.0-2' }}
+
Probability: {{ face.box.probability | number : '1.0-2' }}
diff --git a/ui/src/app/features/face-services/face-picture/face-picture.component.scss b/ui/src/app/features/face-services/face-picture/face-picture.component.scss index aea7b75543..96dd1e614b 100644 --- a/ui/src/app/features/face-services/face-picture/face-picture.component.scss +++ b/ui/src/app/features/face-services/face-picture/face-picture.component.scss @@ -91,7 +91,6 @@ margin-bottom: 4px; font-weight: 600; font-size: 16px; - line-height: 22px; } &_similarity { @@ -99,7 +98,6 @@ margin-bottom: 6px; font-weight: normal; font-size: 14px; - line-height: 14px; } &_gender { diff --git a/ui/src/app/features/face-services/face-picture/face-picture.component.ts b/ui/src/app/features/face-services/face-picture/face-picture.component.ts index 0d8af15cf4..eec951f85c 100644 --- a/ui/src/app/features/face-services/face-picture/face-picture.component.ts +++ b/ui/src/app/features/face-services/face-picture/face-picture.component.ts @@ -90,7 +90,7 @@ export class FacePictureComponent implements OnChanges, OnInit { // eslint-disable-next-line @typescript-eslint/naming-convention const { x_max, x_min, y_max, y_min } = data.box; const frameArea: number = Math.round(x_max - x_min) * Math.round(y_max - y_min); - const sizePoint = frameArea / 3000 / 10 > 1.2 || data.landmarks.length >= 108 ? frameArea / 3000 / 10 : 1.2; + const sizePoint = frameArea / 3000 / 10 > 1.6 || data.landmarks.length >= 108 ? frameArea / 3000 / 10 : 1.6; data.landmarks.forEach(landmark => { ctx.beginPath(); diff --git a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.html b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.html index 8877d8748e..2eedf641f9 100644 --- a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.html +++ b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.html @@ -16,7 +16,13 @@
- + +
diff --git a/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.html b/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.html index a73194ec63..331cd93d00 100644 --- a/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.html +++ b/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.html @@ -18,8 +18,9 @@ @@ -49,8 +50,9 @@ diff --git a/ui/src/assets/i18n/en.json b/ui/src/assets/i18n/en.json index 37ea5f5384..2889d3c86c 100644 --- a/ui/src/assets/i18n/en.json +++ b/ui/src/assets/i18n/en.json @@ -167,14 +167,15 @@ "home": "Home" }, "dnd": { - "title": "Click here or Drag and drop your image here", + "title": "Click here or Drag and drop your {{typeImage}} here", "label": "Available images extensions: jpeg, jpg, ico, png, bmp, gif, tif, tiff, webp \nMax images size = 5Mb (5242880bytes)", "test": "Test model", "add": "Add to model", "train": "Train model", "recognize": "Recognize Face", - "comparable": "Comparable", - "source": "Source" + "image": "image", + "target": "target image", + "source": "source image" }, "test_model": { "title": "Test Model", From bfdbab53858658bcba68967e1a6e1c54063e8000 Mon Sep 17 00:00:00 2001 From: Pavel Bohdan Date: Thu, 10 Jun 2021 17:56:05 +0300 Subject: [PATCH 024/837] Changed Verification selectors. --- .../face-verification-container.component.ts | 29 +++++++++---------- .../app/store/face-verification/selectors.ts | 4 +-- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/ui/src/app/features/face-services/face-verification/face-verification-container.component.ts b/ui/src/app/features/face-services/face-verification/face-verification-container.component.ts index 6b08c266c4..c98a5763c4 100644 --- a/ui/src/app/features/face-services/face-verification/face-verification-container.component.ts +++ b/ui/src/app/features/face-services/face-verification/face-verification-container.component.ts @@ -30,8 +30,9 @@ import { verifyFaceAddCheckFileFile, } from '../../../store/face-verification/action'; import { + selectCheckFile, selectFaceData, - selectFiles, + selectProcessFile, selectRequest, selectStateReady, selectTestIsPending, @@ -70,14 +71,12 @@ export class FaceVerificationContainerComponent implements OnInit, OnDestroy { this.requestInfo$ = this.store.select(selectRequest); this.pending$ = this.store.select(selectTestIsPending); this.isLoaded$ = this.store.select(selectStateReady); - this.processPhoto$ = this.store.select(selectFiles).pipe( - map(obj => obj.processFile), - switchMap(file => defer(() => (!!file ? this.loadingPhotoService.loader(file) : of(null)))) - ); - this.checkPhoto$ = this.store.select(selectFiles).pipe( - map(obj => obj.checkFile), - switchMap(file => defer(() => (!!file ? this.loadingPhotoService.loader(file) : of(null)))) - ); + this.processPhoto$ = this.store + .select(selectProcessFile) + .pipe(switchMap(file => defer(() => (!!file ? this.loadingPhotoService.loader(file) : of(null))))); + this.checkPhoto$ = this.store + .select(selectCheckFile) + .pipe(switchMap(file => defer(() => (!!file ? this.loadingPhotoService.loader(file) : of(null))))); } ngOnDestroy() { @@ -90,6 +89,12 @@ export class FaceVerificationContainerComponent implements OnInit, OnDestroy { } } + checkFileUpload(file) { + if (this.validateImage(file)) { + this.store.dispatch(verifyFaceAddCheckFileFile({ checkFile: file })); + } + } + processFileReset(event?: File) { this.store.dispatch(verifyFaceProcessFileReset()); if (!!event) { @@ -97,12 +102,6 @@ export class FaceVerificationContainerComponent implements OnInit, OnDestroy { } } - checkFileUpload(file) { - if (this.validateImage(file)) { - this.store.dispatch(verifyFaceAddCheckFileFile({ checkFile: file })); - } - } - checkFileReset(event: File) { this.store.dispatch(verifyFaceCheckFileReset()); if (!!event) { diff --git a/ui/src/app/store/face-verification/selectors.ts b/ui/src/app/store/face-verification/selectors.ts index 4a68870d57..33eb86ab15 100644 --- a/ui/src/app/store/face-verification/selectors.ts +++ b/ui/src/app/store/face-verification/selectors.ts @@ -19,9 +19,9 @@ import { FaceVerificationEntityState } from './reducers'; export const selectTestEntityState = createFeatureSelector('faceVerification'); export const selectTestIsPending = createSelector(selectTestEntityState, state => state.isPending); - export const selectFaceData = createSelector(selectTestEntityState, state => (state.model ? state.model.result : null)); - +export const selectProcessFile = createSelector(selectTestEntityState, state => (state.processFile ? state.processFile : null)); +export const selectCheckFile = createSelector(selectTestEntityState, state => (state.checkFile ? state.checkFile : null)); export const selectFiles = createSelector(selectTestEntityState, state => ({ processFile: state.processFile, checkFile: state.checkFile })); export const selectStateReady = createSelector(selectTestEntityState, state => !state.isPending && !!state?.model?.result[0]); export const selectRequest = createSelector(selectTestEntityState, state => ({ From 346b55ac132e5f0f03655eea148e7113066bb62e Mon Sep 17 00:00:00 2001 From: Pavel Bohdan Date: Fri, 11 Jun 2021 12:20:17 +0300 Subject: [PATCH 025/837] Changed PR. --- ui/src/app/data/interfaces/box-size.ts | 24 ++++++++++++ ui/src/app/data/interfaces/box-subjects.ts | 20 ++++++++++ ui/src/app/data/interfaces/face-matches.ts | 24 ++++++++++++ .../interfaces/request-result-recognition.ts | 25 ++++++++++++ ui/src/app/data/interfaces/response-result.ts | 38 +------------------ .../app/data/interfaces/source-image-face.ts | 23 +++++++++++ .../face-picture/face-picture.component.ts | 29 +++++++------- .../face-picture/face-services.directive.ts | 2 +- .../recognition-result.component.ts | 4 +- .../verification-result.component.ts | 11 +++--- .../app/store/face-verification/reducers.ts | 6 +-- ui/src/app/store/role/reducers.ts | 4 +- 12 files changed, 143 insertions(+), 67 deletions(-) create mode 100644 ui/src/app/data/interfaces/box-size.ts create mode 100644 ui/src/app/data/interfaces/box-subjects.ts create mode 100644 ui/src/app/data/interfaces/face-matches.ts create mode 100644 ui/src/app/data/interfaces/request-result-recognition.ts create mode 100644 ui/src/app/data/interfaces/source-image-face.ts diff --git a/ui/src/app/data/interfaces/box-size.ts b/ui/src/app/data/interfaces/box-size.ts new file mode 100644 index 0000000000..710b182ec7 --- /dev/null +++ b/ui/src/app/data/interfaces/box-size.ts @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +/* eslint-disable @typescript-eslint/naming-convention */ +export interface BoxSize { + probability: number; + x_max: number; + x_min: number; + y_max: number; + y_min: number; +} diff --git a/ui/src/app/data/interfaces/box-subjects.ts b/ui/src/app/data/interfaces/box-subjects.ts new file mode 100644 index 0000000000..4b990d9845 --- /dev/null +++ b/ui/src/app/data/interfaces/box-subjects.ts @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +export interface BoxSubjects { + subject: string; + similarity: number; +} diff --git a/ui/src/app/data/interfaces/face-matches.ts b/ui/src/app/data/interfaces/face-matches.ts new file mode 100644 index 0000000000..24a1bed525 --- /dev/null +++ b/ui/src/app/data/interfaces/face-matches.ts @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +import { BoxSize } from './box-size'; + +export interface FaceMatches { + age: number[]; + gender: 'male' | 'female'; + box: BoxSize; + similarity: number; + landmarks: [number[]]; +} diff --git a/ui/src/app/data/interfaces/request-result-recognition.ts b/ui/src/app/data/interfaces/request-result-recognition.ts new file mode 100644 index 0000000000..f2f3345ba3 --- /dev/null +++ b/ui/src/app/data/interfaces/request-result-recognition.ts @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +import { BoxSize } from './box-size'; +import { BoxSubjects } from './box-subjects'; + +export interface RequestResultRecognition { + age: number[]; + gender: 'male' | 'female'; + box: BoxSize; + subjects: BoxSubjects[]; + landmarks: [number[]]; +} diff --git a/ui/src/app/data/interfaces/response-result.ts b/ui/src/app/data/interfaces/response-result.ts index ef66a02558..aeee766770 100644 --- a/ui/src/app/data/interfaces/response-result.ts +++ b/ui/src/app/data/interfaces/response-result.ts @@ -13,44 +13,10 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ +import { SourceImageFace } from './source-image-face'; +import { FaceMatches } from './face-matches'; /* eslint-disable @typescript-eslint/naming-convention */ -export interface BoxSize { - probability: number; - x_max: number; - x_min: number; - y_max: number; - y_min: number; -} - -export interface BoxSubjects { - subject: string; - similarity: number; -} - -export interface RequestResultRecognition { - age: number[]; - gender: 'male' | 'female'; - box: BoxSize; - subjects: BoxSubjects[]; - landmarks: [number[]]; -} - -export interface SourceImageFace { - age: number[]; - gender: 'male' | 'female'; - box: BoxSize; - landmarks: [number[]]; -} - -export interface FaceMatches { - age: number[]; - gender: 'male' | 'female'; - box: BoxSize; - similarity: number; - landmarks: [number[]]; -} - export interface RequestResultVerification { source_image_face: SourceImageFace; face_matches: FaceMatches[]; diff --git a/ui/src/app/data/interfaces/source-image-face.ts b/ui/src/app/data/interfaces/source-image-face.ts new file mode 100644 index 0000000000..547bf11013 --- /dev/null +++ b/ui/src/app/data/interfaces/source-image-face.ts @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +import { BoxSize } from './box-size'; + +export interface SourceImageFace { + age: number[]; + gender: 'male' | 'female'; + box: BoxSize; + landmarks: [number[]]; +} diff --git a/ui/src/app/features/face-services/face-picture/face-picture.component.ts b/ui/src/app/features/face-services/face-picture/face-picture.component.ts index eec951f85c..0d1dd09013 100644 --- a/ui/src/app/features/face-services/face-picture/face-picture.component.ts +++ b/ui/src/app/features/face-services/face-picture/face-picture.component.ts @@ -14,9 +14,12 @@ * permissions and limitations under the License. */ import { ChangeDetectionStrategy, Component, ElementRef, Input, OnChanges, OnInit, ViewChild } from '@angular/core'; + import { ServiceTypes } from '../../../data/enums/service-types.enum'; -import { FaceMatches, RequestResultRecognition, SourceImageFace } from '../../../data/interfaces/response-result'; import { ImageConvert } from '../../../data/interfaces/image-convert'; +import { RequestResultRecognition } from '../../../data/interfaces/request-result-recognition'; +import { FaceMatches } from '../../../data/interfaces/face-matches'; +import { SourceImageFace } from '../../../data/interfaces/source-image-face'; @Component({ selector: 'app-face-picture', @@ -29,8 +32,7 @@ export class FacePictureComponent implements OnChanges, OnInit { @Input() printData: RequestResultRecognition[] | FaceMatches[] | SourceImageFace[]; @Input() isLoaded: boolean; @Input() type: ServiceTypes; - @Input() - set showLandmarks(value: boolean) { + @Input() set showLandmarks(value: boolean) { this.disableLandmarks = value; } @@ -41,7 +43,6 @@ export class FacePictureComponent implements OnChanges, OnInit { disableLandmarks = false; private currentPicture: () => any; - private colorLandmarks = '#27C224'; ngOnInit(): void { this.initCanvasSize(); @@ -51,7 +52,7 @@ export class FacePictureComponent implements OnChanges, OnInit { ngOnChanges(): void { this.initCanvasSize(); if (!!this.currentPicture) this.currentPicture = this.loadPicture(this.canvasPicture, this.picture, this.currentPicture); - this.getLandmarks(this.canvasLandmarks, this.printData, this.colorLandmarks); + this.getLandmarks(this.canvasLandmarks, this.printData); } initCanvasSize(): void { @@ -73,28 +74,24 @@ export class FacePictureComponent implements OnChanges, OnInit { return () => ctx.clearRect(0, 0, sizeCanvas.width, sizeCanvas.height); } - getLandmarks( - canvasEl: ElementRef, - data: RequestResultRecognition[] | FaceMatches[] | SourceImageFace[], - color: string - ): void { + getLandmarks(canvasEl: ElementRef, data: RequestResultRecognition[] | FaceMatches[] | SourceImageFace[]): void { if (!data) return; const ctx: CanvasRenderingContext2D = canvasEl.nativeElement.getContext('2d'); - ctx.fillStyle = color; + ctx.fillStyle = '#27C224'; - data.forEach(val => this.displayLandmarks(ctx, val, color)); + data.forEach(val => this.displayLandmarks(ctx, val)); } - displayLandmarks(ctx: CanvasRenderingContext2D, data: RequestResultRecognition | FaceMatches | SourceImageFace, color: string): void { + displayLandmarks(ctx: CanvasRenderingContext2D, data: RequestResultRecognition | FaceMatches | SourceImageFace): void { // eslint-disable-next-line @typescript-eslint/naming-convention const { x_max, x_min, y_max, y_min } = data.box; - const frameArea: number = Math.round(x_max - x_min) * Math.round(y_max - y_min); - const sizePoint = frameArea / 3000 / 10 > 1.6 || data.landmarks.length >= 108 ? frameArea / 3000 / 10 : 1.6; + const frameArea: number = (Math.round(x_max - x_min) * Math.round(y_max - y_min)) / 19000; + const sizePoint = frameArea > 1.4 || data.landmarks.length >= 108 ? frameArea : 1.4; data.landmarks.forEach(landmark => { ctx.beginPath(); - ctx.strokeStyle = color; + ctx.strokeStyle = '#27C224'; ctx.arc(landmark[0], landmark[1], sizePoint, 0, Math.PI * 2, false); ctx.fill(); ctx.closePath(); diff --git a/ui/src/app/features/face-services/face-picture/face-services.directive.ts b/ui/src/app/features/face-services/face-picture/face-services.directive.ts index cbbd0bca4c..4f199c6e31 100644 --- a/ui/src/app/features/face-services/face-picture/face-services.directive.ts +++ b/ui/src/app/features/face-services/face-picture/face-services.directive.ts @@ -26,7 +26,7 @@ import { } from '@angular/core'; import { FrameSize } from '../../../data/interfaces/frame-size'; -import { BoxSize } from '../../../data/interfaces/response-result'; +import { BoxSize } from '../../../data/interfaces/box-size'; @Directive({ selector: '[appFrameTooltip]', diff --git a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.ts b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.ts index cea9e63932..ee1ae98275 100644 --- a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.ts +++ b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.ts @@ -15,11 +15,11 @@ */ import { Component, Input, OnChanges, SimpleChanges, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'; -import { RequestResultRecognition } from '../../../../data/interfaces/response-result'; import { RequestInfo } from '../../../../data/interfaces/request-info'; import { ServiceTypes } from '../../../../data/enums/service-types.enum'; -import { recalculateFaceCoordinate, recalculateLandmarks, resultRecognitionFormatter } from '../../face-services.helpers'; import { ImageConvert } from '../../../../data/interfaces/image-convert'; +import { RequestResultRecognition } from '../../../../data/interfaces/request-result-recognition'; +import { recalculateFaceCoordinate, recalculateLandmarks, resultRecognitionFormatter } from '../../face-services.helpers'; @Component({ selector: 'app-recognition-result', diff --git a/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.ts b/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.ts index 041bc17dad..6acd59cf18 100644 --- a/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.ts +++ b/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.ts @@ -15,12 +15,13 @@ */ import { Component, Input, OnChanges, Output, EventEmitter, SimpleChanges, ChangeDetectionStrategy } from '@angular/core'; -import { FaceMatches, RequestResultRecognition, SourceImageFace } from '../../../../data/interfaces/response-result'; import { RequestInfo } from '../../../../data/interfaces/request-info'; import { ServiceTypes } from '../../../../data/enums/service-types.enum'; import { ImageConvert } from '../../../../data/interfaces/image-convert'; import { recalculateFaceCoordinate, recalculateLandmarks, resultRecognitionFormatter } from '../../face-services.helpers'; +import { SourceImageFace } from '../../../../data/interfaces/source-image-face'; +import { FaceMatches } from '../../../../data/interfaces/face-matches'; @Component({ selector: 'app-verification-result', @@ -33,13 +34,13 @@ export class VerificationResultComponent implements OnChanges { this.resizeProcessPhoto = this.resize(image); } @Input() set processPhotoData(data: SourceImageFace[]) { - this.printDataProcessPhoto = this.recalculate(data, this.resizeProcessPhoto); + this.printDataProcessPhoto = this.recalculate(data, this.resizeProcessPhoto) as SourceImageFace[]; } @Input() set checkPhoto(image: ImageBitmap) { this.resizeCheckPhoto = this.resize(image); } @Input() set checkPhotoData(data: FaceMatches[]) { - this.printDataCheckPhoto = this.recalculate(data, this.resizeCheckPhoto); + this.printDataCheckPhoto = this.recalculate(data, this.resizeCheckPhoto) as FaceMatches[]; } @Input() requestInfo: RequestInfo; @@ -74,13 +75,13 @@ export class VerificationResultComponent implements OnChanges { : null; } - recalculate(data: any[], image: ImageConvert): any[] { + recalculate(data: any[], image: ImageConvert): SourceImageFace[] | FaceMatches[] { return !!data ? (data.map(val => ({ ...val, box: recalculateFaceCoordinate(val.box, image.imageBitmap, image.sizeCanvas), landmarks: recalculateLandmarks(val.landmarks, image.imageBitmap, image.sizeCanvas), - })) as RequestResultRecognition[]) + })) as SourceImageFace[] | FaceMatches[]) : null; } diff --git a/ui/src/app/store/face-verification/reducers.ts b/ui/src/app/store/face-verification/reducers.ts index bbe416cfd4..351f560c8c 100644 --- a/ui/src/app/store/face-verification/reducers.ts +++ b/ui/src/app/store/face-verification/reducers.ts @@ -43,12 +43,10 @@ const initialStateVerification: FaceVerificationEntityState = { const reducerVerification: ActionReducer = createReducer( initialStateVerification, - on(verifyFaceAddProcessFile, (state, action) => ({ ...state, ...action })), - on(verifyFaceAddCheckFileFile, (state, action) => ({ ...state, ...action })), + on(verifyFaceAddProcessFile, verifyFaceAddCheckFileFile, (state, action) => ({ ...state, ...action })), on(verifyFace, state => ({ ...state, isPending: true })), on(verifyFaceSuccess, (state, action) => ({ ...state, ...action, isPending: false })), - on(verifyFaceFail, state => ({ ...state, isPending: false })), - on(verifyFaceProcessFileReset, verifyFaceFail, state => ({ ...state, processFile: null, request: null, model: null })), + on(verifyFaceProcessFileReset, verifyFaceFail, state => ({ ...state, processFile: null, request: null, model: null, isPending: false })), on(verifyFaceCheckFileReset, state => ({ ...state, checkFile: null, request: null, model: null })), on(verifyFaceReset, () => ({ ...initialStateVerification })) ); diff --git a/ui/src/app/store/role/reducers.ts b/ui/src/app/store/role/reducers.ts index 8fb8d542cb..d1327cf267 100644 --- a/ui/src/app/store/role/reducers.ts +++ b/ui/src/app/store/role/reducers.ts @@ -32,9 +32,7 @@ const reducer: ActionReducer = createReducer( initialState, on(loadRolesEntity, state => ({ ...state, isPending: true })), on(setPendingRoleEntity, (state, { isPending }) => ({ ...state, isPending })), - on(fetchRolesEntity, (state, { role }) => { - return roleAdapter.setOne({ id: 0, accessLevels: role.accessLevels }, { ...state, isPending: false }); - }) + on(fetchRolesEntity, (state, { role }) => roleAdapter.setOne({ id: 0, accessLevels: role.accessLevels }, { ...state, isPending: false })) ); export const roleReducer = (roleState: RoleEntityState, action: Action) => reducer(roleState, action); From e40519a310e750ee0b7b146af0a35d801e505d70 Mon Sep 17 00:00:00 2001 From: Pavel Bohdan Date: Fri, 11 Jun 2021 12:51:11 +0300 Subject: [PATCH 026/837] Changed en.json. --- ui/src/assets/i18n/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/assets/i18n/en.json b/ui/src/assets/i18n/en.json index 2889d3c86c..e1149fdef4 100644 --- a/ui/src/assets/i18n/en.json +++ b/ui/src/assets/i18n/en.json @@ -168,7 +168,7 @@ }, "dnd": { "title": "Click here or Drag and drop your {{typeImage}} here", - "label": "Available images extensions: jpeg, jpg, ico, png, bmp, gif, tif, tiff, webp \nMax images size = 5Mb (5242880bytes)", + "label": "Available images extensions: jpeg, jpg, png, webp \nMax images size = 5Mb (5242880bytes)", "test": "Test model", "add": "Add to model", "train": "Train model", From be7addabe26f3d945f19dbd7c73cd6aa8ddc64d2 Mon Sep 17 00:00:00 2001 From: Pavel Bohdan Date: Fri, 11 Jun 2021 13:04:30 +0300 Subject: [PATCH 027/837] Fixed tests in photo-loader.service.spec.ts --- ui/src/app/core/photo-loader/photo-loader.service.spec.ts | 6 +++--- ui/src/app/core/photo-loader/photo-loader.service.ts | 3 --- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/ui/src/app/core/photo-loader/photo-loader.service.spec.ts b/ui/src/app/core/photo-loader/photo-loader.service.spec.ts index 405be319de..d6fd93cd63 100644 --- a/ui/src/app/core/photo-loader/photo-loader.service.spec.ts +++ b/ui/src/app/core/photo-loader/photo-loader.service.spec.ts @@ -36,9 +36,9 @@ describe('LoadingPhotoService', () => { expect(service).toBeTruthy(); }); - it('should be type check that is not included in list for example: image/x-jg', () => { - const file = new File([''], 'image', { type: 'image/x-jg' }); + it('should check type image example: image/jpg', () => { + const file = new File([''], 'image', { type: 'image/jpg' }); - expect(service.loader(file)).toBeUndefined(); + expect(service.loader(file)).toBeDefined(); }); }); diff --git a/ui/src/app/core/photo-loader/photo-loader.service.ts b/ui/src/app/core/photo-loader/photo-loader.service.ts index 792f225639..6438a83f9b 100644 --- a/ui/src/app/core/photo-loader/photo-loader.service.ts +++ b/ui/src/app/core/photo-loader/photo-loader.service.ts @@ -19,12 +19,10 @@ import { HttpClient } from '@angular/common/http'; import { switchMap } from 'rxjs/operators'; import { Observable } from 'rxjs'; - @Injectable({ providedIn: 'root', }) export class LoadingPhotoService { - constructor(private http: HttpClient) {} createImage(url: string): Observable { @@ -34,7 +32,6 @@ export class LoadingPhotoService { } loader(file: File): Observable { - console.log(file); const url: string = URL.createObjectURL(file); return this.createImage(url); From e9d1ecf121c5bc11435b2258f2920aa309daaba9 Mon Sep 17 00:00:00 2001 From: Ivan Kurnosau Date: Fri, 11 Jun 2021 16:18:05 +0300 Subject: [PATCH 028/837] Add MXNet face mask plugin --- .../plugins/insightface/facemask/__init__.py | 17 +++++ .../plugins/insightface/facemask/facemask.py | 73 +++++++++++++++++++ .../plugins/insightface/insightface.py | 5 +- 3 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 embedding-calculator/src/services/facescan/plugins/insightface/facemask/__init__.py create mode 100644 embedding-calculator/src/services/facescan/plugins/insightface/facemask/facemask.py diff --git a/embedding-calculator/src/services/facescan/plugins/insightface/facemask/__init__.py b/embedding-calculator/src/services/facescan/plugins/insightface/facemask/__init__.py new file mode 100644 index 0000000000..a217275de5 --- /dev/null +++ b/embedding-calculator/src/services/facescan/plugins/insightface/facemask/__init__.py @@ -0,0 +1,17 @@ +# Copyright (c) 2020 the original author or authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing +# permissions and limitations under the License. + +from src.services.facescan.plugins.dependencies import get_mxnet + +requirements = get_mxnet() \ No newline at end of file diff --git a/embedding-calculator/src/services/facescan/plugins/insightface/facemask/facemask.py b/embedding-calculator/src/services/facescan/plugins/insightface/facemask/facemask.py new file mode 100644 index 0000000000..d5c6aa4817 --- /dev/null +++ b/embedding-calculator/src/services/facescan/plugins/insightface/facemask/facemask.py @@ -0,0 +1,73 @@ +# Copyright (c) 2020 the original author or authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing +# permissions and limitations under the License. + +import os +from pathlib import Path +from typing import Tuple, Union +from cached_property import cached_property + +import numpy as np +import mxnet as mx +from mxnet.gluon.model_zoo import vision +from mxnet.gluon.data.vision import transforms + +from src.services.dto import plugin_result +from src.services.imgtools.types import Array3D +from src.services.facescan.plugins import base +from src.services.facescan.plugins.insightface.insightface import InsightFaceMixin + + +class MaskDetector(InsightFaceMixin, base.BasePlugin): + slug = 'mask' + LABELS = ('without_mask', 'with_mask', 'mask_weared_incorrect') + ml_models = ( + ('mobilenet_v2_on_mafa_kaggle123', '1DYUIroNXkuYKQypYtCxQvAItLnrTTt5E'), + ('resnet18_on_mafa_kaggle123', '1A3fNrvgrJqMw54cWRj47LNFNnFvTjmdj') + ) + img_transforms = transforms.Compose([ + transforms.Resize(224), + transforms.ToTensor(), + transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)) + ]) + + @property + def input_image_size(self) -> Tuple[int, int]: + return 224, 224 + + @property + def unzip_with_untouched_structure(self) -> bool: + return True + + @cached_property + def _model(self): + if self.ml_model_name.split('_')[0] == 'resnet18': + model = vision.resnet18_v1(classes=len(self.LABELS)) + else: + model = vision.mobilenet_v2_1_0(classes=len(self.LABELS)) + model_path = Path(self.ml_model.path) / Path(os.listdir(self.ml_model.path)[0]) + model.load_parameters(str(model_path)) + + def get_value(img: Array3D) -> Tuple[Union[str, Tuple], float]: + data = img.reshape((1,) + img.shape) + data = mx.nd.array(data) + + scores = model(self.img_transforms(data)).softmax().asnumpy() + val = self.LABELS[int(np.argmax(scores, axis=1)[0])] + prob = scores[0][int(np.argmax(scores, axis=1)[0])] + return val, prob + return get_value + + def __call__(self, face: plugin_result.FaceDTO): + value, probability = self._model(face._face_img) + return plugin_result.MaskDTO(mask=value, mask_probability=probability) \ No newline at end of file diff --git a/embedding-calculator/src/services/facescan/plugins/insightface/insightface.py b/embedding-calculator/src/services/facescan/plugins/insightface/insightface.py index 8ab1a5cfea..ca3d550551 100644 --- a/embedding-calculator/src/services/facescan/plugins/insightface/insightface.py +++ b/embedding-calculator/src/services/facescan/plugins/insightface/insightface.py @@ -15,7 +15,6 @@ import logging import functools from typing import List, Tuple - import attr import numpy as np import mxnet as mx @@ -160,7 +159,7 @@ class GenderDetector(BaseGenderAge): def __call__(self, face: plugin_result.FaceDTO): gender, age = self._evaluate_model(face) - return plugin_result.GenderDTO(gender=self.GENDERS[int(gender)]) + return plugin_result.GenderDTO(gender=self.GENDERS[int(gender)], gender_probability=1.) class AgeDetector(BaseGenderAge): @@ -168,7 +167,7 @@ class AgeDetector(BaseGenderAge): def __call__(self, face: plugin_result.FaceDTO): gender, age = self._evaluate_model(face) - return plugin_result.AgeDTO(age=(age, age)) + return plugin_result.AgeDTO(age=(age, age), age_probability=1.) class LandmarksDetector(mixins.LandmarksDetectorMixin, base.BasePlugin): From 42e838eb1440845e6d3d47c67141011a59e21f73 Mon Sep 17 00:00:00 2001 From: Ivan Kurnosau Date: Fri, 11 Jun 2021 17:23:20 +0300 Subject: [PATCH 029/837] Better inceptionv3 model for facenet.facemask and fix few bugs --- embedding-calculator/src/services/dto/plugin_result.py | 6 +++--- embedding-calculator/src/services/facescan/plugins/base.py | 4 ++-- .../services/facescan/plugins/facenet/facemask/facemask.py | 5 ++--- .../facescan/plugins/insightface/facemask/facemask.py | 4 ++-- .../services/facescan/plugins/insightface/insightface.py | 4 ++-- 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/embedding-calculator/src/services/dto/plugin_result.py b/embedding-calculator/src/services/dto/plugin_result.py index 5df18528bc..4468e2787f 100644 --- a/embedding-calculator/src/services/dto/plugin_result.py +++ b/embedding-calculator/src/services/dto/plugin_result.py @@ -12,7 +12,7 @@ class EmbeddingDTO(JSONEncodable): class GenderDTO(JSONEncodable): - def __init__(self, gender, gender_probability): + def __init__(self, gender, gender_probability=1.): self.gender = { 'value': gender, 'probability': float(gender_probability) @@ -20,7 +20,7 @@ def __init__(self, gender, gender_probability): class AgeDTO(JSONEncodable): - def __init__(self, age, age_probability): + def __init__(self, age, age_probability=1.): self.age = { 'low': age[0], 'high': age[1], @@ -28,7 +28,7 @@ def __init__(self, age, age_probability): class MaskDTO(JSONEncodable): - def __init__(self, mask, mask_probability): + def __init__(self, mask, mask_probability=1.): self.mask = { 'value': mask, 'probability': float(mask_probability) diff --git a/embedding-calculator/src/services/facescan/plugins/base.py b/embedding-calculator/src/services/facescan/plugins/base.py index 65dbf79b87..d19f580239 100644 --- a/embedding-calculator/src/services/facescan/plugins/base.py +++ b/embedding-calculator/src/services/facescan/plugins/base.py @@ -71,7 +71,7 @@ def _download(cls, url: str, output): def _extract(self, filename: str): os.makedirs(self.path, exist_ok=True) with ZipFile(filename, 'r') as zf: - if self.plugin.unzip_with_untouched_structure: + if self.plugin.retain_folder_structure: for info in zf.infolist(): if info.is_dir(): os.makedirs(Path(self.path) / Path(info.filename)) @@ -133,7 +133,7 @@ def name(self) -> str: return f'{self.backend}.{self.__class__.__name__}' @property - def unzip_with_untouched_structure(self) -> bool: + def retain_folder_structure(self) -> bool: return False def __str__(self): diff --git a/embedding-calculator/src/services/facescan/plugins/facenet/facemask/facemask.py b/embedding-calculator/src/services/facescan/plugins/facenet/facemask/facemask.py index ea53499b9f..bd8a639530 100644 --- a/embedding-calculator/src/services/facescan/plugins/facenet/facemask/facemask.py +++ b/embedding-calculator/src/services/facescan/plugins/facenet/facemask/facemask.py @@ -30,7 +30,7 @@ class MaskDetector(base.BasePlugin): slug = 'mask' LABELS = ('without_mask', 'with_mask', 'mask_weared_incorrect') ml_models = ( - ('inception_v3_on_mafa_kaggle123', '1AeSYb_E_3cqZM67qXnJ__wJDgw6yqTDV'), + ('inception_v3_on_mafa_kaggle123', '1jm2Wd2JEZxhS8O1JjV-kfBOyOYUMxKHq'), ('mobilenet_v2_on_mafa_kaggle123', '1-eqivfTVaXC_9Z5INbYeFVEwBzzqIzm3') ) @@ -42,7 +42,7 @@ def input_image_size(self) -> Tuple[int, int]: return 100, 100 @property - def unzip_with_untouched_structure(self) -> bool: + def retain_folder_structure(self) -> bool: return True @cached_property @@ -52,7 +52,6 @@ def _model(self): def get_value(img: Array3D) -> Tuple[Union[str, Tuple], float]: img = cv2.resize(img, dsize=self.input_image_size, interpolation=cv2.INTER_CUBIC) - img = img[:, :, [2, 1, 0]] img = np.expand_dims(img, 0) scores = model.predict(img) diff --git a/embedding-calculator/src/services/facescan/plugins/insightface/facemask/facemask.py b/embedding-calculator/src/services/facescan/plugins/insightface/facemask/facemask.py index d5c6aa4817..603fbca1f3 100644 --- a/embedding-calculator/src/services/facescan/plugins/insightface/facemask/facemask.py +++ b/embedding-calculator/src/services/facescan/plugins/insightface/facemask/facemask.py @@ -46,12 +46,12 @@ def input_image_size(self) -> Tuple[int, int]: return 224, 224 @property - def unzip_with_untouched_structure(self) -> bool: + def retain_folder_structure(self) -> bool: return True @cached_property def _model(self): - if self.ml_model_name.split('_')[0] == 'resnet18': + if self.ml_model_name and self.ml_model_name.split('_')[0] == 'resnet18': model = vision.resnet18_v1(classes=len(self.LABELS)) else: model = vision.mobilenet_v2_1_0(classes=len(self.LABELS)) diff --git a/embedding-calculator/src/services/facescan/plugins/insightface/insightface.py b/embedding-calculator/src/services/facescan/plugins/insightface/insightface.py index ca3d550551..2d6359e7a3 100644 --- a/embedding-calculator/src/services/facescan/plugins/insightface/insightface.py +++ b/embedding-calculator/src/services/facescan/plugins/insightface/insightface.py @@ -159,7 +159,7 @@ class GenderDetector(BaseGenderAge): def __call__(self, face: plugin_result.FaceDTO): gender, age = self._evaluate_model(face) - return plugin_result.GenderDTO(gender=self.GENDERS[int(gender)], gender_probability=1.) + return plugin_result.GenderDTO(gender=self.GENDERS[int(gender)]) class AgeDetector(BaseGenderAge): @@ -167,7 +167,7 @@ class AgeDetector(BaseGenderAge): def __call__(self, face: plugin_result.FaceDTO): gender, age = self._evaluate_model(face) - return plugin_result.AgeDTO(age=(age, age), age_probability=1.) + return plugin_result.AgeDTO(age=(age, age)) class LandmarksDetector(mixins.LandmarksDetectorMixin, base.BasePlugin): From 10d4539190985a371920c614a2ebe0dea7d2c089 Mon Sep 17 00:00:00 2001 From: Lenovo Date: Mon, 14 Jun 2021 15:39:13 +0400 Subject: [PATCH 030/837] fixed deleted user issue, this version is final --- ui/src/app/app-routing.module.ts | 6 ++++++ ui/src/app/app.module.ts | 6 +++++- ui/src/app/features/tool-bar/tool-bar.component.ts | 2 +- ui/src/app/features/user-list/user-list.component.ts | 2 +- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/ui/src/app/app-routing.module.ts b/ui/src/app/app-routing.module.ts index 85fc9c8bd2..ff3e6e1951 100644 --- a/ui/src/app/app-routing.module.ts +++ b/ui/src/app/app-routing.module.ts @@ -19,6 +19,7 @@ import { RouterModule, Routes } from '@angular/router'; import { MainLayoutComponent } from './ui/main-layout/main-layout.component'; import { DemoLayoutComponent } from './ui/demo-layout/demo-layout.component'; import { UserInfoResolver } from './core/user-info/user-info.resolver'; +import {ManageCollectionComponent} from "./ui/manage-collection/manage-collection.component"; const routes: Routes = [ { @@ -46,6 +47,11 @@ const routes: Routes = [ }, { path: 'login', loadChildren: () => import('./pages/login/login.module').then(m => m.LoginModule) }, { path: 'sign-up', loadChildren: () => import('./pages/sign-up/sign-up.module').then(m => m.SignUpModule) }, + { + path: 'manage-collection', + component: ManageCollectionComponent, + loadChildren: () => import('./ui/manage-collection/manage-collection.module').then(m => m.ManageCollectionModule), + }, { path: '**', redirectTo: '/' }, ]; diff --git a/ui/src/app/app.module.ts b/ui/src/app/app.module.ts index d79d2b4ae6..fe04145040 100644 --- a/ui/src/app/app.module.ts +++ b/ui/src/app/app.module.ts @@ -50,6 +50,7 @@ import { DemoLayoutComponent } from './ui/demo-layout/demo-layout.component'; import { UserInfoResolver } from './core/user-info/user-info.resolver'; import { RoleEditDialogComponent } from './features/role-edit-dialog/role-edit-dialog.component'; import { MatSelectModule } from '@angular/material/select'; +import {ManageCollectionModule} from "./ui/manage-collection/manage-collection.module"; @NgModule({ declarations: [ @@ -93,6 +94,7 @@ import { MatSelectModule } from '@angular/material/select'; }, }), MatSelectModule, + ManageCollectionModule, ], providers: [ FormBuilder, @@ -104,7 +106,9 @@ import { MatSelectModule } from '@angular/material/select'; }, ], bootstrap: [AppComponent], - exports: [], + exports: [ + + ], entryComponents: [CreateDialogComponent, AlertComponent, EditDialogComponent, DeleteDialogComponent, RoleEditDialogComponent], }) export class AppModule {} diff --git a/ui/src/app/features/tool-bar/tool-bar.component.ts b/ui/src/app/features/tool-bar/tool-bar.component.ts index 58eaf4f0b9..c84b05c749 100644 --- a/ui/src/app/features/tool-bar/tool-bar.component.ts +++ b/ui/src/app/features/tool-bar/tool-bar.component.ts @@ -40,7 +40,7 @@ export class ToolBarComponent implements OnInit{ constructor(private dialog: MatDialog, private translate: TranslateService) {} ngOnInit(): void { - console.log(this.userName, 'sana'); + } changeArrowIcon(): void { diff --git a/ui/src/app/features/user-list/user-list.component.ts b/ui/src/app/features/user-list/user-list.component.ts index fdafd697f7..a491d62467 100644 --- a/ui/src/app/features/user-list/user-list.component.ts +++ b/ui/src/app/features/user-list/user-list.component.ts @@ -27,7 +27,7 @@ import { ITableConfig } from '../table/table.component'; import { UserListFacade } from './user-list-facade'; @Component({ - selector: 'app-user-list-container', + selector: ' app-user-list-container', templateUrl: './user-list.component.html', styleUrls: ['./user-list.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, From c20b0c21ee522268940d62290a536532e6261ee6 Mon Sep 17 00:00:00 2001 From: Lenovo Date: Mon, 14 Jun 2021 16:47:59 +0400 Subject: [PATCH 031/837] latest version of fixed deleted user bug --- ui/src/app/app-routing.module.ts | 6 ------ ui/src/app/app.module.ts | 2 -- ui/src/app/features/tool-bar/tool-bar.component.ts | 8 +++----- .../features/user-table/user-table.component.ts | 14 ++++++-------- 4 files changed, 9 insertions(+), 21 deletions(-) diff --git a/ui/src/app/app-routing.module.ts b/ui/src/app/app-routing.module.ts index ff3e6e1951..85fc9c8bd2 100644 --- a/ui/src/app/app-routing.module.ts +++ b/ui/src/app/app-routing.module.ts @@ -19,7 +19,6 @@ import { RouterModule, Routes } from '@angular/router'; import { MainLayoutComponent } from './ui/main-layout/main-layout.component'; import { DemoLayoutComponent } from './ui/demo-layout/demo-layout.component'; import { UserInfoResolver } from './core/user-info/user-info.resolver'; -import {ManageCollectionComponent} from "./ui/manage-collection/manage-collection.component"; const routes: Routes = [ { @@ -47,11 +46,6 @@ const routes: Routes = [ }, { path: 'login', loadChildren: () => import('./pages/login/login.module').then(m => m.LoginModule) }, { path: 'sign-up', loadChildren: () => import('./pages/sign-up/sign-up.module').then(m => m.SignUpModule) }, - { - path: 'manage-collection', - component: ManageCollectionComponent, - loadChildren: () => import('./ui/manage-collection/manage-collection.module').then(m => m.ManageCollectionModule), - }, { path: '**', redirectTo: '/' }, ]; diff --git a/ui/src/app/app.module.ts b/ui/src/app/app.module.ts index fe04145040..4bba48bd9d 100644 --- a/ui/src/app/app.module.ts +++ b/ui/src/app/app.module.ts @@ -50,7 +50,6 @@ import { DemoLayoutComponent } from './ui/demo-layout/demo-layout.component'; import { UserInfoResolver } from './core/user-info/user-info.resolver'; import { RoleEditDialogComponent } from './features/role-edit-dialog/role-edit-dialog.component'; import { MatSelectModule } from '@angular/material/select'; -import {ManageCollectionModule} from "./ui/manage-collection/manage-collection.module"; @NgModule({ declarations: [ @@ -94,7 +93,6 @@ import {ManageCollectionModule} from "./ui/manage-collection/manage-collection.m }, }), MatSelectModule, - ManageCollectionModule, ], providers: [ FormBuilder, diff --git a/ui/src/app/features/tool-bar/tool-bar.component.ts b/ui/src/app/features/tool-bar/tool-bar.component.ts index c84b05c749..b3a9bf263c 100644 --- a/ui/src/app/features/tool-bar/tool-bar.component.ts +++ b/ui/src/app/features/tool-bar/tool-bar.component.ts @@ -13,7 +13,7 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ -import {ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; +import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { TranslateService } from '@ngx-translate/core'; import { filter, first } from 'rxjs/operators'; @@ -26,7 +26,7 @@ import { EditUserInfoDialogComponent } from '../edit-user-info-dialog/edit-user- styleUrls: ['./tool-bar.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ToolBarComponent implements OnInit{ +export class ToolBarComponent implements OnInit { @Input() userAvatarInfo: string; @Input() userName: string; @Input() isUserInfoAvailable: boolean; @@ -39,9 +39,7 @@ export class ToolBarComponent implements OnInit{ constructor(private dialog: MatDialog, private translate: TranslateService) {} - ngOnInit(): void { - - } + ngOnInit(): void {} changeArrowIcon(): void { this.openMenu = !this.openMenu; diff --git a/ui/src/app/features/user-table/user-table.component.ts b/ui/src/app/features/user-table/user-table.component.ts index a26e010a01..0b902d7fd4 100644 --- a/ui/src/app/features/user-table/user-table.component.ts +++ b/ui/src/app/features/user-table/user-table.component.ts @@ -23,7 +23,7 @@ import { TranslateService } from '@ngx-translate/core'; import { RoleEditDialogComponent } from '../role-edit-dialog/role-edit-dialog.component'; import { MatDialog } from '@angular/material/dialog'; import { UserRole } from '../../data/interfaces/user-role'; -import {ActivatedRoute, Router} from "@angular/router"; + @Component({ selector: 'app-user-table', @@ -43,7 +43,7 @@ export class UserTableComponent extends TableComponent implements OnInit, OnChan @Input() searchText: string; @Output() deleteUser = new EventEmitter(); - constructor(private dialog: MatDialog, private translate: TranslateService,) { + constructor(private dialog: MatDialog, private translate: TranslateService) { super(); this.refreshPage(); } @@ -51,7 +51,6 @@ export class UserTableComponent extends TableComponent implements OnInit, OnChan ngOnInit() { this.message = this.createMessage; this.noResultMessage = this.translate.instant('users.search.no_results'); - } ngOnChanges(): void { @@ -103,12 +102,11 @@ export class UserTableComponent extends TableComponent implements OnInit, OnChan } refreshPage(): void { - if (!localStorage.getItem('foo')) { - localStorage.setItem('foo', 'no reload') - location.reload() + if (!localStorage.getItem('refresh-page')) { + localStorage.setItem('refresh-page', 'no reload'); + location.reload(); } else { - localStorage.removeItem('foo') + localStorage.removeItem('refresh-page'); } } - } From 934e6ca6ce2b8c3119674a831edf9a4a2309d1e4 Mon Sep 17 00:00:00 2001 From: MKN Date: Wed, 16 Jun 2021 11:31:53 +0500 Subject: [PATCH 032/837] Developed postgres notification for synchronization Caches #EFRS-835 --- java/api/pom.xml | 5 ++ .../cache/EmbeddingCacheProvider.java | 27 +++++++ .../config/repository/DbConfig.java | 2 + .../repository/NotificationDbConfig.java | 32 ++++++++ .../core/trainservice/dto/CacheActionDto.java | 16 ++++ .../service/NotificationReceiverService.java | 79 +++++++++++++++++++ .../service/NotificationSenderService.java | 43 ++++++++++ .../cache/EmbeddingCacheProviderTest.java | 4 + .../controller/RecognizeControllerTest.java | 8 ++ java/pom.xml | 1 + 10 files changed, 217 insertions(+) create mode 100644 java/api/src/main/java/com/exadel/frs/core/trainservice/config/repository/NotificationDbConfig.java create mode 100644 java/api/src/main/java/com/exadel/frs/core/trainservice/dto/CacheActionDto.java create mode 100644 java/api/src/main/java/com/exadel/frs/core/trainservice/service/NotificationReceiverService.java create mode 100644 java/api/src/main/java/com/exadel/frs/core/trainservice/service/NotificationSenderService.java diff --git a/java/api/pom.xml b/java/api/pom.xml index 8c34c86149..2adfdf898d 100644 --- a/java/api/pom.xml +++ b/java/api/pom.xml @@ -140,6 +140,11 @@ nd4j-native ${nd4j.classifier} + + com.impossibl.pgjdbc-ng + pgjdbc-ng + ${pgjdbc-ng.version} + org.springframework.boot spring-boot-starter-validation diff --git a/java/api/src/main/java/com/exadel/frs/core/trainservice/cache/EmbeddingCacheProvider.java b/java/api/src/main/java/com/exadel/frs/core/trainservice/cache/EmbeddingCacheProvider.java index 32d1d1ca78..231e1b1c06 100644 --- a/java/api/src/main/java/com/exadel/frs/core/trainservice/cache/EmbeddingCacheProvider.java +++ b/java/api/src/main/java/com/exadel/frs/core/trainservice/cache/EmbeddingCacheProvider.java @@ -1,6 +1,8 @@ package com.exadel.frs.core.trainservice.cache; +import com.exadel.frs.core.trainservice.dto.CacheActionDto; import com.exadel.frs.core.trainservice.service.EmbeddingService; +import com.exadel.frs.core.trainservice.service.NotificationSenderService; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import lombok.RequiredArgsConstructor; @@ -19,6 +21,8 @@ public class EmbeddingCacheProvider { private final EmbeddingService embeddingService; + private final NotificationSenderService notificationSenderService; + private static final Cache cache = CacheBuilder.newBuilder() .expireAfterAccess(CACHE_EXPIRATION, TimeUnit.SECONDS) @@ -29,7 +33,10 @@ public EmbeddingCollection getOrLoad(final String apiKey) { var result = cache.getIfPresent(apiKey); if (result == null) { result = embeddingService.doWithEmbeddingsStream(apiKey, EmbeddingCollection::from); + cache.put(apiKey, result); + + notifyCacheEvent("UPDATE", apiKey); } return result; @@ -38,9 +45,29 @@ public EmbeddingCollection getOrLoad(final String apiKey) { public void ifPresent(String apiKey, Consumer consumer) { Optional.ofNullable(cache.getIfPresent(apiKey)) .ifPresent(consumer); + //notify put with key and result + EmbeddingCollection dd = cache.getIfPresent(apiKey); + notifyCacheEvent("UPDATE", apiKey); } public void invalidate(final String apiKey) { cache.invalidate(apiKey); + notifyCacheEvent("DELETE", apiKey); + } + + + public void receivePutOnCache(String apiKey) { + var result = embeddingService.doWithEmbeddingsStream(apiKey, EmbeddingCollection::from); + cache.put(apiKey, result); + notifyCacheEvent("UPDATE", apiKey); + } + + public void receiveInvalidateCache(final String apiKey) { + cache.invalidate(apiKey); + } + + private void notifyCacheEvent(String event, String apiKey) { + CacheActionDto cacheActionDto = new CacheActionDto(event, apiKey); + notificationSenderService.notifyCacheChange(cacheActionDto); } } diff --git a/java/api/src/main/java/com/exadel/frs/core/trainservice/config/repository/DbConfig.java b/java/api/src/main/java/com/exadel/frs/core/trainservice/config/repository/DbConfig.java index c68b9aa986..057574cffa 100644 --- a/java/api/src/main/java/com/exadel/frs/core/trainservice/config/repository/DbConfig.java +++ b/java/api/src/main/java/com/exadel/frs/core/trainservice/config/repository/DbConfig.java @@ -24,6 +24,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; import org.springframework.core.env.Environment; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.orm.jpa.JpaTransactionManager; @@ -60,6 +61,7 @@ public LocalContainerEntityManagerFactoryBean pgEntityManager(@Autowired DataSou } @Bean(name = "dsPg") + @Primary @ConfigurationProperties(prefix = "spring.datasource-pg.hikari") public HikariDataSource pgDataSource(@Autowired DataSourceProperties dataSourceProperties) { return dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build(); diff --git a/java/api/src/main/java/com/exadel/frs/core/trainservice/config/repository/NotificationDbConfig.java b/java/api/src/main/java/com/exadel/frs/core/trainservice/config/repository/NotificationDbConfig.java new file mode 100644 index 0000000000..2abc0d446a --- /dev/null +++ b/java/api/src/main/java/com/exadel/frs/core/trainservice/config/repository/NotificationDbConfig.java @@ -0,0 +1,32 @@ +package com.exadel.frs.core.trainservice.config.repository; + +import com.impossibl.postgres.jdbc.PGDataSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; + +@Configuration +public class NotificationDbConfig { + + @Autowired + private Environment env; + + @Bean(name = "dsPgNot") + public PGDataSource pgNotificationDatasource() { + PGDataSource dataSource = new PGDataSource(); + + String dbUrl = env.getProperty("spring.datasource-pg.url"); + String dbUsername= env.getProperty("spring.datasource-pg.username"); + String dbPassword= env.getProperty("spring.datasource-pg.password"); + + String databaseUrl = dbUrl.replaceAll("postgresql", "pgsql"); + + dataSource.setDatabaseUrl(databaseUrl); + dataSource.setUser(dbUsername); + dataSource.setPassword(dbPassword); + + return dataSource; + } +} diff --git a/java/api/src/main/java/com/exadel/frs/core/trainservice/dto/CacheActionDto.java b/java/api/src/main/java/com/exadel/frs/core/trainservice/dto/CacheActionDto.java new file mode 100644 index 0000000000..67bef814e1 --- /dev/null +++ b/java/api/src/main/java/com/exadel/frs/core/trainservice/dto/CacheActionDto.java @@ -0,0 +1,16 @@ +package com.exadel.frs.core.trainservice.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class CacheActionDto { + @JsonProperty("cacheAction") + private String cacheAction; + @JsonProperty("apiKey") + private String apiKey; + } diff --git a/java/api/src/main/java/com/exadel/frs/core/trainservice/service/NotificationReceiverService.java b/java/api/src/main/java/com/exadel/frs/core/trainservice/service/NotificationReceiverService.java new file mode 100644 index 0000000000..e27cede8a9 --- /dev/null +++ b/java/api/src/main/java/com/exadel/frs/core/trainservice/service/NotificationReceiverService.java @@ -0,0 +1,79 @@ +package com.exadel.frs.core.trainservice.service; + +import com.exadel.frs.core.trainservice.cache.EmbeddingCacheProvider; +import com.exadel.frs.core.trainservice.dto.CacheActionDto; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.impossibl.postgres.api.jdbc.PGConnection; +import com.impossibl.postgres.api.jdbc.PGNotificationListener; +import com.impossibl.postgres.jdbc.PGDataSource; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import java.sql.SQLException; +import java.sql.Statement; + +@Service("notificationReceiverService") +@Slf4j +@RequiredArgsConstructor +public class NotificationReceiverService { + + @Qualifier("dsPgNot") + private final PGDataSource pgNotificationDatasource; + + private final EmbeddingCacheProvider embeddingCacheProvider; + + private final ObjectMapper objectMapper; + + @PostConstruct + public void setUpNotification() { + PGConnection connection = null; + PGNotificationListener listener = new PGNotificationListener() { + @Override + public void notification(int processId, String channelName, String payload) { + log.info(String.format("/channels3/ channel name: %1$s payload %2$s", channelName, payload)); + + if (channelName.equals("face_collection_update_msg")) { + updateCacheWithNotification(payload); + } + } + + @Override + public void closed() { + log.info("face_collection_update_msg closed"); + } + }; + + try { + connection = (PGConnection) pgNotificationDatasource.getConnection(); + Statement statement = connection.createStatement(); + statement.executeUpdate("LISTEN face_collection_update_msg"); + + statement.close(); + } catch (SQLException ex) { + log.error(ex.getMessage()); + } + connection.addNotificationListener(listener); + } + + private void updateCacheWithNotification(String payload) { + + try { + CacheActionDto cacheActionDto = objectMapper.readValue(payload, CacheActionDto.class); + if (cacheActionDto != null && !StringUtils.isBlank(cacheActionDto.getApiKey()) && !StringUtils.isBlank(cacheActionDto.getCacheAction())) { + + if (cacheActionDto.getCacheAction().equals("UPDATE")) { + embeddingCacheProvider.receivePutOnCache(cacheActionDto.getApiKey()); + } else if (cacheActionDto.getCacheAction().equals("DELETE")) { + embeddingCacheProvider.receiveInvalidateCache(cacheActionDto.getApiKey()); + } + } + } catch (JsonProcessingException e) { + log.error(e.getMessage()); + } + } +} diff --git a/java/api/src/main/java/com/exadel/frs/core/trainservice/service/NotificationSenderService.java b/java/api/src/main/java/com/exadel/frs/core/trainservice/service/NotificationSenderService.java new file mode 100644 index 0000000000..26cd71977f --- /dev/null +++ b/java/api/src/main/java/com/exadel/frs/core/trainservice/service/NotificationSenderService.java @@ -0,0 +1,43 @@ +package com.exadel.frs.core.trainservice.service; + +import com.exadel.frs.core.trainservice.dto.CacheActionDto; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.impossibl.postgres.api.jdbc.PGConnection; +import com.impossibl.postgres.jdbc.PGDataSource; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import java.sql.SQLException; +import java.sql.Statement; + + +@Service("notificationSenderService") +@Slf4j +@RequiredArgsConstructor +public class NotificationSenderService { + @Qualifier("dsPgNot") + private final PGDataSource pgNotificationDatasource; + + public void notifyCacheChange(CacheActionDto cacheActionDto) { + try { + PGConnection connection = (PGConnection) pgNotificationDatasource.getConnection(); + Statement statement = connection.createStatement(); + ObjectMapper mapper = new ObjectMapper(); + + try { + String actionString = String.format("NOTIFY face_collection_update_msg, '%s'", mapper.writerWithDefaultPrettyPrinter().writeValueAsString(cacheActionDto)); + statement.execute(actionString); + } catch (JsonProcessingException e) { + log.error(e.getMessage()); + } + + statement.close(); + } catch (SQLException e) { + log.error(e.getMessage()); + } + } +} diff --git a/java/api/src/test/java/com/exadel/frs/core/trainservice/cache/EmbeddingCacheProviderTest.java b/java/api/src/test/java/com/exadel/frs/core/trainservice/cache/EmbeddingCacheProviderTest.java index 79686139e5..bc304aa359 100644 --- a/java/api/src/test/java/com/exadel/frs/core/trainservice/cache/EmbeddingCacheProviderTest.java +++ b/java/api/src/test/java/com/exadel/frs/core/trainservice/cache/EmbeddingCacheProviderTest.java @@ -18,6 +18,7 @@ import com.exadel.frs.commonservice.entity.Embedding; import com.exadel.frs.core.trainservice.service.EmbeddingService; +import com.exadel.frs.core.trainservice.service.NotificationSenderService; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -43,6 +44,9 @@ class EmbeddingCacheProviderTest { @Mock private EmbeddingService embeddingService; + @Mock + private NotificationSenderService notificationSenderService; + @InjectMocks private EmbeddingCacheProvider embeddingCacheProvider; diff --git a/java/api/src/test/java/com/exadel/frs/core/trainservice/controller/RecognizeControllerTest.java b/java/api/src/test/java/com/exadel/frs/core/trainservice/controller/RecognizeControllerTest.java index b3a0ece485..69f1a4baed 100644 --- a/java/api/src/test/java/com/exadel/frs/core/trainservice/controller/RecognizeControllerTest.java +++ b/java/api/src/test/java/com/exadel/frs/core/trainservice/controller/RecognizeControllerTest.java @@ -24,11 +24,14 @@ import com.exadel.frs.core.trainservice.component.FaceClassifierPredictor; import com.exadel.frs.core.trainservice.config.IntegrationTest; import com.exadel.frs.core.trainservice.dto.Base64File; +import com.exadel.frs.core.trainservice.service.NotificationReceiverService; +import com.exadel.frs.core.trainservice.service.NotificationSenderService; import com.exadel.frs.core.trainservice.validation.ImageExtensionValidator; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.val; import org.apache.commons.lang3.tuple.Pair; import org.junit.jupiter.api.Test; +import org.mockito.Mock; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.mock.mockito.MockBean; @@ -61,6 +64,11 @@ class RecognizeControllerTest { @MockBean private ImageExtensionValidator validator; + @Mock + private NotificationSenderService notificationSenderService; + @MockBean + private NotificationReceiverService notificationReceiverService; + @MockBean private FacesApiClient client; diff --git a/java/pom.xml b/java/pom.xml index fac84ec5c6..8bee428f4b 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -46,6 +46,7 @@ 11 linux-x86_64 4.3.5 + 0.8.9 From c63cd83d054e9448e619a3efee29ec592c67b0a8 Mon Sep 17 00:00:00 2001 From: Ivan Kurnosau Date: Wed, 16 Jun 2021 10:16:09 +0300 Subject: [PATCH 033/837] MXNet GPU support and new README --- embedding-calculator/README.md | 12 +++++++++++- .../plugins/insightface/facemask/facemask.py | 9 ++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/embedding-calculator/README.md b/embedding-calculator/README.md index f5bcee9b55..fc066349b6 100644 --- a/embedding-calculator/README.md +++ b/embedding-calculator/README.md @@ -102,6 +102,8 @@ Pass to `EXTRA_PLUGINS` comma-separated names of plugins. | facenet.LandmarksDetector | landmarks | Facenet | Tensorflow | + | | insightface.LandmarksDetector | landmarks | insightface | MXNet | + | | insightface.Landmarks2d106Detector | landmarks2d106 | insightface | MXNet | + | +| facenet.facemask.MaskDetector | mask | facemask | Tensorflow | + | +| insightface.facemask.MaskDetector | mask | facemask | MXNet | + | Notes: * `facenet.LandmarksDetector` and `insightface.LandmarksDetector` extract landmarks @@ -114,7 +116,7 @@ Notes: ``` FACE_DETECTION_PLUGIN=facenet.FaceDetector CALCULATION_PLUGIN=facenet.Calculator -EXTRA_PLUGINS=agegender.AgeDetector,agegender.GenderDetector +EXTRA_PLUGINS=agegender.AgeDetector,agegender.GenderDetector,facenet.facemask.MaskDetector ``` #### Pre-trained models @@ -143,6 +145,14 @@ List of pre-trained models: * arcface_mobilefacenet * [arcface-r50-msfdrop75](https://github.com/deepinsight/insightface/tree/master/recognition/SubCenter-ArcFace) * [arcface-r100-msfdrop75](https://github.com/deepinsight/insightface/tree/master/recognition/SubCenter-ArcFace) + +* facenet.facemask.MaskDetector + * inception_v3_on_mafa_kaggle123 (default) + * mobilenet_v2_on_mafa_kaggle123 + +* insightface.facemask.MaskDetector + * mobilenet_v2_on_mafa_kaggle123 (default) + * resnet18_on_mafa_kaggle123 #### Optimization diff --git a/embedding-calculator/src/services/facescan/plugins/insightface/facemask/facemask.py b/embedding-calculator/src/services/facescan/plugins/insightface/facemask/facemask.py index 603fbca1f3..633889741a 100644 --- a/embedding-calculator/src/services/facescan/plugins/insightface/facemask/facemask.py +++ b/embedding-calculator/src/services/facescan/plugins/insightface/facemask/facemask.py @@ -51,10 +51,13 @@ def retain_folder_structure(self) -> bool: @cached_property def _model(self): + gpu_count = mx.context.num_gpus() + ctx = mx.gpu() if gpu_count > 0 else mx.cpu() + if self.ml_model_name and self.ml_model_name.split('_')[0] == 'resnet18': - model = vision.resnet18_v1(classes=len(self.LABELS)) + model = vision.resnet18_v1(classes=len(self.LABELS), ctx=ctx) else: - model = vision.mobilenet_v2_1_0(classes=len(self.LABELS)) + model = vision.mobilenet_v2_1_0(classes=len(self.LABELS), ctx=ctx) model_path = Path(self.ml_model.path) / Path(os.listdir(self.ml_model.path)[0]) model.load_parameters(str(model_path)) @@ -62,7 +65,7 @@ def get_value(img: Array3D) -> Tuple[Union[str, Tuple], float]: data = img.reshape((1,) + img.shape) data = mx.nd.array(data) - scores = model(self.img_transforms(data)).softmax().asnumpy() + scores = model(mx.nd.array(self.img_transforms(data), ctx=ctx)).softmax().asnumpy() val = self.LABELS[int(np.argmax(scores, axis=1)[0])] prob = scores[0][int(np.argmax(scores, axis=1)[0])] return val, prob From d141c88759d5e06ee32cd028b8e454927d92bb34 Mon Sep 17 00:00:00 2001 From: Lenovo Date: Fri, 18 Jun 2021 14:37:02 +0400 Subject: [PATCH 034/837] implemented left side subject list of manage collection --- ui/src/app/app-routing.module.ts | 5 ++ ui/src/app/app.module.ts | 2 + ...ection-manager-subject-left.component.html | 19 ++++ ...ection-manager-subject-left.component.scss | 88 +++++++++++++++++++ ...ion-manager-subject-left.component.spec.ts | 25 ++++++ ...llection-manager-subject-left.component.ts | 11 +++ ...ction-manager-subject-right.component.html | 1 + ...ction-manager-subject-right.component.scss | 0 ...on-manager-subject-right.component.spec.ts | 25 ++++++ ...lection-manager-subject-right.component.ts | 10 +++ .../app/pages/dashboard/dashboard.module.ts | 3 +- .../manage-collection.component.html | 14 +++ .../manage-collection.component.scss | 57 ++++++++++++ .../manage-collection.component.spec.ts | 25 ++++++ .../manage-collection.component.ts | 10 +++ .../manage-collection.module.ts | 12 +++ .../avatar-subject-hover.svg | 11 +++ .../img/manage-collection/avatar-subject.svg | 11 +++ 18 files changed, 328 insertions(+), 1 deletion(-) create mode 100644 ui/src/app/features/collection-manager-subject-left/collection-manager-subject-left.component.html create mode 100644 ui/src/app/features/collection-manager-subject-left/collection-manager-subject-left.component.scss create mode 100644 ui/src/app/features/collection-manager-subject-left/collection-manager-subject-left.component.spec.ts create mode 100644 ui/src/app/features/collection-manager-subject-left/collection-manager-subject-left.component.ts create mode 100644 ui/src/app/features/collection-manager-subject-right/collection-manager-subject-right.component.html create mode 100644 ui/src/app/features/collection-manager-subject-right/collection-manager-subject-right.component.scss create mode 100644 ui/src/app/features/collection-manager-subject-right/collection-manager-subject-right.component.spec.ts create mode 100644 ui/src/app/features/collection-manager-subject-right/collection-manager-subject-right.component.ts create mode 100644 ui/src/app/pages/manage-collection/manage-collection.component.html create mode 100644 ui/src/app/pages/manage-collection/manage-collection.component.scss create mode 100644 ui/src/app/pages/manage-collection/manage-collection.component.spec.ts create mode 100644 ui/src/app/pages/manage-collection/manage-collection.component.ts create mode 100644 ui/src/app/pages/manage-collection/manage-collection.module.ts create mode 100644 ui/src/assets/img/manage-collection/avatar-subject-hover.svg create mode 100644 ui/src/assets/img/manage-collection/avatar-subject.svg diff --git a/ui/src/app/app-routing.module.ts b/ui/src/app/app-routing.module.ts index 85fc9c8bd2..af904fc542 100644 --- a/ui/src/app/app-routing.module.ts +++ b/ui/src/app/app-routing.module.ts @@ -19,6 +19,7 @@ import { RouterModule, Routes } from '@angular/router'; import { MainLayoutComponent } from './ui/main-layout/main-layout.component'; import { DemoLayoutComponent } from './ui/demo-layout/demo-layout.component'; import { UserInfoResolver } from './core/user-info/user-info.resolver'; +import {ManageCollectionComponent} from "./pages/manage-collection/manage-collection.component"; const routes: Routes = [ { @@ -46,6 +47,10 @@ const routes: Routes = [ }, { path: 'login', loadChildren: () => import('./pages/login/login.module').then(m => m.LoginModule) }, { path: 'sign-up', loadChildren: () => import('./pages/sign-up/sign-up.module').then(m => m.SignUpModule) }, + { + path: 'manage-collection', + component: ManageCollectionComponent, + }, { path: '**', redirectTo: '/' }, ]; diff --git a/ui/src/app/app.module.ts b/ui/src/app/app.module.ts index d79d2b4ae6..4713e8e4e6 100644 --- a/ui/src/app/app.module.ts +++ b/ui/src/app/app.module.ts @@ -50,6 +50,7 @@ import { DemoLayoutComponent } from './ui/demo-layout/demo-layout.component'; import { UserInfoResolver } from './core/user-info/user-info.resolver'; import { RoleEditDialogComponent } from './features/role-edit-dialog/role-edit-dialog.component'; import { MatSelectModule } from '@angular/material/select'; +import { ManageCollectionModule } from './pages/manage-collection/manage-collection.module'; @NgModule({ declarations: [ @@ -85,6 +86,7 @@ import { MatSelectModule } from '@angular/material/select'; MatRadioModule, BreadcrumbsModule, BreadcrumbsContainerModule, + ManageCollectionModule, TranslateModule.forRoot({ loader: { provide: TranslateLoader, diff --git a/ui/src/app/features/collection-manager-subject-left/collection-manager-subject-left.component.html b/ui/src/app/features/collection-manager-subject-left/collection-manager-subject-left.component.html new file mode 100644 index 0000000000..a62310c70b --- /dev/null +++ b/ui/src/app/features/collection-manager-subject-left/collection-manager-subject-left.component.html @@ -0,0 +1,19 @@ +
+
+ +
+ +
+
+
+ + +
+ John Done +
+
+
+
+
diff --git a/ui/src/app/features/collection-manager-subject-left/collection-manager-subject-left.component.scss b/ui/src/app/features/collection-manager-subject-left/collection-manager-subject-left.component.scss new file mode 100644 index 0000000000..7c8b45e93f --- /dev/null +++ b/ui/src/app/features/collection-manager-subject-left/collection-manager-subject-left.component.scss @@ -0,0 +1,88 @@ +.main { + padding: 24px 8px 0px 8px; + //min-height: 425px; + //height: 425px; + //overflow-y: scroll; + .button { + padding-bottom: 16px; + border-bottom: 1px solid #f1f1f1; + .btn { + border: none; + border-radius: 4px; + outline: none; + width: 100%; + height: 43px; + color: #fff; + text-align: center; + font-family: Poppins; + font-size: 16px; + font-style: normal; + background: #0082ca; + cursor: pointer; + } + } + .search { + margin-top: 15px; + display: flex; + margin-bottom: 24px; + input { + outline: none; + border: 1px solid #e5e5e5; + height: 39px; + width: 100%; + font-size: 16px; + font-family: Poppins; + padding-left: 12px; + color: rgba(34, 34, 34, 0.5); + border-radius: 4px; + } + ::placeholder { + color: rgba(34, 34, 34, 0.5); + } + } + .subjects { + padding-bottom: 20px; + height: 260px; + min-height: 260px; + overflow-y: scroll; + .section { + padding: 13px 0px 13px 8px; + border-bottom: 1px solid rgba(229, 229, 229, 1); + border-radius: 1px; + .avatar { + margin-right: 13px; + display: flex; + align-items: center; + .hover { + display: none; + } + .default { + display: block; + } + .name { + span { + font-family: Poppins; + font-size: 18px; + color: rgba(34, 34, 34, 0.8); + margin-left: 13px; + } + } + } + &:hover { + border-bottom: 1px solid rgba(0, 130, 202, 1); + background: rgba(0, 130, 202, 0.1); + .hover { + display: block; + } + .default { + display: none; + } + .name { + span { + color: rgba(34, 34, 34, 1); + } + } + } + } + } +} diff --git a/ui/src/app/features/collection-manager-subject-left/collection-manager-subject-left.component.spec.ts b/ui/src/app/features/collection-manager-subject-left/collection-manager-subject-left.component.spec.ts new file mode 100644 index 0000000000..f08818428a --- /dev/null +++ b/ui/src/app/features/collection-manager-subject-left/collection-manager-subject-left.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CollectionManagerSubjectLeftComponent } from './collection-manager-subject-left.component'; + +describe('CollectionManagerSubjectLeftComponent', () => { + let component: CollectionManagerSubjectLeftComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ CollectionManagerSubjectLeftComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(CollectionManagerSubjectLeftComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ui/src/app/features/collection-manager-subject-left/collection-manager-subject-left.component.ts b/ui/src/app/features/collection-manager-subject-left/collection-manager-subject-left.component.ts new file mode 100644 index 0000000000..cf3c8adf4b --- /dev/null +++ b/ui/src/app/features/collection-manager-subject-left/collection-manager-subject-left.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-collection-manager-subject-left', + templateUrl: './collection-manager-subject-left.component.html', + styleUrls: ['./collection-manager-subject-left.component.scss'], +}) +export class CollectionManagerSubjectLeftComponent { + testArr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + constructor() {} +} diff --git a/ui/src/app/features/collection-manager-subject-right/collection-manager-subject-right.component.html b/ui/src/app/features/collection-manager-subject-right/collection-manager-subject-right.component.html new file mode 100644 index 0000000000..acb5664ec5 --- /dev/null +++ b/ui/src/app/features/collection-manager-subject-right/collection-manager-subject-right.component.html @@ -0,0 +1 @@ +

collection-manager-subject-right works!

\ No newline at end of file diff --git a/ui/src/app/features/collection-manager-subject-right/collection-manager-subject-right.component.scss b/ui/src/app/features/collection-manager-subject-right/collection-manager-subject-right.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ui/src/app/features/collection-manager-subject-right/collection-manager-subject-right.component.spec.ts b/ui/src/app/features/collection-manager-subject-right/collection-manager-subject-right.component.spec.ts new file mode 100644 index 0000000000..107b5a7ed6 --- /dev/null +++ b/ui/src/app/features/collection-manager-subject-right/collection-manager-subject-right.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CollectionManagerSubjectRightComponent } from './collection-manager-subject-right.component'; + +describe('CollectionManagerSubjectRightComponent', () => { + let component: CollectionManagerSubjectRightComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ CollectionManagerSubjectRightComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(CollectionManagerSubjectRightComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ui/src/app/features/collection-manager-subject-right/collection-manager-subject-right.component.ts b/ui/src/app/features/collection-manager-subject-right/collection-manager-subject-right.component.ts new file mode 100644 index 0000000000..d46d629fb9 --- /dev/null +++ b/ui/src/app/features/collection-manager-subject-right/collection-manager-subject-right.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-collection-manager-subject-right', + templateUrl: './collection-manager-subject-right.component.html', + styleUrls: ['./collection-manager-subject-right.component.scss'], +}) +export class CollectionManagerSubjectRightComponent { + constructor() {} +} diff --git a/ui/src/app/pages/dashboard/dashboard.module.ts b/ui/src/app/pages/dashboard/dashboard.module.ts index 023d15ee83..7b8e889b35 100644 --- a/ui/src/app/pages/dashboard/dashboard.module.ts +++ b/ui/src/app/pages/dashboard/dashboard.module.ts @@ -29,6 +29,7 @@ import { UserTableModule } from 'src/app/features/user-table/user-table.module'; import { ToolBarModule } from '../../features/tool-bar/tool-bar.module'; import { DashboardComponent } from './dashboard.component'; +import {ManageCollectionComponent} from "../manage-collection/manage-collection.component"; @NgModule({ declarations: [DashboardComponent], @@ -43,7 +44,7 @@ import { DashboardComponent } from './dashboard.component'; MatFormFieldModule, MatInputModule, FormsModule, - RouterModule.forChild([{ path: '', component: DashboardComponent }]), + RouterModule.forChild([{ path: '', component: DashboardComponent }, {path: 'manage-collection', component: ManageCollectionComponent}]), ToolBarModule, MatCardModule, ], diff --git a/ui/src/app/pages/manage-collection/manage-collection.component.html b/ui/src/app/pages/manage-collection/manage-collection.component.html new file mode 100644 index 0000000000..dc75c8770f --- /dev/null +++ b/ui/src/app/pages/manage-collection/manage-collection.component.html @@ -0,0 +1,14 @@ +
+
+ + + + + + + +
+
diff --git a/ui/src/app/pages/manage-collection/manage-collection.component.scss b/ui/src/app/pages/manage-collection/manage-collection.component.scss new file mode 100644 index 0000000000..78113cef4e --- /dev/null +++ b/ui/src/app/pages/manage-collection/manage-collection.component.scss @@ -0,0 +1,57 @@ +.grid-container { + .navigation { + display: flex; + align-items: center; + span { + font-size: 14px; + font-family: Poppins; + &:first-child { + color: #0082ca; + } + &:last-child { + color: rgba(34, 34, 34, 1); + } + } + } + display: flex; + justify-content: space-between; + .card-left { + width: 220px; + transform: perspective(1px); + padding: 0px !important; + } + .card-right { + padding: 0px !important; + width: 100%; + transform: perspective(1px); + max-width: 100%; + margin-left: 20px; + } + .search { + .input { + width: 100%; + height: 43px; + border: 1px solid #e5e5e5; + border-radius: 4px; + font-size: 15px; + padding-left: 20px; + font-family: Poppins; + color: #222222; + } + } +} +@media (max-width: 990px) { + .grid-container { + display: flex; + flex-direction: column; + justify-content: unset; + .card-left { + width: 100%; + } + .card-right { + width: 100%; + margin-left: unset; + margin-top: 20px; + } + } +} diff --git a/ui/src/app/pages/manage-collection/manage-collection.component.spec.ts b/ui/src/app/pages/manage-collection/manage-collection.component.spec.ts new file mode 100644 index 0000000000..58813e8c25 --- /dev/null +++ b/ui/src/app/pages/manage-collection/manage-collection.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ManageCollectionComponent } from './manage-collection.component'; + +describe('ManageCollectionComponent', () => { + let component: ManageCollectionComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ManageCollectionComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ManageCollectionComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ui/src/app/pages/manage-collection/manage-collection.component.ts b/ui/src/app/pages/manage-collection/manage-collection.component.ts new file mode 100644 index 0000000000..f3a7cf5d59 --- /dev/null +++ b/ui/src/app/pages/manage-collection/manage-collection.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-manage-collection', + templateUrl: './manage-collection.component.html', + styleUrls: ['./manage-collection.component.scss'], +}) +export class ManageCollectionComponent { + constructor() {} +} diff --git a/ui/src/app/pages/manage-collection/manage-collection.module.ts b/ui/src/app/pages/manage-collection/manage-collection.module.ts new file mode 100644 index 0000000000..5ea86dab2c --- /dev/null +++ b/ui/src/app/pages/manage-collection/manage-collection.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ManageCollectionComponent } from './manage-collection.component'; +import { CollectionManagerSubjectLeftComponent } from '../../features/collection-manager-subject-left/collection-manager-subject-left.component'; +import { CollectionManagerSubjectRightComponent } from '../../features/collection-manager-subject-right/collection-manager-subject-right.component'; +import { MatCardModule } from '@angular/material/card'; + +@NgModule({ + declarations: [ManageCollectionComponent, CollectionManagerSubjectLeftComponent, CollectionManagerSubjectRightComponent], + imports: [CommonModule, MatCardModule], +}) +export class ManageCollectionModule {} diff --git a/ui/src/assets/img/manage-collection/avatar-subject-hover.svg b/ui/src/assets/img/manage-collection/avatar-subject-hover.svg new file mode 100644 index 0000000000..5ccfad1efa --- /dev/null +++ b/ui/src/assets/img/manage-collection/avatar-subject-hover.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/ui/src/assets/img/manage-collection/avatar-subject.svg b/ui/src/assets/img/manage-collection/avatar-subject.svg new file mode 100644 index 0000000000..6b9a01c586 --- /dev/null +++ b/ui/src/assets/img/manage-collection/avatar-subject.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + From f1537b0df207bb2e326e3ca22f8460fa220ef499 Mon Sep 17 00:00:00 2001 From: Lenovo Date: Fri, 18 Jun 2021 14:40:51 +0400 Subject: [PATCH 035/837] fixed previous task issue --- .../features/user-table/user-table.component.ts | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/ui/src/app/features/user-table/user-table.component.ts b/ui/src/app/features/user-table/user-table.component.ts index a26e010a01..50ca98fd1e 100644 --- a/ui/src/app/features/user-table/user-table.component.ts +++ b/ui/src/app/features/user-table/user-table.component.ts @@ -23,7 +23,6 @@ import { TranslateService } from '@ngx-translate/core'; import { RoleEditDialogComponent } from '../role-edit-dialog/role-edit-dialog.component'; import { MatDialog } from '@angular/material/dialog'; import { UserRole } from '../../data/interfaces/user-role'; -import {ActivatedRoute, Router} from "@angular/router"; @Component({ selector: 'app-user-table', @@ -43,15 +42,13 @@ export class UserTableComponent extends TableComponent implements OnInit, OnChan @Input() searchText: string; @Output() deleteUser = new EventEmitter(); - constructor(private dialog: MatDialog, private translate: TranslateService,) { + constructor(private dialog: MatDialog, private translate: TranslateService) { super(); - this.refreshPage(); } ngOnInit() { this.message = this.createMessage; this.noResultMessage = this.translate.instant('users.search.no_results'); - } ngOnChanges(): void { @@ -101,14 +98,4 @@ export class UserTableComponent extends TableComponent implements OnInit, OnChan this.message = this.createMessage; } } - - refreshPage(): void { - if (!localStorage.getItem('foo')) { - localStorage.setItem('foo', 'no reload') - location.reload() - } else { - localStorage.removeItem('foo') - } - } - } From 34dcc36475e3cc572d3a7ba212451f24fdeb74e7 Mon Sep 17 00:00:00 2001 From: Lenovo Date: Mon, 21 Jun 2021 12:55:04 +0400 Subject: [PATCH 036/837] fixed issues with navigation and it's final version --- ui/src/app/app-routing.module.ts | 2 +- .../manage-collection.component.html | 8 +-- .../manage-collection.component.scss | 57 ++++++++++--------- 3 files changed, 36 insertions(+), 31 deletions(-) diff --git a/ui/src/app/app-routing.module.ts b/ui/src/app/app-routing.module.ts index af904fc542..e0321714df 100644 --- a/ui/src/app/app-routing.module.ts +++ b/ui/src/app/app-routing.module.ts @@ -19,7 +19,7 @@ import { RouterModule, Routes } from '@angular/router'; import { MainLayoutComponent } from './ui/main-layout/main-layout.component'; import { DemoLayoutComponent } from './ui/demo-layout/demo-layout.component'; import { UserInfoResolver } from './core/user-info/user-info.resolver'; -import {ManageCollectionComponent} from "./pages/manage-collection/manage-collection.component"; +import { ManageCollectionComponent } from './pages/manage-collection/manage-collection.component'; const routes: Routes = [ { diff --git a/ui/src/app/pages/manage-collection/manage-collection.component.html b/ui/src/app/pages/manage-collection/manage-collection.component.html index dc75c8770f..bd9d9d8a41 100644 --- a/ui/src/app/pages/manage-collection/manage-collection.component.html +++ b/ui/src/app/pages/manage-collection/manage-collection.component.html @@ -1,9 +1,9 @@
+
- diff --git a/ui/src/app/pages/manage-collection/manage-collection.component.scss b/ui/src/app/pages/manage-collection/manage-collection.component.scss index 78113cef4e..7a29700cf6 100644 --- a/ui/src/app/pages/manage-collection/manage-collection.component.scss +++ b/ui/src/app/pages/manage-collection/manage-collection.component.scss @@ -1,7 +1,6 @@ -.grid-container { +.app-page { .navigation { - display: flex; - align-items: center; + margin-bottom: 20px; span { font-size: 14px; font-family: Poppins; @@ -13,30 +12,36 @@ } } } - display: flex; - justify-content: space-between; - .card-left { - width: 220px; - transform: perspective(1px); - padding: 0px !important; - } - .card-right { - padding: 0px !important; - width: 100%; - transform: perspective(1px); - max-width: 100%; - margin-left: 20px; - } - .search { - .input { + .grid-container { + .navigation { + display: flex; + align-items: center; + } + display: flex; + justify-content: space-between; + .card-left { + width: 220px; + transform: perspective(1px); + padding: 0px !important; + } + .card-right { + padding: 0px !important; width: 100%; - height: 43px; - border: 1px solid #e5e5e5; - border-radius: 4px; - font-size: 15px; - padding-left: 20px; - font-family: Poppins; - color: #222222; + transform: perspective(1px); + max-width: 100%; + margin-left: 20px; + } + .search { + .input { + width: 100%; + height: 43px; + border: 1px solid #e5e5e5; + border-radius: 4px; + font-size: 15px; + padding-left: 20px; + font-family: Poppins; + color: #222222; + } } } } From a232b7ea6ae31f29fad86b57867decedeafd3313 Mon Sep 17 00:00:00 2001 From: MKN Date: Mon, 21 Jun 2021 14:51:39 +0500 Subject: [PATCH 037/837] Edit CacheActionDto #EFRS-835 --- .../cache/EmbeddingCacheProvider.java | 4 ++- .../repository/NotificationDbConfig.java | 35 ++++++++++++++----- .../core/trainservice/dto/CacheActionDto.java | 2 ++ .../service/NotificationReceiverService.java | 32 +++++++++++------ .../service/NotificationSenderService.java | 13 ++++--- .../trainservice/system/global/Constants.java | 3 ++ 6 files changed, 64 insertions(+), 25 deletions(-) diff --git a/java/api/src/main/java/com/exadel/frs/core/trainservice/cache/EmbeddingCacheProvider.java b/java/api/src/main/java/com/exadel/frs/core/trainservice/cache/EmbeddingCacheProvider.java index 231e1b1c06..d44dfa8d39 100644 --- a/java/api/src/main/java/com/exadel/frs/core/trainservice/cache/EmbeddingCacheProvider.java +++ b/java/api/src/main/java/com/exadel/frs/core/trainservice/cache/EmbeddingCacheProvider.java @@ -12,6 +12,8 @@ import java.util.concurrent.TimeUnit; import java.util.function.Consumer; +import static com.exadel.frs.core.trainservice.system.global.Constants.SERVER_UUID; + @Component @RequiredArgsConstructor public class EmbeddingCacheProvider { @@ -67,7 +69,7 @@ public void receiveInvalidateCache(final String apiKey) { } private void notifyCacheEvent(String event, String apiKey) { - CacheActionDto cacheActionDto = new CacheActionDto(event, apiKey); + CacheActionDto cacheActionDto = new CacheActionDto(event, apiKey, SERVER_UUID); notificationSenderService.notifyCacheChange(cacheActionDto); } } diff --git a/java/api/src/main/java/com/exadel/frs/core/trainservice/config/repository/NotificationDbConfig.java b/java/api/src/main/java/com/exadel/frs/core/trainservice/config/repository/NotificationDbConfig.java index 2abc0d446a..ae1f6317da 100644 --- a/java/api/src/main/java/com/exadel/frs/core/trainservice/config/repository/NotificationDbConfig.java +++ b/java/api/src/main/java/com/exadel/frs/core/trainservice/config/repository/NotificationDbConfig.java @@ -1,5 +1,7 @@ package com.exadel.frs.core.trainservice.config.repository; +import com.impossibl.postgres.api.jdbc.PGConnection; +import com.impossibl.postgres.api.jdbc.PGNotificationListener; import com.impossibl.postgres.jdbc.PGDataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -7,26 +9,41 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; +import java.sql.DriverManager; +import java.sql.SQLException; + @Configuration public class NotificationDbConfig { @Autowired private Environment env; - @Bean(name = "dsPgNot") - public PGDataSource pgNotificationDatasource() { - PGDataSource dataSource = new PGDataSource(); - +// @Bean(name = "dsPgNot") +// public PGDataSource pgNotificationDatasource() { +// PGDataSource dataSource = new PGDataSource(); +// +// String dbUrl = env.getProperty("spring.datasource-pg.url"); +// String dbUsername= env.getProperty("spring.datasource-pg.username"); +// String dbPassword= env.getProperty("spring.datasource-pg.password"); +// +// String databaseUrl = dbUrl.replaceAll("postgresql", "pgsql"); +// +// dataSource.setDatabaseUrl(databaseUrl); +// dataSource.setUser(dbUsername); +// dataSource.setPassword(dbPassword); +// +// return dataSource; +// } + + @Bean + public PGConnection pgConnection() throws SQLException { String dbUrl = env.getProperty("spring.datasource-pg.url"); String dbUsername= env.getProperty("spring.datasource-pg.username"); String dbPassword= env.getProperty("spring.datasource-pg.password"); String databaseUrl = dbUrl.replaceAll("postgresql", "pgsql"); + return DriverManager.getConnection(databaseUrl, dbUsername, dbPassword).unwrap(PGConnection.class); + } - dataSource.setDatabaseUrl(databaseUrl); - dataSource.setUser(dbUsername); - dataSource.setPassword(dbPassword); - return dataSource; - } } diff --git a/java/api/src/main/java/com/exadel/frs/core/trainservice/dto/CacheActionDto.java b/java/api/src/main/java/com/exadel/frs/core/trainservice/dto/CacheActionDto.java index 67bef814e1..8288a3b64a 100644 --- a/java/api/src/main/java/com/exadel/frs/core/trainservice/dto/CacheActionDto.java +++ b/java/api/src/main/java/com/exadel/frs/core/trainservice/dto/CacheActionDto.java @@ -13,4 +13,6 @@ public class CacheActionDto { private String cacheAction; @JsonProperty("apiKey") private String apiKey; + @JsonProperty("uuid") + private String serverUUID; } diff --git a/java/api/src/main/java/com/exadel/frs/core/trainservice/service/NotificationReceiverService.java b/java/api/src/main/java/com/exadel/frs/core/trainservice/service/NotificationReceiverService.java index e27cede8a9..7c3186ef2d 100644 --- a/java/api/src/main/java/com/exadel/frs/core/trainservice/service/NotificationReceiverService.java +++ b/java/api/src/main/java/com/exadel/frs/core/trainservice/service/NotificationReceiverService.java @@ -17,28 +17,34 @@ import java.sql.SQLException; import java.sql.Statement; +import static com.exadel.frs.core.trainservice.system.global.Constants.SERVER_UUID; + @Service("notificationReceiverService") @Slf4j @RequiredArgsConstructor public class NotificationReceiverService { - @Qualifier("dsPgNot") - private final PGDataSource pgNotificationDatasource; +// @Qualifier("dsPgNot") +// private final PGDataSource pgNotificationDatasource; + + private final PGConnection connection; private final EmbeddingCacheProvider embeddingCacheProvider; private final ObjectMapper objectMapper; @PostConstruct public void setUpNotification() { - PGConnection connection = null; + + System.out.println("!!!!"); PGNotificationListener listener = new PGNotificationListener() { + @Override public void notification(int processId, String channelName, String payload) { log.info(String.format("/channels3/ channel name: %1$s payload %2$s", channelName, payload)); - + System.out.println("!"); if (channelName.equals("face_collection_update_msg")) { - updateCacheWithNotification(payload); + synchronizeCacheWithNotification(payload); } } @@ -49,22 +55,28 @@ public void closed() { }; try { - connection = (PGConnection) pgNotificationDatasource.getConnection(); +// PGConnection connection = (PGConnection) pgNotificationDatasource.getConnection(); Statement statement = connection.createStatement(); statement.executeUpdate("LISTEN face_collection_update_msg"); statement.close(); + connection.addNotificationListener(listener); } catch (SQLException ex) { log.error(ex.getMessage()); } - connection.addNotificationListener(listener); - } - private void updateCacheWithNotification(String payload) { + } + private void synchronizeCacheWithNotification(String payload) { + System.out.println("222"); try { CacheActionDto cacheActionDto = objectMapper.readValue(payload, CacheActionDto.class); - if (cacheActionDto != null && !StringUtils.isBlank(cacheActionDto.getApiKey()) && !StringUtils.isBlank(cacheActionDto.getCacheAction())) { + if (cacheActionDto != null + && !StringUtils.isBlank(cacheActionDto.getServerUUID()) + && !cacheActionDto.getServerUUID().equals(SERVER_UUID) + && !StringUtils.isBlank(cacheActionDto.getApiKey()) + && !StringUtils.isBlank(cacheActionDto.getCacheAction()) + ) { if (cacheActionDto.getCacheAction().equals("UPDATE")) { embeddingCacheProvider.receivePutOnCache(cacheActionDto.getApiKey()); diff --git a/java/api/src/main/java/com/exadel/frs/core/trainservice/service/NotificationSenderService.java b/java/api/src/main/java/com/exadel/frs/core/trainservice/service/NotificationSenderService.java index 26cd71977f..c6267775e4 100644 --- a/java/api/src/main/java/com/exadel/frs/core/trainservice/service/NotificationSenderService.java +++ b/java/api/src/main/java/com/exadel/frs/core/trainservice/service/NotificationSenderService.java @@ -19,17 +19,20 @@ @Slf4j @RequiredArgsConstructor public class NotificationSenderService { - @Qualifier("dsPgNot") - private final PGDataSource pgNotificationDatasource; +// @Qualifier("dsPgNot") +// private final PGDataSource pgNotificationDatasource; + + private final PGConnection connection; public void notifyCacheChange(CacheActionDto cacheActionDto) { try { - PGConnection connection = (PGConnection) pgNotificationDatasource.getConnection(); +// PGConnection connection = (PGConnection) pgNotificationDatasource.getConnection(); Statement statement = connection.createStatement(); - ObjectMapper mapper = new ObjectMapper(); + try { - String actionString = String.format("NOTIFY face_collection_update_msg, '%s'", mapper.writerWithDefaultPrettyPrinter().writeValueAsString(cacheActionDto)); + ObjectMapper objectMapper = new ObjectMapper(); + String actionString = String.format("NOTIFY face_collection_update_msg, '%s'", objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(cacheActionDto)); statement.execute(actionString); } catch (JsonProcessingException e) { log.error(e.getMessage()); diff --git a/java/api/src/main/java/com/exadel/frs/core/trainservice/system/global/Constants.java b/java/api/src/main/java/com/exadel/frs/core/trainservice/system/global/Constants.java index 8fffa6a231..b280402943 100644 --- a/java/api/src/main/java/com/exadel/frs/core/trainservice/system/global/Constants.java +++ b/java/api/src/main/java/com/exadel/frs/core/trainservice/system/global/Constants.java @@ -19,6 +19,8 @@ import lombok.AccessLevel; import lombok.NoArgsConstructor; +import java.util.UUID; + @NoArgsConstructor(access = AccessLevel.PRIVATE) public class Constants { @@ -55,4 +57,5 @@ public class Constants { public static final String DEMO_API_KEY = "00000000-0000-0000-0000-000000000002"; public static final String FACENET2018 = "Facenet2018"; + public static final String SERVER_UUID = UUID.randomUUID().toString(); } \ No newline at end of file From e2f79ef72014c12cd0f31f706c104d75db0626cd Mon Sep 17 00:00:00 2001 From: MKN Date: Tue, 22 Jun 2021 12:00:42 +0500 Subject: [PATCH 038/837] Edit NotificationDbConfig #EFRS-835 --- .../cache/EmbeddingCacheProvider.java | 7 +++- .../repository/NotificationDbConfig.java | 40 +++++-------------- .../service/NotificationReceiverService.java | 24 ++++++----- .../service/NotificationSenderService.java | 13 +++--- 4 files changed, 35 insertions(+), 49 deletions(-) diff --git a/java/api/src/main/java/com/exadel/frs/core/trainservice/cache/EmbeddingCacheProvider.java b/java/api/src/main/java/com/exadel/frs/core/trainservice/cache/EmbeddingCacheProvider.java index d44dfa8d39..f8e38978ea 100644 --- a/java/api/src/main/java/com/exadel/frs/core/trainservice/cache/EmbeddingCacheProvider.java +++ b/java/api/src/main/java/com/exadel/frs/core/trainservice/cache/EmbeddingCacheProvider.java @@ -6,6 +6,7 @@ import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.util.Optional; @@ -15,6 +16,7 @@ import static com.exadel.frs.core.trainservice.system.global.Constants.SERVER_UUID; @Component +@Slf4j @RequiredArgsConstructor public class EmbeddingCacheProvider { @@ -32,7 +34,9 @@ public class EmbeddingCacheProvider { .build(); public EmbeddingCollection getOrLoad(final String apiKey) { + var result = cache.getIfPresent(apiKey); + if (result == null) { result = embeddingService.doWithEmbeddingsStream(apiKey, EmbeddingCollection::from); @@ -47,7 +51,7 @@ public EmbeddingCollection getOrLoad(final String apiKey) { public void ifPresent(String apiKey, Consumer consumer) { Optional.ofNullable(cache.getIfPresent(apiKey)) .ifPresent(consumer); - //notify put with key and result + EmbeddingCollection dd = cache.getIfPresent(apiKey); notifyCacheEvent("UPDATE", apiKey); } @@ -61,7 +65,6 @@ public void invalidate(final String apiKey) { public void receivePutOnCache(String apiKey) { var result = embeddingService.doWithEmbeddingsStream(apiKey, EmbeddingCollection::from); cache.put(apiKey, result); - notifyCacheEvent("UPDATE", apiKey); } public void receiveInvalidateCache(final String apiKey) { diff --git a/java/api/src/main/java/com/exadel/frs/core/trainservice/config/repository/NotificationDbConfig.java b/java/api/src/main/java/com/exadel/frs/core/trainservice/config/repository/NotificationDbConfig.java index ae1f6317da..140d72f3f2 100644 --- a/java/api/src/main/java/com/exadel/frs/core/trainservice/config/repository/NotificationDbConfig.java +++ b/java/api/src/main/java/com/exadel/frs/core/trainservice/config/repository/NotificationDbConfig.java @@ -1,49 +1,31 @@ package com.exadel.frs.core.trainservice.config.repository; -import com.impossibl.postgres.api.jdbc.PGConnection; -import com.impossibl.postgres.api.jdbc.PGNotificationListener; import com.impossibl.postgres.jdbc.PGDataSource; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; -import java.sql.DriverManager; -import java.sql.SQLException; - @Configuration public class NotificationDbConfig { @Autowired private Environment env; -// @Bean(name = "dsPgNot") -// public PGDataSource pgNotificationDatasource() { -// PGDataSource dataSource = new PGDataSource(); -// -// String dbUrl = env.getProperty("spring.datasource-pg.url"); -// String dbUsername= env.getProperty("spring.datasource-pg.username"); -// String dbPassword= env.getProperty("spring.datasource-pg.password"); -// -// String databaseUrl = dbUrl.replaceAll("postgresql", "pgsql"); -// -// dataSource.setDatabaseUrl(databaseUrl); -// dataSource.setUser(dbUsername); -// dataSource.setPassword(dbPassword); -// -// return dataSource; -// } + @Bean(name = "dsPgNot") + public PGDataSource pgNotificationDatasource() { + PGDataSource dataSource = new PGDataSource(); - @Bean - public PGConnection pgConnection() throws SQLException { String dbUrl = env.getProperty("spring.datasource-pg.url"); - String dbUsername= env.getProperty("spring.datasource-pg.username"); - String dbPassword= env.getProperty("spring.datasource-pg.password"); + String dbUsername = env.getProperty("spring.datasource-pg.username"); + String dbPassword = env.getProperty("spring.datasource-pg.password"); String databaseUrl = dbUrl.replaceAll("postgresql", "pgsql"); - return DriverManager.getConnection(databaseUrl, dbUsername, dbPassword).unwrap(PGConnection.class); - } - + dataSource.setDatabaseUrl(databaseUrl); + dataSource.setUser(dbUsername); + dataSource.setPassword(dbPassword); + dataSource.setHousekeeper(false); + return dataSource; + } } diff --git a/java/api/src/main/java/com/exadel/frs/core/trainservice/service/NotificationReceiverService.java b/java/api/src/main/java/com/exadel/frs/core/trainservice/service/NotificationReceiverService.java index 7c3186ef2d..9d000ba244 100644 --- a/java/api/src/main/java/com/exadel/frs/core/trainservice/service/NotificationReceiverService.java +++ b/java/api/src/main/java/com/exadel/frs/core/trainservice/service/NotificationReceiverService.java @@ -7,6 +7,7 @@ import com.impossibl.postgres.api.jdbc.PGConnection; import com.impossibl.postgres.api.jdbc.PGNotificationListener; import com.impossibl.postgres.jdbc.PGDataSource; + import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -24,25 +25,26 @@ @RequiredArgsConstructor public class NotificationReceiverService { -// @Qualifier("dsPgNot") -// private final PGDataSource pgNotificationDatasource; + @Qualifier("dsPgNot") + private final PGDataSource pgNotificationDatasource; + + private PGConnection connection; - private final PGConnection connection; private final EmbeddingCacheProvider embeddingCacheProvider; private final ObjectMapper objectMapper; + private static PGNotificationListener listener; + @PostConstruct public void setUpNotification() { - System.out.println("!!!!"); - PGNotificationListener listener = new PGNotificationListener() { + listener = new PGNotificationListener() { @Override public void notification(int processId, String channelName, String payload) { log.info(String.format("/channels3/ channel name: %1$s payload %2$s", channelName, payload)); - System.out.println("!"); if (channelName.equals("face_collection_update_msg")) { synchronizeCacheWithNotification(payload); } @@ -53,22 +55,22 @@ public void closed() { log.info("face_collection_update_msg closed"); } }; - try { -// PGConnection connection = (PGConnection) pgNotificationDatasource.getConnection(); + connection = pgNotificationDatasource.getConnection().unwrap(PGConnection.class); + Statement statement = connection.createStatement(); statement.executeUpdate("LISTEN face_collection_update_msg"); statement.close(); - connection.addNotificationListener(listener); } catch (SQLException ex) { - log.error(ex.getMessage()); + ex.printStackTrace(); } + connection.addNotificationListener(listener); } private void synchronizeCacheWithNotification(String payload) { - System.out.println("222"); + try { CacheActionDto cacheActionDto = objectMapper.readValue(payload, CacheActionDto.class); if (cacheActionDto != null diff --git a/java/api/src/main/java/com/exadel/frs/core/trainservice/service/NotificationSenderService.java b/java/api/src/main/java/com/exadel/frs/core/trainservice/service/NotificationSenderService.java index c6267775e4..8075bceac8 100644 --- a/java/api/src/main/java/com/exadel/frs/core/trainservice/service/NotificationSenderService.java +++ b/java/api/src/main/java/com/exadel/frs/core/trainservice/service/NotificationSenderService.java @@ -10,7 +10,6 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; -import javax.annotation.PostConstruct; import java.sql.SQLException; import java.sql.Statement; @@ -19,20 +18,20 @@ @Slf4j @RequiredArgsConstructor public class NotificationSenderService { -// @Qualifier("dsPgNot") -// private final PGDataSource pgNotificationDatasource; + @Qualifier("dsPgNot") + private final PGDataSource pgNotificationDatasource; + private PGConnection connection; - private final PGConnection connection; public void notifyCacheChange(CacheActionDto cacheActionDto) { try { -// PGConnection connection = (PGConnection) pgNotificationDatasource.getConnection(); + connection = (PGConnection) pgNotificationDatasource.getConnection(); Statement statement = connection.createStatement(); - try { ObjectMapper objectMapper = new ObjectMapper(); - String actionString = String.format("NOTIFY face_collection_update_msg, '%s'", objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(cacheActionDto)); + String actionString = String.format("NOTIFY face_collection_update_msg, '%s'", + objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(cacheActionDto)); statement.execute(actionString); } catch (JsonProcessingException e) { log.error(e.getMessage()); From 04601d580f5184a68887ba383f375d289f7f5f28 Mon Sep 17 00:00:00 2001 From: MKN Date: Wed, 23 Jun 2021 17:54:59 +0500 Subject: [PATCH 039/837] Editing FaceVerification dto #EFRS-1086 --- .../frs/core/trainservice/dto/FaceVerification.java | 10 ++++++---- .../exadel/frs/core/trainservice/dto/FacesAge.java | 12 ++++++++++++ .../frs/core/trainservice/dto/FacesGender.java | 11 +++++++++++ .../exadel/frs/core/trainservice/dto/FacesMask.java | 11 +++++++++++ .../core/trainservice/dto/FindFacesResultDto.java | 8 ++++++-- .../core/trainservice/dto/VerifyFacesResultDto.java | 5 +++-- .../core/trainservice/service/SubjectService.java | 3 ++- .../exadel/frs/commonservice/dto/FacesAgeDto.java | 12 ++++++++++++ .../exadel/frs/commonservice/dto/FacesGenderDto.java | 11 +++++++++++ .../exadel/frs/commonservice/dto/FacesMaskDto.java | 11 +++++++++++ .../frs/commonservice/dto/FindFacesResultDto.java | 5 +++-- .../commonservice/sdk/faces/feign/dto/FacesAge.java | 12 ++++++++++++ .../sdk/faces/feign/dto/FacesGender.java | 11 +++++++++++ .../commonservice/sdk/faces/feign/dto/FacesMask.java | 11 +++++++++++ .../sdk/faces/feign/dto/FindFacesResult.java | 5 +++-- 15 files changed, 125 insertions(+), 13 deletions(-) create mode 100644 java/api/src/main/java/com/exadel/frs/core/trainservice/dto/FacesAge.java create mode 100644 java/api/src/main/java/com/exadel/frs/core/trainservice/dto/FacesGender.java create mode 100644 java/api/src/main/java/com/exadel/frs/core/trainservice/dto/FacesMask.java create mode 100644 java/common/src/main/java/com/exadel/frs/commonservice/dto/FacesAgeDto.java create mode 100644 java/common/src/main/java/com/exadel/frs/commonservice/dto/FacesGenderDto.java create mode 100644 java/common/src/main/java/com/exadel/frs/commonservice/dto/FacesMaskDto.java create mode 100644 java/common/src/main/java/com/exadel/frs/commonservice/sdk/faces/feign/dto/FacesAge.java create mode 100644 java/common/src/main/java/com/exadel/frs/commonservice/sdk/faces/feign/dto/FacesGender.java create mode 100644 java/common/src/main/java/com/exadel/frs/commonservice/sdk/faces/feign/dto/FacesMask.java diff --git a/java/api/src/main/java/com/exadel/frs/core/trainservice/dto/FaceVerification.java b/java/api/src/main/java/com/exadel/frs/core/trainservice/dto/FaceVerification.java index 112045d49e..7bf91ae072 100644 --- a/java/api/src/main/java/com/exadel/frs/core/trainservice/dto/FaceVerification.java +++ b/java/api/src/main/java/com/exadel/frs/core/trainservice/dto/FaceVerification.java @@ -17,8 +17,10 @@ package com.exadel.frs.core.trainservice.dto; import com.exadel.frs.commonservice.dto.ExecutionTimeDto; -import com.exadel.frs.commonservice.dto.PluginsVersionsDto; import com.exadel.frs.commonservice.sdk.faces.feign.dto.FacesBox; +import com.exadel.frs.commonservice.sdk.faces.feign.dto.FacesGender; +import com.exadel.frs.commonservice.sdk.faces.feign.dto.FacesAge; +import com.exadel.frs.commonservice.sdk.faces.feign.dto.FacesMask; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; @@ -42,12 +44,12 @@ public class FaceVerification extends FaceProcessResponse { private String subject; private float similarity; private List> landmarks; - private Integer[] age; - private String gender; + private FacesAge age; + private FacesGender gender; private Double[] embedding; @JsonProperty(value = "execution_time") private ExecutionTimeDto executionTime; - + private FacesMask mask; @Override public FaceVerification prepareResponse(ProcessImageParams processImageParams) { String facePlugins = processImageParams.getFacePlugins(); diff --git a/java/api/src/main/java/com/exadel/frs/core/trainservice/dto/FacesAge.java b/java/api/src/main/java/com/exadel/frs/core/trainservice/dto/FacesAge.java new file mode 100644 index 0000000000..072e9cd3db --- /dev/null +++ b/java/api/src/main/java/com/exadel/frs/core/trainservice/dto/FacesAge.java @@ -0,0 +1,12 @@ +package com.exadel.frs.core.trainservice.dto; + +import lombok.Data; +import lombok.experimental.Accessors; + +@Data +@Accessors(chain = true) +public class FacesAge { + private Double probability; + private Integer high; + private Integer low; +} diff --git a/java/api/src/main/java/com/exadel/frs/core/trainservice/dto/FacesGender.java b/java/api/src/main/java/com/exadel/frs/core/trainservice/dto/FacesGender.java new file mode 100644 index 0000000000..68948b23cf --- /dev/null +++ b/java/api/src/main/java/com/exadel/frs/core/trainservice/dto/FacesGender.java @@ -0,0 +1,11 @@ +package com.exadel.frs.core.trainservice.dto; + +import lombok.Data; +import lombok.experimental.Accessors; + +@Data +@Accessors(chain = true) +public class FacesGender { + private Double probability; + private String value; +} diff --git a/java/api/src/main/java/com/exadel/frs/core/trainservice/dto/FacesMask.java b/java/api/src/main/java/com/exadel/frs/core/trainservice/dto/FacesMask.java new file mode 100644 index 0000000000..9770c2377d --- /dev/null +++ b/java/api/src/main/java/com/exadel/frs/core/trainservice/dto/FacesMask.java @@ -0,0 +1,11 @@ +package com.exadel.frs.core.trainservice.dto; + +import lombok.Data; +import lombok.experimental.Accessors; + +@Data +@Accessors(chain = true) +public class FacesMask { + private Double probability; + private String value; +} diff --git a/java/api/src/main/java/com/exadel/frs/core/trainservice/dto/FindFacesResultDto.java b/java/api/src/main/java/com/exadel/frs/core/trainservice/dto/FindFacesResultDto.java index e73d352dc5..07e2c4f8d9 100644 --- a/java/api/src/main/java/com/exadel/frs/core/trainservice/dto/FindFacesResultDto.java +++ b/java/api/src/main/java/com/exadel/frs/core/trainservice/dto/FindFacesResultDto.java @@ -16,6 +16,9 @@ package com.exadel.frs.core.trainservice.dto; import com.exadel.frs.commonservice.dto.ExecutionTimeDto; +import com.exadel.frs.commonservice.dto.FacesAgeDto; +import com.exadel.frs.commonservice.dto.FacesGenderDto; +import com.exadel.frs.commonservice.dto.FacesMaskDto; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; @@ -32,10 +35,11 @@ @JsonInclude(NON_NULL) public class FindFacesResultDto { - private Integer[] age; - private String gender; + private FacesAgeDto age; + private FacesGenderDto gender; private Double[] embedding; private FacesBox box; @JsonProperty(value = "execution_time") private ExecutionTimeDto executionTime; + private FacesMaskDto mask; } diff --git a/java/api/src/main/java/com/exadel/frs/core/trainservice/dto/VerifyFacesResultDto.java b/java/api/src/main/java/com/exadel/frs/core/trainservice/dto/VerifyFacesResultDto.java index d8d506f8d7..97fc5c21be 100644 --- a/java/api/src/main/java/com/exadel/frs/core/trainservice/dto/VerifyFacesResultDto.java +++ b/java/api/src/main/java/com/exadel/frs/core/trainservice/dto/VerifyFacesResultDto.java @@ -34,11 +34,12 @@ @JsonInclude(NON_NULL) public class VerifyFacesResultDto { - private Integer[] age; - private String gender; + private FacesAge age; + private FacesGender gender; private Double[] embedding; private FacesBox box; @JsonProperty(value = "execution_time") private ExecutionTimeDto executionTime; private List> landmarks; + private FacesMask mask; } diff --git a/java/api/src/main/java/com/exadel/frs/core/trainservice/service/SubjectService.java b/java/api/src/main/java/com/exadel/frs/core/trainservice/service/SubjectService.java index ec34100704..8c4f0813d4 100644 --- a/java/api/src/main/java/com/exadel/frs/core/trainservice/service/SubjectService.java +++ b/java/api/src/main/java/com/exadel/frs/core/trainservice/service/SubjectService.java @@ -228,10 +228,11 @@ public Pair, PluginsVersions> verifyFace(ProcessImagePara .subject(subjectName) .similarity(pred.floatValue()) .landmarks(findResult.getLandmarks()) - .age(findResult.getAge()) .gender(findResult.getGender()) .embedding(findResult.getEmbedding()) .executionTime(findResult.getExecutionTime()) + .age(findResult.getAge()) + .mask(findResult.getMask()) .build() .prepareResponse(processImageParams); // do some tricks with obj diff --git a/java/common/src/main/java/com/exadel/frs/commonservice/dto/FacesAgeDto.java b/java/common/src/main/java/com/exadel/frs/commonservice/dto/FacesAgeDto.java new file mode 100644 index 0000000000..c5a285d2a2 --- /dev/null +++ b/java/common/src/main/java/com/exadel/frs/commonservice/dto/FacesAgeDto.java @@ -0,0 +1,12 @@ +package com.exadel.frs.commonservice.dto; + +import lombok.Data; +import lombok.experimental.Accessors; + +@Data +@Accessors(chain = true) +public class FacesAgeDto { + private Double probability; + private Integer high; + private Integer low; +} diff --git a/java/common/src/main/java/com/exadel/frs/commonservice/dto/FacesGenderDto.java b/java/common/src/main/java/com/exadel/frs/commonservice/dto/FacesGenderDto.java new file mode 100644 index 0000000000..a8bac343ec --- /dev/null +++ b/java/common/src/main/java/com/exadel/frs/commonservice/dto/FacesGenderDto.java @@ -0,0 +1,11 @@ +package com.exadel.frs.commonservice.dto; + +import lombok.Data; +import lombok.experimental.Accessors; + +@Data +@Accessors(chain = true) +public class FacesGenderDto { + private Double probability; + private String value; +} diff --git a/java/common/src/main/java/com/exadel/frs/commonservice/dto/FacesMaskDto.java b/java/common/src/main/java/com/exadel/frs/commonservice/dto/FacesMaskDto.java new file mode 100644 index 0000000000..e5373e377f --- /dev/null +++ b/java/common/src/main/java/com/exadel/frs/commonservice/dto/FacesMaskDto.java @@ -0,0 +1,11 @@ +package com.exadel.frs.commonservice.dto; + +import lombok.Data; +import lombok.experimental.Accessors; + +@Data +@Accessors(chain = true) +public class FacesMaskDto { + private Double probability; + private String value; +} diff --git a/java/common/src/main/java/com/exadel/frs/commonservice/dto/FindFacesResultDto.java b/java/common/src/main/java/com/exadel/frs/commonservice/dto/FindFacesResultDto.java index 137e7ed6bb..da8d3a6bf5 100644 --- a/java/common/src/main/java/com/exadel/frs/commonservice/dto/FindFacesResultDto.java +++ b/java/common/src/main/java/com/exadel/frs/commonservice/dto/FindFacesResultDto.java @@ -33,11 +33,12 @@ @JsonInclude(NON_NULL) public class FindFacesResultDto { - private Integer[] age; - private String gender; + private FacesAgeDto age; + private FacesGenderDto gender; private Double[] embedding; private FacesBox box; @JsonProperty(value = "execution_time") private ExecutionTimeDto executionTime; private List> landmarks; + private FacesMaskDto maskDto; } diff --git a/java/common/src/main/java/com/exadel/frs/commonservice/sdk/faces/feign/dto/FacesAge.java b/java/common/src/main/java/com/exadel/frs/commonservice/sdk/faces/feign/dto/FacesAge.java new file mode 100644 index 0000000000..9d9c2be500 --- /dev/null +++ b/java/common/src/main/java/com/exadel/frs/commonservice/sdk/faces/feign/dto/FacesAge.java @@ -0,0 +1,12 @@ +package com.exadel.frs.commonservice.sdk.faces.feign.dto; + +import lombok.Data; +import lombok.experimental.Accessors; + +@Data +@Accessors(chain = true) +public class FacesAge { + private Double probability; + private Integer high; + private Integer low; +} diff --git a/java/common/src/main/java/com/exadel/frs/commonservice/sdk/faces/feign/dto/FacesGender.java b/java/common/src/main/java/com/exadel/frs/commonservice/sdk/faces/feign/dto/FacesGender.java new file mode 100644 index 0000000000..eb00f1f63c --- /dev/null +++ b/java/common/src/main/java/com/exadel/frs/commonservice/sdk/faces/feign/dto/FacesGender.java @@ -0,0 +1,11 @@ +package com.exadel.frs.commonservice.sdk.faces.feign.dto; + +import lombok.Data; +import lombok.experimental.Accessors; + +@Data +@Accessors(chain = true) +public class FacesGender { + private Double probability; + private String value; +} diff --git a/java/common/src/main/java/com/exadel/frs/commonservice/sdk/faces/feign/dto/FacesMask.java b/java/common/src/main/java/com/exadel/frs/commonservice/sdk/faces/feign/dto/FacesMask.java new file mode 100644 index 0000000000..70acc366f3 --- /dev/null +++ b/java/common/src/main/java/com/exadel/frs/commonservice/sdk/faces/feign/dto/FacesMask.java @@ -0,0 +1,11 @@ +package com.exadel.frs.commonservice.sdk.faces.feign.dto; + +import lombok.Data; +import lombok.experimental.Accessors; + +@Data +@Accessors(chain = true) +public class FacesMask { + private Double probability; + private String value; +} diff --git a/java/common/src/main/java/com/exadel/frs/commonservice/sdk/faces/feign/dto/FindFacesResult.java b/java/common/src/main/java/com/exadel/frs/commonservice/sdk/faces/feign/dto/FindFacesResult.java index 4ee2557100..bf7a1cd564 100644 --- a/java/common/src/main/java/com/exadel/frs/commonservice/sdk/faces/feign/dto/FindFacesResult.java +++ b/java/common/src/main/java/com/exadel/frs/commonservice/sdk/faces/feign/dto/FindFacesResult.java @@ -34,11 +34,12 @@ @JsonInclude(NON_NULL) public class FindFacesResult { - private Integer[] age; - private String gender; + private FacesAge age; + private FacesGender gender; private Double[] embedding; private FacesBox box; @JsonProperty(value = "execution_time") private ExecutionTimeDto executionTime; private List> landmarks; + private FacesMask mask; } From d223cd5594c7322033134eb0ba3394ee7796c598 Mon Sep 17 00:00:00 2001 From: MKN Date: Tue, 29 Jun 2021 10:55:19 +0500 Subject: [PATCH 040/837] Adding remove case when subject name is blank #EFRS-1074 --- .../core/trainservice/service/SubjectService.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/java/api/src/main/java/com/exadel/frs/core/trainservice/service/SubjectService.java b/java/api/src/main/java/com/exadel/frs/core/trainservice/service/SubjectService.java index ec34100704..aeb81fed6f 100644 --- a/java/api/src/main/java/com/exadel/frs/core/trainservice/service/SubjectService.java +++ b/java/api/src/main/java/com/exadel/frs/core/trainservice/service/SubjectService.java @@ -17,9 +17,10 @@ import com.exadel.frs.core.trainservice.system.global.Constants; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; + import org.springframework.web.multipart.MultipartFile; import java.io.IOException; @@ -72,7 +73,15 @@ public int removeAllSubjectEmbeddings(final String apiKey, final String subjectN return removed; } - public Subject deleteSubjectByName(final String apiKey, final String subjectName) { + public void deleteSubjectByName(final String apiKey, final String subjectName) { + if (StringUtils.isBlank(subjectName)) { + deleteSubjectsByApiKey(apiKey); + } else { + deleteSubjectByNameAndApiKey(apiKey, subjectName); + } + } + + public Subject deleteSubjectByNameAndApiKey(final String apiKey, final String subjectName) { var subject = subjectDao.deleteSubjectByName(apiKey, subjectName); // remove subject from cache if required From 798df3efaee36611b545d92b9ad06c12e480b5bf Mon Sep 17 00:00:00 2001 From: Pavel Bohdan Date: Tue, 29 Jun 2021 10:51:52 +0300 Subject: [PATCH 041/837] Changed UI --- ui/src/app/data/enums/person-gender.enum.ts | 19 +++++++++++++++++ ui/src/app/data/interfaces/face-matches.ts | 6 ++++-- ui/src/app/data/interfaces/person-age.ts | 20 ++++++++++++++++++ ui/src/app/data/interfaces/person-gender.ts | 21 +++++++++++++++++++ .../interfaces/request-result-recognition.ts | 6 ++++-- .../app/data/interfaces/source-image-face.ts | 6 ++++-- .../face-picture/face-picture.component.html | 2 +- 7 files changed, 73 insertions(+), 7 deletions(-) create mode 100644 ui/src/app/data/enums/person-gender.enum.ts create mode 100644 ui/src/app/data/interfaces/person-age.ts create mode 100644 ui/src/app/data/interfaces/person-gender.ts diff --git a/ui/src/app/data/enums/person-gender.enum.ts b/ui/src/app/data/enums/person-gender.enum.ts new file mode 100644 index 0000000000..7e7b1d7cd4 --- /dev/null +++ b/ui/src/app/data/enums/person-gender.enum.ts @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +export enum Gender { + Male = 'male', + Female = 'female', +} diff --git a/ui/src/app/data/interfaces/face-matches.ts b/ui/src/app/data/interfaces/face-matches.ts index 24a1bed525..5356fa0c2a 100644 --- a/ui/src/app/data/interfaces/face-matches.ts +++ b/ui/src/app/data/interfaces/face-matches.ts @@ -14,10 +14,12 @@ * permissions and limitations under the License. */ import { BoxSize } from './box-size'; +import { PersonAge } from './person-age'; +import { PersonGender } from './person-gender'; export interface FaceMatches { - age: number[]; - gender: 'male' | 'female'; + age: PersonAge; + gender: PersonGender; box: BoxSize; similarity: number; landmarks: [number[]]; diff --git a/ui/src/app/data/interfaces/person-age.ts b/ui/src/app/data/interfaces/person-age.ts new file mode 100644 index 0000000000..c10fb11033 --- /dev/null +++ b/ui/src/app/data/interfaces/person-age.ts @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +export interface PersonAge { + probability: number; + high: number; + low: number; +} diff --git a/ui/src/app/data/interfaces/person-gender.ts b/ui/src/app/data/interfaces/person-gender.ts new file mode 100644 index 0000000000..d3b5d5c4cf --- /dev/null +++ b/ui/src/app/data/interfaces/person-gender.ts @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +import { Gender } from '../enums/person-gender.enum'; + +export interface PersonGender { + probability: number; + value: Gender.Male | Gender.Female; +} diff --git a/ui/src/app/data/interfaces/request-result-recognition.ts b/ui/src/app/data/interfaces/request-result-recognition.ts index f2f3345ba3..c17e14aef7 100644 --- a/ui/src/app/data/interfaces/request-result-recognition.ts +++ b/ui/src/app/data/interfaces/request-result-recognition.ts @@ -15,10 +15,12 @@ */ import { BoxSize } from './box-size'; import { BoxSubjects } from './box-subjects'; +import { PersonAge } from './person-age'; +import { PersonGender } from './person-gender'; export interface RequestResultRecognition { - age: number[]; - gender: 'male' | 'female'; + age: PersonAge; + gender: PersonGender; box: BoxSize; subjects: BoxSubjects[]; landmarks: [number[]]; diff --git a/ui/src/app/data/interfaces/source-image-face.ts b/ui/src/app/data/interfaces/source-image-face.ts index 547bf11013..a301885780 100644 --- a/ui/src/app/data/interfaces/source-image-face.ts +++ b/ui/src/app/data/interfaces/source-image-face.ts @@ -14,10 +14,12 @@ * permissions and limitations under the License. */ import { BoxSize } from './box-size'; +import { PersonAge } from './person-age'; +import { PersonGender } from './person-gender'; export interface SourceImageFace { - age: number[]; - gender: 'male' | 'female'; + age: PersonAge; + gender: PersonGender; box: BoxSize; landmarks: [number[]]; } diff --git a/ui/src/app/features/face-services/face-picture/face-picture.component.html b/ui/src/app/features/face-services/face-picture/face-picture.component.html index 669359dbfe..ba62bdd804 100644 --- a/ui/src/app/features/face-services/face-picture/face-picture.component.html +++ b/ui/src/app/features/face-services/face-picture/face-picture.component.html @@ -48,7 +48,7 @@
Similarity: {{ face.similarity | number : '1.0-2' }}
-
{{ face.gender | titlecase }}, Age: {{ face.age[0] + '-' + face.age[1] }}
+
{{ face.gender.value | titlecase }}, Age: {{ face.age.high + '-' + face.age.low }}
From 372d2a33b66f5958e28dd086c1a7ccd700a45218 Mon Sep 17 00:00:00 2001 From: Pavel Bohdan Date: Tue, 29 Jun 2021 11:43:52 +0300 Subject: [PATCH 042/837] Changed UI --- .../face-services/face-picture/face-picture.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/app/features/face-services/face-picture/face-picture.component.html b/ui/src/app/features/face-services/face-picture/face-picture.component.html index ba62bdd804..b4a41b96db 100644 --- a/ui/src/app/features/face-services/face-picture/face-picture.component.html +++ b/ui/src/app/features/face-services/face-picture/face-picture.component.html @@ -48,7 +48,7 @@
Similarity: {{ face.similarity | number : '1.0-2' }}
-
{{ face.gender.value | titlecase }}, Age: {{ face.age.high + '-' + face.age.low }}
+
{{ face.gender.value | titlecase }}, Age: {{ face.age.low + '-' + face.age.high }}
From 3ac6d4a2dfae34e242d34f1a41e8a39524e9ad03 Mon Sep 17 00:00:00 2001 From: Pavel Bohdan Date: Tue, 29 Jun 2021 16:35:20 +0300 Subject: [PATCH 043/837] Delete blurred image. --- .../app-change-photo/app-change-photo.component.html | 2 +- .../features/app-change-photo/app-change-photo.component.ts | 1 + .../face-recognition-container.component.html | 1 + .../recognition-result/recognition-result.component.html | 5 +++-- .../recognition-result/recognition-result.component.ts | 1 + .../verification-result/verification-result.component.html | 2 ++ ui/src/app/store/face-verification/reducers.ts | 3 ++- 7 files changed, 11 insertions(+), 4 deletions(-) diff --git a/ui/src/app/features/app-change-photo/app-change-photo.component.html b/ui/src/app/features/app-change-photo/app-change-photo.component.html index 4d5ccbf77d..870ae3ab9c 100644 --- a/ui/src/app/features/app-change-photo/app-change-photo.component.html +++ b/ui/src/app/features/app-change-photo/app-change-photo.component.html @@ -37,7 +37,7 @@ class="change-photo--btn" mat-flat-button (click)="addLandmark.emit()" - [disabled]="disabledButtons" + [disabled]="disabledButtons || disabledLandmarksButton" [class.landmarks-active]="showLandmarks" > diff --git a/ui/src/app/features/app-change-photo/app-change-photo.component.ts b/ui/src/app/features/app-change-photo/app-change-photo.component.ts index 86cdc54423..65d4e08b6c 100644 --- a/ui/src/app/features/app-change-photo/app-change-photo.component.ts +++ b/ui/src/app/features/app-change-photo/app-change-photo.component.ts @@ -23,6 +23,7 @@ import { ChangeDetectionStrategy, Component, Output, EventEmitter, ViewChild, El }) export class AppChangePhotoComponent { @Input() disabledButtons: boolean; + @Input() disabledLandmarksButton: boolean; @Output() changePhoto = new EventEmitter(); @Output() resetPhoto = new EventEmitter(); diff --git a/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.html b/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.html index 64bb8b6640..7976cb1dc6 100644 --- a/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.html +++ b/ui/src/app/features/face-services/face-recognition/face-recognition-container.component.html @@ -15,6 +15,7 @@ -->
diff --git a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.ts b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.ts index ee1ae98275..e23f8fc7a3 100644 --- a/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.ts +++ b/ui/src/app/features/face-services/face-recognition/recognition-result/recognition-result.component.ts @@ -38,6 +38,7 @@ export class RecognitionResultComponent implements OnChanges { @Input() requestInfo: RequestInfo; @Input() isLoaded: boolean; + @Input() pending: boolean; @Input() type: ServiceTypes; @Output() selectFile = new EventEmitter(); diff --git a/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.html b/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.html index 331cd93d00..bed7834393 100644 --- a/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.html +++ b/ui/src/app/features/face-services/face-verification/verification-result/verification-result.component.html @@ -29,6 +29,7 @@ = createRe on(verifyFaceAddProcessFile, verifyFaceAddCheckFileFile, (state, action) => ({ ...state, ...action })), on(verifyFace, state => ({ ...state, isPending: true })), on(verifyFaceSuccess, (state, action) => ({ ...state, ...action, isPending: false })), - on(verifyFaceProcessFileReset, verifyFaceFail, state => ({ ...state, processFile: null, request: null, model: null, isPending: false })), + on(verifyFaceFail, state => ({ ...state, isPending: false })), + on(verifyFaceProcessFileReset, state => ({ ...state, processFile: null, request: null, model: null })), on(verifyFaceCheckFileReset, state => ({ ...state, checkFile: null, request: null, model: null })), on(verifyFaceReset, () => ({ ...initialStateVerification })) ); From d75acc185cbd1720130c88f42af404299ee216f5 Mon Sep 17 00:00:00 2001 From: Pavel Bohdan Date: Tue, 29 Jun 2021 16:59:58 +0300 Subject: [PATCH 044/837] Changed fonts to Poppins for Request/Response sections and Search fields --- .../features/app-search-table/app-search-table.component.html | 2 +- ui/src/styles/custom-theme.scss | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ui/src/app/features/app-search-table/app-search-table.component.html b/ui/src/app/features/app-search-table/app-search-table.component.html index ae9bb34c9f..11d70504ed 100644 --- a/ui/src/app/features/app-search-table/app-search-table.component.html +++ b/ui/src/app/features/app-search-table/app-search-table.component.html @@ -17,7 +17,7 @@ diff --git a/ui/src/app/pages/application/application.component.scss b/ui/src/app/pages/application/application.component.scss index 86b27b84c3..59f5fc1d1f 100644 --- a/ui/src/app/pages/application/application.component.scss +++ b/ui/src/app/pages/application/application.component.scss @@ -43,7 +43,6 @@ } mat-card { - width: 49%; transform: perspective(1px); @include mobile { @@ -71,8 +70,7 @@ } } - app-application-list-container, - app-user-list-container { + app-application-list-container { display: flex; flex-direction: column; justify-content: space-between; diff --git a/ui/src/app/pages/manage-collection/manage-collection.component.html b/ui/src/app/pages/manage-collection/manage-collection.component.html index e7177696ab..85ea053a99 100644 --- a/ui/src/app/pages/manage-collection/manage-collection.component.html +++ b/ui/src/app/pages/manage-collection/manage-collection.component.html @@ -13,7 +13,7 @@ ~ or implied. See the License for the specific language governing ~ permissions and limitations under the License. --> -
+
diff --git a/ui/src/app/pages/manage-collection/manage-collection.module.ts b/ui/src/app/pages/manage-collection/manage-collection.module.ts index 4ac87aec0e..17af17b29b 100644 --- a/ui/src/app/pages/manage-collection/manage-collection.module.ts +++ b/ui/src/app/pages/manage-collection/manage-collection.module.ts @@ -16,19 +16,16 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ManageCollectionComponent } from './manage-collection.component'; -import { MatCardModule } from '@angular/material/card'; import { RouterModule } from '@angular/router'; import { BreadcrumbsContainerModule } from '../../features/breadcrumbs.container/breadcrumbs.container.module'; import { ManageCollectionPageService } from './manage-collection.service'; -import { TranslateModule } from '@ngx-translate/core'; import { CollectionManagerModule } from 'src/app/features/collection-manager/collection-manager.module'; @NgModule({ declarations: [ManageCollectionComponent], imports: [ CommonModule, - MatCardModule, CollectionManagerModule, RouterModule.forChild([ { @@ -37,7 +34,6 @@ import { CollectionManagerModule } from 'src/app/features/collection-manager/col }, ]), BreadcrumbsContainerModule, - TranslateModule, ], providers: [ManageCollectionPageService], }) diff --git a/ui/src/app/pages/model-dashboard/model-dashboard.component.html b/ui/src/app/pages/model-dashboard/model-dashboard.component.html new file mode 100644 index 0000000000..fee5faeb08 --- /dev/null +++ b/ui/src/app/pages/model-dashboard/model-dashboard.component.html @@ -0,0 +1,20 @@ + +
+ + + +
diff --git a/ui/src/app/pages/model-dashboard/model-dashboard.component.scss b/ui/src/app/pages/model-dashboard/model-dashboard.component.scss new file mode 100644 index 0000000000..610d029637 --- /dev/null +++ b/ui/src/app/pages/model-dashboard/model-dashboard.component.scss @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ diff --git a/ui/src/app/pages/model-dashboard/model-dashboard.component.ts b/ui/src/app/pages/model-dashboard/model-dashboard.component.ts new file mode 100644 index 0000000000..c103ee7c5e --- /dev/null +++ b/ui/src/app/pages/model-dashboard/model-dashboard.component.ts @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-model-dashboard', + templateUrl: './model-dashboard.component.html', +}) +export class ModelDashboardComponent {} diff --git a/ui/src/app/pages/model-dashboard/model-dashboard.module.ts b/ui/src/app/pages/model-dashboard/model-dashboard.module.ts new file mode 100644 index 0000000000..e21943dce1 --- /dev/null +++ b/ui/src/app/pages/model-dashboard/model-dashboard.module.ts @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterModule } from '@angular/router'; + +import { ModelDashboardComponent } from './model-dashboard.component'; +import { BreadcrumbsContainerModule } from '../../features/breadcrumbs.container/breadcrumbs.container.module'; +import { ModelInfoModule } from 'src/app/features/model-info/model-info.module'; + +@NgModule({ + declarations: [ModelDashboardComponent], + imports: [ + CommonModule, + RouterModule.forChild([ + { + path: '', + component: ModelDashboardComponent, + }, + ]), + BreadcrumbsContainerModule, + ModelInfoModule, + ], +}) +export class ModelDashboardModule {} diff --git a/ui/src/app/store/manage-collectiom/reducers.ts b/ui/src/app/store/manage-collectiom/reducers.ts index c5d98decf5..01d70e3dae 100644 --- a/ui/src/app/store/manage-collectiom/reducers.ts +++ b/ui/src/app/store/manage-collectiom/reducers.ts @@ -38,12 +38,12 @@ import { uploadImage, uploadImageSuccess, uploadImageFail, - getSubjectExamplesSuccess, deleteSubjectExample, deleteSubjectExampleFail, deleteSubjectExampleSuccess, deleteItemFromUploadOrder, getSubjectExamples, + getSubjectExamplesSuccess, getSubjectExamplesFail, resetSubjectExamples, setSubjectMode, diff --git a/ui/src/app/ui/main-layout/main-layout.component.scss b/ui/src/app/ui/main-layout/main-layout.component.scss index 6171968152..5dd281ce30 100644 --- a/ui/src/app/ui/main-layout/main-layout.component.scss +++ b/ui/src/app/ui/main-layout/main-layout.component.scss @@ -15,6 +15,8 @@ */ .layout { + overflow-y: scroll; + .container { display: flex; justify-content: center; diff --git a/ui/src/assets/i18n/en.json b/ui/src/assets/i18n/en.json index 385fd0a8f6..fc089642bf 100644 --- a/ui/src/assets/i18n/en.json +++ b/ui/src/assets/i18n/en.json @@ -152,7 +152,7 @@ "manage_users_title": "Manage Users", "search_label": "Search Users", "user_name": "User Name", - "users": "Users", + "users": "Names", "roles": "Roles", "save": "Save" } @@ -180,6 +180,13 @@ "face_collection": "Face Collection", "test": "Test" }, + "model_info": { + "service_info": "Service Info", + "created_on": "Created on", + "service_name": "Service Name", + "total_images": "Total Images", + "total_faces": "Total Faces" + }, "app_users": { "add_users_info": "All registered CompreFace users except the first one won't see any applications. To give them access you need to add them to individual application. Global Owner and Global Administrators have full access to all applications.", "add_user_dialog": { diff --git a/ui/src/styles.scss b/ui/src/styles.scss index ce38326ccc..1160fefde9 100644 --- a/ui/src/styles.scss +++ b/ui/src/styles.scss @@ -195,6 +195,7 @@ app-model-table { } .mat-tooltip { + word-break: break-all; text-align: center; font-size: 13px; font-weight: 500; diff --git a/ui/src/styles/_layout.scss b/ui/src/styles/_layout.scss index b4b9501098..6d56a05323 100644 --- a/ui/src/styles/_layout.scss +++ b/ui/src/styles/_layout.scss @@ -27,8 +27,8 @@ &--main { grid-area: main; - padding: 48px 0; - overflow: auto; + padding: 20px 0; + overflow: hidden; } &--footer { From df315a4c73db257fe6c6edd49e6f6db72146bee8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Mar 2022 21:26:06 +0000 Subject: [PATCH 306/837] Bump liquibase-core from 4.3.5 to 4.8.0 in /java Bumps [liquibase-core](https://github.com/liquibase/liquibase) from 4.3.5 to 4.8.0. - [Release notes](https://github.com/liquibase/liquibase/releases) - [Changelog](https://github.com/liquibase/liquibase/blob/master/changelog.txt) - [Commits](https://github.com/liquibase/liquibase/compare/v4.3.5...v4.8.0) --- updated-dependencies: - dependency-name: org.liquibase:liquibase-core dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- java/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/pom.xml b/java/pom.xml index 8bee428f4b..168b46f0f3 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -45,7 +45,7 @@ 1.0.0-beta7 11 linux-x86_64 - 4.3.5 + 4.8.0 0.8.9 From 59b412bec30025608f1e3023259735d77ca08b66 Mon Sep 17 00:00:00 2001 From: ksidikov Date: Wed, 9 Mar 2022 17:49:25 +0500 Subject: [PATCH 307/837] EFRS-1207: Subject special chars bug fixed on backend --- .../trainservice/controller/SubjectController.java | 8 ++++---- .../trainservice/dto/RenameSubjectRequest.java | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 java/api/src/main/java/com/exadel/frs/core/trainservice/dto/RenameSubjectRequest.java diff --git a/java/api/src/main/java/com/exadel/frs/core/trainservice/controller/SubjectController.java b/java/api/src/main/java/com/exadel/frs/core/trainservice/controller/SubjectController.java index eedc05376d..15dc941c96 100644 --- a/java/api/src/main/java/com/exadel/frs/core/trainservice/controller/SubjectController.java +++ b/java/api/src/main/java/com/exadel/frs/core/trainservice/controller/SubjectController.java @@ -1,6 +1,7 @@ package com.exadel.frs.core.trainservice.controller; +import com.exadel.frs.core.trainservice.dto.RenameSubjectRequest; import com.exadel.frs.core.trainservice.dto.SubjectDto; import com.exadel.frs.core.trainservice.service.SubjectService; import io.swagger.annotations.ApiParam; @@ -41,14 +42,13 @@ public Map listSubjects( ); } - @PutMapping("/{subject}") + @PutMapping public Map renameSubject( @ApiParam(value = API_KEY_DESC, required = true) @RequestHeader(X_FRS_API_KEY_HEADER) final String apiKey, - @ApiParam(value = SUBJECT_DESC, required = true) @Valid @NotBlank(message = SUBJECT_NAME_IS_EMPTY) @PathVariable("subject") final String oldSubjectName, - @Valid @RequestBody final SubjectDto subjectDto) { + @Valid @RequestBody final RenameSubjectRequest renameSubjectRequest) { return Map.of( "updated", - subjectService.updateSubjectName(apiKey, oldSubjectName, subjectDto.getSubjectName()) + subjectService.updateSubjectName(apiKey, renameSubjectRequest.getOldSubjectName(), renameSubjectRequest.getSubjectDto().getSubjectName()) ); } diff --git a/java/api/src/main/java/com/exadel/frs/core/trainservice/dto/RenameSubjectRequest.java b/java/api/src/main/java/com/exadel/frs/core/trainservice/dto/RenameSubjectRequest.java new file mode 100644 index 0000000000..53261ea198 --- /dev/null +++ b/java/api/src/main/java/com/exadel/frs/core/trainservice/dto/RenameSubjectRequest.java @@ -0,0 +1,14 @@ +package com.exadel.frs.core.trainservice.dto; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +@Data +public class RenameSubjectRequest { + @NotNull + private SubjectDto subjectDto; + @NotBlank + private String oldSubjectName; +} From 701ac290b089b2c24239ba03de6454f860d55418 Mon Sep 17 00:00:00 2001 From: ksidikov Date: Thu, 10 Mar 2022 15:15:14 +0500 Subject: [PATCH 308/837] EFRS-1207: Added migration to update created_date of model table --- .../main/resources/db/changelog/db.changelog-0.1.9.yaml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/java/admin/src/main/resources/db/changelog/db.changelog-0.1.9.yaml b/java/admin/src/main/resources/db/changelog/db.changelog-0.1.9.yaml index 31677da72f..60669c5a30 100644 --- a/java/admin/src/main/resources/db/changelog/db.changelog-0.1.9.yaml +++ b/java/admin/src/main/resources/db/changelog/db.changelog-0.1.9.yaml @@ -8,4 +8,11 @@ databaseChangeLog: columns: - column: name: created_date - type: timestamp \ No newline at end of file + type: timestamp + - changeSet: + id: update-model-table-created_date-column + author: Khasan Sidikov + changes: + - sql: + comment: Update created_date to now() + sql: UPDATE model SET created_date=now() WHERE created_date IS NULL; \ No newline at end of file From 5a26522b13c6471073a84d1064861b7e799f5718 Mon Sep 17 00:00:00 2001 From: ksidikov Date: Thu, 10 Mar 2022 19:22:40 +0500 Subject: [PATCH 309/837] EFRS-1207: Improved special characters in subject renaming --- .../core/trainservice/controller/SubjectController.java | 8 ++++---- .../java/com/exadel/frs/commonservice/entity/Model.java | 1 + ui/src/app/core/collection/collection.service.ts | 6 ++++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/java/api/src/main/java/com/exadel/frs/core/trainservice/controller/SubjectController.java b/java/api/src/main/java/com/exadel/frs/core/trainservice/controller/SubjectController.java index 15dc941c96..eedc05376d 100644 --- a/java/api/src/main/java/com/exadel/frs/core/trainservice/controller/SubjectController.java +++ b/java/api/src/main/java/com/exadel/frs/core/trainservice/controller/SubjectController.java @@ -1,7 +1,6 @@ package com.exadel.frs.core.trainservice.controller; -import com.exadel.frs.core.trainservice.dto.RenameSubjectRequest; import com.exadel.frs.core.trainservice.dto.SubjectDto; import com.exadel.frs.core.trainservice.service.SubjectService; import io.swagger.annotations.ApiParam; @@ -42,13 +41,14 @@ public Map listSubjects( ); } - @PutMapping + @PutMapping("/{subject}") public Map renameSubject( @ApiParam(value = API_KEY_DESC, required = true) @RequestHeader(X_FRS_API_KEY_HEADER) final String apiKey, - @Valid @RequestBody final RenameSubjectRequest renameSubjectRequest) { + @ApiParam(value = SUBJECT_DESC, required = true) @Valid @NotBlank(message = SUBJECT_NAME_IS_EMPTY) @PathVariable("subject") final String oldSubjectName, + @Valid @RequestBody final SubjectDto subjectDto) { return Map.of( "updated", - subjectService.updateSubjectName(apiKey, renameSubjectRequest.getOldSubjectName(), renameSubjectRequest.getSubjectDto().getSubjectName()) + subjectService.updateSubjectName(apiKey, oldSubjectName, subjectDto.getSubjectName()) ); } diff --git a/java/common/src/main/java/com/exadel/frs/commonservice/entity/Model.java b/java/common/src/main/java/com/exadel/frs/commonservice/entity/Model.java index 6bcf337542..dc9ccf80f1 100644 --- a/java/common/src/main/java/com/exadel/frs/commonservice/entity/Model.java +++ b/java/common/src/main/java/com/exadel/frs/commonservice/entity/Model.java @@ -68,6 +68,7 @@ public Model(Model model) { @OneToMany(mappedBy = "model", cascade = CascadeType.ALL, orphanRemoval = true) private List appModelAccess = new ArrayList<>(); + @Column(name = "created_date") private LocalDateTime createdDate; public void addAppModelAccess(App app, AppModelAccess access) { diff --git a/ui/src/app/core/collection/collection.service.ts b/ui/src/app/core/collection/collection.service.ts index 96095c584f..dae3c0b97f 100644 --- a/ui/src/app/core/collection/collection.service.ts +++ b/ui/src/app/core/collection/collection.service.ts @@ -41,8 +41,9 @@ export class CollectionService { } editSubject(editName: string, apiKey: string, subject: string): Observable<{ updated: boolean }> { + const subjectEncoded = encodeURIComponent(subject); return this.http.put<{ updated: boolean }>( - `${environment.userApiUrl}recognition/subjects/${subject}`, + `${environment.userApiUrl}recognition/subjects/${subjectEncoded}`, { subject: editName }, { headers: { 'x-api-key': apiKey }, @@ -87,8 +88,9 @@ export class CollectionService { const { file } = item; const formData = new FormData(); formData.append('file', file, file.name); + const subjectEncoded = encodeURIComponent(subject); - return this.http.post(`${environment.userApiUrl}recognition/faces?subject=${subject}`, formData, { + return this.http.post(`${environment.userApiUrl}recognition/faces?subject=${subjectEncoded}`, formData, { headers: { 'x-api-key': apiKey }, }); } From e8d7d9f076260ace0dfd11f29f845c10eb64f8ce Mon Sep 17 00:00:00 2001 From: Hasan <82145753+ksidikov@users.noreply.github.com> Date: Thu, 10 Mar 2022 19:44:18 +0500 Subject: [PATCH 310/837] Delete RenameSubjectRequest.java --- .../trainservice/dto/RenameSubjectRequest.java | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 java/api/src/main/java/com/exadel/frs/core/trainservice/dto/RenameSubjectRequest.java diff --git a/java/api/src/main/java/com/exadel/frs/core/trainservice/dto/RenameSubjectRequest.java b/java/api/src/main/java/com/exadel/frs/core/trainservice/dto/RenameSubjectRequest.java deleted file mode 100644 index 53261ea198..0000000000 --- a/java/api/src/main/java/com/exadel/frs/core/trainservice/dto/RenameSubjectRequest.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.exadel.frs.core.trainservice.dto; - -import lombok.Data; - -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; - -@Data -public class RenameSubjectRequest { - @NotNull - private SubjectDto subjectDto; - @NotBlank - private String oldSubjectName; -} From b22535d3ea0f5d5a40e39e43a2fc6f2caa90359c Mon Sep 17 00:00:00 2001 From: smchedlidze826 Date: Thu, 10 Mar 2022 19:03:35 +0400 Subject: [PATCH 311/837] EFRS-1207 EFRS-1207 encoding url with collectin subject operations --- ui/src/app/core/collection/collection.service.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ui/src/app/core/collection/collection.service.ts b/ui/src/app/core/collection/collection.service.ts index dae3c0b97f..c71a7db55c 100644 --- a/ui/src/app/core/collection/collection.service.ts +++ b/ui/src/app/core/collection/collection.service.ts @@ -52,7 +52,8 @@ export class CollectionService { } deleteSubject(subject: string, apiKey: string): Observable<{ subject: string }> { - return this.http.delete<{ subject: string }>(`${environment.userApiUrl}recognition/subjects/${subject}`, { + const subjectEncoded = encodeURIComponent(subject); + return this.http.delete<{ subject: string }>(`${environment.userApiUrl}recognition/subjects/${subjectEncoded}`, { headers: { 'x-api-key': apiKey }, }); } @@ -63,8 +64,9 @@ export class CollectionService { } getSubjectMedia(apiKey: string, subject: string, page: number = 0, size: number = 15) { + const subjectEncoded = encodeURIComponent(subject); return this.http - .get(`${environment.userApiUrl}recognition/faces?size=${size}&subject=${subject}&page=${page}`, { + .get(`${environment.userApiUrl}recognition/faces?size=${size}&subject=${subjectEncoded}&page=${page}`, { headers: { 'x-api-key': apiKey }, }) .pipe( From 01124dcf18cc56d302bcc7eac318291ad9e472c4 Mon Sep 17 00:00:00 2001 From: Saba <69626492+smchedlidze826@users.noreply.github.com> Date: Sun, 13 Mar 2022 12:49:22 +0400 Subject: [PATCH 312/837] Efrs 1191/update add service application user dialog (#745) * BugFix/prevent-copping-invalid-images-on-scroll * From 1177 Task - restart uploading functionality fixed * EFRS-1177 failing build fix * EFRS: Improved is_save_images_to_db * fixed python build * fixed migration * EFRS: Improved is_save_images_to_db * fix for custom builds * EFRS-1191 updated create dialogs * EFRS-1191 updated view of add users dialog * Reset to 1191 task Co-authored-by: ksidikov Co-authored-by: Pospielov Serhii --- .../application-list-container.component.ts | 1 - .../create-dialog.component.html | 31 +++++---- .../create-dialog.component.scss | 54 +++++++++++++++ .../create-dialog/create-dialog.component.ts | 1 + .../invite-dialog.component.html | 37 +++++----- .../invite-dialog.component.scss | 55 +++++++++++++++ .../invite-dialog/invite-dialog.component.ts | 1 + .../model-create-dialog.component.html | 49 +++++++------ .../model-create-dialog.component.scss | 68 +++++++++++++++++++ .../model-create-dialog.component.ts | 1 + ui/src/assets/i18n/en.json | 5 ++ ui/src/styles/custom-theme.scss | 2 +- 12 files changed, 247 insertions(+), 58 deletions(-) create mode 100644 ui/src/app/features/create-dialog/create-dialog.component.scss create mode 100644 ui/src/app/features/invite-dialog/invite-dialog.component.scss create mode 100644 ui/src/app/features/mode-create-dialog/model-create-dialog.component.scss diff --git a/ui/src/app/features/application-list/application-list-container.component.ts b/ui/src/app/features/application-list/application-list-container.component.ts index 9c0b8ab7e5..a6784f33e9 100644 --- a/ui/src/app/features/application-list/application-list-container.component.ts +++ b/ui/src/app/features/application-list/application-list-container.component.ts @@ -18,7 +18,6 @@ import { MatDialog } from '@angular/material/dialog'; import { Router } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; import { Observable } from 'rxjs'; -import { Role } from 'src/app/data/enums/role.enum'; import { AppUser } from 'src/app/data/interfaces/app-user'; import { Application } from 'src/app/data/interfaces/application'; import { UserDeletion } from 'src/app/data/interfaces/user-deletion'; diff --git a/ui/src/app/features/create-dialog/create-dialog.component.html b/ui/src/app/features/create-dialog/create-dialog.component.html index 972bffa330..a41c6f31ec 100644 --- a/ui/src/app/features/create-dialog/create-dialog.component.html +++ b/ui/src/app/features/create-dialog/create-dialog.component.html @@ -14,19 +14,22 @@ ~ permissions and limitations under the License. --> -

{{ 'common.create' | translate }} {{ data.entityType }}

-
- - {{ data.placeholder }} - - -
-
- - +
+
- diff --git a/ui/src/app/features/create-dialog/create-dialog.component.scss b/ui/src/app/features/create-dialog/create-dialog.component.scss new file mode 100644 index 0000000000..ef3867702b --- /dev/null +++ b/ui/src/app/features/create-dialog/create-dialog.component.scss @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language go verning + * permissions and limitations under the License. + */ + +.dialog { + &__header { + width: 100%; + &--title { + margin: 1rem 0 0; + font-size: 2rem; + } + } + + &__content { + box-sizing: border-box; + overflow: hidden; + + &--form_field { + margin: 20px 0; + + label { + display: block; + margin-bottom: 20px; + } + + input { + width: 100%; + outline: none; + border: none; + border-bottom: 1px solid grey; + padding-bottom: 10px; + } + } + } + + &__actions { + button { + border: 1px solid grey; + border-radius: 15px; + } + } +} diff --git a/ui/src/app/features/create-dialog/create-dialog.component.ts b/ui/src/app/features/create-dialog/create-dialog.component.ts index 438920b98c..71e1d3dde6 100644 --- a/ui/src/app/features/create-dialog/create-dialog.component.ts +++ b/ui/src/app/features/create-dialog/create-dialog.component.ts @@ -19,6 +19,7 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; @Component({ selector: 'app-create-dialog', templateUrl: './create-dialog.component.html', + styleUrls: ['./create-dialog.component.scss'], }) export class CreateDialogComponent { constructor(public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: any) {} diff --git a/ui/src/app/features/invite-dialog/invite-dialog.component.html b/ui/src/app/features/invite-dialog/invite-dialog.component.html index ce2c2d508d..fe67619bc2 100644 --- a/ui/src/app/features/invite-dialog/invite-dialog.component.html +++ b/ui/src/app/features/invite-dialog/invite-dialog.component.html @@ -14,33 +14,30 @@ ~ permissions and limitations under the License. --> -

{{ 'common.capital.add' | translate }} {{ 'users.user' | translate }}

-
-
- - {{ 'common.email' | translate }} - - {{ 'app_users.add_user_dialog.email_hint' | translate }} - +
+
+

{{ 'common.capital.add' | translate }} {{ 'users.user' | translate }}

+
+ +
+ - - - - {{ 'common.type' | translate }} +
+
+ {{ role }} - +
{{ option }} +
+ +
+
-
- - -
- diff --git a/ui/src/app/features/invite-dialog/invite-dialog.component.scss b/ui/src/app/features/invite-dialog/invite-dialog.component.scss new file mode 100644 index 0000000000..29a75e8755 --- /dev/null +++ b/ui/src/app/features/invite-dialog/invite-dialog.component.scss @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language go verning + * permissions and limitations under the License. + */ + +.dialog { + &__header { + width: 100%; + &--title { + margin: 1rem 0 0; + font-size: 2rem; + } + } + + &__content { + box-sizing: border-box; + overflow: hidden; + + &--form_field { + margin: 20px 0; + + label { + display: block; + margin-bottom: 20px; + } + + input, + .mat-select { + width: 100%; + outline: none; + border: none; + border-bottom: 1px solid grey; + padding-bottom: 10px; + } + } + } + + &__actions { + button { + border: 1px solid grey; + border-radius: 15px; + } + } +} diff --git a/ui/src/app/features/invite-dialog/invite-dialog.component.ts b/ui/src/app/features/invite-dialog/invite-dialog.component.ts index 40be3e2c3e..cda461c74d 100644 --- a/ui/src/app/features/invite-dialog/invite-dialog.component.ts +++ b/ui/src/app/features/invite-dialog/invite-dialog.component.ts @@ -23,6 +23,7 @@ import { EMAIL_REGEXP_PATTERN } from 'src/app/core/constants'; @Component({ selector: 'app-invite-dialog', templateUrl: './invite-dialog.component.html', + styleUrls: ['./invite-dialog.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) export class InviteDialogComponent implements OnInit { diff --git a/ui/src/app/features/mode-create-dialog/model-create-dialog.component.html b/ui/src/app/features/mode-create-dialog/model-create-dialog.component.html index 5ff6e40f1e..c9eddd9761 100644 --- a/ui/src/app/features/mode-create-dialog/model-create-dialog.component.html +++ b/ui/src/app/features/mode-create-dialog/model-create-dialog.component.html @@ -1,4 +1,4 @@ - - -

{{ 'common.create' | translate }} {{ data.entityType }}

-
- - {{ 'common.name' | translate }} - - - - {{ 'common.type' | translate }} - - {{ type | uppercase }} - - -
-
- - +
+
- diff --git a/ui/src/app/features/mode-create-dialog/model-create-dialog.component.scss b/ui/src/app/features/mode-create-dialog/model-create-dialog.component.scss new file mode 100644 index 0000000000..986065c73a --- /dev/null +++ b/ui/src/app/features/mode-create-dialog/model-create-dialog.component.scss @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language go verning + * permissions and limitations under the License. + */ + +.dialog { + height: 50vh; + + &__header { + width: 100%; + height: 30%; + display: flex; + flex-direction: column; + justify-content: space-between; + + &--title { + margin: 1rem 0 0; + font-size: 2rem; + } + &--info { + font-size: 13px; + } + } + + &__content { + height: 60%; + padding-top: 20px; + box-sizing: border-box; + overflow: hidden; + + &--form_field { + margin: 20px 0; + label { + display: block; + margin-bottom: 20px; + } + + input, + .mat-select { + width: 100%; + outline: none; + border: none; + border-bottom: 1px solid grey; + padding-bottom: 10px; + } + } + } + + &__actions { + height: 10%; + + button { + border: 1px solid grey; + border-radius: 15px; + } + } +} diff --git a/ui/src/app/features/mode-create-dialog/model-create-dialog.component.ts b/ui/src/app/features/mode-create-dialog/model-create-dialog.component.ts index 852587b3c4..847033d330 100644 --- a/ui/src/app/features/mode-create-dialog/model-create-dialog.component.ts +++ b/ui/src/app/features/mode-create-dialog/model-create-dialog.component.ts @@ -20,6 +20,7 @@ import { ServiceTypes } from '../../data/enums/service-types.enum'; @Component({ selector: 'app-model-create-dialog', templateUrl: './model-create-dialog.component.html', + styleUrls: ['./model-create-dialog.component.scss'], }) export class ModelCreateDialogComponent extends CreateDialogComponent { constructor(public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: any) { diff --git a/ui/src/assets/i18n/en.json b/ui/src/assets/i18n/en.json index fc089642bf..5913d0dffa 100644 --- a/ui/src/assets/i18n/en.json +++ b/ui/src/assets/i18n/en.json @@ -105,7 +105,12 @@ "api_key": "API key", "header": "Service", "create_model_info": "Create one of face recognition services: Face Recognition, Face Detection or Face Verification.", + "info_about_service": "Services provide you with different face recognition functionality. Each service has an API key you need to make requests. Please choose service type according to your business needs.", + "model_name_label": "What would you like to name this service ?", + "model_type_label": "What kind of service is it?", + "model_name": "Service Name", "create_dialog": { + "create": "Create Service ", "title": "Services", "name": "Name" }, diff --git a/ui/src/styles/custom-theme.scss b/ui/src/styles/custom-theme.scss index 6339a02057..17725ea4cd 100644 --- a/ui/src/styles/custom-theme.scss +++ b/ui/src/styles/custom-theme.scss @@ -315,7 +315,7 @@ $custom-theme: mat-light-theme($custom-theme-primary, $custom-theme-accent, $cus } .custom-mat-dialog { - width: 500px; + width: 600px; .mat-dialog { &-container { From 121faad7060e242770fc82269909ba4edbfaf278 Mon Sep 17 00:00:00 2001 From: smchedlidze826 Date: Wed, 16 Mar 2022 13:46:20 +0400 Subject: [PATCH 313/837] EFRS-1216 increase width of application name --- .../application-collection.component.scss | 1 + .../application-list/application-list-container.component.ts | 2 +- ui/src/app/features/application-list/application-list.module.ts | 2 +- .../manage-users.component.html | 0 .../manage-users.component.scss | 0 .../manage-users.component.ts | 0 .../manage-users.module.ts | 0 7 files changed, 3 insertions(+), 2 deletions(-) rename ui/src/app/features/{magage-users-dialog => manage-users-dialog}/manage-users.component.html (100%) rename ui/src/app/features/{magage-users-dialog => manage-users-dialog}/manage-users.component.scss (100%) rename ui/src/app/features/{magage-users-dialog => manage-users-dialog}/manage-users.component.ts (100%) rename ui/src/app/features/{magage-users-dialog => manage-users-dialog}/manage-users.module.ts (100%) diff --git a/ui/src/app/features/application-collection-container/application-collection/application-collection.component.scss b/ui/src/app/features/application-collection-container/application-collection/application-collection.component.scss index 54a343c2ab..f1971f34db 100644 --- a/ui/src/app/features/application-collection-container/application-collection/application-collection.component.scss +++ b/ui/src/app/features/application-collection-container/application-collection/application-collection.component.scss @@ -34,6 +34,7 @@ justify-content: center; font-size: 1.8rem; margin-bottom: 20px; + width: 100%; &_title { word-break: break-all; diff --git a/ui/src/app/features/application-list/application-list-container.component.ts b/ui/src/app/features/application-list/application-list-container.component.ts index a6784f33e9..b02adf1f8c 100644 --- a/ui/src/app/features/application-list/application-list-container.component.ts +++ b/ui/src/app/features/application-list/application-list-container.component.ts @@ -24,7 +24,7 @@ import { UserDeletion } from 'src/app/data/interfaces/user-deletion'; import { CreateDialogComponent } from 'src/app/features/create-dialog/create-dialog.component'; import { Routes } from '../../data/enums/routers-url.enum'; -import { ManageUsersDialog } from '../magage-users-dialog/manage-users.component'; +import { ManageUsersDialog } from '../manage-users-dialog/manage-users.component'; import { UserListFacade } from '../user-list/user-list-facade'; import { ApplicationListFacade } from './application-list-facade'; diff --git a/ui/src/app/features/application-list/application-list.module.ts b/ui/src/app/features/application-list/application-list.module.ts index dac6d2b762..6bdc099f9e 100644 --- a/ui/src/app/features/application-list/application-list.module.ts +++ b/ui/src/app/features/application-list/application-list.module.ts @@ -30,7 +30,7 @@ import { TablePipeModule } from '../../ui/search-pipe/table-filter.module'; import { MatInputModule } from '@angular/material/input'; import { AppSearchTableModule } from '../app-search-table/app-search-table.module'; import { ApplicationCollectionContainerModule } from '../application-collection-container/application-collection-container.module'; -import { ManageUsersModule } from '../magage-users-dialog/manage-users.module'; +import { ManageUsersModule } from '../manage-users-dialog/manage-users.module'; @NgModule({ declarations: [ApplicationListContainerComponent, ApplicationListComponent], diff --git a/ui/src/app/features/magage-users-dialog/manage-users.component.html b/ui/src/app/features/manage-users-dialog/manage-users.component.html similarity index 100% rename from ui/src/app/features/magage-users-dialog/manage-users.component.html rename to ui/src/app/features/manage-users-dialog/manage-users.component.html diff --git a/ui/src/app/features/magage-users-dialog/manage-users.component.scss b/ui/src/app/features/manage-users-dialog/manage-users.component.scss similarity index 100% rename from ui/src/app/features/magage-users-dialog/manage-users.component.scss rename to ui/src/app/features/manage-users-dialog/manage-users.component.scss diff --git a/ui/src/app/features/magage-users-dialog/manage-users.component.ts b/ui/src/app/features/manage-users-dialog/manage-users.component.ts similarity index 100% rename from ui/src/app/features/magage-users-dialog/manage-users.component.ts rename to ui/src/app/features/manage-users-dialog/manage-users.component.ts diff --git a/ui/src/app/features/magage-users-dialog/manage-users.module.ts b/ui/src/app/features/manage-users-dialog/manage-users.module.ts similarity index 100% rename from ui/src/app/features/magage-users-dialog/manage-users.module.ts rename to ui/src/app/features/manage-users-dialog/manage-users.module.ts From c8b32644811d4d8ab59902777f709908e3a82f63 Mon Sep 17 00:00:00 2001 From: smchedlidze826 Date: Wed, 16 Mar 2022 14:33:41 +0400 Subject: [PATCH 314/837] EFRS-1217 added tooltip to the manage users dialog on user names with length more then 30 characters --- .../manage-users-dialog/manage-users.component.html | 8 +++++++- .../manage-users-dialog/manage-users.module.ts | 13 ++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/ui/src/app/features/manage-users-dialog/manage-users.component.html b/ui/src/app/features/manage-users-dialog/manage-users.component.html index 626ffabb7c..4595c4cbae 100644 --- a/ui/src/app/features/manage-users-dialog/manage-users.component.html +++ b/ui/src/app/features/manage-users-dialog/manage-users.component.html @@ -29,7 +29,13 @@

{{ 'users.manage.manage_users_title' | translate }}
- +

placeholder="{{ 'change_password.old_password' | translate }}" required /> - +
- - {{ 'change_password.new_password' | translate }} - {{ 'registration.password_restriction' | translate }} - +
+ +
{{ 'registration.password_restriction' | translate }} - +
{{ 'toolbar.change_password' | translate }} placeholder="{{ 'change_password.new_password' | translate }}" required /> - +
-
-
- - +
- +
+
+ {{ 'applications.build' | translate }} {{ env.buildNumber }}
diff --git a/ui/src/app/features/login-form/login-form.component.scss b/ui/src/app/features/login-form/login-form.component.scss index b015067a88..94b37fc455 100644 --- a/ui/src/app/features/login-form/login-form.component.scss +++ b/ui/src/app/features/login-form/login-form.component.scss @@ -14,93 +14,23 @@ * permissions and limitations under the License. */ @import 'forms.scss'; -@import 'media.scss'; @import 'colors.scss'; .login-card { - color: $medium-gray; - min-height: 450px; - padding: 2rem; - box-shadow: 0 0 10px $lighter-gray, 0 0 10px $lighter-gray; - border-radius: 2rem; - &--title { - margin: 0.5rem 0 2rem; - padding-bottom: 1.5rem; - font-size: 2rem; - font-weight: bold; - display: block; - text-align: center; - color: $medium-gray; - border-bottom: 1px solid $lighter-gray; - } + @include login-card($medium-gray, $lighter-gray); } .form-field { - display: flex; - flex-direction: column; - align-items: flex-start; - margin-top: 1.5rem; - - width: 100%; - label { - font-weight: bold; - font-size: 18px; - } - input { - margin-top: 0.5rem; - padding: 0.5rem 0; - width: 100%; - border: none; - background: none; - outline: none; - border-bottom: 1px solid $lighter-gray; - - &::placeholder { - color: $light-gray; - font-weight: bold; - } - } - .err { - font-weight: bold; - font-size: 12px; - color: $red; - } + @include form-field($light-gray, $lighter-gray); } + .btn { - background: $lighter-gray; - border: 2px solid $light-gray; + @include submit-btn; width: 100%; margin: 1.5rem 0 1rem; padding: 5px; - border-radius: 1rem; -} -.link { - font-size: 14px; - font-weight: 500; - span { - a { - color: $medium-gray; - text-decoration: underline; - } - } } -.desktop-screen { - display: block; -} -.mobile-screen { - display: none; -} - -@include mobile-portrait { - .desktop-screen { - display: none; - } - .mobile-screen { - height: 150px; - transform: scale(1.3); - object-fit: cover; - overflow: hidden; - display: block; - } +.link { + @include login-link($medium-gray); } diff --git a/ui/src/app/features/manage-app-users-dialog/manage-app-users.component.html b/ui/src/app/features/manage-app-users-dialog/manage-app-users.component.html index 0cdfae6e49..608f6c488b 100644 --- a/ui/src/app/features/manage-app-users-dialog/manage-app-users.component.html +++ b/ui/src/app/features/manage-app-users-dialog/manage-app-users.component.html @@ -19,9 +19,6 @@

Manage Application Users

-
@@ -33,14 +30,16 @@

Manage Application Users

-

- {{ user.fullName | truncate: 15 }} + [matTooltipDisabled]="user.fullName.length <= 30" + > + {{ user.fullName | truncate: 15 }}

{{ user.email | truncate: 15 }}

- +
-
+
{{ role }} @@ -34,8 +34,8 @@

{{ 'common.capital.add' | translate }} {{ 'users.user' | tr {{ option }} -
- +
+
@@ -45,16 +45,13 @@

{{ 'toolbar.change_password' | translate }}

formControlName="newPassword" type="password" name="newPassword" - placeholder="{{ 'change_password.new_password' | translate }}" + placeholder="{{ 'change_password.enter_password' | translate }}" required />
diff --git a/ui/src/app/features/collection-manager-subject-left/collection-manager-subject-left.container.component.ts b/ui/src/app/features/collection-manager-subject-left/collection-manager-subject-left.container.component.ts index 1a3dee65ac..5662cdbdee 100644 --- a/ui/src/app/features/collection-manager-subject-left/collection-manager-subject-left.container.component.ts +++ b/ui/src/app/features/collection-manager-subject-left/collection-manager-subject-left.container.component.ts @@ -40,7 +40,7 @@ import { Input } from '@angular/core'; [search]="search" (editSubject)="edit($event)" (deleteSubject)="delete($event)" - (addSubject)="addSubject()" + (addSubject)="addSubject($event)" (selectedSubject)="onSelectedSubject($event)" (initApiKey)="initApiKey($event)" >`, @@ -143,7 +143,7 @@ export class CollectionManagerSubjectLeftContainerComponent implements OnInit, O .subscribe(() => this.collectionLeftFacade.edit(editName, name, this.apiKey)); } - addSubject(): void { + openCreateDialog(): void { const dialog = this.dialog.open(CreateDialogComponent, { panelClass: 'custom-mat-dialog', data: { @@ -156,14 +156,17 @@ export class CollectionManagerSubjectLeftContainerComponent implements OnInit, O }); const dialogSubscription = dialog.afterClosed().subscribe(name => { - if (name) { - this.collectionLeftFacade.addSubject(name, this.apiKey); - dialogSubscription.unsubscribe(); - } + dialogSubscription.unsubscribe(); + if (!name) return; + this.collectionLeftFacade.addSubject(name, this.apiKey); }); } - onSelectedSubject(subject): void { + addSubject(currentSubject: string): void { + this.itemsInProgress ? this.openDialog(currentSubject, true) : this.openCreateDialog(); + } + + onSelectedSubject(subject: string): void { this.setDefaultMode.emit(); if (this.itemsInProgress) { @@ -174,18 +177,20 @@ export class CollectionManagerSubjectLeftContainerComponent implements OnInit, O } } - openDialog(subject): void { + openDialog(subject: string, addSubjectInprogress = false): void { const dialog = this.dialog.open(ConfirmDialogComponent, { panelClass: 'custom-mat-dialog', }); dialog.afterClosed().subscribe(confirm => { - if (confirm) { - this.collectionLeftFacade.onSelectedSubject(subject); - this.collectionRightFacade.loadSubjectMedia(subject); - } + if (!confirm) return; this.itemsInProgress = false; + + if (addSubjectInprogress) this.addSubject(subject); + + this.collectionLeftFacade.onSelectedSubject(subject); + this.collectionRightFacade.loadSubjectMedia(subject); }); } diff --git a/ui/src/app/features/collection-manager-subject-left/collection-manager-subject-left/collection-manager-subject-left.component.html b/ui/src/app/features/collection-manager-subject-left/collection-manager-subject-left/collection-manager-subject-left.component.html index 8e58394c8a..d28cc720f3 100644 --- a/ui/src/app/features/collection-manager-subject-left/collection-manager-subject-left/collection-manager-subject-left.component.html +++ b/ui/src/app/features/collection-manager-subject-left/collection-manager-subject-left/collection-manager-subject-left.component.html @@ -49,7 +49,7 @@
- diff --git a/ui/src/app/features/collection-manager-subject-left/collection-manager-subject-left/collection-manager-subject-left.component.ts b/ui/src/app/features/collection-manager-subject-left/collection-manager-subject-left/collection-manager-subject-left.component.ts index 4edcd18292..6f4327004a 100644 --- a/ui/src/app/features/collection-manager-subject-left/collection-manager-subject-left/collection-manager-subject-left.component.ts +++ b/ui/src/app/features/collection-manager-subject-left/collection-manager-subject-left/collection-manager-subject-left.component.ts @@ -33,7 +33,7 @@ export class CollectionManagerSubjectLeftComponent { @Output() deleteSubject = new EventEmitter(); @Output() editSubject = new EventEmitter(); - @Output() addSubject = new EventEmitter(); + @Output() addSubject = new EventEmitter(); @Output() selectedSubject = new EventEmitter(); @Output() initApiKey = new EventEmitter(); diff --git a/ui/src/app/features/edit-user-info-dialog/edit-user-info-dialog.component.html b/ui/src/app/features/edit-user-info-dialog/edit-user-info-dialog.component.html index adbe868efe..7ba5bdb558 100644 --- a/ui/src/app/features/edit-user-info-dialog/edit-user-info-dialog.component.html +++ b/ui/src/app/features/edit-user-info-dialog/edit-user-info-dialog.component.html @@ -14,7 +14,7 @@ ~ permissions and limitations under the License. --> -

{{ 'toolbar.user_info' | translate }}

+

{{ 'toolbar.user_info_change' | translate }}

diff --git a/ui/src/app/features/sign-up-form/sign-up-form.component.html b/ui/src/app/features/sign-up-form/sign-up-form.component.html index adb8f30a20..163243b9b6 100644 --- a/ui/src/app/features/sign-up-form/sign-up-form.component.html +++ b/ui/src/app/features/sign-up-form/sign-up-form.component.html @@ -78,5 +78,8 @@ {{ 'registration.have_account' | translate }} {{ 'registration.login' | translate }}
+
+ {{ 'applications.build' | translate }} {{ env.buildNumber }} +
diff --git a/ui/src/assets/i18n/en.json b/ui/src/assets/i18n/en.json index 4a46e19eaf..48d311f5e5 100644 --- a/ui/src/assets/i18n/en.json +++ b/ui/src/assets/i18n/en.json @@ -55,12 +55,14 @@ }, "change_password": { "old_password": "Old password", - "new_password": "New Password" + "new_password": "New Password", + "enter_password": "Enter Password" }, "toolbar": { "logout": "Logout", "change_password": "Change password", "user_info": "Change profile", + "user_info_change": "Change Profile Name", "user_avatar_info": "User avatar", "logo": "CompreFace logo", "signup": "Sign Up" From 47337ccde7515918c8f54aa6d51cae7f9e5cd858 Mon Sep 17 00:00:00 2001 From: smchedlidze826 Date: Tue, 14 Jun 2022 01:58:33 +0400 Subject: [PATCH 386/837] EFRS-1243 --- .../change-password-dialog.component.html | 2 +- ui/src/assets/i18n/en.json | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ui/src/app/features/change-password-dialog/change-password-dialog.component.html b/ui/src/app/features/change-password-dialog/change-password-dialog.component.html index d6c43af5c4..3c6e20334f 100644 --- a/ui/src/app/features/change-password-dialog/change-password-dialog.component.html +++ b/ui/src/app/features/change-password-dialog/change-password-dialog.component.html @@ -72,7 +72,7 @@

{{ 'toolbar.change_password' | translate }}

diff --git a/ui/src/assets/i18n/en.json b/ui/src/assets/i18n/en.json index 48d311f5e5..e0651a9e8b 100644 --- a/ui/src/assets/i18n/en.json +++ b/ui/src/assets/i18n/en.json @@ -51,7 +51,10 @@ "have_account": "Already have an account?", "sign_up": "Sign Up", "statistics": "Allow to send anonymous statistics for product improvement", - "more_info": "More info" + "more_info": "More info", + "name_placeholder": "Enter Name", + "password_placeholder": "Enter Password", + "confirm_password_placeholder": "Confirm Password" }, "change_password": { "old_password": "Old password", From b01bf8e1fef910d7e67ca022cb91f3ad1525075f Mon Sep 17 00:00:00 2001 From: smchedlidze826 Date: Tue, 14 Jun 2022 02:18:01 +0400 Subject: [PATCH 387/837] EFRS-1245 --- .../collection-manager-subject-right.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/app/features/collection-manager-subject-right/collection-manager-subject-right/collection-manager-subject-right.component.html b/ui/src/app/features/collection-manager-subject-right/collection-manager-subject-right/collection-manager-subject-right.component.html index 7c95dc051c..fbe22dffa3 100644 --- a/ui/src/app/features/collection-manager-subject-right/collection-manager-subject-right/collection-manager-subject-right.component.html +++ b/ui/src/app/features/collection-manager-subject-right/collection-manager-subject-right/collection-manager-subject-right.component.html @@ -19,7 +19,7 @@
{{ defaultSubject }} {{ collectionItems.length }} {{ 'common.capital.images' | translate }}{{ totalElements }} {{ 'common.capital.images' | translate }}
From 4cd27a3dac8bedab882460b777f64a1c10954bb9 Mon Sep 17 00:00:00 2001 From: Volodymyr Bushko Date: Wed, 15 Jun 2022 12:26:32 +0300 Subject: [PATCH 388/837] EFRS-1239: Optimized query performance of EmbeddingRepository --- .../cache/EmbeddingCacheProvider.java | 4 ++-- .../cache/EmbeddingCollection.java | 18 ++++++++++-------- .../trainservice/service/EmbeddingService.java | 6 ++++-- .../cache/EmbeddingCacheProviderTest.java | 2 +- .../entity/EmbeddingSubjectProjection.java | 12 ++++++++++++ .../repository/EmbeddingRepository.java | 16 +++++++++------- 6 files changed, 38 insertions(+), 20 deletions(-) create mode 100644 java/common/src/main/java/com/exadel/frs/commonservice/entity/EmbeddingSubjectProjection.java diff --git a/java/api/src/main/java/com/exadel/frs/core/trainservice/cache/EmbeddingCacheProvider.java b/java/api/src/main/java/com/exadel/frs/core/trainservice/cache/EmbeddingCacheProvider.java index f8e38978ea..84f83f048d 100644 --- a/java/api/src/main/java/com/exadel/frs/core/trainservice/cache/EmbeddingCacheProvider.java +++ b/java/api/src/main/java/com/exadel/frs/core/trainservice/cache/EmbeddingCacheProvider.java @@ -38,7 +38,7 @@ public EmbeddingCollection getOrLoad(final String apiKey) { var result = cache.getIfPresent(apiKey); if (result == null) { - result = embeddingService.doWithEmbeddingsStream(apiKey, EmbeddingCollection::from); + result = embeddingService.doWithEmbeddingSubjectProjectionStream(apiKey, EmbeddingCollection::from); cache.put(apiKey, result); @@ -63,7 +63,7 @@ public void invalidate(final String apiKey) { public void receivePutOnCache(String apiKey) { - var result = embeddingService.doWithEmbeddingsStream(apiKey, EmbeddingCollection::from); + var result = embeddingService.doWithEmbeddingSubjectProjectionStream(apiKey, EmbeddingCollection::from); cache.put(apiKey, result); } diff --git a/java/api/src/main/java/com/exadel/frs/core/trainservice/cache/EmbeddingCollection.java b/java/api/src/main/java/com/exadel/frs/core/trainservice/cache/EmbeddingCollection.java index 2ea29107e5..eb2706137a 100644 --- a/java/api/src/main/java/com/exadel/frs/core/trainservice/cache/EmbeddingCollection.java +++ b/java/api/src/main/java/com/exadel/frs/core/trainservice/cache/EmbeddingCollection.java @@ -2,11 +2,13 @@ import com.exadel.frs.commonservice.entity.Embedding; import com.exadel.frs.commonservice.entity.EmbeddingProjection; +import com.exadel.frs.commonservice.entity.EmbeddingSubjectProjection; import com.exadel.frs.commonservice.exception.IncorrectImageIdException; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import lombok.AccessLevel; import lombok.AllArgsConstructor; +import lombok.val; import org.nd4j.linalg.api.ndarray.INDArray; import org.nd4j.linalg.factory.Nd4j; import org.nd4j.linalg.indexing.NDArrayIndex; @@ -23,15 +25,15 @@ public class EmbeddingCollection { private final BiMap projection2Index; private INDArray embeddings; - public static EmbeddingCollection from(final Stream embeddings) { - final var rawEmbeddings = new LinkedList(); - final Map projections2Index = new HashMap<>(); + public static EmbeddingCollection from(final Stream projectionStream) { + val rawEmbeddings = new LinkedList(); + val projections2Index = new HashMap(); + val index = new AtomicInteger(); // just to bypass 'final' variables restriction inside lambdas - var index = new AtomicInteger(); // just to bypass 'final' variables restriction inside lambdas - - embeddings.forEach(embedding -> { - rawEmbeddings.add(embedding.getEmbedding()); - projections2Index.put(EmbeddingProjection.from(embedding), index.getAndIncrement()); + projectionStream.forEach(projection -> { + val embeddingProjection = new EmbeddingProjection(projection.getEmbeddingId(), projection.getSubjectName()); + projections2Index.put(embeddingProjection, index.getAndIncrement()); + rawEmbeddings.add(projection.getEmbeddingData()); }); return new EmbeddingCollection( diff --git a/java/api/src/main/java/com/exadel/frs/core/trainservice/service/EmbeddingService.java b/java/api/src/main/java/com/exadel/frs/core/trainservice/service/EmbeddingService.java index 5e17ce5862..8c2017ac72 100644 --- a/java/api/src/main/java/com/exadel/frs/core/trainservice/service/EmbeddingService.java +++ b/java/api/src/main/java/com/exadel/frs/core/trainservice/service/EmbeddingService.java @@ -2,11 +2,13 @@ import com.exadel.frs.commonservice.entity.Embedding; import com.exadel.frs.commonservice.entity.EmbeddingProjection; +import com.exadel.frs.commonservice.entity.EmbeddingSubjectProjection; import com.exadel.frs.commonservice.entity.Img; import com.exadel.frs.commonservice.repository.EmbeddingRepository; import com.exadel.frs.commonservice.repository.ImgRepository; import com.exadel.frs.core.trainservice.system.global.Constants; import lombok.RequiredArgsConstructor; +import lombok.val; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -31,8 +33,8 @@ public int updateEmbedding(UUID embeddingId, double[] embedding, String calculat } @Transactional - public T doWithEmbeddingsStream(String apiKey, Function, T> func) { - try (Stream stream = embeddingRepository.findBySubjectApiKey(apiKey)) { + public T doWithEmbeddingSubjectProjectionStream(String apiKey, Function, T> func) { + try (val stream = embeddingRepository.findEmbeddingSubjectProjectionBySubjectApiKey(apiKey)) { return func.apply(stream); } } diff --git a/java/api/src/test/java/com/exadel/frs/core/trainservice/cache/EmbeddingCacheProviderTest.java b/java/api/src/test/java/com/exadel/frs/core/trainservice/cache/EmbeddingCacheProviderTest.java index bc304aa359..c72b034c9c 100644 --- a/java/api/src/test/java/com/exadel/frs/core/trainservice/cache/EmbeddingCacheProviderTest.java +++ b/java/api/src/test/java/com/exadel/frs/core/trainservice/cache/EmbeddingCacheProviderTest.java @@ -58,7 +58,7 @@ void getOrLoad() { makeEmbedding("C", API_KEY) }; - when(embeddingService.doWithEmbeddingsStream(eq(API_KEY), any())) + when(embeddingService.doWithEmbeddingSubjectProjectionStream(eq(API_KEY), any())) .thenAnswer(invocation -> { var function = (Function, ?>) invocation.getArgument(1); return function.apply(Stream.of(embeddings)); diff --git a/java/common/src/main/java/com/exadel/frs/commonservice/entity/EmbeddingSubjectProjection.java b/java/common/src/main/java/com/exadel/frs/commonservice/entity/EmbeddingSubjectProjection.java new file mode 100644 index 0000000000..a4d1f03eab --- /dev/null +++ b/java/common/src/main/java/com/exadel/frs/commonservice/entity/EmbeddingSubjectProjection.java @@ -0,0 +1,12 @@ +package com.exadel.frs.commonservice.entity; + +import java.util.UUID; +import lombok.Value; + +@Value +public class EmbeddingSubjectProjection { + + UUID embeddingId; + double[] embeddingData; + String subjectName; +} diff --git a/java/common/src/main/java/com/exadel/frs/commonservice/repository/EmbeddingRepository.java b/java/common/src/main/java/com/exadel/frs/commonservice/repository/EmbeddingRepository.java index 5bbe8612ae..924367043b 100644 --- a/java/common/src/main/java/com/exadel/frs/commonservice/repository/EmbeddingRepository.java +++ b/java/common/src/main/java/com/exadel/frs/commonservice/repository/EmbeddingRepository.java @@ -2,8 +2,11 @@ import com.exadel.frs.commonservice.entity.Embedding; import com.exadel.frs.commonservice.entity.EmbeddingProjection; -import com.exadel.frs.commonservice.entity.Img; +import com.exadel.frs.commonservice.entity.EmbeddingSubjectProjection; import com.exadel.frs.commonservice.entity.Subject; +import java.util.List; +import java.util.UUID; +import java.util.stream.Stream; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.EntityGraph; @@ -12,15 +15,14 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import java.util.List; -import java.util.UUID; -import java.util.stream.Stream; - public interface EmbeddingRepository extends JpaRepository { // Note: consumer should consume in transaction - @EntityGraph("embedding-with-subject") - Stream findBySubjectApiKey(String apiKey); + @Query("select new com.exadel.frs.commonservice.entity.EmbeddingSubjectProjection(e.id, e.embedding, s.subjectName) " + + "from Embedding e " + + "left join e.subject s " + + "where s.apiKey = :apiKey") + Stream findEmbeddingSubjectProjectionBySubjectApiKey(@Param("apiKey") String apiKey); @EntityGraph("embedding-with-subject") List findBySubjectId(UUID subjectId); From e3ab7afe11b197f140896b39c4e69579023dd3da Mon Sep 17 00:00:00 2001 From: Volodymyr Bushko Date: Wed, 15 Jun 2022 22:23:08 +0300 Subject: [PATCH 389/837] EFRS-1239: Refactoring --- .../trainservice/cache/EmbeddingCacheProvider.java | 4 ++-- .../core/trainservice/cache/EmbeddingCollection.java | 10 +++++----- .../core/trainservice/service/EmbeddingService.java | 8 ++++---- .../trainservice/cache/EmbeddingCacheProviderTest.java | 2 +- .../frs/commonservice/entity/EmbeddingProjection.java | 10 ++++++++-- ...rojection.java => EnhancedEmbeddingProjection.java} | 4 ++-- .../commonservice/repository/EmbeddingRepository.java | 6 +++--- 7 files changed, 25 insertions(+), 19 deletions(-) rename java/common/src/main/java/com/exadel/frs/commonservice/entity/{EmbeddingSubjectProjection.java => EnhancedEmbeddingProjection.java} (57%) diff --git a/java/api/src/main/java/com/exadel/frs/core/trainservice/cache/EmbeddingCacheProvider.java b/java/api/src/main/java/com/exadel/frs/core/trainservice/cache/EmbeddingCacheProvider.java index 84f83f048d..af69ffe6b5 100644 --- a/java/api/src/main/java/com/exadel/frs/core/trainservice/cache/EmbeddingCacheProvider.java +++ b/java/api/src/main/java/com/exadel/frs/core/trainservice/cache/EmbeddingCacheProvider.java @@ -38,7 +38,7 @@ public EmbeddingCollection getOrLoad(final String apiKey) { var result = cache.getIfPresent(apiKey); if (result == null) { - result = embeddingService.doWithEmbeddingSubjectProjectionStream(apiKey, EmbeddingCollection::from); + result = embeddingService.doWithEnhancedEmbeddingProjectionStream(apiKey, EmbeddingCollection::from); cache.put(apiKey, result); @@ -63,7 +63,7 @@ public void invalidate(final String apiKey) { public void receivePutOnCache(String apiKey) { - var result = embeddingService.doWithEmbeddingSubjectProjectionStream(apiKey, EmbeddingCollection::from); + var result = embeddingService.doWithEnhancedEmbeddingProjectionStream(apiKey, EmbeddingCollection::from); cache.put(apiKey, result); } diff --git a/java/api/src/main/java/com/exadel/frs/core/trainservice/cache/EmbeddingCollection.java b/java/api/src/main/java/com/exadel/frs/core/trainservice/cache/EmbeddingCollection.java index eb2706137a..f8f26525ce 100644 --- a/java/api/src/main/java/com/exadel/frs/core/trainservice/cache/EmbeddingCollection.java +++ b/java/api/src/main/java/com/exadel/frs/core/trainservice/cache/EmbeddingCollection.java @@ -2,10 +2,11 @@ import com.exadel.frs.commonservice.entity.Embedding; import com.exadel.frs.commonservice.entity.EmbeddingProjection; -import com.exadel.frs.commonservice.entity.EmbeddingSubjectProjection; +import com.exadel.frs.commonservice.entity.EnhancedEmbeddingProjection; import com.exadel.frs.commonservice.exception.IncorrectImageIdException; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; +import java.util.stream.IntStream; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.val; @@ -25,14 +26,13 @@ public class EmbeddingCollection { private final BiMap projection2Index; private INDArray embeddings; - public static EmbeddingCollection from(final Stream projectionStream) { + public static EmbeddingCollection from(final Stream stream) { val rawEmbeddings = new LinkedList(); val projections2Index = new HashMap(); val index = new AtomicInteger(); // just to bypass 'final' variables restriction inside lambdas - projectionStream.forEach(projection -> { - val embeddingProjection = new EmbeddingProjection(projection.getEmbeddingId(), projection.getSubjectName()); - projections2Index.put(embeddingProjection, index.getAndIncrement()); + stream.forEach(projection -> { + projections2Index.put(EmbeddingProjection.from(projection), index.getAndIncrement()); rawEmbeddings.add(projection.getEmbeddingData()); }); diff --git a/java/api/src/main/java/com/exadel/frs/core/trainservice/service/EmbeddingService.java b/java/api/src/main/java/com/exadel/frs/core/trainservice/service/EmbeddingService.java index 8c2017ac72..34918ce466 100644 --- a/java/api/src/main/java/com/exadel/frs/core/trainservice/service/EmbeddingService.java +++ b/java/api/src/main/java/com/exadel/frs/core/trainservice/service/EmbeddingService.java @@ -2,11 +2,12 @@ import com.exadel.frs.commonservice.entity.Embedding; import com.exadel.frs.commonservice.entity.EmbeddingProjection; -import com.exadel.frs.commonservice.entity.EmbeddingSubjectProjection; +import com.exadel.frs.commonservice.entity.EnhancedEmbeddingProjection; import com.exadel.frs.commonservice.entity.Img; import com.exadel.frs.commonservice.repository.EmbeddingRepository; import com.exadel.frs.commonservice.repository.ImgRepository; import com.exadel.frs.core.trainservice.system.global.Constants; +import java.util.stream.Stream; import lombok.RequiredArgsConstructor; import lombok.val; import org.springframework.data.domain.Page; @@ -18,7 +19,6 @@ import java.util.Optional; import java.util.UUID; import java.util.function.Function; -import java.util.stream.Stream; @Service @RequiredArgsConstructor @@ -33,8 +33,8 @@ public int updateEmbedding(UUID embeddingId, double[] embedding, String calculat } @Transactional - public T doWithEmbeddingSubjectProjectionStream(String apiKey, Function, T> func) { - try (val stream = embeddingRepository.findEmbeddingSubjectProjectionBySubjectApiKey(apiKey)) { + public T doWithEnhancedEmbeddingProjectionStream(String apiKey, Function, T> func) { + try (val stream = embeddingRepository.findBySubjectApiKey(apiKey)) { return func.apply(stream); } } diff --git a/java/api/src/test/java/com/exadel/frs/core/trainservice/cache/EmbeddingCacheProviderTest.java b/java/api/src/test/java/com/exadel/frs/core/trainservice/cache/EmbeddingCacheProviderTest.java index c72b034c9c..4e5230b10b 100644 --- a/java/api/src/test/java/com/exadel/frs/core/trainservice/cache/EmbeddingCacheProviderTest.java +++ b/java/api/src/test/java/com/exadel/frs/core/trainservice/cache/EmbeddingCacheProviderTest.java @@ -58,7 +58,7 @@ void getOrLoad() { makeEmbedding("C", API_KEY) }; - when(embeddingService.doWithEmbeddingSubjectProjectionStream(eq(API_KEY), any())) + when(embeddingService.doWithEnhancedEmbeddingProjectionStream(eq(API_KEY), any())) .thenAnswer(invocation -> { var function = (Function, ?>) invocation.getArgument(1); return function.apply(Stream.of(embeddings)); diff --git a/java/common/src/main/java/com/exadel/frs/commonservice/entity/EmbeddingProjection.java b/java/common/src/main/java/com/exadel/frs/commonservice/entity/EmbeddingProjection.java index 36ef7927f3..101df66eb5 100644 --- a/java/common/src/main/java/com/exadel/frs/commonservice/entity/EmbeddingProjection.java +++ b/java/common/src/main/java/com/exadel/frs/commonservice/entity/EmbeddingProjection.java @@ -1,8 +1,7 @@ package com.exadel.frs.commonservice.entity; -import lombok.Value; - import java.util.UUID; +import lombok.Value; @Value public class EmbeddingProjection { @@ -17,6 +16,13 @@ public static EmbeddingProjection from(Embedding embedding) { ); } + public static EmbeddingProjection from(EnhancedEmbeddingProjection projection) { + return new EmbeddingProjection( + projection.getEmbeddingId(), + projection.getSubjectName() + ); + } + public EmbeddingProjection withNewSubjectName(String newSubjectName) { return new EmbeddingProjection( this.getEmbeddingId(), diff --git a/java/common/src/main/java/com/exadel/frs/commonservice/entity/EmbeddingSubjectProjection.java b/java/common/src/main/java/com/exadel/frs/commonservice/entity/EnhancedEmbeddingProjection.java similarity index 57% rename from java/common/src/main/java/com/exadel/frs/commonservice/entity/EmbeddingSubjectProjection.java rename to java/common/src/main/java/com/exadel/frs/commonservice/entity/EnhancedEmbeddingProjection.java index a4d1f03eab..2f31623d0f 100644 --- a/java/common/src/main/java/com/exadel/frs/commonservice/entity/EmbeddingSubjectProjection.java +++ b/java/common/src/main/java/com/exadel/frs/commonservice/entity/EnhancedEmbeddingProjection.java @@ -4,9 +4,9 @@ import lombok.Value; @Value -public class EmbeddingSubjectProjection { +public class EnhancedEmbeddingProjection { UUID embeddingId; - double[] embeddingData; + double[] embeddingData; // embedding column of embedding table String subjectName; } diff --git a/java/common/src/main/java/com/exadel/frs/commonservice/repository/EmbeddingRepository.java b/java/common/src/main/java/com/exadel/frs/commonservice/repository/EmbeddingRepository.java index 924367043b..3bde314fa1 100644 --- a/java/common/src/main/java/com/exadel/frs/commonservice/repository/EmbeddingRepository.java +++ b/java/common/src/main/java/com/exadel/frs/commonservice/repository/EmbeddingRepository.java @@ -2,7 +2,7 @@ import com.exadel.frs.commonservice.entity.Embedding; import com.exadel.frs.commonservice.entity.EmbeddingProjection; -import com.exadel.frs.commonservice.entity.EmbeddingSubjectProjection; +import com.exadel.frs.commonservice.entity.EnhancedEmbeddingProjection; import com.exadel.frs.commonservice.entity.Subject; import java.util.List; import java.util.UUID; @@ -18,11 +18,11 @@ public interface EmbeddingRepository extends JpaRepository { // Note: consumer should consume in transaction - @Query("select new com.exadel.frs.commonservice.entity.EmbeddingSubjectProjection(e.id, e.embedding, s.subjectName) " + + @Query("select new com.exadel.frs.commonservice.entity.EnhancedEmbeddingProjection(e.id, e.embedding, s.subjectName) " + "from Embedding e " + "left join e.subject s " + "where s.apiKey = :apiKey") - Stream findEmbeddingSubjectProjectionBySubjectApiKey(@Param("apiKey") String apiKey); + Stream findBySubjectApiKey(@Param("apiKey") String apiKey); @EntityGraph("embedding-with-subject") List findBySubjectId(UUID subjectId); From b8c1f31bb46190f3097e49ab2c5588c284225559 Mon Sep 17 00:00:00 2001 From: Volodymyr Bushko Date: Thu, 16 Jun 2022 12:40:47 +0300 Subject: [PATCH 390/837] EFRS-1239: Fixed tests and updated database changelogs of tests --- .../cache/EmbeddingCollection.java | 1 - .../frs/core/trainservice/ItemsBuilder.java | 25 +++++--- .../cache/EmbeddingCacheProviderTest.java | 36 ++++++----- .../cache/EmbeddingCollectionTest.java | 61 +++++++++---------- .../service/SubjectServiceTest.java | 56 ++++++++--------- .../db/changelog/db.changelog-0.1.8.yaml | 52 ++++++++++++++++ .../db/changelog/db.changelog-0.1.9.yaml | 18 ++++++ .../db/changelog/db.changelog-master.yaml | 6 +- 8 files changed, 165 insertions(+), 90 deletions(-) create mode 100644 java/api/src/test/resources/db/changelog/db.changelog-0.1.8.yaml create mode 100644 java/api/src/test/resources/db/changelog/db.changelog-0.1.9.yaml diff --git a/java/api/src/main/java/com/exadel/frs/core/trainservice/cache/EmbeddingCollection.java b/java/api/src/main/java/com/exadel/frs/core/trainservice/cache/EmbeddingCollection.java index f8f26525ce..9d1308262f 100644 --- a/java/api/src/main/java/com/exadel/frs/core/trainservice/cache/EmbeddingCollection.java +++ b/java/api/src/main/java/com/exadel/frs/core/trainservice/cache/EmbeddingCollection.java @@ -6,7 +6,6 @@ import com.exadel.frs.commonservice.exception.IncorrectImageIdException; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; -import java.util.stream.IntStream; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.val; diff --git a/java/api/src/test/java/com/exadel/frs/core/trainservice/ItemsBuilder.java b/java/api/src/test/java/com/exadel/frs/core/trainservice/ItemsBuilder.java index d7771ab6a4..9bbe85b84a 100644 --- a/java/api/src/test/java/com/exadel/frs/core/trainservice/ItemsBuilder.java +++ b/java/api/src/test/java/com/exadel/frs/core/trainservice/ItemsBuilder.java @@ -10,20 +10,20 @@ public class ItemsBuilder { public static App makeApp(String apiKey) { return App.builder() - .name("App" + System.currentTimeMillis()) - .guid(UUID.randomUUID().toString()) - .apiKey(apiKey) - .build(); + .name("App" + System.currentTimeMillis()) + .guid(UUID.randomUUID().toString()) + .apiKey(apiKey) + .build(); } public static Model makeModel(String apiKey, ModelType type, App app) { return Model.builder() - .apiKey(apiKey) - .name("Model" + UUID.randomUUID()) - .type(type) - .guid(UUID.randomUUID().toString()) - .app(app) - .build(); + .apiKey(apiKey) + .name("Model" + UUID.randomUUID()) + .type(type) + .guid(UUID.randomUUID().toString()) + .app(app) + .build(); } public static Embedding makeEmbedding(String subjectName, String apiKey) { @@ -32,6 +32,7 @@ public static Embedding makeEmbedding(String subjectName, String apiKey) { makeImg() ); } + public static Embedding makeEmbedding(UUID embeddingId, String subjectName, String apiKey) { return makeEmbedding( makeSubject(apiKey, subjectName), @@ -55,6 +56,10 @@ public static Embedding makeEmbedding(Subject subject, Img img) { return makeEmbedding(subject, null, null, img); } + public static EnhancedEmbeddingProjection makeEnhancedEmbeddingProjection(String subject) { + return new EnhancedEmbeddingProjection(UUID.randomUUID(), new double[]{1.1, 2.2, 3.3}, subject); + } + public static Img makeImg(byte[] content) { return new Img() .setContent(content); diff --git a/java/api/src/test/java/com/exadel/frs/core/trainservice/cache/EmbeddingCacheProviderTest.java b/java/api/src/test/java/com/exadel/frs/core/trainservice/cache/EmbeddingCacheProviderTest.java index 4e5230b10b..87cd6f0f0c 100644 --- a/java/api/src/test/java/com/exadel/frs/core/trainservice/cache/EmbeddingCacheProviderTest.java +++ b/java/api/src/test/java/com/exadel/frs/core/trainservice/cache/EmbeddingCacheProviderTest.java @@ -16,26 +16,24 @@ package com.exadel.frs.core.trainservice.cache; -import com.exadel.frs.commonservice.entity.Embedding; +import static com.exadel.frs.core.trainservice.ItemsBuilder.makeEnhancedEmbeddingProjection; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; +import com.exadel.frs.commonservice.entity.EnhancedEmbeddingProjection; import com.exadel.frs.core.trainservice.service.EmbeddingService; import com.exadel.frs.core.trainservice.service.NotificationSenderService; +import java.util.function.Function; +import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import java.util.function.Function; -import java.util.stream.Stream; - -import static com.exadel.frs.core.trainservice.ItemsBuilder.makeEmbedding; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - @ExtendWith(MockitoExtension.class) class EmbeddingCacheProviderTest { @@ -52,23 +50,23 @@ class EmbeddingCacheProviderTest { @Test void getOrLoad() { - var embeddings = new Embedding[]{ - makeEmbedding("A", API_KEY), - makeEmbedding("B", API_KEY), - makeEmbedding("C", API_KEY) + var projections = new EnhancedEmbeddingProjection[]{ + makeEnhancedEmbeddingProjection("A"), + makeEnhancedEmbeddingProjection("B"), + makeEnhancedEmbeddingProjection("C") }; when(embeddingService.doWithEnhancedEmbeddingProjectionStream(eq(API_KEY), any())) .thenAnswer(invocation -> { - var function = (Function, ?>) invocation.getArgument(1); - return function.apply(Stream.of(embeddings)); + var function = (Function, ?>) invocation.getArgument(1); + return function.apply(Stream.of(projections)); }); var actual = embeddingCacheProvider.getOrLoad(API_KEY); assertThat(actual, notNullValue()); assertThat(actual.getProjections(), notNullValue()); - assertThat(actual.getProjections().size(), is(embeddings.length)); + assertThat(actual.getProjections().size(), is(projections.length)); assertThat(actual.getEmbeddings(), notNullValue()); } } diff --git a/java/api/src/test/java/com/exadel/frs/core/trainservice/cache/EmbeddingCollectionTest.java b/java/api/src/test/java/com/exadel/frs/core/trainservice/cache/EmbeddingCollectionTest.java index daac66b3bf..f0f869236d 100644 --- a/java/api/src/test/java/com/exadel/frs/core/trainservice/cache/EmbeddingCollectionTest.java +++ b/java/api/src/test/java/com/exadel/frs/core/trainservice/cache/EmbeddingCollectionTest.java @@ -16,15 +16,14 @@ package com.exadel.frs.core.trainservice.cache; -import com.exadel.frs.commonservice.entity.Embedding; +import static com.exadel.frs.core.trainservice.ItemsBuilder.makeEmbedding; +import static com.exadel.frs.core.trainservice.ItemsBuilder.makeEnhancedEmbeddingProjection; +import static org.assertj.core.api.Assertions.assertThat; import com.exadel.frs.commonservice.entity.EmbeddingProjection; -import org.junit.jupiter.api.Test; - +import com.exadel.frs.commonservice.entity.EnhancedEmbeddingProjection; import java.util.UUID; import java.util.stream.Stream; - -import static com.exadel.frs.core.trainservice.ItemsBuilder.makeEmbedding; -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.Test; class EmbeddingCollectionTest { @@ -53,50 +52,50 @@ void testAddToEmpty() { @Test void testCreate() { - var embedding1 = makeEmbedding("A", API_KEY); - var embedding2 = makeEmbedding("B", API_KEY); - var embedding3 = makeEmbedding("C", API_KEY); - var embeddings = new Embedding[]{embedding1, embedding2, embedding3}; - var embeddingCollection = EmbeddingCollection.from(Stream.of(embeddings)); + var projection1 = makeEnhancedEmbeddingProjection("A"); + var projection2 = makeEnhancedEmbeddingProjection("B"); + var projection3 = makeEnhancedEmbeddingProjection("C"); + var projections = new EnhancedEmbeddingProjection[]{projection1, projection2, projection3}; + var embeddingCollection = EmbeddingCollection.from(Stream.of(projections)); assertThat(embeddingCollection).isNotNull(); assertThat(embeddingCollection.getIndexMap()).isNotNull(); - assertThat(embeddingCollection.getIndexMap()).hasSize(embeddings.length); + assertThat(embeddingCollection.getIndexMap()).hasSize(projections.length); - assertThat(embeddingCollection.getIndexMap()).containsEntry(0, EmbeddingProjection.from(embedding1)); - assertThat(embeddingCollection.getIndexMap()).containsEntry(1, EmbeddingProjection.from(embedding2)); - assertThat(embeddingCollection.getIndexMap()).containsEntry(2, EmbeddingProjection.from(embedding3)); + assertThat(embeddingCollection.getIndexMap()).containsEntry(0, EmbeddingProjection.from(projection1)); + assertThat(embeddingCollection.getIndexMap()).containsEntry(1, EmbeddingProjection.from(projection2)); + assertThat(embeddingCollection.getIndexMap()).containsEntry(2, EmbeddingProjection.from(projection3)); } @Test void testAdd() { - var embeddings = new Embedding[]{ - makeEmbedding("A", API_KEY), - makeEmbedding("B", API_KEY), - makeEmbedding("C", API_KEY) + var projections = new EnhancedEmbeddingProjection[]{ + makeEnhancedEmbeddingProjection("A"), + makeEnhancedEmbeddingProjection("B"), + makeEnhancedEmbeddingProjection("C") }; - var embeddingCollection = EmbeddingCollection.from(Stream.of(embeddings)); + var embeddingCollection = EmbeddingCollection.from(Stream.of(projections)); var newEmbedding = makeEmbedding("D", API_KEY); var key = embeddingCollection.addEmbedding(newEmbedding); assertThat(key).isNotNull(); - assertThat(embeddingCollection.getProjections()).hasSize(embeddings.length + 1); - assertThat(embeddingCollection.getIndexMap()).containsEntry(embeddings.length, EmbeddingProjection.from(newEmbedding)); + assertThat(embeddingCollection.getProjections()).hasSize(projections.length + 1); + assertThat(embeddingCollection.getIndexMap()).containsEntry(projections.length, EmbeddingProjection.from(newEmbedding)); } @Test void testRemove() { - var embedding1 = makeEmbedding("A", API_KEY); - var embedding2 = makeEmbedding("B", API_KEY); - var embedding3 = makeEmbedding("C", API_KEY); - var embeddings = new Embedding[]{embedding1, embedding2, embedding3}; - var embeddingCollection = EmbeddingCollection.from(Stream.of(embeddings)); + var projection1 = makeEnhancedEmbeddingProjection("A"); + var projection2 = makeEnhancedEmbeddingProjection("B"); + var projection3 = makeEnhancedEmbeddingProjection("C"); + var projections = new EnhancedEmbeddingProjection[]{projection1, projection2, projection3}; + var embeddingCollection = EmbeddingCollection.from(Stream.of(projections)); - embeddingCollection.removeEmbedding(embedding1); + embeddingCollection.removeEmbedding(EmbeddingProjection.from(projection1)); - assertThat(embeddingCollection.getProjections()).hasSize(embeddings.length - 1); - assertThat(embeddingCollection.getIndexMap()).containsEntry(0, EmbeddingProjection.from(embedding2)); - assertThat(embeddingCollection.getIndexMap()).containsEntry(1, EmbeddingProjection.from(embedding3)); + assertThat(embeddingCollection.getProjections()).hasSize(projections.length - 1); + assertThat(embeddingCollection.getIndexMap()).containsEntry(0, EmbeddingProjection.from(projection2)); + assertThat(embeddingCollection.getIndexMap()).containsEntry(1, EmbeddingProjection.from(projection3)); } } \ No newline at end of file diff --git a/java/api/src/test/java/com/exadel/frs/core/trainservice/service/SubjectServiceTest.java b/java/api/src/test/java/com/exadel/frs/core/trainservice/service/SubjectServiceTest.java index 5c4d7ca705..eb47f0d0b5 100644 --- a/java/api/src/test/java/com/exadel/frs/core/trainservice/service/SubjectServiceTest.java +++ b/java/api/src/test/java/com/exadel/frs/core/trainservice/service/SubjectServiceTest.java @@ -16,6 +16,19 @@ package com.exadel.frs.core.trainservice.service; +import static com.exadel.frs.core.trainservice.ItemsBuilder.makeEnhancedEmbeddingProjection; +import static com.exadel.frs.core.trainservice.service.SubjectService.MAX_FACES_TO_RECOGNIZE; +import static com.exadel.frs.core.trainservice.system.global.Constants.IMAGE_ID; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.initMocks; import com.exadel.frs.commonservice.dto.ExecutionTimeDto; import com.exadel.frs.commonservice.entity.Embedding; import com.exadel.frs.commonservice.entity.Subject; @@ -32,6 +45,13 @@ import com.exadel.frs.core.trainservice.component.classifiers.EuclideanDistanceClassifier; import com.exadel.frs.core.trainservice.dao.SubjectDao; import com.exadel.frs.core.trainservice.dto.ProcessImageParams; +import java.io.IOException; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; import org.apache.commons.lang3.tuple.Pair; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -44,26 +64,6 @@ import org.springframework.mock.web.MockMultipartFile; import org.springframework.web.multipart.MultipartFile; -import java.io.IOException; -import java.util.Map; -import java.util.UUID; -import java.util.stream.Collectors; -import java.util.stream.IntStream; -import java.util.stream.Stream; - -import static com.exadel.frs.core.trainservice.ItemsBuilder.makeEmbedding; -import static com.exadel.frs.core.trainservice.service.SubjectService.MAX_FACES_TO_RECOGNIZE; -import static com.exadel.frs.core.trainservice.system.global.Constants.IMAGE_ID; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.when; -import static org.mockito.MockitoAnnotations.initMocks; - class SubjectServiceTest { private static final String API_KEY = "apiKey"; @@ -214,10 +214,8 @@ void tooManyFacesFound() { void testVerifyFaces(boolean status) { var detProbThreshold = 0.7; var randomUUId = UUID.randomUUID(); - MultipartFile file = new MockMultipartFile("anyname", new byte[]{0xA}); - EmbeddingCollection embeddingCollection = EmbeddingCollection.from(Stream.of( - makeEmbedding(randomUUId,"A", API_KEY), - makeEmbedding("B", API_KEY))); + var file = new MockMultipartFile("anyname", new byte[]{0xA}); + var embeddingCollection = mock(EmbeddingCollection.class); when(facesApiClient.findFacesWithCalculator(any(), any(), any(), any())) .thenReturn(findFacesResponse(2)); @@ -225,6 +223,8 @@ void testVerifyFaces(boolean status) { .thenReturn(embeddingCollection); when(classifierPredictor.verify(any(), any(), any())) .thenReturn(0.0); + when(embeddingCollection.getSubjectNameByEmbeddingId(randomUUId)) + .thenReturn(Optional.of("A")); var result = subjectService.verifyFace( ProcessImageParams.builder() @@ -254,10 +254,10 @@ void testVerifyFaces(boolean status) { void testInvalidImageIdException(boolean status){ var detProbThreshold = 0.7; var randomUUId = UUID.randomUUID(); - MultipartFile file = new MockMultipartFile("anyname", new byte[]{0xA}); - EmbeddingCollection embeddingCollection = EmbeddingCollection.from(Stream.of( - makeEmbedding("A", API_KEY), - makeEmbedding("B", API_KEY))); + var file = new MockMultipartFile("anyname", new byte[]{0xA}); + var embeddingCollection = EmbeddingCollection.from(Stream.of( + makeEnhancedEmbeddingProjection("A"), + makeEnhancedEmbeddingProjection("B"))); when(facesApiClient.findFacesWithCalculator(any(), any(), any(), any())) .thenReturn(findFacesResponse(2)); diff --git a/java/api/src/test/resources/db/changelog/db.changelog-0.1.8.yaml b/java/api/src/test/resources/db/changelog/db.changelog-0.1.8.yaml new file mode 100644 index 0000000000..eb5f0614a8 --- /dev/null +++ b/java/api/src/test/resources/db/changelog/db.changelog-0.1.8.yaml @@ -0,0 +1,52 @@ +databaseChangeLog: + - changeSet: + id: add-detection-verification-services + author: Shreyansh Sancheti + preConditions: + - onFail: MARK_RAN + - sqlCheck: + expectedResult: 1 + sql: select count(1) from app where id=0 + changes: + - insert: + tableName: model + columns: + - column: + name: id + value: 9223372036854775807 + - column: + name: name + value: Detection_Demo + - column: + name: type + value: D + - column: + name: guid + value: 00000000-0000-0000-0000-000000000003 + - column: + name: app_id + value: 0 + - column: + name: api_key + value: 00000000-0000-0000-0000-000000000003 + - insert: + tableName: model + columns: + - column: + name: id + value: 9223372036854775806 + - column: + name: name + value: Verification_Demo + - column: + name: type + value: V + - column: + name: guid + value: 00000000-0000-0000-0000-000000000004 + - column: + name: app_id + value: 0 + - column: + name: api_key + value: 00000000-0000-0000-0000-000000000004 \ No newline at end of file diff --git a/java/api/src/test/resources/db/changelog/db.changelog-0.1.9.yaml b/java/api/src/test/resources/db/changelog/db.changelog-0.1.9.yaml new file mode 100644 index 0000000000..60669c5a30 --- /dev/null +++ b/java/api/src/test/resources/db/changelog/db.changelog-0.1.9.yaml @@ -0,0 +1,18 @@ +databaseChangeLog: + - changeSet: + id: add-created_date-field-to-model-entity + author: Khasan Sidikov + changes: + - addColumn: + tableName: model + columns: + - column: + name: created_date + type: timestamp + - changeSet: + id: update-model-table-created_date-column + author: Khasan Sidikov + changes: + - sql: + comment: Update created_date to now() + sql: UPDATE model SET created_date=now() WHERE created_date IS NULL; \ No newline at end of file diff --git a/java/api/src/test/resources/db/changelog/db.changelog-master.yaml b/java/api/src/test/resources/db/changelog/db.changelog-master.yaml index b3ae37e3bb..86032033ac 100644 --- a/java/api/src/test/resources/db/changelog/db.changelog-master.yaml +++ b/java/api/src/test/resources/db/changelog/db.changelog-master.yaml @@ -32,4 +32,8 @@ databaseChangeLog: - include: file: db/changelog/db.changelog-0.1.6.yaml - include: - file: db/changelog/db.changelog-0.1.7.yaml \ No newline at end of file + file: db/changelog/db.changelog-0.1.7.yaml + - include: + file: db/changelog/db.changelog-0.1.8.yaml + - include: + file: db/changelog/db.changelog-0.1.9.yaml From 1f5f814a9ffc148e3b81143ed9b41f84df017085 Mon Sep 17 00:00:00 2001 From: Volodymyr Bushko Date: Fri, 17 Jun 2022 11:16:56 +0300 Subject: [PATCH 391/837] EFRS-1239: Refactoring --- .../repository/EmbeddingRepository.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/java/common/src/main/java/com/exadel/frs/commonservice/repository/EmbeddingRepository.java b/java/common/src/main/java/com/exadel/frs/commonservice/repository/EmbeddingRepository.java index 3bde314fa1..3cb01df3d5 100644 --- a/java/common/src/main/java/com/exadel/frs/commonservice/repository/EmbeddingRepository.java +++ b/java/common/src/main/java/com/exadel/frs/commonservice/repository/EmbeddingRepository.java @@ -18,10 +18,14 @@ public interface EmbeddingRepository extends JpaRepository { // Note: consumer should consume in transaction - @Query("select new com.exadel.frs.commonservice.entity.EnhancedEmbeddingProjection(e.id, e.embedding, s.subjectName) " + - "from Embedding e " + - "left join e.subject s " + - "where s.apiKey = :apiKey") + @Query("select " + + " new com.exadel.frs.commonservice.entity.EnhancedEmbeddingProjection(e.id, e.embedding, s.subjectName)" + + " from " + + " Embedding e " + + " left join " + + " e.subject s " + + " where " + + " s.apiKey = :apiKey") Stream findBySubjectApiKey(@Param("apiKey") String apiKey); @EntityGraph("embedding-with-subject") From 4408dc19719a4ee0d5ed34228871d7ea291a15e9 Mon Sep 17 00:00:00 2001 From: smchedlidze826 Date: Mon, 20 Jun 2022 15:10:52 +0400 Subject: [PATCH 392/837] EFRS-1248/Feature added new icons --- ui/src/app/core/custom-icons/custom-icons.ts | 4 ++ .../app-search-table.component.html | 7 ++- .../app-search-table.component.scss | 24 +++++------ .../app-search-table.component.ts | 1 - .../breadcrumbs/breadcrumbs.component.html | 2 +- ...ction-manager-subject-right.component.html | 6 +-- ...ction-manager-subject-right.component.scss | 37 +++------------- .../model-table/model-table.component.html | 24 ++++++----- .../sign-up-form/sign-up-form.component.html | 40 +++++++++++++++--- ui/src/assets/i18n/en.json | 1 + ui/src/assets/img/icons/@1xcopy-icon.png | Bin 0 -> 557 bytes ui/src/assets/img/icons/@2xcopy-icon.png | Bin 0 -> 859 bytes ui/src/assets/img/icons/copy-icon.svg | 3 ++ ui/src/assets/img/icons/copy.svg | 21 +++++++-- ui/src/assets/img/icons/dashboard-icon@1x.png | Bin 0 -> 879 bytes ui/src/assets/img/icons/dashboard-icon@2x.png | Bin 0 -> 1640 bytes ui/src/assets/img/icons/dashboard-iconSVG.svg | 23 ++++++++++ ui/src/assets/img/icons/edit-icon.svg | 3 ++ ui/src/assets/img/icons/edit-icon@1x.png | Bin 0 -> 493 bytes ui/src/assets/img/icons/edit-icon@2x.png | Bin 0 -> 862 bytes ui/src/assets/img/icons/edit.svg | 21 +++++++-- ui/src/assets/img/icons/face-icon.svg | 24 +++++++++++ ui/src/assets/img/icons/face-icon@1x.png | Bin 0 -> 773 bytes ui/src/assets/img/icons/face-icon@2x.png | Bin 0 -> 1495 bytes ui/src/assets/img/icons/image-icon@1x.png | Bin 0 -> 2807 bytes ui/src/assets/img/icons/image-icon@2x.png | Bin 0 -> 5649 bytes ui/src/assets/img/icons/image-iconSVG.svg | 21 +++++++++ ui/src/assets/img/icons/play-icon.svg | 21 +++++++++ ui/src/assets/img/icons/play-icon@1x.png | Bin 0 -> 888 bytes ui/src/assets/img/icons/play-icon@2x.png | Bin 0 -> 1846 bytes ui/src/assets/img/icons/search-icon@1x.png | Bin 0 -> 841 bytes ui/src/assets/img/icons/search-icon@2x.png | Bin 0 -> 1670 bytes ui/src/assets/img/icons/search-iconSVG.svg | 18 ++++++++ ui/src/assets/img/icons/trash-icon@1x.png | Bin 0 -> 614 bytes ui/src/assets/img/icons/trash-icon@2x.png | Bin 0 -> 1112 bytes ui/src/assets/img/icons/trash-iconSVG.svg | 26 ++++++++++++ ui/src/assets/img/icons/user-icon.png | Bin 0 -> 1006 bytes ui/src/assets/img/icons/user-icon.svg | 18 ++++++++ ui/src/assets/img/icons/user-icon@2x.png | Bin 0 -> 2092 bytes ui/src/styles/colors.scss | 3 ++ 40 files changed, 275 insertions(+), 73 deletions(-) create mode 100644 ui/src/assets/img/icons/@1xcopy-icon.png create mode 100644 ui/src/assets/img/icons/@2xcopy-icon.png create mode 100644 ui/src/assets/img/icons/copy-icon.svg create mode 100644 ui/src/assets/img/icons/dashboard-icon@1x.png create mode 100644 ui/src/assets/img/icons/dashboard-icon@2x.png create mode 100644 ui/src/assets/img/icons/dashboard-iconSVG.svg create mode 100644 ui/src/assets/img/icons/edit-icon.svg create mode 100644 ui/src/assets/img/icons/edit-icon@1x.png create mode 100644 ui/src/assets/img/icons/edit-icon@2x.png create mode 100644 ui/src/assets/img/icons/face-icon.svg create mode 100644 ui/src/assets/img/icons/face-icon@1x.png create mode 100644 ui/src/assets/img/icons/face-icon@2x.png create mode 100644 ui/src/assets/img/icons/image-icon@1x.png create mode 100644 ui/src/assets/img/icons/image-icon@2x.png create mode 100644 ui/src/assets/img/icons/image-iconSVG.svg create mode 100644 ui/src/assets/img/icons/play-icon.svg create mode 100644 ui/src/assets/img/icons/play-icon@1x.png create mode 100644 ui/src/assets/img/icons/play-icon@2x.png create mode 100644 ui/src/assets/img/icons/search-icon@1x.png create mode 100644 ui/src/assets/img/icons/search-icon@2x.png create mode 100644 ui/src/assets/img/icons/search-iconSVG.svg create mode 100644 ui/src/assets/img/icons/trash-icon@1x.png create mode 100644 ui/src/assets/img/icons/trash-icon@2x.png create mode 100644 ui/src/assets/img/icons/trash-iconSVG.svg create mode 100644 ui/src/assets/img/icons/user-icon.png create mode 100644 ui/src/assets/img/icons/user-icon.svg create mode 100644 ui/src/assets/img/icons/user-icon@2x.png diff --git a/ui/src/app/core/custom-icons/custom-icons.ts b/ui/src/app/core/custom-icons/custom-icons.ts index c48d555ad3..302900b531 100644 --- a/ui/src/app/core/custom-icons/custom-icons.ts +++ b/ui/src/app/core/custom-icons/custom-icons.ts @@ -30,4 +30,8 @@ export enum Icons { Trash = 'trash', Upload = 'upload', Warning = 'warning', + User = 'user-icon', + Play = 'play-icon', + Face = 'face-icon', + Settings = 'settings', } diff --git a/ui/src/app/features/app-search-table/app-search-table.component.html b/ui/src/app/features/app-search-table/app-search-table.component.html index 09f748589f..d32657fd9e 100644 --- a/ui/src/app/features/app-search-table/app-search-table.component.html +++ b/ui/src/app/features/app-search-table/app-search-table.component.html @@ -24,14 +24,13 @@
-
diff --git a/ui/src/app/features/app-search-table/app-search-table.component.scss b/ui/src/app/features/app-search-table/app-search-table.component.scss index 1b1666b124..36f46f518a 100644 --- a/ui/src/app/features/app-search-table/app-search-table.component.scss +++ b/ui/src/app/features/app-search-table/app-search-table.component.scss @@ -81,27 +81,25 @@ padding: 0; &_user_list { + display: flex; + align-items: center; cursor: pointer; border: none; background: none; } - @include mobile { - margin: 0; - } - button { - padding: 10px 8px; + background: $dark-bg; + padding: 1rem 2rem; + border-radius: 7px; + box-shadow: inset 1px 1px 1px 1px white, inset 1px 1px 1px 1px white; + border: none; + color: white; line-height: 10px; - } - .mat-icon { - @include sm-icon-size(); - margin-right: 5px; - } - - &_text { - color: $primary; + span { + margin-left: 10px; + } } } } diff --git a/ui/src/app/features/app-search-table/app-search-table.component.ts b/ui/src/app/features/app-search-table/app-search-table.component.ts index 0b0160a429..bb14f335f6 100644 --- a/ui/src/app/features/app-search-table/app-search-table.component.ts +++ b/ui/src/app/features/app-search-table/app-search-table.component.ts @@ -15,7 +15,6 @@ */ import { ChangeDetectionStrategy, Component, Input, Output, EventEmitter } from '@angular/core'; -import { Application } from 'src/app/data/interfaces/application'; @Component({ selector: 'app-search-table', diff --git a/ui/src/app/features/breadcrumbs/breadcrumbs.component.html b/ui/src/app/features/breadcrumbs/breadcrumbs.component.html index 94a074d2cb..1d25086939 100644 --- a/ui/src/app/features/breadcrumbs/breadcrumbs.component.html +++ b/ui/src/app/features/breadcrumbs/breadcrumbs.component.html @@ -55,7 +55,7 @@ diff --git a/ui/src/app/features/sign-up-form/sign-up-form.component.html b/ui/src/app/features/sign-up-form/sign-up-form.component.html index 163243b9b6..fb69f7a62c 100644 --- a/ui/src/app/features/sign-up-form/sign-up-form.component.html +++ b/ui/src/app/features/sign-up-form/sign-up-form.component.html @@ -21,21 +21,39 @@
- + {{ 'registration.name_restriction' | translate }}
- + {{ 'registration.last_name_restriction' | translate }}
- + {{ 'registration.email_hint' | translate }} @@ -45,14 +63,26 @@ >{{ 'registration.password' | translate }} {{ 'registration.password_restriction' | translate }} - + {{ 'registration.password_restriction' | translate }}
- + {{ 'registration.password_restriction' | translate }} diff --git a/ui/src/assets/i18n/en.json b/ui/src/assets/i18n/en.json index e0651a9e8b..3e2a10faf2 100644 --- a/ui/src/assets/i18n/en.json +++ b/ui/src/assets/i18n/en.json @@ -54,6 +54,7 @@ "more_info": "More info", "name_placeholder": "Enter Name", "password_placeholder": "Enter Password", + "email_placeholder": "Enter E-mail", "confirm_password_placeholder": "Confirm Password" }, "change_password": { diff --git a/ui/src/assets/img/icons/@1xcopy-icon.png b/ui/src/assets/img/icons/@1xcopy-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..bacb863e7c6703ffcc0b8050aee5fcfe406bf25a GIT binary patch literal 557 zcmeAS@N?(olHy`uVBq!ia0vp^{6H+k!3HE-=Cy!0jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(wmXG%|{O| zEVOIX)e@bx~-_Q1~LUg zP8q_w#&N4wRUK2ZXw-YxV9N3}aSijz)uswBo^aguO=tVqCn#VT38 zBOF(r>25nElbD!lx#C64sh&Wsxqi!=1X)yda;(=&+1@X(@KIIa@}8Nvqha#@&-qKw z7|eUzUgP8ac=9a6=9d=d7$Qh#I?;46Ho5hoG1~v d{r2B!TjPxb_gme`nfV74Ri3VXF6*2Ung9_K*VF(2 literal 0 HcmV?d00001 diff --git a/ui/src/assets/img/icons/@2xcopy-icon.png b/ui/src/assets/img/icons/@2xcopy-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..9a34079b3b65cc757e363c710cd405e0f6284593 GIT binary patch literal 859 zcmV-h1ElPx#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR919-spN1ONa40RR91Bme*a03pzqA^-pa#Ysd#R9Fe^m(ObxQ4q(oN&FFt z76OX@Kv2-LqSaOeQEy_s54!MpGv7Be-#7Dq?5>lEd8*awQP*`JV=_;ljWKntu@Bj7_ExLa zdS~Ij8W$`B*&^-YZ%8GWW3`im!C<3StDW|lEFqTIahzk8eHwMP-|w$Sk;f8SEDwvt z;=#DaL{}}JAvsHS#=*@@eGT#}tod6VPm{6wB~Zo{G2F3zo%q*~8rcaj8_=SFhK1EvJBLPE-eMa>dP_xcN?}^V7p(?_bj|REBVP zT&U6^3<6~jbuhq*juXT;i1l6KDLGfi5^|egTEM7X>Acas>bY002ovPDHLkV1i$3eQf{$ literal 0 HcmV?d00001 diff --git a/ui/src/assets/img/icons/copy-icon.svg b/ui/src/assets/img/icons/copy-icon.svg new file mode 100644 index 0000000000..206175ee35 --- /dev/null +++ b/ui/src/assets/img/icons/copy-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/src/assets/img/icons/copy.svg b/ui/src/assets/img/icons/copy.svg index 206175ee35..7a06639260 100644 --- a/ui/src/assets/img/icons/copy.svg +++ b/ui/src/assets/img/icons/copy.svg @@ -1,3 +1,18 @@ - - - + + + mdpi/copy-icon + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ui/src/assets/img/icons/dashboard-icon@1x.png b/ui/src/assets/img/icons/dashboard-icon@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..8640f0a7983918aa1e6e87ccf378b3d1485912ab GIT binary patch literal 879 zcmeAS@N?(olHy`uVBq!ia0vp^vOp})!3HFyqmQuwDaPU;cPEB*=VV?2IWDOYo@u_m z3|c@o2Loe!CIeUrkS7Ji3=GT*7#Wy>G$Rl)EMS7m$}V7rv*kewm+W>mWME*r>FMGa z;=%iLnsASSg*EI*=x(){*GZmzt0TzKMl>_r~T#s`es392B+7peuk*D zb^X6IqMV-kLvQe{&bbzY05Y$!o2}yR_f`9dzGjZl9t$`DE_Z ztgj+wpXOxU`P+Uo=bPVd%WX@=ADA+vGx!Dh6>iNdp8IWE^R`&cW1n>Vie;aqtP6-! zdbPinr7XF6?&Mmlsb0A&L$titTywu~Trzh?m{kAq@}xpxS!Z_DqDA|*=7r5>D|@tB z&w17|(^O^AW1?(JldMG5O?{NX)Z$#<2@ihGTQFy{-wuZeO^uCwe{yk8vS;3}4( zp5nJQth&En>aDs_fQCrVe!rF7GZw_W+jRUfr-2yTq?AvJ>#tY)^i(Rm+j;k$ZHVxq zE9yJLrl!jDlh!`bi^%urRcF3KLpKzYx+~V?Z_P1J*A6-lA z8Rx2eSj}c4*3J8&OUEc7YG>?&W%9jizOJ_ozL;@^z2rz*$HTs2Bj=|ERny+ij&8PM z6rTUGq-sNq-syuj>JG~-)b^)b_;_^Yg0-^s44Zt^Q#J^)ZUQBCPgg&ebxsLQ0Nh|{ AR{#J2 literal 0 HcmV?d00001 diff --git a/ui/src/assets/img/icons/dashboard-icon@2x.png b/ui/src/assets/img/icons/dashboard-icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..386c72b74fee36d837d77d32e2386552bf67c284 GIT binary patch literal 1640 zcmV-u2ABDXP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91I-mmp1ONa40RR91J^%m!09GpvGynhv(n&-?RA>e5nq6pIMHI)gn@s|V zNolE7Yl|N>Ej~!vsC}?6jUc{+JVXM8e3H;Wun0x)sbKOVieN=Ffk;R;9~jU+sHmVK zRVbo0HA%r5Lurz>P#dIa$cOpb^>=P^mbv*b*Ui23-nav^=ggTiGv`0&%-p%NH!Urd zw$}`VOgQ!R^*d%}W_pnohQs0d&d$zH&H5no37`UUdV2b6z#Lrxv=|D7PIPy7p9?ZT zwLM*!bwG+ZOfD@bDEMt~aPW!_HS z4vE#Q$J^v`1HliJo249&$1@fd79KO|0>`qshEHJidw?l|zp4lT87nC%c?J3!`HlSi z{Li&6h%~Y5it$xdRY#YWmVOc*aOc@^i-g|}NkaF3ky1dno15j);c04UXxKS5HB}so z#SSu1rVPfW7i04qgZ++uU`S+MCr^R0xgD9Docsz9512pzZ57$@7Ah+%Pjz*5wVL%_ z@Sd(w0KJdVHjpL=c|McR0(Orn_k#BfXk%kzCLpX67oNL?@nz6r$%JkR^cw+$PByvF z3vgzio`vq%*w}6cvib+eA3Z%ipO^zzR8%}g`2~?tzt^nug7?g$i;Ihi!wBA6bA~o$ zcsQZ!1xV@BGoW4>oNT`-pvlghU)!ZT>Q~PsYgfvn$irxe zck`UJp2N^P?d|QO<}=voIKB+s^78Tu&cA* zV^IL!uSS|w%z80kth8tqSJ?X0$vJ`O0ZtgNiEwzjrW)!{|p3F?NWjGlmY zoE!sXkeB#x=0kekY4(l>tRl^JIYR#*?mG5o$2 zKNB8saYASZ z$HRPz^PudAyh(^B_wXk2xYXm>N&Z~2PENde+(jhy0%#v?9)tbQ2a6d)w8>6f~G6>=V)<_tWH z>>&97`q#A1q)bkXciPK_O!_F3GGq3rocCa5ElRszZGue-jzX7@AAh9dk2mRS9-T9$ z4ihAw?yPqlaxq}aOy2qyPz8MV_b{`6 zQpyZA!Z(0Xtt%}pJvT8iag(yV`AS&ckc{3{=of_YNxb+Ad?F1D3=Gldmm)xKihPSK zZAK7%&qTb*U;A%My{5jtzPZA}!Vb#wnBz&evyt-vw>0000 + + dashboard-iconSVG + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ui/src/assets/img/icons/edit-icon.svg b/ui/src/assets/img/icons/edit-icon.svg new file mode 100644 index 0000000000..1d61b06d7e --- /dev/null +++ b/ui/src/assets/img/icons/edit-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/src/assets/img/icons/edit-icon@1x.png b/ui/src/assets/img/icons/edit-icon@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..4fdad37ec0f1379a15aad38114c24c7c2fefdbc2 GIT binary patch literal 493 zcmeAS@N?(olHy`uVBq!ia0vp^f*{Pn1|+R>-G2co#^NA%Cx&(BWL^R}E~ycoX}-P; zT0k}j17mw80}DtA5K93u0|WB{Mh0de%?J`(zyy~ST)>QAgB12no(xpM81Cuf7-Au~ zbkbh0BMu_1b5%mQ5(S&qKTy8Gar=bQqzKpPDa~{B6P`Lpbd}bHEZU%^uwt&#zNX%U z+|=oMozGVYKi)K3eosNMapJk@#qWC}UP;b-&imQMUd(BY)2|how;FG?Kiu_hhensC z$@&u_mU^d)d|z@YGBh##zR!DD!Zom@<-2grZJ}5Np$4&eic#)!U+vmAKlP^UwdOE& z(*sL0XE`%``|wzn;a*)sd#?az!wW8k>)YPSe3g2n{-kS>MxI35c5cPehSW$t#zL9Z zo|AqwN|1C0^64;6vTSAO8 znOCfObv1fH#Ifm<^%(AdKffenTi1UkjwcEJhHIqf)R*?iD5W0|nFVdQ&MBb@0A8Q4(EtDd literal 0 HcmV?d00001 diff --git a/ui/src/assets/img/icons/edit-icon@2x.png b/ui/src/assets/img/icons/edit-icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..49b1d8e806dbd8c37a25a506478d18dc16cf9138 GIT binary patch literal 862 zcmeAS@N?(olHy`uVBq!ia0vp^N+8U^1|+TAxeoy;#^NA%Cx&(BWL^R}E~ycoX}-P; zT0k}j17mw80}DtA5K93u0|WB{Mh0de%?J`(zyz07TEL88gA_7VEervYCp=voLn1hj zPWR3h4iq_VJ^3VyP_(?LxNcsEhDJ$v!1ie-i=?ktPI~`fk-S6UVtIw`3D3{9K3KN4 zgLQ4~i&L?#8+Las2ncR%vHA1Az4~#9)mr0|cb*)0GOzgkS=;@V&+pANzIB8{Wv*ZR ztFKl34_`5^i+pjvB(`3Xt99N~ucfw18GiybPo@+xzTSVp^zoO)nYZ%4++BY)>$R7o z(abYz^S1A9;_wk<>f4>M@6f)UYa+Gv3Flv$gmg4SY;~+@ns0gMuj!H$wgBxf-5)i+ zq^b%Qb{>hoZ1P^T`)J#q%ri6HwtQn0Vmp!dK(Nq><9Pb*x7((2aR#jnsdKo$oOi}e z2gf-tC-^u-M{S)<7!LaG(PT_{&Fvz}l4#SsqU8h6BB?{dCo~lPaT{rM1Ut619(crg zMehB}Bi%dlu7zkFH9o&NfA4_>?|)b>n(>~yX~A)aV~mA7DKP@>4#zkzvP+t?IF`)g z+rpgh6Y`||?;Jj>QekFRZ(inke(a}qhn`WjxW9y`HF@G;#iN*}$m+ zU-I>P_RasIwcYuFeY>NZ6U#%6jyJEj>txLlXiHRS@^Zf~IHk^I!smIl45rVntWbCn z-t5eIf$5C6&9$Xx*MI-a^e!M&<;X8#wrr0d&#v$4{I*MG!&a|vExEPcR~bKb7@T9g m_2Pd|ucUkLNq(iJpTuumC_NnNr283^wmn__T-G@yGywn;32e#$ literal 0 HcmV?d00001 diff --git a/ui/src/assets/img/icons/edit.svg b/ui/src/assets/img/icons/edit.svg index 1d61b06d7e..d75222df98 100644 --- a/ui/src/assets/img/icons/edit.svg +++ b/ui/src/assets/img/icons/edit.svg @@ -1,3 +1,18 @@ - - - + + + edit-iconSVG + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ui/src/assets/img/icons/face-icon.svg b/ui/src/assets/img/icons/face-icon.svg new file mode 100644 index 0000000000..21a44f3e4e --- /dev/null +++ b/ui/src/assets/img/icons/face-icon.svg @@ -0,0 +1,24 @@ + + + face-iconSVG + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ui/src/assets/img/icons/face-icon@1x.png b/ui/src/assets/img/icons/face-icon@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..a7f86a247d762968e5c84ff78ca20b4d2bb7eef7 GIT binary patch literal 773 zcmeAS@N?(olHy`uVBq!ia0vp^q9Dw{1|(OCFP#RY7>k44ofy`glX(f`xTHpSruq6Z zXaU(A42G$TlC0TWzSbOAGh4N}M@1g6j zOG~l`6qP@Eejvi->d~~uAVr7mRU0GzC>$#cUm8@Y|MLm!n;6N1Vc!&Zj(Vz1UcRu{ z&-BRg$T{VoI?qbQmwGuZz8JCY`s<@1TBXab>$h&uH-0zi)$3!HO;tAY>T~v}Ii5`` zjyI36NDGdc)7*VXc<;W2n@UVp2+vHJ;NKu??YsQ4;<4E+|13g}@8k7-`uS(G-#@#6 zT^2dBp0RoQOa5hKJ@-w`ZSlp^FW!Gx|8m#9NoUiRSN&D04rTu3`7I|Zj`Ncy-&48p zr`zto{~xjXYTCy82lJfHFJ%35{Na7Y>aV4z6#pLjJz4zQ)Vw@~B9Ypw20hz9{0sgU)%bF_J{i!YGk_>*mJcy{d8KGu;ORm0wI4SIgx>won$ zSpK}dqH*!7n5~jY<{D)ut!i1CFE&~C1s1-ss7##HfACg3_mqiE7lc=ueEFX0T-xHI zq`2K#?sT#a(;drNt+VA$F?weIQ^Vz^uKxL-@q3u%@1JW`{(zF8r>mdKI;Vst0F3}w AEC2ui literal 0 HcmV?d00001 diff --git a/ui/src/assets/img/icons/face-icon@2x.png b/ui/src/assets/img/icons/face-icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..3541275ce8147875a6b24c1d6918b309244b9ac2 GIT binary patch literal 1495 zcmV;|1t|K7P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91Dxd=Z1ONa40RR91DgXcg0Mr_#3jhEGK}keGR9Fe^m|JLERTPG2GMBU& zXVjQRX&+2YA}B~=!DmJAMGB!_f;KluAVHD~lcND-ebn&$QdiZbF zKE;rUc|Ps;qQ(DiWS^;L0Yw=F?DJ&tSJPo~a&q3t#o{wFGlOJx)%XX)o6Mb zqF{%IWA@$K4KePykBYlEGeCD+bcw=>Jq6$CUC3VndJvZ<_U`2DrDweQ077TEzd*Y5qiMmdcI z%~9EEzAY^+J*`aoe7=#b=BJ4#JEp)h)Z@WmaCmTV@RDi!pO+#71r2?7cXw4d9PVVk zorlhHJ^QK<&|O_!w?-n7$C);3Oq-KqV`Jx>+B*iy)$0&WKY`5Lx{iRyxSkz%5)AuS z>GuM38M_288iXIl{v#*$j(~DGIDq4?Z5--r;3S!S$fhl$!u|yIT^U7M0BD3udL8%Q zUvk@W>yEZ3g4_jNQYMT81wqyg{1DlC$>?)AAho5nNsrAsw1D_~+E_*kBp4OIOFd_{t2CiKY;uw*;xYIMKZj`(fU2QPPG9thU{bG zKg@o*fvowiX%_?Z7w|2MBcN4PRi6_q^&tpv^+;XRNku0X0q;*8Rwj0rUu@)qVx? z0bcv88P*6}&>QT%Cx(WGE*RTF`QHL+4WFwe8FPx#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91N}vM(1ONa40RR91JOBUy07zeuCjbBnVM#w zYDS!*GKU)s>NXY?0>%wC!J-lnXz9;=p1b?Jp8MYS{^ei<&s9V2|B9@vth|bG-@>5Qfk(Lx4&G2!R@Q9KiWMtb zM@L8NjEcuPJ3E`TJQ;JGXNx}!>F`&3Vce`G31S5W1&?ca66fn|V=vAGsM!Yd?Ut67 zD@HjfBrA#c#EBCRpfzXx-YU1YzP|p3aB|74!-fEl7NA^tWV#Iw4(_D0&zc=dT%_Df z{)x%Q&RJKkT-izP+KV*%x6`fxMNC@CpucPw&=0+O>(;F^ySlpOvX`4lc`blhF%tQ? zOu?l_o0O5R0mTR-`}BZ3Gyfi_511k9#zhL+Kef2EK*=RNalKm5^%WRh_+BKjG3k$me=S>|bfaH)`@T~Iy)+;$H z+y?5cLqp^buzh$=+oUpg4JbTjlz}fH**}w}0oZMTFwF!4xieFzPOTsh*dd!TZqlv+ zC4<5{-ecf*K=y_kBr=ECXO*_KwVetl=VqO&K*_)aEY6~D&&bGF1|U7K-X{%0pO!Ld zDKe!U9UUh{exf{?sW3#QA^kq>PeCSd%n`(MVlZVroJM?u*e2VFe8B%}Sy|Z|q4sD_ zIzsVyVIK5b)vkM#)pMK#N4JfvlJAa;jJ(Q#o+AB{lZij<-o5(_bv1FS4{9zRC9qA1 zFZ3Ym92jfXtjXrUzZGCA%{Oj{%IQQqE9w0>MJ?0#l2I2knN}VpcaMPdynqyG#KGQAG+LJmOJ!CQ`&K||9`*zyKJ z-?~IG>Y^rNo_g}h0SN8%^f&`>{CxuaYe}ym_h8f(*#rfovuDq~0Q*};)fgW8J%D+_ z;Q2^++X3r-3Kz8w8?GnN|1qAI786k2-Q5{{yvprzjT$342Ff)12WeQ!{wE8$9eCan zWkj3vgOR+xzCO9?zt?CZGhiiN)N1eoNWgj-#y3eH8WjcT*~>H(5Xb$D8z=#ZC0IZT z2&lZ_7VZSEVG7&|V7HKt7}iKE+&&(#So-rXU%tGDcK4_rbV?wqrw_Tw4*MW;h)JJAt=5|9Yoa_AOtZ4?P4?JOKvywM7k zBYs`QHbVv?dokZ5ATgM_ctBEJ=(v=hpI?s7?aGrJ{Z+)m9stWX`F1)iP=1iMqs%^4 z);mo%yK*fHSbVU`@*Xe^(c2}SC7#3p1f;KN8vzOWVW!R&<;9!x6Q&^mtCWSu)P)@j zNUz{o^7-R?=s#deh6KtF(hd~Zm-Y1+|5H^MZO+d4qYz-Va)Z2!e&n3W_XtR&T3$wn z@)DJ!cMP!PqV-WzC;gY=S^Gm><2wQ+bC#por)g~8D8>Ym8l+%hf*m&w3=I5rx$0Kg_P*3uM^fDlQ)v#g&Z>k|?r1bs{&pM19 zvN1hr*k+M-psWfSJQ~aDW+|`{21s*_suA42I@KVlJ=k#!&$^THTgubi5hwvkRs|J` zHxC68{j9+4WivZr9_;xK|KcHwjrEHfG0q7n*-W|u68!}1k|zpUn>TO%09#I(_Sk{y zbz{2W-c=J7Bn5{!YAtfYvjcTn1>?;_O*a9Fz_}w{{mE|6)C&t#Li!H{5`nui9YAsd zA;6>TC^|KW#0v)!fl_w->GCAcqe6f*%nMkaW*`N<%m9y~HUPyS=`f5?AQ6+66S%v> zG@X!DLBX82ZQht<#A3C8#J$^Il$)E$VF5|LrDD;U>Nema7b5zMh)0>Zx+M_m8>BZ%?aB%fG<%P-xqze}=$VFoSNVH#;&LA;EZUTqjnBxYhmppQ) z5A&a-0-PnF>T+SNB)?JW^r3zqX_uB0oztKFbR%J1VE1%x zhvee+JM?3D6CM0((V|6#zK-QKgvxZ<<0K5A2#WggruoF$!@gCH|H6K&q@+ZCOSym) z)5;g2!*r}Z#h1h~K-w?HTc7~en{(#OkpO%(=?kPR(xiKsdhg*y8~M-l<#Hvj065Nh zmPaqYBFW=IGK*EsQAQOcBj?8zF>ichwFmDR;p%-re?MW@^Ix+U^QZ|`c_9D*002ov JPDHLkV1g4)Hzxo9 literal 0 HcmV?d00001 diff --git a/ui/src/assets/img/icons/image-icon@2x.png b/ui/src/assets/img/icons/image-icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..8eee7e490fb1a6601b2b698e7c7fab024d9501f7 GIT binary patch literal 5649 zcmV+s7VhbZP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91l%N9u1ONa40RR91cmMzZ092EqSpWbPdr3q=RCodHoePu|#hJ&M0S6cy zVF1x!5>!CU10NfW#u%d@ppXpsIAjxiBEi)o8q6j}fk=FtM94v7&W?hDXmW5h3K}8` z4r=fbHqq#A*1(D;5wnm8Gst^@{QlSJ@lH=yRrkGpXYTavKBsSYSAF$WeP8|itE#W6 z>y~wjaLk-JvwZ#f^;O%pZL6%PsOY|X_wLHFva<3BEK<@48YnL>-xq;A7ScU?_B2wr z+ji~RwH=0AA9&z_tx*`4xrnDuo!Wil#*HJO8@F%YzOQoa>q?WxCr1Mqj5n$G$2)iK z{Mh{Y^IziG3UVu;F1X-=uYuTEsHcB&RFq1Rs|EnmOIXdz7c5xtdlQd9KwWs@g#!W7 ze}K&4ri5vD$!cH^!1`HbW#u)swYA&)T4)8R^UgbO2t;*&>0m!``kZPYO9RjL>(_70 z?Af!`*J?gm0E#B=h`oFF3QQksrbus64Hz_lrFyzcmo7E;-+%uOkED5oHhJ>ozJO^( z3MNlA>D8qHuri{tvGM2Gbj=oO!h{J6h&VqxGQ|BpSFb+=kCdc{Gyq7NjZD~LF#?=G zNBtquX8?8n`R5PAIxP>u;B^2r7rMHts;WQVefQm)Q$U5#S_dLov}jT1B}FBEe4LuE5DPHwBPMOUVq!*z^D_vp6T) zB*dK{eEhVGQQgNN{tLvN4GH!TJ{t5bGsLICC7=O}&Mk;}twq$=!N^hNSR8r3O<+>` zJ5n%N)L0@>3l}b&23B6Oh&%M6i!M629P9Ks6A=t{^yty!N2d5RymU06t`5!G4GxKq zRFD2x6A@s00dMHvP4Q`XX=$Kaw{FiM@dqZUnOzy!g-D|l6GXmjO5BmdHIiApc=4Bb zVb0>sI~rLI;q1@zZm#bUwx07vM!t_@>R0RS?8x%QRd$TaZu;DBpr;dqcoN=IfI1*3 zvMFqXC9cCo;k>QfzJ2@EfazNZpBV~s^~=$dup>E#5iyNX&R;oi>)yRP`m7b;O8qcm z0-f!zCU1X}hIm7b=FOC*Si^a84p174`GG&1SbX~3pU zo3122olR&`QsOL>oR)y)Y6}QD5&tGD4UfZV`7=M5lbGv)x|3bJN+mvV zfTDuO+A0s{+st|M=DlU3$&O>C#sBbpb9R{jo~H3Ee^{wLCk{|FQ?}I;+xqtHdr#EL z?%A{FUGQyQi0@Aip#H@lR;tg50~8f$^M=>?Kl=$&@ho-z0L!=1L<`t#d76?XGCXmB zqT+3p8@j0rsO@zDVP24N#fC>vD%ZpTs!$b&720d!4Tu{}aitcXI6zVH>rItN#UE;l zbA^*ulf!|BEziIN2@Xsgps1_&EcJCN>z=z=;sc{5PMlZ;Xucd6Ng!Z**A!Q3;fVtj zmAl$fiTm@V>!p_Xz$o^?x&ppNASQKXYoIb5fr$f@_AzqEJd;V?K@OVcXjT<`7Pr84 z%>ltTt~`TNB``E`fI9r}!&lKf*_NsHq8^uq0TtUCeFiZ73VP$JR^tA`G>y$FCJ;7e zkrD?e=GFEBs5{IeXZk#YXu@9M<3;9yTdx+-Y6SrI9hygdhxQ2@FXC=<#FtcbB3B#G z?9IWlTtQ9Rz)(&6999e540x7vFCUebLl0yN$l(C42luXH&EUawm(5pG8a@KG!fa$9&)+8 zj=s}Vxg<<-n#2N1mByY$Gq5DTh`R#eITyg*9yi}4@JlqHsQ7(Yk#A5@H^o$0+F)%O zV44&Yzr?XgG@w*l0u^hTW^j8p_kYf{21&b)&VCJG(zZ6~VbDP00p-aJsD4>pU3~<= z&^LPj?}fXr(Ep9T*wupu4LXu0^a&SzsmCYMZD6WYtraKk$BfKo@d2nde9-9bI6$K% zFWM}F+sx#$CYV>t>wef_hrKA@7SHJsLIWiQC{G_v09nPe&h&^;10@Nl81Mg3%DH<0rYwueF7+*SKmL=^^@UYq>9$!@3tn3aECdCjMaHSUmaD_{XQS z$-cRQ0Fg@5l~az@gE>bLrpat=FnZ1#90A)`04lKn|16qrPMY^4x-t=*3 z*h3XGl61qFd;Dqd-o3xu)E5k!K5n|w-F+o)P3FUI(xgcTG1;s>TNE&5XQ_>KuVn6V zB@6yEh$zaVWZVYrsePKbPwv>U;{`0uvH6w*9w*RLzM^$n`SL16`V>%w&}tUXOD?%& z08QLVfG{X4IImlH9YP=MIo4}^F)v01NSp$y07~^((q}pp%cSr54)wxfyjIhMK8>Z) zKJ!I$6x}EfA5jGxg`LV^L4Qj-gtze^uKLrzce#HRul3{1&%IGJ8Z;Dye$L@LckZks z+O{UkOT7m9YB_(4j2iVF&v^;=Lp%b*wL6yTmvq)AGZQe+KofUx^Qbh4anS%uBOuPhKSL z3MiD8*`b#T_yvmicvMM9^REDLwJI@c+CtC+?!oZYKcZS?0U(-FJO_*NpRRJyysrRM zUnb3aE?TVjx6s8x_G91AMVm)_%cjG}sNDd?+tdS-z8K}_wL>rI^+oS0D)ShhY;F3e zzt_e2C@{Hp&f5t<&cgaU8Mc6|a`*#)`XXnTDVT4tr}`7pZ&>~j>8uv8T>vFvd7FNP z^2bCJCXgIH2U3~K+Y3}!g(1jON8zjvM_DG$vjND`KtU{lSgLjW*UHI|IHzCG$4JVv zLVx>fVVP@GaapL#FTZ?$1eQWi$yFjly50hn=E)541qtTm_!NwtfBY>l$ua>9edm|c zaqqqNY8{uFzm*{vd>%{ng8X11Xk#O3Qwt?9H8eCV1yElKAweUGdztetXqR%;CabQa zH~q0^+6q*RtUiq;coumyk!w6CyZTJyaxFQDR$wN?#RI9=QhO zCo>+oIZ4{YLJ3UFKS`m^sREjX z+W^lnK=jw_(la95MWx)LW8lNe|Fcw7zks}(gD9uI4;)J~ z_f&IWOgH1lkKcnnCP`l|mXxy>s`c^wn0t`@p~gHU&~XfoWVJz?7Y@^hG}3Cd^vV#CNC6 zkJ3y}%uXNoeL*c0g+D-GqR?M(6&$6^EmltNZ zW-Jwb9^>x7hIB20)zJqevP^wiMED*&FwtbU>9i2~SU}$6D9(0Yky<_SQs^JGeu6C4 z4tA@AAp!~lDEuEi5pEd;KG9VM=t5g2KQDob^jb#i1eIpya|M-}O?C9i$2u)US$05E z)7D_}^dfyQ0vtg(HhW>FD|K@fmg>Q{z3Q<8N?_ujGXI zR01KgIKf1rWEu1!=+i9<*v1d@=g+ABW%bgQfF=GW3qn}blKSRN+mDD6y>|pW~CQxB1oE>$6 zgwqHrEYpc}TxD%+e{H60)y#dh*ubQM>0mUx0Xa|ORt{L}0o@Vus>{b_C3HmQ4Jd(W z%a$#AJDsB_-j?sj)y$dXqH>>z0xC6Y*}Sk!U!a`wn;noH9&n!l6Y?w%h%1^P%GbcB zS7%b7M?(~6a1acNKpFr3e!{#FTiB-ztvR+bkL2#$RoU9 z-*nOX<8uNO=Gq>uKUP<5}3%S=)vR(9syK|2a~50K=l*uFW_wS!c13QGX4Ae z-CC|~qR9>qMdT&W3P8Z5>4&4eFw?a?Et9`Y0u`3!T*6%*b$`;#vn&%g9j=)R6s9Q< zP%T6!W1D2Nk?Ox}F5_#Yhv{o}vl0>+8c=LKb08jYf$1nG)AI5eZPfvHmBou^2)!g1 zP@(XxoTHJa&f}GoYttmVzJJ^lLZ-8E4Qaq~u#Gkppado@*?^JLM?s~{prHDetmqDb ziVpR70AgGolU?X0FpVMI%IxqY-SeWXqRvi1ru8A@5}i)t;RKWiCXne|Mq12Z@-%=P zn!w%TJ)5qj047g%UYIxb+iyR4)&Jy$<$AROiVP0HGU@A-M1Y9_#c9a$D5v$iHGv6B zHaf|c$qyJ!)c`6vH2=#V7va~e5iAoUl~Wl|Jb^rwHU$Yx#GlEm{c|?D zB*xu$-+ddqcI|o^I#`jbd{#igw8F)>qksuHo^*i;nh!Y7q+jv8OYWq`&zm=I3)^px z22_vaBBKjXR-PRJOlsy3A{3ZVqDFHi3ns6!HC4lhA{xBZdSPL&IRmPrfGJEfhq6AP z&s5Tu$qyKPD8kEiDfN7_KhE}?6QGg^CY+xy*c4T&aSTkRQ{Pu1ziX-{qoP~@HJwa~ z($C~ox|+GLmPudXreN|k7ks6@=U~ClqY2y{Oz}}@R6tF~Wgp1|ew0<)+=T@b%Gts> zN^7v&reqyE7hbxD7}QL3Nf8B5i2@UfIYVAzmx9vxk2Ik9Kc4>gqmb5jVF8tbskQD( zHRMg3;7&lP1D>E}t^s*QrIyJC340vf1Z(Hq3MdlS6?+GqRfWUCbik3Nfj+<}`T{T7 zRhGs&G>|)>V64x*i}nzh0Lc=U2iyY_miDEh6(%7Cb6F@&MHJs=W#&U#a6&6e|tQVN*1HK%Zc}nQi06Mi=s4h+pMMNMl zsRK@tx;u(hk7suz9dP~QLVC2J2CNp!g~m24r#`|=!PJ%tH|Ld?g|a3#=NWHI6flwV z2rScY<1InTEvyEt7Ah>0Lehl=6V`My8WLkqTT#yUEQE^2-KMkJcx6C^%9sc$G|9H(r&jb zj?3#CvK)(9>dB1z8-SPHvQQf=8al`l7i&}$FwxYVo`NY>^+#c5+@*%4LEEf=0&&j$ zpQ4n+L>m=MtY>m=L~6<_4bNQzd_HkTHdaG-I`1jewVR#Cr zTs0j}I$EkL!NM>LxAp9eu(o{e+_~>VXtYu=(BEP`P17{e!Yoo$R4}nK$g7buNQq<9 z0Ciu6LAi$OY#URI(!-n|{u>bqP9lNx`!o_fNfUVwf2LelTU%Ri%OEIj(V|727cX8s znYdZxXB>ViEqOK2%neINb3vLcd>H?8*q=Ym97R28gV&+99I30_?AkIIIh0IoO9O zitAUR7gveGl?Hulja(nfA9^rGn!6Y*O;HZb3+%8wlj(HHQCO`A*FlE8pwZdlAl&-g zBGU2z9|7-BKR-*7G%5xSMAbK7`cq|PdttgZTPQD( z1#0slR$uDxURZifHQ>|0QkuDA^uK(*@J_~#W7F=x(@5}dw5whH3Df7&(*TA>vnt=C z1OAvvdLy8`1o~4Q{?}y^%tiyOV+nVr@zSQ(j;{gg{XOdaQ3h=f4;(n~dF^axlPVH8 za4j5w*SbGjO;?eDPM4RvId8n;&G)(7AXv-cV?-wTrf=`^M-;aed06=w={HiZ^~|7b r=DvQwfC2w-fv8nxX+WxhxHRxj5VnfEf}o~s00000NkvXXu0mjffBcS? literal 0 HcmV?d00001 diff --git a/ui/src/assets/img/icons/image-iconSVG.svg b/ui/src/assets/img/icons/image-iconSVG.svg new file mode 100644 index 0000000000..fa7b46d796 --- /dev/null +++ b/ui/src/assets/img/icons/image-iconSVG.svg @@ -0,0 +1,21 @@ + + + image-iconSVG + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ui/src/assets/img/icons/play-icon.svg b/ui/src/assets/img/icons/play-icon.svg new file mode 100644 index 0000000000..e75b9fa229 --- /dev/null +++ b/ui/src/assets/img/icons/play-icon.svg @@ -0,0 +1,21 @@ + + + play-iconSVG + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ui/src/assets/img/icons/play-icon@1x.png b/ui/src/assets/img/icons/play-icon@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..c19c7d6d83372000cd4810d23fc8f7c7210f3c23 GIT binary patch literal 888 zcmV-;1Bd*HP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR915}*SB1ONa40RR916951J0H?{Dk7R5%fRlwC+uVHn5Vv2Bhq zau=-+yjbdrte`-Qii)oK0v8BFb939x%E~UnE=mf8f`Z0`3d@VZ=5`Ym5hZ$MUF3xj zxG;!Z7fIh;j9e|Uee`=a&pg`43(tAJ{_p>Np65O1=qmoR0)c?uY&I`TrBVgE-Jb33 z?alrRu3MUDZ*Q;B>2zlcg+dFtl}qw3@nedj^bZXUeb?p|5kYbsk{~2ifSzKHV$Y!U z`0J2n{Pp-Bkygl_sAOd#x7&RQ&^Z#5m6er8qtWPu3eswDF8phV6>-I4vHOx&=Y8O*IOsKrHhTXjYeY=@wZ5fE*b=*^LRXFNRmhfe``D*pY7=AIFQTbhVjR! z?ij?c1gu@`+mz4epAj2l&wi=M1Ia1|*drtY22d@UwHNz2r(WR345d)%$$X};ZX)}* z-8u!}VVtiflj&{=vgj$dmmtzNJw2VEiapva0}qRz$z*mbI2)0P!NI{_T9DYpCmjyQ zcB(yzy@o>{GtY2mXQvFhf_cdy-;lKmC)zMRA%9)8l&6gwkZp$S19*QroepZipRuhn zqZCPXCYt<$NlV+P<^j0XMCJ9+5?MJQEv>yqwk&Kbwp0+GDwZ?o>ky3N*5h|OozBKc zB=SP?KA+Eyk~ZH7(L;bW^uCM0qQxr^C2#_H1W5xe23;;!P6d%~6B~|kMX%RKFytM& z%sh%OC~f?l2Yi8`g`qa{F3m_xU4FlR1-LWpovN*^O(@}T_&pU}L3SjQ$*Tgi*=!>K zju;Gv4ODSY-K=U_s+yadyMr&YJuMef{MMurNMx9YAc$EkmQw=*11WW*Rx^TnaB*Ty zRC}I-E=s=q(ROup8Ts$r0^}v00u(`yAjPkrHXP)f9Mp=I5p?q<^rMTbOr!?5>Vp9L zkVZA8(OHOwksd0MPo%v5AW}2Tp_*HfpPYqQjtXb3R_mKkDD*?cX!UQ*Qc0KRI`B6D O0000Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91B%lKT1ONa40RR91CIA2c08q}boB#j@ph-kQR9Fesn0-vuWfaG`m#YW_ zY?kk4bI7KX7o%t`wUxCB6*Z}wsc1=Q!?9JSMyFU+13tcYP zqW=E=hZ*dqC<8X3djQ>iyLazC8AaFbjtXe?>ea(KJ3H54@jA{&*_MN24g+rEu3fw8 zqnX_hP(eY#96*#)Q$rmwKp(OT)brGf)CBYP)i_58~sQ6uFLW2MSCLg1{D#xgqx?nI- zF`b9<%a$#B71^7H|Ayq`_K-a1X=&L=qHlxswzaj*oiSs^kM;HSvcnt~0hN`NH5G&OfKgUsFpkeq$Yo;L(smaY7so;8X(!I#76WZNq1a4f zDQigDeMEbL6Pvueyb3_s`3b*R?A(68UlPo^mk{<5ojpz*qO%A6PeXJU1MCdIin)NT zh==ssO|RG6i@oemqACzb86Ya;fYF^iT5aNha_JI=`tN(UNQ9gXZSu z6GZEFm4~B@eZY`UFDxt^DYHmZA&9?H-YI1QhewYdoer=~*mtWP!0e)Zt+0mMvSl09FE^Y^u3Ct)wR{BhhFGU1WgW5N%YC9@I2+sh})Ln$Qk()RZDCY(QJ+5@yYEiG-oDH9&Ky+ZI;JQ&Iv zPL@viXn-Q#UA%!(Ny;+nL*h#dgUCVqd{tFd*yDsT?Q;vh-Gjk77|Wh*n5B-KQhDNb zb#=YOga0*b;?)kn@}k_HkdRQqLrxz50}44cje9)VA&$oRw<_G?$%nztOf?^y~C{fx(N=0>LsPx#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR917N7$F1ONa40RR91761SM030DRS^xk7vq?ljR5%f}l|N`xQ544eUJ_9Z zq;7@?4(a05szp$7D-KfP+CeI`WKn3-G=xlAsZ*dynn4k4YjttZA>DMVU38M#MNEYH zXHFtQAht>ToyWb8o7V&@;)Tn(=R4o|?%~{f-?IihV_{*zu2!qpfhR1>8trsC5!QRm zvTfUM@K=|Ymp^#Y4!D+y%*@Q3$6sL%n|P1)GoR0w$mjFb9x(@Ug=c4HV<69|=$Jv? zrs@j*QRaxMUWY&W{r;=DT<)6&OzQ|sr3h>pqMMN7#bU9n$;7N=G8t>P+YecX75;u` zXy_vOP0iERFFmc*YLB%j8{97x3U{y_O%kj8(%jtK7UCQ5F9V;gR4N(lJ>i(cIVeaY z3f$}^Y9>fu+2jWEr{LHG#RW}{tZn9m0mfBj26S-A=DVPa5}~NUdc8g-YjfBLjVfT< z?5RoWwdU%*)Htn?wsZ}f9V3FU!RM~XADmDqv3`+lwsGi<}GbuT0h~S`hOlxT;olc)>Hk&09 z&uL6qxl;P{Jt3#4HbqVIXr7osxfRaHX0zX;(dZ>ecbRpKW4)rE_+-Xt-z2;?g~(f2 zNhA_eM$Uj+u?eP9spG9yYfQbUFp0m2f8{gzYH4Zd9fD3zX_vHRO~kPs@HjsK`JVR{ zmcU_$SDd_QJ`HPbD-J8<1tfx98te;dC6GXoCg TxN&(B00000NkvXXu0mjf4-IdU literal 0 HcmV?d00001 diff --git a/ui/src/assets/img/icons/search-icon@2x.png b/ui/src/assets/img/icons/search-icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..91a12c4e34975bb8ca790f11509b59864c9bb228 GIT binary patch literal 1670 zcmV;126_33P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91ET97b1ONa40RR91EC2ui0Q_#}qW}N}@JU2LR9Fe^m}yL0RTRfN1D3^F z1g)rzMjouZ!s9|1p|nVH#BR#rA0l*i=aapR@3vN8!0&(KTg zTiw_Mb^i2vz3;`v#T{vCY8qXFoug`;qXYruJmcfz2j}PKKV#!p1;6fj*pK&%0u*rrXyH= zeSQ4c*x3CH57X}yOlb>{-UI0qip&vOBurF15boi+OVcKboh(q*($X@f$u&%Tl3pd) zz0UY*QwLmp9p0}+-wODdej!7ZjTk*$PmtBEglF3$!V&JcFC+-8j(EYau2r&p99Dw~2v zwlfxFWsTWppxdXW- zuFbuq!)6R@+bxb?X=rHhX+|r-s4&mrd&cxZj!P+w+PsjJgN)hyhaO@F)Cy zYWjjlp^0g8A-qKqe6f`BrPxP=i0}uBLS9Wxjji;VdXmH0Hqh(sH?dgGsYs_3e{nN2 zGdC-E7$k&m4?~y`^$R8z%Q@BJyylNIyg|dQSfEN+0U2}4elxLH&Plh+tTAr-B=W8Z z%bP-2Bf{&$fEZ6WYsj;+PH`LctSk|pQVgT!l49c;nwXfl9%b9nmZ+<#Vs$KpzqJCv z@n!{!8AQ#!LvxmtlypwRt(cd8^_3L}j*H}osNgYyAiPI$bji&|CAoCkGzLrKU2dm| z$6`(())J(Bimz@3vq9d})KuAZE}0UZ*uy3ki@DF|dy0T;G<{L(x=c))d-=yR2;Mb? zINc^^WD{S>ysoZp1NP4=K785P*}V#Gg93G!TXY18-;@&NH#i*G4o0S)n|Vh^$5oS) zlU)QtDnwXg!)PomEwz=`Fs0xmK$usQvygsGQCJ7*k>DapTo3X+AtNs@Z)A9Q*tV^jWcXN;kd>8H&i9RDkj*mXGH0zh`HVF< zIQXN?-L$|vomG)YK2AjC#n7ouvrrGco8F(2l5&w-K1GkG6sBV<$JJs;$~pT+Cnq5s zAD7YH%^!62(Afx!BVNs`ani_8m4_CyTqjsqzN49U#-idp6w*ld@+^Jkto5(Nkq~)R zgvvYg>!LYXiX3ut{4s48{hRdI%Slf51;^$X5&2-p`&TXtME%!}-Yo+Bd{6n>PnR83 zJ8&hkWp{ITAF!C&hK>=oYU1Q><}VSM?N;37*az8H`MBL0ss_9v@8BDSe4@XMVx?gl1tAq%zRO7avB$sy+QK`pa4SX&Atq`~>2n!k6 z>6}O-&O{c-hE+p25@{koKo5NNTvdcaq`BS$!4a;q<@ZvTpAgyq?Mg)8A2W$)i&Lzz Q+yDRo07*qoM6N<$g0Ha-x&QzG literal 0 HcmV?d00001 diff --git a/ui/src/assets/img/icons/search-iconSVG.svg b/ui/src/assets/img/icons/search-iconSVG.svg new file mode 100644 index 0000000000..3315a026ca --- /dev/null +++ b/ui/src/assets/img/icons/search-iconSVG.svg @@ -0,0 +1,18 @@ + + + search-iconSVG + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ui/src/assets/img/icons/trash-icon@1x.png b/ui/src/assets/img/icons/trash-icon@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..762e00bd3083d474b35b60e13afeb383e11a8489 GIT binary patch literal 614 zcmeAS@N?(olHy`uVBq!ia0vp^{6H+i!3HFQj;9L&DaPU;cPEB*=VV?2IWDOYo@u_m z3|c@o2Loe!CIbsd2@p#GF#`kh0!9XAAk7F8TfhXDEak- zA-FVXzxUw;f!w++ODC&baLm~_rQ?$0%O}hJaqbdn;d6C-^+rqTj_i_dK0n=EJAa73 zP?5co<7NFq&o?JfYO={b@5vIkw}{$U?EC$0Z~4yM`V&vs_i$bo>pq%%d)xL#--Guh z+bq{#&8lvW$unSlXw{;ABHpotxBhUg`uy|uT3w%Ho@_qIeN;j3l4jSVu&q&dr>m@V zDL=hw+6x}3X8pU48)SU^I&#IsDEzuL-b@!c&e6O47VFM)rxrdf|>YW=G`-<#Hhzv_L-0a1q z^6$#Fyh}W-?_D^Og()Hel-`3Sp>Ri?SxJ7Ro%XjyltnUnq3T~?QSm!kGyL_0>^4?9k lB3VEzYiGT?QZ}8 literal 0 HcmV?d00001 diff --git a/ui/src/assets/img/icons/trash-icon@2x.png b/ui/src/assets/img/icons/trash-icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..f5cbae93adc4283e84fc2cb3f4f48e6da34cc8fa GIT binary patch literal 1112 zcmV-e1gHCnP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR919-spN1ONa40RR91C;$Ke0OTNgBLDyc!bwCyR9Fe+SW8b+Q4sFaS_l+1 z!HuzH1q+o#|9~WJhzTpKK?;e4xYU^V*mgsdjT)AO5Yp0xC7P5)UAXDWAAl|l8;P&j zjfk&O`tqB@o#FIyTl%=@!js(2oH;Y!H#7I#b5Mule;dw53r;4J?$y=R+m%Y?iqq*l z2YJ+p%|X6~ZDnL+I<|b{fCs3+d zXL7WAx&y#j$;Kixy9pH@w_fdPBAuL^9EBXPN&wE1V5`{xx8icS-XQ)tVJJY5UMrPK ziB>_Ii9-#e*!9oU0a7lPL(K%Y>Dy27s@2G13!PAS(5W3S)Wp7@Vc?%3ILL1#nRt^X z#(0ZX+~bKI5@$sSIEomBDK2jHSB>D9rrV_+*MRnjISXU)Fo*?{xm3kG`J2S*9lPD` zO?*z=4ashNeEbxWN?}7({C@xCg@uJIIZaGV^x;cLlkW9;hnAL>zK}jWJ$*8l%ViKl zV>lj zglYKvL?T7f*+9C=gcM{ht)aOwS~@m$EF>>qj}Rj(A;$Z*BIX2|k|r$S5MrQ-5lPra zY{C)_A=cYqa&D?Zv$F|nKe1jhbX75u*F*~l^$ zi+zF~tZX+H50000 + + trash-iconSVG + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ui/src/assets/img/icons/user-icon.png b/ui/src/assets/img/icons/user-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..20216f98e5587248343bfe8e8b59a37094a926ff GIT binary patch literal 1006 zcmVPx#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR917oY^2SV=@dR7ee#m0d`aVHC%G_o2mv zRu>9M7lk2eZ9!>5K^8_B$SOjDO*bOeg+&?BMK^j?RFKs|$_ppz3cM&Z$qOlW6ZA#F zOih?{(-%}|QiIy&`kj||XlrY0X7Wr*y$^#r65FpCZo_a%BU5C}ZUzzYm| z!0YvzT`rd|9*;lPh$kVuU^bg=B_$=6!NI{|hr?kat^_}e&QM>Aj#GDD&n159P4 z#*vYchmbab&r9(Df=y%=CF9P{PD3~xc2jc;tQft+=BJvQo8yv`B6m8Sw~5;z`+#la zl(Uks5}~$=yhrFJ>bs~tCv_@0J#L^C&2+rUg%mCwMq?E|RR6892!=!^34%gf8}Lp;M5?1yKM(Ppzr ze`)~(*v)8z-ELPxp->P%F9`;N32F-=KY;#_w(JrHf=fJ# z>kR}O2oj9YEaa0(B5*HbHpM1~lGsWt(;K1-QM62v`cchvdklw-hS5Y}K_5H&%x1%E46tw>r4Sf4?M!A_%= ztmG`DUal1JAYNfa&oE*ZY$ZACvC>k}>%@W`<%_2|%t^3xaxQccP)?9@O1QA!q`a2A zq;PLn#5b8tf0CG3>32$cdwZpql2FAMX8o#Evd-ber4kku6@Agc>EsYgBd18V%+1Yh z*C*!kY10nYN~wL@)6)~t=cS!o79j4@tWSR5^~ssOtdoCDaL5NT*Z(8uG0E@4EJZbu zNTfU02J&0Zx}Usy^21iEwR>4|`Jt!a2n7q+6A(O;SK8j*zFLZTC|0Jy#p|79-3c_$ zMK<|-zO>7fT!lpGv#2^L0%{^;nN#x%O#X57@|Qp+NH10bMJ8exb)aj|^5p=T chY)=9A7}G?+~k+(H~;_u07*qoM6N<$f{-e_Pyhe` literal 0 HcmV?d00001 diff --git a/ui/src/assets/img/icons/user-icon.svg b/ui/src/assets/img/icons/user-icon.svg new file mode 100644 index 0000000000..83b8718810 --- /dev/null +++ b/ui/src/assets/img/icons/user-icon.svg @@ -0,0 +1,18 @@ + + + user-icon@2x + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ui/src/assets/img/icons/user-icon@2x.png b/ui/src/assets/img/icons/user-icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4f9864c8d2b39153a69184c43cac33b1039ab90f GIT binary patch literal 2092 zcmV+{2-Ek8P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91E}#Pd1ONa40RR91E&u=k08)}91ONaBmPtfGRA>dwnR`r?WgN#jTm!t| zvM>wLfTnH%T3*`n!YU;**P7C%g5nr19GcCR=2lcz%UZfvQ_+C9ti^ITa5}A}d1LOYH~wPl74VXh62J2D@??O%NuA(rGI#cXt|ZpmMMXvXy?KB> z15Zv)ewGC9Q>VBCpvwTzf!$SPq14e-KV9#xjnB`%zP{^u1IOIyt+HN$OD~s|m2C&e z%T{OMN!X3<6iE*-&d&t}1yv^|CSFQPO6pSf85tQPnwy&^b#-+uz$Ou&Irs!Ay`mC} z`d(C26jLXwQ_0;?Tj1&G>0_Ikno4j?usW0AH)J1C_w$gp%kT>&{sx?0 zfm$bc6?x`)>J+!Y*Q{AHvaPM{1OekMNWP{gmgnW=)mh{NVpCI7Bih^BO9}FbCD8fc z;NbcD_U*f7k^A^s1+p#^O9I}K$UfT!c);=;)>L(4NeHa7MY4<-wOQCvesh6s`z zp4KIUKM@)ly6(2=@cnWm*uc;{sb!Ro{1l9VO6CH2@#4iygbyo)P{!Scfu1S!cztb4 zPJl0^$|j~tWOa3Qiee1lvdOS9$g*TAot>Q@v*revgu}!X78YLT?Z0bDw^^`?%m9Ay z;6XVsMOut}aGb-Amu-~~7tb5tORu{&H}FJ(b*bbxfYaLUgU`^uRWjC1Z#w-7rw=Q8!V*$d$`g0Bab@Hca~sm*RHA!Gmt)iOV=&++)!V z8SCfgX95o9$-aP-UCLEB)Kn=3gJ#v%De`AJIy!!#_mg$`P_;*m8udM~d$joB(&Kk) zf_4`F295XF6hC^ufMde9;GgHwa1a#E$4BB1p# zK9>hL&I8EhcV~2T^daVe?d?Sm*jt-i4mw8WVGwO)ZU7&lI1=0THu_?m%dyX)nvT8j z3Fs24s;aKB`V_*K4dgRKN=nL1I^+dFClU7n;<*a^Na^uD7aSbL#l=6+qcb?$tcBl7 zm79`Y!jWx4u!-wgBM)%`{wE&#JoEyzo)xQ^+QOvcg?9OIX^8~nRD7mlBXt_`2tBB^ zZ3~=Wu6HAjGIWGGhr%ZtH*UIr@T5P>y^|9Kk zBM)M--?3vy3%luuOO`B&#QAx8^C)!C#3~tTAnYFs4oG zI=vvTc&LiL5z0~ex?Lh$;7p907H3&KhN_s>ZQkw`yF|9YaXinHyM_zFrq~7Rua3Af zcV;Pna(0J>g_%|nrBl=vxLlnutWd_#2ib~aHGWWsY#?6+ckQ8+?eEvSFzr%b_<7TLO1uj)6WWXIlkuc z0A6oMMpk|R?MsY3#CTN6BnSK!jrZ8>6L2Q!?+lqNOO)jdnZy0Ld&*~H?ya8_Z?zs% za}0Ju@FagZC6K2Lx8JE zCcGGUECleVQwnTk2#)WIt@vQ)?)Id@xuuez)~)8w7G9PQLhHeJ>CVP!LU)=N Wf;{UT^#cF^0000 Date: Tue, 21 Jun 2022 12:12:39 +0300 Subject: [PATCH 393/837] EFRS-1249: Fixed tests --- .../db/changelog/db.changelog-0.1.8.yaml | 52 +++++++++++++++++++ .../db/changelog/db.changelog-0.1.9.yaml | 18 +++++++ .../db/changelog/db.changelog-master.yaml | 6 ++- 3 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 java/api/src/test/resources/db/changelog/db.changelog-0.1.8.yaml create mode 100644 java/api/src/test/resources/db/changelog/db.changelog-0.1.9.yaml diff --git a/java/api/src/test/resources/db/changelog/db.changelog-0.1.8.yaml b/java/api/src/test/resources/db/changelog/db.changelog-0.1.8.yaml new file mode 100644 index 0000000000..eb5f0614a8 --- /dev/null +++ b/java/api/src/test/resources/db/changelog/db.changelog-0.1.8.yaml @@ -0,0 +1,52 @@ +databaseChangeLog: + - changeSet: + id: add-detection-verification-services + author: Shreyansh Sancheti + preConditions: + - onFail: MARK_RAN + - sqlCheck: + expectedResult: 1 + sql: select count(1) from app where id=0 + changes: + - insert: + tableName: model + columns: + - column: + name: id + value: 9223372036854775807 + - column: + name: name + value: Detection_Demo + - column: + name: type + value: D + - column: + name: guid + value: 00000000-0000-0000-0000-000000000003 + - column: + name: app_id + value: 0 + - column: + name: api_key + value: 00000000-0000-0000-0000-000000000003 + - insert: + tableName: model + columns: + - column: + name: id + value: 9223372036854775806 + - column: + name: name + value: Verification_Demo + - column: + name: type + value: V + - column: + name: guid + value: 00000000-0000-0000-0000-000000000004 + - column: + name: app_id + value: 0 + - column: + name: api_key + value: 00000000-0000-0000-0000-000000000004 \ No newline at end of file diff --git a/java/api/src/test/resources/db/changelog/db.changelog-0.1.9.yaml b/java/api/src/test/resources/db/changelog/db.changelog-0.1.9.yaml new file mode 100644 index 0000000000..60669c5a30 --- /dev/null +++ b/java/api/src/test/resources/db/changelog/db.changelog-0.1.9.yaml @@ -0,0 +1,18 @@ +databaseChangeLog: + - changeSet: + id: add-created_date-field-to-model-entity + author: Khasan Sidikov + changes: + - addColumn: + tableName: model + columns: + - column: + name: created_date + type: timestamp + - changeSet: + id: update-model-table-created_date-column + author: Khasan Sidikov + changes: + - sql: + comment: Update created_date to now() + sql: UPDATE model SET created_date=now() WHERE created_date IS NULL; \ No newline at end of file diff --git a/java/api/src/test/resources/db/changelog/db.changelog-master.yaml b/java/api/src/test/resources/db/changelog/db.changelog-master.yaml index b3ae37e3bb..86032033ac 100644 --- a/java/api/src/test/resources/db/changelog/db.changelog-master.yaml +++ b/java/api/src/test/resources/db/changelog/db.changelog-master.yaml @@ -32,4 +32,8 @@ databaseChangeLog: - include: file: db/changelog/db.changelog-0.1.6.yaml - include: - file: db/changelog/db.changelog-0.1.7.yaml \ No newline at end of file + file: db/changelog/db.changelog-0.1.7.yaml + - include: + file: db/changelog/db.changelog-0.1.8.yaml + - include: + file: db/changelog/db.changelog-0.1.9.yaml From 2bd11b8ae79929d5011d084f9ec1fa8745c33e0b Mon Sep 17 00:00:00 2001 From: Volodymyr Bushko Date: Tue, 21 Jun 2022 12:12:58 +0300 Subject: [PATCH 394/837] EFRS-1249: Fixed tests --- .../com/exadel/frs/dto/ui/ModelResponseDto.java | 6 ++++++ .../test/java/com/exadel/frs/ModelServiceTest.java | 14 ++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/java/admin/src/main/java/com/exadel/frs/dto/ui/ModelResponseDto.java b/java/admin/src/main/java/com/exadel/frs/dto/ui/ModelResponseDto.java index 5c89d71719..437cc26a1c 100644 --- a/java/admin/src/main/java/com/exadel/frs/dto/ui/ModelResponseDto.java +++ b/java/admin/src/main/java/com/exadel/frs/dto/ui/ModelResponseDto.java @@ -17,11 +17,17 @@ package com.exadel.frs.dto.ui; import com.exadel.frs.commonservice.enums.ModelType; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; import java.time.LocalDateTime; +import lombok.NoArgsConstructor; @Data +@Builder +@AllArgsConstructor +@NoArgsConstructor public class ModelResponseDto { private String id; diff --git a/java/admin/src/test/java/com/exadel/frs/ModelServiceTest.java b/java/admin/src/test/java/com/exadel/frs/ModelServiceTest.java index f842687863..e2b610c254 100644 --- a/java/admin/src/test/java/com/exadel/frs/ModelServiceTest.java +++ b/java/admin/src/test/java/com/exadel/frs/ModelServiceTest.java @@ -20,11 +20,13 @@ import com.exadel.frs.commonservice.entity.Model; import com.exadel.frs.commonservice.entity.User; import com.exadel.frs.commonservice.enums.AppModelAccess; +import com.exadel.frs.commonservice.enums.ModelType; import com.exadel.frs.commonservice.repository.ImgRepository; import com.exadel.frs.commonservice.repository.ModelRepository; import com.exadel.frs.commonservice.repository.SubjectRepository; import com.exadel.frs.dto.ui.ModelCloneDto; import com.exadel.frs.dto.ui.ModelCreateDto; +import com.exadel.frs.dto.ui.ModelResponseDto; import com.exadel.frs.dto.ui.ModelUpdateDto; import com.exadel.frs.exception.NameIsNotUniqueException; import com.exadel.frs.mapper.MlModelMapper; @@ -33,6 +35,7 @@ import com.exadel.frs.service.ModelService; import com.exadel.frs.service.UserService; import com.exadel.frs.system.security.AuthorizationManager; +import java.time.LocalDateTime; import lombok.val; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; @@ -113,8 +116,13 @@ void successGetModel() { .id(USER_ID) .build(); + val modelDto = ModelResponseDto.builder() + .id(MODEL_GUID) + .build(); + when(modelRepositoryMock.findByGuid(MODEL_GUID)).thenReturn(Optional.of(model)); when(userServiceMock.getUser(USER_ID)).thenReturn(user); + when(modelMapper.toResponseDto(model, APPLICATION_GUID)).thenReturn(modelDto); val result = modelService.getModelDto(APPLICATION_GUID, MODEL_GUID, USER_ID); @@ -134,6 +142,7 @@ void successGetModels() { val model = Model.builder() .id(MODEL_ID) .guid(MODEL_GUID) + .apiKey(MODEL_API_KEY) .app(app) .build(); @@ -141,9 +150,14 @@ void successGetModels() { .id(USER_ID) .build(); + val modelDto = ModelResponseDto.builder() + .id(MODEL_GUID) + .build(); + when(modelRepositoryMock.findAllByAppId(anyLong())).thenReturn(List.of(model)); when(appServiceMock.getApp(APPLICATION_GUID)).thenReturn(app); when(userServiceMock.getUser(USER_ID)).thenReturn(user); + when(modelMapper.toResponseDto(model, MODEL_API_KEY)).thenReturn(modelDto); val result = modelService.getModels(APPLICATION_GUID, USER_ID); From db8dda948a4bf0dde01d096182fc057832c0943a Mon Sep 17 00:00:00 2001 From: Volodymyr Bushko Date: Tue, 21 Jun 2022 13:30:53 +0300 Subject: [PATCH 395/837] EFRS-1249: Fixed tests --- .../com/exadel/frs/core/trainservice/dao/SubjectDaoTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/api/src/test/java/com/exadel/frs/core/trainservice/dao/SubjectDaoTest.java b/java/api/src/test/java/com/exadel/frs/core/trainservice/dao/SubjectDaoTest.java index fea7f63627..3f72c225f1 100644 --- a/java/api/src/test/java/com/exadel/frs/core/trainservice/dao/SubjectDaoTest.java +++ b/java/api/src/test/java/com/exadel/frs/core/trainservice/dao/SubjectDaoTest.java @@ -104,8 +104,8 @@ void testRemoveAllSubjectEmbeddings() { assertThat(embeddingRepository.findBySubjectId(subject.getId())).isEmpty(); // no images assertThat(imgRepository.getImgByEmbeddingId(subject.getApiKey(), embedding.getId())).isEmpty(); - // subject still exists - assertThat(subjectRepository.findById(subject.getId())).isPresent(); + // subject doesn't exist + assertThat(subjectRepository.findById(subject.getId())).isEmpty(); } @Test From 5993e04e6cfc697603c2769933824e6e87398b9f Mon Sep 17 00:00:00 2001 From: smchedlidze826 Date: Tue, 21 Jun 2022 14:38:07 +0400 Subject: [PATCH 396/837] Added restriction on uploading files with the size exceeding the max body and image sizes --- dev/docker-compose.yml | 6 ++-- ui/src/app/app.component.ts | 11 ++++++-- .../app/core/image-size/image-size.service.ts | 16 +++++++++++ ui/src/app/data/interfaces/size.interface.ts | 4 +++ .../collection-manager-right-facade.ts | 4 +++ ...nager-subject-right.container.component.ts | 11 ++++++++ .../login-form/login-form.component.ts | 1 - ui/src/app/store/app-store.module.ts | 2 ++ ui/src/app/store/image-size/actions.ts | 6 ++++ ui/src/app/store/image-size/effects.ts | 26 ++++++++++++++++++ .../app/store/image-size/image-size.module.ts | 10 +++++++ ui/src/app/store/image-size/reducers.ts | 18 ++++++++++++ ui/src/app/store/image-size/selectors.ts | 5 ++++ ui/src/app/store/manage-collectiom/effects.ts | 11 ++++++-- ui/src/assets/img/icons/@1xcopy-icon.png | Bin 557 -> 0 bytes ui/src/assets/img/icons/@2xcopy-icon.png | Bin 859 -> 0 bytes ui/src/assets/img/icons/dashboard-icon@1x.png | Bin 879 -> 0 bytes ui/src/assets/img/icons/dashboard-icon@2x.png | Bin 1640 -> 0 bytes ui/src/assets/img/icons/edit-icon@1x.png | Bin 493 -> 0 bytes ui/src/assets/img/icons/edit-icon@2x.png | Bin 862 -> 0 bytes ui/src/assets/img/icons/face-icon@1x.png | Bin 773 -> 0 bytes ui/src/assets/img/icons/face-icon@2x.png | Bin 1495 -> 0 bytes ui/src/assets/img/icons/image-icon@1x.png | Bin 2807 -> 0 bytes ui/src/assets/img/icons/image-icon@2x.png | Bin 5649 -> 0 bytes ui/src/assets/img/icons/play-icon@1x.png | Bin 888 -> 0 bytes ui/src/assets/img/icons/play-icon@2x.png | Bin 1846 -> 0 bytes ui/src/assets/img/icons/search-icon@1x.png | Bin 841 -> 0 bytes ui/src/assets/img/icons/search-icon@2x.png | Bin 1670 -> 0 bytes ui/src/assets/img/icons/trash-icon@1x.png | Bin 614 -> 0 bytes ui/src/assets/img/icons/trash-icon@2x.png | Bin 1112 -> 0 bytes ui/src/assets/img/icons/user-icon.png | Bin 1006 -> 0 bytes ui/src/assets/img/icons/user-icon@2x.png | Bin 2092 -> 0 bytes 32 files changed, 121 insertions(+), 10 deletions(-) create mode 100644 ui/src/app/core/image-size/image-size.service.ts create mode 100644 ui/src/app/data/interfaces/size.interface.ts create mode 100644 ui/src/app/store/image-size/actions.ts create mode 100644 ui/src/app/store/image-size/effects.ts create mode 100644 ui/src/app/store/image-size/image-size.module.ts create mode 100644 ui/src/app/store/image-size/reducers.ts create mode 100644 ui/src/app/store/image-size/selectors.ts delete mode 100644 ui/src/assets/img/icons/@1xcopy-icon.png delete mode 100644 ui/src/assets/img/icons/@2xcopy-icon.png delete mode 100644 ui/src/assets/img/icons/dashboard-icon@1x.png delete mode 100644 ui/src/assets/img/icons/dashboard-icon@2x.png delete mode 100644 ui/src/assets/img/icons/edit-icon@1x.png delete mode 100644 ui/src/assets/img/icons/edit-icon@2x.png delete mode 100644 ui/src/assets/img/icons/face-icon@1x.png delete mode 100644 ui/src/assets/img/icons/face-icon@2x.png delete mode 100644 ui/src/assets/img/icons/image-icon@1x.png delete mode 100644 ui/src/assets/img/icons/image-icon@2x.png delete mode 100644 ui/src/assets/img/icons/play-icon@1x.png delete mode 100644 ui/src/assets/img/icons/play-icon@2x.png delete mode 100644 ui/src/assets/img/icons/search-icon@1x.png delete mode 100644 ui/src/assets/img/icons/search-icon@2x.png delete mode 100644 ui/src/assets/img/icons/trash-icon@1x.png delete mode 100644 ui/src/assets/img/icons/trash-icon@2x.png delete mode 100644 ui/src/assets/img/icons/user-icon.png delete mode 100644 ui/src/assets/img/icons/user-icon@2x.png diff --git a/dev/docker-compose.yml b/dev/docker-compose.yml index d7d03b4ff0..7d9ea977a8 100644 --- a/dev/docker-compose.yml +++ b/dev/docker-compose.yml @@ -87,12 +87,12 @@ services: - CLIENT_MAX_BODY_SIZE=${max_request_size} compreface-core: - image: ${registry}compreface-core:${CORE_VERSION} + image: pospielov/compreface-core:1.0.0-mobilenet-2d106 restart: always container_name: "compreface-core" ports: - "3300:3000" - build: - context: ../embedding-calculator + # build: + # context: ../embedding-calculator environment: - ML_PORT=3000 diff --git a/ui/src/app/app.component.ts b/ui/src/app/app.component.ts index 265d7bc20e..a3d9b2fba9 100644 --- a/ui/src/app/app.component.ts +++ b/ui/src/app/app.component.ts @@ -13,27 +13,32 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; import { TranslateService } from '@ngx-translate/core'; import { AuthService } from './core/auth/auth.service'; import { AppState } from './store'; import { CustomIconsService } from './core/custom-icons/custom-icons.service'; +import { getMaxImageSize } from './store/image-size/actions'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], }) -export class AppComponent { +export class AppComponent implements OnInit { constructor( auth: AuthService, - store: Store, + private store: Store, private translate: TranslateService, private customIconsService: CustomIconsService ) { translate.setDefaultLang('en'); customIconsService.registerIcons(); } + + ngOnInit(): void { + this.store.dispatch(getMaxImageSize()); + } } diff --git a/ui/src/app/core/image-size/image-size.service.ts b/ui/src/app/core/image-size/image-size.service.ts new file mode 100644 index 0000000000..48eb888a03 --- /dev/null +++ b/ui/src/app/core/image-size/image-size.service.ts @@ -0,0 +1,16 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { MaxImageSize } from 'src/app/data/interfaces/size.interface'; +import { environment } from 'src/environments/environment'; + +@Injectable({ + providedIn: 'root', +}) +export class ImageSizeService { + constructor(private http: HttpClient) {} + + fetchMaxSize(): Observable { + return this.http.get(`${environment.userApiUrl}config`); + } +} diff --git a/ui/src/app/data/interfaces/size.interface.ts b/ui/src/app/data/interfaces/size.interface.ts new file mode 100644 index 0000000000..4124fbd869 --- /dev/null +++ b/ui/src/app/data/interfaces/size.interface.ts @@ -0,0 +1,4 @@ +export interface MaxImageSize { + clientMaxFileSize: number; + clientMaxBodySize: number; +} diff --git a/ui/src/app/features/collection-manager-subject-right/collection-manager-right-facade.ts b/ui/src/app/features/collection-manager-subject-right/collection-manager-right-facade.ts index 89e8a3c4fa..b73dbb40bf 100644 --- a/ui/src/app/features/collection-manager-subject-right/collection-manager-right-facade.ts +++ b/ui/src/app/features/collection-manager-subject-right/collection-manager-right-facade.ts @@ -42,6 +42,8 @@ import { import { CollectionItem } from 'src/app/data/interfaces/collection'; import { SubjectModeEnum } from 'src/app/data/enums/subject-mode.enum'; import { filter, map } from 'rxjs/operators'; +import { selectMaxFileSize } from 'src/app/store/image-size/selectors'; +import { MaxImageSize } from 'src/app/data/interfaces/size.interface'; @Injectable() export class CollectionRightFacade { @@ -54,8 +56,10 @@ export class CollectionRightFacade { isCollectionPending$: Observable; subjectMode$: Observable; currentModelName$: Observable; + maxBodySize$: Observable; constructor(private store: Store) { + this.maxBodySize$ = this.store.select(selectMaxFileSize); this.defaultSubject$ = this.store.select(selectCollectionSubject); this.subjects$ = this.store.select(selectCollectionSubjects); this.subject$ = this.store.select(selectCollectionSubject); diff --git a/ui/src/app/features/collection-manager-subject-right/collection-manager-subject-right.container.component.ts b/ui/src/app/features/collection-manager-subject-right/collection-manager-subject-right.container.component.ts index 37038ad4d1..b855086cdf 100644 --- a/ui/src/app/features/collection-manager-subject-right/collection-manager-subject-right.container.component.ts +++ b/ui/src/app/features/collection-manager-subject-right/collection-manager-subject-right.container.component.ts @@ -21,6 +21,7 @@ import { CircleLoadingProgressEnum } from 'src/app/data/enums/circle-loading-pro import { SubjectModeEnum } from 'src/app/data/enums/subject-mode.enum'; import { CollectionItem } from 'src/app/data/interfaces/collection'; import { tap } from 'rxjs/operators'; +import { MaxImageSize } from 'src/app/data/interfaces/size.interface'; @Component({ selector: 'app-application-right-container', @@ -51,6 +52,8 @@ export class CollectionManagerSubjectRightContainerComponent implements OnInit, collectionItems$: Observable; mode$: Observable; apiKeyInitSubscription: Subscription; + maxFIleSizeSubs: Subscription; + maxFIleSize: number; @Output() setDefaultMode = new EventEmitter(); private apiKey: string; @@ -68,6 +71,9 @@ export class CollectionManagerSubjectRightContainerComponent implements OnInit, this.defaultSubject$ = this.collectionRightFacade.defaultSubject$.pipe( tap(subject => this.collectionRightFacade.loadSubjectMedia(subject)) ); + this.maxFIleSizeSubs = this.collectionRightFacade.maxBodySize$ + .pipe(tap((size: MaxImageSize) => (this.maxFIleSize = size.clientMaxBodySize))) + .subscribe(); } initApiKey(apiKey: string): void { @@ -84,6 +90,10 @@ export class CollectionManagerSubjectRightContainerComponent implements OnInit, readFiles(fileList: File[]): void { this.setDefaultMode.emit(); + const fileBodySize = fileList.map(item => item.size).reduce((previousValue, currentValue) => previousValue + currentValue); + + if (fileBodySize > this.maxFIleSize) return; + this.collectionRightFacade.addImageFilesToCollection(fileList); } @@ -107,5 +117,6 @@ export class CollectionManagerSubjectRightContainerComponent implements OnInit, ngOnDestroy(): void { this.collectionRightFacade.resetSubjectExamples(); + this.maxFIleSizeSubs.unsubscribe(); } } diff --git a/ui/src/app/features/login-form/login-form.component.ts b/ui/src/app/features/login-form/login-form.component.ts index 088bf66167..d38668499a 100644 --- a/ui/src/app/features/login-form/login-form.component.ts +++ b/ui/src/app/features/login-form/login-form.component.ts @@ -47,7 +47,6 @@ export class LoginFormComponent implements OnInit, OnDestroy { email: new FormControl(null, [Validators.required, Validators.pattern(EMAIL_REGEXP_PATTERN)]), password: new FormControl(null, [Validators.required]), }); - console.log(this.form.controls.password); } ngOnDestroy() { diff --git a/ui/src/app/store/app-store.module.ts b/ui/src/app/store/app-store.module.ts index 66ab3d181d..a69b8ea2ed 100644 --- a/ui/src/app/store/app-store.module.ts +++ b/ui/src/app/store/app-store.module.ts @@ -33,6 +33,7 @@ import { AppSerializer } from './router/reducer'; import { UserStoreModule } from './user/user.module'; import { UserInfoStoreModule } from './userInfo/user-info.module'; import { CollectionStoreModule } from './manage-collectiom/collection.module'; +import { ImageSizeStoreModule } from './image-size/image-size.module'; @NgModule({ declarations: [], @@ -49,6 +50,7 @@ import { CollectionStoreModule } from './manage-collectiom/collection.module'; DemoStoreModule, AppUserStoreModule, CollectionStoreModule, + ImageSizeStoreModule, StoreRouterConnectingModule.forRoot({ serializer: DefaultRouterStateSerializer, stateKey: 'router', diff --git a/ui/src/app/store/image-size/actions.ts b/ui/src/app/store/image-size/actions.ts new file mode 100644 index 0000000000..a81d1e7473 --- /dev/null +++ b/ui/src/app/store/image-size/actions.ts @@ -0,0 +1,6 @@ +import { createAction, props } from '@ngrx/store'; +import { MaxImageSize } from 'src/app/data/interfaces/size.interface'; + +export const getMaxImageSize = createAction('[Application] Get max image & files size'); +export const getMaxImageSizeSuccess = createAction('[Application] Get max image size success', props()); +export const getMaxImageSizeFail = createAction('[Application] Get max image size fail', props<{ error: any }>()); diff --git a/ui/src/app/store/image-size/effects.ts b/ui/src/app/store/image-size/effects.ts new file mode 100644 index 0000000000..6f83c637f2 --- /dev/null +++ b/ui/src/app/store/image-size/effects.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@angular/core'; +import { ofType } from '@ngrx/effects'; +import { Actions, Effect } from '@ngrx/effects'; +import { Store } from '@ngrx/store'; +import { of } from 'rxjs'; +import { catchError, filter, switchMap, tap } from 'rxjs/operators'; +import { ImageSizeService } from 'src/app/core/image-size/image-size.service'; +import { MaxImageSize } from 'src/app/data/interfaces/size.interface'; +import { getMaxImageSize, getMaxImageSizeFail, getMaxImageSizeSuccess } from './actions'; + +@Injectable() +export class MaxImageSizeEffect { + constructor(private maxSizeService: ImageSizeService, private actions: Actions, private store: Store) {} + + @Effect({ dispatch: false }) + getMaxSize$ = this.actions.pipe( + ofType(getMaxImageSize), + switchMap(() => + this.maxSizeService.fetchMaxSize().pipe( + filter(data => !!data), + tap((data: MaxImageSize) => this.store.dispatch(getMaxImageSizeSuccess(data))), + catchError(error => of(getMaxImageSizeFail(error))) + ) + ) + ); +} diff --git a/ui/src/app/store/image-size/image-size.module.ts b/ui/src/app/store/image-size/image-size.module.ts new file mode 100644 index 0000000000..c1676bd0df --- /dev/null +++ b/ui/src/app/store/image-size/image-size.module.ts @@ -0,0 +1,10 @@ +import { NgModule } from '@angular/core'; +import { EffectsModule } from '@ngrx/effects'; +import { StoreModule } from '@ngrx/store'; +import { MaxImageSizeEffect } from './effects'; +import { maxSizeReducer } from './reducers'; + +@NgModule({ + imports: [EffectsModule.forFeature([MaxImageSizeEffect]), StoreModule.forFeature('maxFileSize', maxSizeReducer)], +}) +export class ImageSizeStoreModule {} diff --git a/ui/src/app/store/image-size/reducers.ts b/ui/src/app/store/image-size/reducers.ts new file mode 100644 index 0000000000..19bc2bcba1 --- /dev/null +++ b/ui/src/app/store/image-size/reducers.ts @@ -0,0 +1,18 @@ +import { Action } from '@ngrx/store'; +import { ActionReducer, createReducer, on } from '@ngrx/store'; +import { MaxImageSize } from 'src/app/data/interfaces/size.interface'; +import { getMaxImageSize, getMaxImageSizeFail, getMaxImageSizeSuccess } from './actions'; + +const initialState: MaxImageSize = { + clientMaxFileSize: null, + clientMaxBodySize: null, +}; + +const reducer: ActionReducer = createReducer( + initialState, + on(getMaxImageSize, () => ({ ...initialState })), + on(getMaxImageSizeSuccess, (state, action) => ({ ...state, ...action })), + on(getMaxImageSizeFail, state => ({ ...state })) +); + +export const maxSizeReducer = (maxSizeState: MaxImageSize, action: Action) => reducer(maxSizeState, action); diff --git a/ui/src/app/store/image-size/selectors.ts b/ui/src/app/store/image-size/selectors.ts new file mode 100644 index 0000000000..214c478950 --- /dev/null +++ b/ui/src/app/store/image-size/selectors.ts @@ -0,0 +1,5 @@ +import { createFeatureSelector, createSelector } from '@ngrx/store'; +import { MaxImageSize } from 'src/app/data/interfaces/size.interface'; + +export const selectMaxSizeState = createFeatureSelector('maxFileSize'); +export const selectMaxFileSize = createSelector(selectMaxSizeState, (state: MaxImageSize) => state); diff --git a/ui/src/app/store/manage-collectiom/effects.ts b/ui/src/app/store/manage-collectiom/effects.ts index e0de29059b..583fc9d594 100644 --- a/ui/src/app/store/manage-collectiom/effects.ts +++ b/ui/src/app/store/manage-collectiom/effects.ts @@ -62,6 +62,7 @@ import { CircleLoadingProgressEnum } from 'src/app/data/enums/circle-loading-pro import { selectCurrentApiKey } from '../model/selectors'; import { SubjectModeEnum } from 'src/app/data/enums/subject-mode.enum'; import { CollectionItem } from 'src/app/data/interfaces/collection'; +import { selectMaxFileSize } from '../image-size/selectors'; @Injectable() export class CollectionEffects { @@ -234,10 +235,14 @@ export class CollectionEffects { @Effect() uploadImage$ = this.actions.pipe( ofType(uploadImage), - withLatestFrom(this.store.select(selectCurrentApiKey), this.store.select(selectCollectionSubject)), - switchMap(([{ item, continueUpload }, apiKey, subject]) => { + withLatestFrom( + this.store.select(selectCurrentApiKey), + this.store.select(selectCollectionSubject), + this.store.select(selectMaxFileSize) + ), + switchMap(([{ item, continueUpload }, apiKey, subject, maxFileSize]) => { const { file } = item; - const sizeInBytes = 5242880; + const sizeInBytes = maxFileSize.clientMaxFileSize; const ext = /(\.jpg|\.jpeg|\.webp|\.png)$/i; const type = /(\/jpg|\/jpeg|\/webp|\/png)$/i; diff --git a/ui/src/assets/img/icons/@1xcopy-icon.png b/ui/src/assets/img/icons/@1xcopy-icon.png deleted file mode 100644 index bacb863e7c6703ffcc0b8050aee5fcfe406bf25a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 557 zcmeAS@N?(olHy`uVBq!ia0vp^{6H+k!3HE-=Cy!0jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(wmXG%|{O| zEVOIX)e@bx~-_Q1~LUg zP8q_w#&N4wRUK2ZXw-YxV9N3}aSijz)uswBo^aguO=tVqCn#VT38 zBOF(r>25nElbD!lx#C64sh&Wsxqi!=1X)yda;(=&+1@X(@KIIa@}8Nvqha#@&-qKw z7|eUzUgP8ac=9a6=9d=d7$Qh#I?;46Ho5hoG1~v d{r2B!TjPxb_gme`nfV74Ri3VXF6*2Ung9_K*VF(2 diff --git a/ui/src/assets/img/icons/@2xcopy-icon.png b/ui/src/assets/img/icons/@2xcopy-icon.png deleted file mode 100644 index 9a34079b3b65cc757e363c710cd405e0f6284593..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 859 zcmV-h1ElPx#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR919-spN1ONa40RR91Bme*a03pzqA^-pa#Ysd#R9Fe^m(ObxQ4q(oN&FFt z76OX@Kv2-LqSaOeQEy_s54!MpGv7Be-#7Dq?5>lEd8*awQP*`JV=_;ljWKntu@Bj7_ExLa zdS~Ij8W$`B*&^-YZ%8GWW3`im!C<3StDW|lEFqTIahzk8eHwMP-|w$Sk;f8SEDwvt z;=#DaL{}}JAvsHS#=*@@eGT#}tod6VPm{6wB~Zo{G2F3zo%q*~8rcaj8_=SFhK1EvJBLPE-eMa>dP_xcN?}^V7p(?_bj|REBVP zT&U6^3<6~jbuhq*juXT;i1l6KDLGfi5^|egTEM7X>Acas>bY002ovPDHLkV1i$3eQf{$ diff --git a/ui/src/assets/img/icons/dashboard-icon@1x.png b/ui/src/assets/img/icons/dashboard-icon@1x.png deleted file mode 100644 index 8640f0a7983918aa1e6e87ccf378b3d1485912ab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 879 zcmeAS@N?(olHy`uVBq!ia0vp^vOp})!3HFyqmQuwDaPU;cPEB*=VV?2IWDOYo@u_m z3|c@o2Loe!CIeUrkS7Ji3=GT*7#Wy>G$Rl)EMS7m$}V7rv*kewm+W>mWME*r>FMGa z;=%iLnsASSg*EI*=x(){*GZmzt0TzKMl>_r~T#s`es392B+7peuk*D zb^X6IqMV-kLvQe{&bbzY05Y$!o2}yR_f`9dzGjZl9t$`DE_Z ztgj+wpXOxU`P+Uo=bPVd%WX@=ADA+vGx!Dh6>iNdp8IWE^R`&cW1n>Vie;aqtP6-! zdbPinr7XF6?&Mmlsb0A&L$titTywu~Trzh?m{kAq@}xpxS!Z_DqDA|*=7r5>D|@tB z&w17|(^O^AW1?(JldMG5O?{NX)Z$#<2@ihGTQFy{-wuZeO^uCwe{yk8vS;3}4( zp5nJQth&En>aDs_fQCrVe!rF7GZw_W+jRUfr-2yTq?AvJ>#tY)^i(Rm+j;k$ZHVxq zE9yJLrl!jDlh!`bi^%urRcF3KLpKzYx+~V?Z_P1J*A6-lA z8Rx2eSj}c4*3J8&OUEc7YG>?&W%9jizOJ_ozL;@^z2rz*$HTs2Bj=|ERny+ij&8PM z6rTUGq-sNq-syuj>JG~-)b^)b_;_^Yg0-^s44Zt^Q#J^)ZUQBCPgg&ebxsLQ0Nh|{ AR{#J2 diff --git a/ui/src/assets/img/icons/dashboard-icon@2x.png b/ui/src/assets/img/icons/dashboard-icon@2x.png deleted file mode 100644 index 386c72b74fee36d837d77d32e2386552bf67c284..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1640 zcmV-u2ABDXP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91I-mmp1ONa40RR91J^%m!09GpvGynhv(n&-?RA>e5nq6pIMHI)gn@s|V zNolE7Yl|N>Ej~!vsC}?6jUc{+JVXM8e3H;Wun0x)sbKOVieN=Ffk;R;9~jU+sHmVK zRVbo0HA%r5Lurz>P#dIa$cOpb^>=P^mbv*b*Ui23-nav^=ggTiGv`0&%-p%NH!Urd zw$}`VOgQ!R^*d%}W_pnohQs0d&d$zH&H5no37`UUdV2b6z#Lrxv=|D7PIPy7p9?ZT zwLM*!bwG+ZOfD@bDEMt~aPW!_HS z4vE#Q$J^v`1HliJo249&$1@fd79KO|0>`qshEHJidw?l|zp4lT87nC%c?J3!`HlSi z{Li&6h%~Y5it$xdRY#YWmVOc*aOc@^i-g|}NkaF3ky1dno15j);c04UXxKS5HB}so z#SSu1rVPfW7i04qgZ++uU`S+MCr^R0xgD9Docsz9512pzZ57$@7Ah+%Pjz*5wVL%_ z@Sd(w0KJdVHjpL=c|McR0(Orn_k#BfXk%kzCLpX67oNL?@nz6r$%JkR^cw+$PByvF z3vgzio`vq%*w}6cvib+eA3Z%ipO^zzR8%}g`2~?tzt^nug7?g$i;Ihi!wBA6bA~o$ zcsQZ!1xV@BGoW4>oNT`-pvlghU)!ZT>Q~PsYgfvn$irxe zck`UJp2N^P?d|QO<}=voIKB+s^78Tu&cA* zV^IL!uSS|w%z80kth8tqSJ?X0$vJ`O0ZtgNiEwzjrW)!{|p3F?NWjGlmY zoE!sXkeB#x=0kekY4(l>tRl^JIYR#*?mG5o$2 zKNB8saYASZ z$HRPz^PudAyh(^B_wXk2xYXm>N&Z~2PENde+(jhy0%#v?9)tbQ2a6d)w8>6f~G6>=V)<_tWH z>>&97`q#A1q)bkXciPK_O!_F3GGq3rocCa5ElRszZGue-jzX7@AAh9dk2mRS9-T9$ z4ihAw?yPqlaxq}aOy2qyPz8MV_b{`6 zQpyZA!Z(0Xtt%}pJvT8iag(yV`AS&ckc{3{=of_YNxb+Ad?F1D3=Gldmm)xKihPSK zZAK7%&qTb*U;A%My{5jtzPZA}!Vb#wnBz&evyt-vw>0000-G2co#^NA%Cx&(BWL^R}E~ycoX}-P; zT0k}j17mw80}DtA5K93u0|WB{Mh0de%?J`(zyy~ST)>QAgB12no(xpM81Cuf7-Au~ zbkbh0BMu_1b5%mQ5(S&qKTy8Gar=bQqzKpPDa~{B6P`Lpbd}bHEZU%^uwt&#zNX%U z+|=oMozGVYKi)K3eosNMapJk@#qWC}UP;b-&imQMUd(BY)2|how;FG?Kiu_hhensC z$@&u_mU^d)d|z@YGBh##zR!DD!Zom@<-2grZJ}5Np$4&eic#)!U+vmAKlP^UwdOE& z(*sL0XE`%``|wzn;a*)sd#?az!wW8k>)YPSe3g2n{-kS>MxI35c5cPehSW$t#zL9Z zo|AqwN|1C0^64;6vTSAO8 znOCfObv1fH#Ifm<^%(AdKffenTi1UkjwcEJhHIqf)R*?iD5W0|nFVdQ&MBb@0A8Q4(EtDd diff --git a/ui/src/assets/img/icons/edit-icon@2x.png b/ui/src/assets/img/icons/edit-icon@2x.png deleted file mode 100644 index 49b1d8e806dbd8c37a25a506478d18dc16cf9138..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 862 zcmeAS@N?(olHy`uVBq!ia0vp^N+8U^1|+TAxeoy;#^NA%Cx&(BWL^R}E~ycoX}-P; zT0k}j17mw80}DtA5K93u0|WB{Mh0de%?J`(zyz07TEL88gA_7VEervYCp=voLn1hj zPWR3h4iq_VJ^3VyP_(?LxNcsEhDJ$v!1ie-i=?ktPI~`fk-S6UVtIw`3D3{9K3KN4 zgLQ4~i&L?#8+Las2ncR%vHA1Az4~#9)mr0|cb*)0GOzgkS=;@V&+pANzIB8{Wv*ZR ztFKl34_`5^i+pjvB(`3Xt99N~ucfw18GiybPo@+xzTSVp^zoO)nYZ%4++BY)>$R7o z(abYz^S1A9;_wk<>f4>M@6f)UYa+Gv3Flv$gmg4SY;~+@ns0gMuj!H$wgBxf-5)i+ zq^b%Qb{>hoZ1P^T`)J#q%ri6HwtQn0Vmp!dK(Nq><9Pb*x7((2aR#jnsdKo$oOi}e z2gf-tC-^u-M{S)<7!LaG(PT_{&Fvz}l4#SsqU8h6BB?{dCo~lPaT{rM1Ut619(crg zMehB}Bi%dlu7zkFH9o&NfA4_>?|)b>n(>~yX~A)aV~mA7DKP@>4#zkzvP+t?IF`)g z+rpgh6Y`||?;Jj>QekFRZ(inke(a}qhn`WjxW9y`HF@G;#iN*}$m+ zU-I>P_RasIwcYuFeY>NZ6U#%6jyJEj>txLlXiHRS@^Zf~IHk^I!smIl45rVntWbCn z-t5eIf$5C6&9$Xx*MI-a^e!M&<;X8#wrr0d&#v$4{I*MG!&a|vExEPcR~bKb7@T9g m_2Pd|ucUkLNq(iJpTuumC_NnNr283^wmn__T-G@yGywn;32e#$ diff --git a/ui/src/assets/img/icons/face-icon@1x.png b/ui/src/assets/img/icons/face-icon@1x.png deleted file mode 100644 index a7f86a247d762968e5c84ff78ca20b4d2bb7eef7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 773 zcmeAS@N?(olHy`uVBq!ia0vp^q9Dw{1|(OCFP#RY7>k44ofy`glX(f`xTHpSruq6Z zXaU(A42G$TlC0TWzSbOAGh4N}M@1g6j zOG~l`6qP@Eejvi->d~~uAVr7mRU0GzC>$#cUm8@Y|MLm!n;6N1Vc!&Zj(Vz1UcRu{ z&-BRg$T{VoI?qbQmwGuZz8JCY`s<@1TBXab>$h&uH-0zi)$3!HO;tAY>T~v}Ii5`` zjyI36NDGdc)7*VXc<;W2n@UVp2+vHJ;NKu??YsQ4;<4E+|13g}@8k7-`uS(G-#@#6 zT^2dBp0RoQOa5hKJ@-w`ZSlp^FW!Gx|8m#9NoUiRSN&D04rTu3`7I|Zj`Ncy-&48p zr`zto{~xjXYTCy82lJfHFJ%35{Na7Y>aV4z6#pLjJz4zQ)Vw@~B9Ypw20hz9{0sgU)%bF_J{i!YGk_>*mJcy{d8KGu;ORm0wI4SIgx>won$ zSpK}dqH*!7n5~jY<{D)ut!i1CFE&~C1s1-ss7##HfACg3_mqiE7lc=ueEFX0T-xHI zq`2K#?sT#a(;drNt+VA$F?weIQ^Vz^uKxL-@q3u%@1JW`{(zF8r>mdKI;Vst0F3}w AEC2ui diff --git a/ui/src/assets/img/icons/face-icon@2x.png b/ui/src/assets/img/icons/face-icon@2x.png deleted file mode 100644 index 3541275ce8147875a6b24c1d6918b309244b9ac2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1495 zcmV;|1t|K7P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91Dxd=Z1ONa40RR91DgXcg0Mr_#3jhEGK}keGR9Fe^m|JLERTPG2GMBU& zXVjQRX&+2YA}B~=!DmJAMGB!_f;KluAVHD~lcND-ebn&$QdiZbF zKE;rUc|Ps;qQ(DiWS^;L0Yw=F?DJ&tSJPo~a&q3t#o{wFGlOJx)%XX)o6Mb zqF{%IWA@$K4KePykBYlEGeCD+bcw=>Jq6$CUC3VndJvZ<_U`2DrDweQ077TEzd*Y5qiMmdcI z%~9EEzAY^+J*`aoe7=#b=BJ4#JEp)h)Z@WmaCmTV@RDi!pO+#71r2?7cXw4d9PVVk zorlhHJ^QK<&|O_!w?-n7$C);3Oq-KqV`Jx>+B*iy)$0&WKY`5Lx{iRyxSkz%5)AuS z>GuM38M_288iXIl{v#*$j(~DGIDq4?Z5--r;3S!S$fhl$!u|yIT^U7M0BD3udL8%Q zUvk@W>yEZ3g4_jNQYMT81wqyg{1DlC$>?)AAho5nNsrAsw1D_~+E_*kBp4OIOFd_{t2CiKY;uw*;xYIMKZj`(fU2QPPG9thU{bG zKg@o*fvowiX%_?Z7w|2MBcN4PRi6_q^&tpv^+;XRNku0X0q;*8Rwj0rUu@)qVx? z0bcv88P*6}&>QT%Cx(WGE*RTF`QHL+4WFwe8FPx#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91N}vM(1ONa40RR91JOBUy07zeuCjbBnVM#w zYDS!*GKU)s>NXY?0>%wC!J-lnXz9;=p1b?Jp8MYS{^ei<&s9V2|B9@vth|bG-@>5Qfk(Lx4&G2!R@Q9KiWMtb zM@L8NjEcuPJ3E`TJQ;JGXNx}!>F`&3Vce`G31S5W1&?ca66fn|V=vAGsM!Yd?Ut67 zD@HjfBrA#c#EBCRpfzXx-YU1YzP|p3aB|74!-fEl7NA^tWV#Iw4(_D0&zc=dT%_Df z{)x%Q&RJKkT-izP+KV*%x6`fxMNC@CpucPw&=0+O>(;F^ySlpOvX`4lc`blhF%tQ? zOu?l_o0O5R0mTR-`}BZ3Gyfi_511k9#zhL+Kef2EK*=RNalKm5^%WRh_+BKjG3k$me=S>|bfaH)`@T~Iy)+;$H z+y?5cLqp^buzh$=+oUpg4JbTjlz}fH**}w}0oZMTFwF!4xieFzPOTsh*dd!TZqlv+ zC4<5{-ecf*K=y_kBr=ECXO*_KwVetl=VqO&K*_)aEY6~D&&bGF1|U7K-X{%0pO!Ld zDKe!U9UUh{exf{?sW3#QA^kq>PeCSd%n`(MVlZVroJM?u*e2VFe8B%}Sy|Z|q4sD_ zIzsVyVIK5b)vkM#)pMK#N4JfvlJAa;jJ(Q#o+AB{lZij<-o5(_bv1FS4{9zRC9qA1 zFZ3Ym92jfXtjXrUzZGCA%{Oj{%IQQqE9w0>MJ?0#l2I2knN}VpcaMPdynqyG#KGQAG+LJmOJ!CQ`&K||9`*zyKJ z-?~IG>Y^rNo_g}h0SN8%^f&`>{CxuaYe}ym_h8f(*#rfovuDq~0Q*};)fgW8J%D+_ z;Q2^++X3r-3Kz8w8?GnN|1qAI786k2-Q5{{yvprzjT$342Ff)12WeQ!{wE8$9eCan zWkj3vgOR+xzCO9?zt?CZGhiiN)N1eoNWgj-#y3eH8WjcT*~>H(5Xb$D8z=#ZC0IZT z2&lZ_7VZSEVG7&|V7HKt7}iKE+&&(#So-rXU%tGDcK4_rbV?wqrw_Tw4*MW;h)JJAt=5|9Yoa_AOtZ4?P4?JOKvywM7k zBYs`QHbVv?dokZ5ATgM_ctBEJ=(v=hpI?s7?aGrJ{Z+)m9stWX`F1)iP=1iMqs%^4 z);mo%yK*fHSbVU`@*Xe^(c2}SC7#3p1f;KN8vzOWVW!R&<;9!x6Q&^mtCWSu)P)@j zNUz{o^7-R?=s#deh6KtF(hd~Zm-Y1+|5H^MZO+d4qYz-Va)Z2!e&n3W_XtR&T3$wn z@)DJ!cMP!PqV-WzC;gY=S^Gm><2wQ+bC#por)g~8D8>Ym8l+%hf*m&w3=I5rx$0Kg_P*3uM^fDlQ)v#g&Z>k|?r1bs{&pM19 zvN1hr*k+M-psWfSJQ~aDW+|`{21s*_suA42I@KVlJ=k#!&$^THTgubi5hwvkRs|J` zHxC68{j9+4WivZr9_;xK|KcHwjrEHfG0q7n*-W|u68!}1k|zpUn>TO%09#I(_Sk{y zbz{2W-c=J7Bn5{!YAtfYvjcTn1>?;_O*a9Fz_}w{{mE|6)C&t#Li!H{5`nui9YAsd zA;6>TC^|KW#0v)!fl_w->GCAcqe6f*%nMkaW*`N<%m9y~HUPyS=`f5?AQ6+66S%v> zG@X!DLBX82ZQht<#A3C8#J$^Il$)E$VF5|LrDD;U>Nema7b5zMh)0>Zx+M_m8>BZ%?aB%fG<%P-xqze}=$VFoSNVH#;&LA;EZUTqjnBxYhmppQ) z5A&a-0-PnF>T+SNB)?JW^r3zqX_uB0oztKFbR%J1VE1%x zhvee+JM?3D6CM0((V|6#zK-QKgvxZ<<0K5A2#WggruoF$!@gCH|H6K&q@+ZCOSym) z)5;g2!*r}Z#h1h~K-w?HTc7~en{(#OkpO%(=?kPR(xiKsdhg*y8~M-l<#Hvj065Nh zmPaqYBFW=IGK*EsQAQOcBj?8zF>ichwFmDR;p%-re?MW@^Ix+U^QZ|`c_9D*002ov JPDHLkV1g4)Hzxo9 diff --git a/ui/src/assets/img/icons/image-icon@2x.png b/ui/src/assets/img/icons/image-icon@2x.png deleted file mode 100644 index 8eee7e490fb1a6601b2b698e7c7fab024d9501f7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5649 zcmV+s7VhbZP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91l%N9u1ONa40RR91cmMzZ092EqSpWbPdr3q=RCodHoePu|#hJ&M0S6cy zVF1x!5>!CU10NfW#u%d@ppXpsIAjxiBEi)o8q6j}fk=FtM94v7&W?hDXmW5h3K}8` z4r=fbHqq#A*1(D;5wnm8Gst^@{QlSJ@lH=yRrkGpXYTavKBsSYSAF$WeP8|itE#W6 z>y~wjaLk-JvwZ#f^;O%pZL6%PsOY|X_wLHFva<3BEK<@48YnL>-xq;A7ScU?_B2wr z+ji~RwH=0AA9&z_tx*`4xrnDuo!Wil#*HJO8@F%YzOQoa>q?WxCr1Mqj5n$G$2)iK z{Mh{Y^IziG3UVu;F1X-=uYuTEsHcB&RFq1Rs|EnmOIXdz7c5xtdlQd9KwWs@g#!W7 ze}K&4ri5vD$!cH^!1`HbW#u)swYA&)T4)8R^UgbO2t;*&>0m!``kZPYO9RjL>(_70 z?Af!`*J?gm0E#B=h`oFF3QQksrbus64Hz_lrFyzcmo7E;-+%uOkED5oHhJ>ozJO^( z3MNlA>D8qHuri{tvGM2Gbj=oO!h{J6h&VqxGQ|BpSFb+=kCdc{Gyq7NjZD~LF#?=G zNBtquX8?8n`R5PAIxP>u;B^2r7rMHts;WQVefQm)Q$U5#S_dLov}jT1B}FBEe4LuE5DPHwBPMOUVq!*z^D_vp6T) zB*dK{eEhVGQQgNN{tLvN4GH!TJ{t5bGsLICC7=O}&Mk;}twq$=!N^hNSR8r3O<+>` zJ5n%N)L0@>3l}b&23B6Oh&%M6i!M629P9Ks6A=t{^yty!N2d5RymU06t`5!G4GxKq zRFD2x6A@s00dMHvP4Q`XX=$Kaw{FiM@dqZUnOzy!g-D|l6GXmjO5BmdHIiApc=4Bb zVb0>sI~rLI;q1@zZm#bUwx07vM!t_@>R0RS?8x%QRd$TaZu;DBpr;dqcoN=IfI1*3 zvMFqXC9cCo;k>QfzJ2@EfazNZpBV~s^~=$dup>E#5iyNX&R;oi>)yRP`m7b;O8qcm z0-f!zCU1X}hIm7b=FOC*Si^a84p174`GG&1SbX~3pU zo3122olR&`QsOL>oR)y)Y6}QD5&tGD4UfZV`7=M5lbGv)x|3bJN+mvV zfTDuO+A0s{+st|M=DlU3$&O>C#sBbpb9R{jo~H3Ee^{wLCk{|FQ?}I;+xqtHdr#EL z?%A{FUGQyQi0@Aip#H@lR;tg50~8f$^M=>?Kl=$&@ho-z0L!=1L<`t#d76?XGCXmB zqT+3p8@j0rsO@zDVP24N#fC>vD%ZpTs!$b&720d!4Tu{}aitcXI6zVH>rItN#UE;l zbA^*ulf!|BEziIN2@Xsgps1_&EcJCN>z=z=;sc{5PMlZ;Xucd6Ng!Z**A!Q3;fVtj zmAl$fiTm@V>!p_Xz$o^?x&ppNASQKXYoIb5fr$f@_AzqEJd;V?K@OVcXjT<`7Pr84 z%>ltTt~`TNB``E`fI9r}!&lKf*_NsHq8^uq0TtUCeFiZ73VP$JR^tA`G>y$FCJ;7e zkrD?e=GFEBs5{IeXZk#YXu@9M<3;9yTdx+-Y6SrI9hygdhxQ2@FXC=<#FtcbB3B#G z?9IWlTtQ9Rz)(&6999e540x7vFCUebLl0yN$l(C42luXH&EUawm(5pG8a@KG!fa$9&)+8 zj=s}Vxg<<-n#2N1mByY$Gq5DTh`R#eITyg*9yi}4@JlqHsQ7(Yk#A5@H^o$0+F)%O zV44&Yzr?XgG@w*l0u^hTW^j8p_kYf{21&b)&VCJG(zZ6~VbDP00p-aJsD4>pU3~<= z&^LPj?}fXr(Ep9T*wupu4LXu0^a&SzsmCYMZD6WYtraKk$BfKo@d2nde9-9bI6$K% zFWM}F+sx#$CYV>t>wef_hrKA@7SHJsLIWiQC{G_v09nPe&h&^;10@Nl81Mg3%DH<0rYwueF7+*SKmL=^^@UYq>9$!@3tn3aECdCjMaHSUmaD_{XQS z$-cRQ0Fg@5l~az@gE>bLrpat=FnZ1#90A)`04lKn|16qrPMY^4x-t=*3 z*h3XGl61qFd;Dqd-o3xu)E5k!K5n|w-F+o)P3FUI(xgcTG1;s>TNE&5XQ_>KuVn6V zB@6yEh$zaVWZVYrsePKbPwv>U;{`0uvH6w*9w*RLzM^$n`SL16`V>%w&}tUXOD?%& z08QLVfG{X4IImlH9YP=MIo4}^F)v01NSp$y07~^((q}pp%cSr54)wxfyjIhMK8>Z) zKJ!I$6x}EfA5jGxg`LV^L4Qj-gtze^uKLrzce#HRul3{1&%IGJ8Z;Dye$L@LckZks z+O{UkOT7m9YB_(4j2iVF&v^;=Lp%b*wL6yTmvq)AGZQe+KofUx^Qbh4anS%uBOuPhKSL z3MiD8*`b#T_yvmicvMM9^REDLwJI@c+CtC+?!oZYKcZS?0U(-FJO_*NpRRJyysrRM zUnb3aE?TVjx6s8x_G91AMVm)_%cjG}sNDd?+tdS-z8K}_wL>rI^+oS0D)ShhY;F3e zzt_e2C@{Hp&f5t<&cgaU8Mc6|a`*#)`XXnTDVT4tr}`7pZ&>~j>8uv8T>vFvd7FNP z^2bCJCXgIH2U3~K+Y3}!g(1jON8zjvM_DG$vjND`KtU{lSgLjW*UHI|IHzCG$4JVv zLVx>fVVP@GaapL#FTZ?$1eQWi$yFjly50hn=E)541qtTm_!NwtfBY>l$ua>9edm|c zaqqqNY8{uFzm*{vd>%{ng8X11Xk#O3Qwt?9H8eCV1yElKAweUGdztetXqR%;CabQa zH~q0^+6q*RtUiq;coumyk!w6CyZTJyaxFQDR$wN?#RI9=QhO zCo>+oIZ4{YLJ3UFKS`m^sREjX z+W^lnK=jw_(la95MWx)LW8lNe|Fcw7zks}(gD9uI4;)J~ z_f&IWOgH1lkKcnnCP`l|mXxy>s`c^wn0t`@p~gHU&~XfoWVJz?7Y@^hG}3Cd^vV#CNC6 zkJ3y}%uXNoeL*c0g+D-GqR?M(6&$6^EmltNZ zW-Jwb9^>x7hIB20)zJqevP^wiMED*&FwtbU>9i2~SU}$6D9(0Yky<_SQs^JGeu6C4 z4tA@AAp!~lDEuEi5pEd;KG9VM=t5g2KQDob^jb#i1eIpya|M-}O?C9i$2u)US$05E z)7D_}^dfyQ0vtg(HhW>FD|K@fmg>Q{z3Q<8N?_ujGXI zR01KgIKf1rWEu1!=+i9<*v1d@=g+ABW%bgQfF=GW3qn}blKSRN+mDD6y>|pW~CQxB1oE>$6 zgwqHrEYpc}TxD%+e{H60)y#dh*ubQM>0mUx0Xa|ORt{L}0o@Vus>{b_C3HmQ4Jd(W z%a$#AJDsB_-j?sj)y$dXqH>>z0xC6Y*}Sk!U!a`wn;noH9&n!l6Y?w%h%1^P%GbcB zS7%b7M?(~6a1acNKpFr3e!{#FTiB-ztvR+bkL2#$RoU9 z-*nOX<8uNO=Gq>uKUP<5}3%S=)vR(9syK|2a~50K=l*uFW_wS!c13QGX4Ae z-CC|~qR9>qMdT&W3P8Z5>4&4eFw?a?Et9`Y0u`3!T*6%*b$`;#vn&%g9j=)R6s9Q< zP%T6!W1D2Nk?Ox}F5_#Yhv{o}vl0>+8c=LKb08jYf$1nG)AI5eZPfvHmBou^2)!g1 zP@(XxoTHJa&f}GoYttmVzJJ^lLZ-8E4Qaq~u#Gkppado@*?^JLM?s~{prHDetmqDb ziVpR70AgGolU?X0FpVMI%IxqY-SeWXqRvi1ru8A@5}i)t;RKWiCXne|Mq12Z@-%=P zn!w%TJ)5qj047g%UYIxb+iyR4)&Jy$<$AROiVP0HGU@A-M1Y9_#c9a$D5v$iHGv6B zHaf|c$qyJ!)c`6vH2=#V7va~e5iAoUl~Wl|Jb^rwHU$Yx#GlEm{c|?D zB*xu$-+ddqcI|o^I#`jbd{#igw8F)>qksuHo^*i;nh!Y7q+jv8OYWq`&zm=I3)^px z22_vaBBKjXR-PRJOlsy3A{3ZVqDFHi3ns6!HC4lhA{xBZdSPL&IRmPrfGJEfhq6AP z&s5Tu$qyKPD8kEiDfN7_KhE}?6QGg^CY+xy*c4T&aSTkRQ{Pu1ziX-{qoP~@HJwa~ z($C~ox|+GLmPudXreN|k7ks6@=U~ClqY2y{Oz}}@R6tF~Wgp1|ew0<)+=T@b%Gts> zN^7v&reqyE7hbxD7}QL3Nf8B5i2@UfIYVAzmx9vxk2Ik9Kc4>gqmb5jVF8tbskQD( zHRMg3;7&lP1D>E}t^s*QrIyJC340vf1Z(Hq3MdlS6?+GqRfWUCbik3Nfj+<}`T{T7 zRhGs&G>|)>V64x*i}nzh0Lc=U2iyY_miDEh6(%7Cb6F@&MHJs=W#&U#a6&6e|tQVN*1HK%Zc}nQi06Mi=s4h+pMMNMl zsRK@tx;u(hk7suz9dP~QLVC2J2CNp!g~m24r#`|=!PJ%tH|Ld?g|a3#=NWHI6flwV z2rScY<1InTEvyEt7Ah>0Lehl=6V`My8WLkqTT#yUEQE^2-KMkJcx6C^%9sc$G|9H(r&jb zj?3#CvK)(9>dB1z8-SPHvQQf=8al`l7i&}$FwxYVo`NY>^+#c5+@*%4LEEf=0&&j$ zpQ4n+L>m=MtY>m=L~6<_4bNQzd_HkTHdaG-I`1jewVR#Cr zTs0j}I$EkL!NM>LxAp9eu(o{e+_~>VXtYu=(BEP`P17{e!Yoo$R4}nK$g7buNQq<9 z0Ciu6LAi$OY#URI(!-n|{u>bqP9lNx`!o_fNfUVwf2LelTU%Ri%OEIj(V|727cX8s znYdZxXB>ViEqOK2%neINb3vLcd>H?8*q=Ym97R28gV&+99I30_?AkIIIh0IoO9O zitAUR7gveGl?Hulja(nfA9^rGn!6Y*O;HZb3+%8wlj(HHQCO`A*FlE8pwZdlAl&-g zBGU2z9|7-BKR-*7G%5xSMAbK7`cq|PdttgZTPQD( z1#0slR$uDxURZifHQ>|0QkuDA^uK(*@J_~#W7F=x(@5}dw5whH3Df7&(*TA>vnt=C z1OAvvdLy8`1o~4Q{?}y^%tiyOV+nVr@zSQ(j;{gg{XOdaQ3h=f4;(n~dF^axlPVH8 za4j5w*SbGjO;?eDPM4RvId8n;&G)(7AXv-cV?-wTrf=`^M-;aed06=w={HiZ^~|7b r=DvQwfC2w-fv8nxX+WxhxHRxj5VnfEf}o~s00000NkvXXu0mjffBcS? diff --git a/ui/src/assets/img/icons/play-icon@1x.png b/ui/src/assets/img/icons/play-icon@1x.png deleted file mode 100644 index c19c7d6d83372000cd4810d23fc8f7c7210f3c23..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 888 zcmV-;1Bd*HP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR915}*SB1ONa40RR916951J0H?{Dk7R5%fRlwC+uVHn5Vv2Bhq zau=-+yjbdrte`-Qii)oK0v8BFb939x%E~UnE=mf8f`Z0`3d@VZ=5`Ym5hZ$MUF3xj zxG;!Z7fIh;j9e|Uee`=a&pg`43(tAJ{_p>Np65O1=qmoR0)c?uY&I`TrBVgE-Jb33 z?alrRu3MUDZ*Q;B>2zlcg+dFtl}qw3@nedj^bZXUeb?p|5kYbsk{~2ifSzKHV$Y!U z`0J2n{Pp-Bkygl_sAOd#x7&RQ&^Z#5m6er8qtWPu3eswDF8phV6>-I4vHOx&=Y8O*IOsKrHhTXjYeY=@wZ5fE*b=*^LRXFNRmhfe``D*pY7=AIFQTbhVjR! z?ij?c1gu@`+mz4epAj2l&wi=M1Ia1|*drtY22d@UwHNz2r(WR345d)%$$X};ZX)}* z-8u!}VVtiflj&{=vgj$dmmtzNJw2VEiapva0}qRz$z*mbI2)0P!NI{_T9DYpCmjyQ zcB(yzy@o>{GtY2mXQvFhf_cdy-;lKmC)zMRA%9)8l&6gwkZp$S19*QroepZipRuhn zqZCPXCYt<$NlV+P<^j0XMCJ9+5?MJQEv>yqwk&Kbwp0+GDwZ?o>ky3N*5h|OozBKc zB=SP?KA+Eyk~ZH7(L;bW^uCM0qQxr^C2#_H1W5xe23;;!P6d%~6B~|kMX%RKFytM& z%sh%OC~f?l2Yi8`g`qa{F3m_xU4FlR1-LWpovN*^O(@}T_&pU}L3SjQ$*Tgi*=!>K zju;Gv4ODSY-K=U_s+yadyMr&YJuMef{MMurNMx9YAc$EkmQw=*11WW*Rx^TnaB*Ty zRC}I-E=s=q(ROup8Ts$r0^}v00u(`yAjPkrHXP)f9Mp=I5p?q<^rMTbOr!?5>Vp9L zkVZA8(OHOwksd0MPo%v5AW}2Tp_*HfpPYqQjtXb3R_mKkDD*?cX!UQ*Qc0KRI`B6D O0000Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91B%lKT1ONa40RR91CIA2c08q}boB#j@ph-kQR9Fesn0-vuWfaG`m#YW_ zY?kk4bI7KX7o%t`wUxCB6*Z}wsc1=Q!?9JSMyFU+13tcYP zqW=E=hZ*dqC<8X3djQ>iyLazC8AaFbjtXe?>ea(KJ3H54@jA{&*_MN24g+rEu3fw8 zqnX_hP(eY#96*#)Q$rmwKp(OT)brGf)CBYP)i_58~sQ6uFLW2MSCLg1{D#xgqx?nI- zF`b9<%a$#B71^7H|Ayq`_K-a1X=&L=qHlxswzaj*oiSs^kM;HSvcnt~0hN`NH5G&OfKgUsFpkeq$Yo;L(smaY7so;8X(!I#76WZNq1a4f zDQigDeMEbL6Pvueyb3_s`3b*R?A(68UlPo^mk{<5ojpz*qO%A6PeXJU1MCdIin)NT zh==ssO|RG6i@oemqACzb86Ya;fYF^iT5aNha_JI=`tN(UNQ9gXZSu z6GZEFm4~B@eZY`UFDxt^DYHmZA&9?H-YI1QhewYdoer=~*mtWP!0e)Zt+0mMvSl09FE^Y^u3Ct)wR{BhhFGU1WgW5N%YC9@I2+sh})Ln$Qk()RZDCY(QJ+5@yYEiG-oDH9&Ky+ZI;JQ&Iv zPL@viXn-Q#UA%!(Ny;+nL*h#dgUCVqd{tFd*yDsT?Q;vh-Gjk77|Wh*n5B-KQhDNb zb#=YOga0*b;?)kn@}k_HkdRQqLrxz50}44cje9)VA&$oRw<_G?$%nztOf?^y~C{fx(N=0>LsPx#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR917N7$F1ONa40RR91761SM030DRS^xk7vq?ljR5%f}l|N`xQ544eUJ_9Z zq;7@?4(a05szp$7D-KfP+CeI`WKn3-G=xlAsZ*dynn4k4YjttZA>DMVU38M#MNEYH zXHFtQAht>ToyWb8o7V&@;)Tn(=R4o|?%~{f-?IihV_{*zu2!qpfhR1>8trsC5!QRm zvTfUM@K=|Ymp^#Y4!D+y%*@Q3$6sL%n|P1)GoR0w$mjFb9x(@Ug=c4HV<69|=$Jv? zrs@j*QRaxMUWY&W{r;=DT<)6&OzQ|sr3h>pqMMN7#bU9n$;7N=G8t>P+YecX75;u` zXy_vOP0iERFFmc*YLB%j8{97x3U{y_O%kj8(%jtK7UCQ5F9V;gR4N(lJ>i(cIVeaY z3f$}^Y9>fu+2jWEr{LHG#RW}{tZn9m0mfBj26S-A=DVPa5}~NUdc8g-YjfBLjVfT< z?5RoWwdU%*)Htn?wsZ}f9V3FU!RM~XADmDqv3`+lwsGi<}GbuT0h~S`hOlxT;olc)>Hk&09 z&uL6qxl;P{Jt3#4HbqVIXr7osxfRaHX0zX;(dZ>ecbRpKW4)rE_+-Xt-z2;?g~(f2 zNhA_eM$Uj+u?eP9spG9yYfQbUFp0m2f8{gzYH4Zd9fD3zX_vHRO~kPs@HjsK`JVR{ zmcU_$SDd_QJ`HPbD-J8<1tfx98te;dC6GXoCg TxN&(B00000NkvXXu0mjf4-IdU diff --git a/ui/src/assets/img/icons/search-icon@2x.png b/ui/src/assets/img/icons/search-icon@2x.png deleted file mode 100644 index 91a12c4e34975bb8ca790f11509b59864c9bb228..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1670 zcmV;126_33P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91ET97b1ONa40RR91EC2ui0Q_#}qW}N}@JU2LR9Fe^m}yL0RTRfN1D3^F z1g)rzMjouZ!s9|1p|nVH#BR#rA0l*i=aapR@3vN8!0&(KTg zTiw_Mb^i2vz3;`v#T{vCY8qXFoug`;qXYruJmcfz2j}PKKV#!p1;6fj*pK&%0u*rrXyH= zeSQ4c*x3CH57X}yOlb>{-UI0qip&vOBurF15boi+OVcKboh(q*($X@f$u&%Tl3pd) zz0UY*QwLmp9p0}+-wODdej!7ZjTk*$PmtBEglF3$!V&JcFC+-8j(EYau2r&p99Dw~2v zwlfxFWsTWppxdXW- zuFbuq!)6R@+bxb?X=rHhX+|r-s4&mrd&cxZj!P+w+PsjJgN)hyhaO@F)Cy zYWjjlp^0g8A-qKqe6f`BrPxP=i0}uBLS9Wxjji;VdXmH0Hqh(sH?dgGsYs_3e{nN2 zGdC-E7$k&m4?~y`^$R8z%Q@BJyylNIyg|dQSfEN+0U2}4elxLH&Plh+tTAr-B=W8Z z%bP-2Bf{&$fEZ6WYsj;+PH`LctSk|pQVgT!l49c;nwXfl9%b9nmZ+<#Vs$KpzqJCv z@n!{!8AQ#!LvxmtlypwRt(cd8^_3L}j*H}osNgYyAiPI$bji&|CAoCkGzLrKU2dm| z$6`(())J(Bimz@3vq9d})KuAZE}0UZ*uy3ki@DF|dy0T;G<{L(x=c))d-=yR2;Mb? zINc^^WD{S>ysoZp1NP4=K785P*}V#Gg93G!TXY18-;@&NH#i*G4o0S)n|Vh^$5oS) zlU)QtDnwXg!)PomEwz=`Fs0xmK$usQvygsGQCJ7*k>DapTo3X+AtNs@Z)A9Q*tV^jWcXN;kd>8H&i9RDkj*mXGH0zh`HVF< zIQXN?-L$|vomG)YK2AjC#n7ouvrrGco8F(2l5&w-K1GkG6sBV<$JJs;$~pT+Cnq5s zAD7YH%^!62(Afx!BVNs`ani_8m4_CyTqjsqzN49U#-idp6w*ld@+^Jkto5(Nkq~)R zgvvYg>!LYXiX3ut{4s48{hRdI%Slf51;^$X5&2-p`&TXtME%!}-Yo+Bd{6n>PnR83 zJ8&hkWp{ITAF!C&hK>=oYU1Q><}VSM?N;37*az8H`MBL0ss_9v@8BDSe4@XMVx?gl1tAq%zRO7avB$sy+QK`pa4SX&Atq`~>2n!k6 z>6}O-&O{c-hE+p25@{koKo5NNTvdcaq`BS$!4a;q<@ZvTpAgyq?Mg)8A2W$)i&Lzz Q+yDRo07*qoM6N<$g0Ha-x&QzG diff --git a/ui/src/assets/img/icons/trash-icon@1x.png b/ui/src/assets/img/icons/trash-icon@1x.png deleted file mode 100644 index 762e00bd3083d474b35b60e13afeb383e11a8489..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 614 zcmeAS@N?(olHy`uVBq!ia0vp^{6H+i!3HFQj;9L&DaPU;cPEB*=VV?2IWDOYo@u_m z3|c@o2Loe!CIbsd2@p#GF#`kh0!9XAAk7F8TfhXDEak- zA-FVXzxUw;f!w++ODC&baLm~_rQ?$0%O}hJaqbdn;d6C-^+rqTj_i_dK0n=EJAa73 zP?5co<7NFq&o?JfYO={b@5vIkw}{$U?EC$0Z~4yM`V&vs_i$bo>pq%%d)xL#--Guh z+bq{#&8lvW$unSlXw{;ABHpotxBhUg`uy|uT3w%Ho@_qIeN;j3l4jSVu&q&dr>m@V zDL=hw+6x}3X8pU48)SU^I&#IsDEzuL-b@!c&e6O47VFM)rxrdf|>YW=G`-<#Hhzv_L-0a1q z^6$#Fyh}W-?_D^Og()Hel-`3Sp>Ri?SxJ7Ro%XjyltnUnq3T~?QSm!kGyL_0>^4?9k lB3VEzYiGT?QZ}8 diff --git a/ui/src/assets/img/icons/trash-icon@2x.png b/ui/src/assets/img/icons/trash-icon@2x.png deleted file mode 100644 index f5cbae93adc4283e84fc2cb3f4f48e6da34cc8fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1112 zcmV-e1gHCnP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR919-spN1ONa40RR91C;$Ke0OTNgBLDyc!bwCyR9Fe+SW8b+Q4sFaS_l+1 z!HuzH1q+o#|9~WJhzTpKK?;e4xYU^V*mgsdjT)AO5Yp0xC7P5)UAXDWAAl|l8;P&j zjfk&O`tqB@o#FIyTl%=@!js(2oH;Y!H#7I#b5Mule;dw53r;4J?$y=R+m%Y?iqq*l z2YJ+p%|X6~ZDnL+I<|b{fCs3+d zXL7WAx&y#j$;Kixy9pH@w_fdPBAuL^9EBXPN&wE1V5`{xx8icS-XQ)tVJJY5UMrPK ziB>_Ii9-#e*!9oU0a7lPL(K%Y>Dy27s@2G13!PAS(5W3S)Wp7@Vc?%3ILL1#nRt^X z#(0ZX+~bKI5@$sSIEomBDK2jHSB>D9rrV_+*MRnjISXU)Fo*?{xm3kG`J2S*9lPD` zO?*z=4ashNeEbxWN?}7({C@xCg@uJIIZaGV^x;cLlkW9;hnAL>zK}jWJ$*8l%ViKl zV>lj zglYKvL?T7f*+9C=gcM{ht)aOwS~@m$EF>>qj}Rj(A;$Z*BIX2|k|r$S5MrQ-5lPra zY{C)_A=cYqa&D?Zv$F|nKe1jhbX75u*F*~l^$ zi+zF~tZX+H50000Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR917oY^2SV=@dR7ee#m0d`aVHC%G_o2mv zRu>9M7lk2eZ9!>5K^8_B$SOjDO*bOeg+&?BMK^j?RFKs|$_ppz3cM&Z$qOlW6ZA#F zOih?{(-%}|QiIy&`kj||XlrY0X7Wr*y$^#r65FpCZo_a%BU5C}ZUzzYm| z!0YvzT`rd|9*;lPh$kVuU^bg=B_$=6!NI{|hr?kat^_}e&QM>Aj#GDD&n159P4 z#*vYchmbab&r9(Df=y%=CF9P{PD3~xc2jc;tQft+=BJvQo8yv`B6m8Sw~5;z`+#la zl(Uks5}~$=yhrFJ>bs~tCv_@0J#L^C&2+rUg%mCwMq?E|RR6892!=!^34%gf8}Lp;M5?1yKM(Ppzr ze`)~(*v)8z-ELPxp->P%F9`;N32F-=KY;#_w(JrHf=fJ# z>kR}O2oj9YEaa0(B5*HbHpM1~lGsWt(;K1-QM62v`cchvdklw-hS5Y}K_5H&%x1%E46tw>r4Sf4?M!A_%= ztmG`DUal1JAYNfa&oE*ZY$ZACvC>k}>%@W`<%_2|%t^3xaxQccP)?9@O1QA!q`a2A zq;PLn#5b8tf0CG3>32$cdwZpql2FAMX8o#Evd-ber4kku6@Agc>EsYgBd18V%+1Yh z*C*!kY10nYN~wL@)6)~t=cS!o79j4@tWSR5^~ssOtdoCDaL5NT*Z(8uG0E@4EJZbu zNTfU02J&0Zx}Usy^21iEwR>4|`Jt!a2n7q+6A(O;SK8j*zFLZTC|0Jy#p|79-3c_$ zMK<|-zO>7fT!lpGv#2^L0%{^;nN#x%O#X57@|Qp+NH10bMJ8exb)aj|^5p=T chY)=9A7}G?+~k+(H~;_u07*qoM6N<$f{-e_Pyhe` diff --git a/ui/src/assets/img/icons/user-icon@2x.png b/ui/src/assets/img/icons/user-icon@2x.png deleted file mode 100644 index 4f9864c8d2b39153a69184c43cac33b1039ab90f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2092 zcmV+{2-Ek8P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91E}#Pd1ONa40RR91E&u=k08)}91ONaBmPtfGRA>dwnR`r?WgN#jTm!t| zvM>wLfTnH%T3*`n!YU;**P7C%g5nr19GcCR=2lcz%UZfvQ_+C9ti^ITa5}A}d1LOYH~wPl74VXh62J2D@??O%NuA(rGI#cXt|ZpmMMXvXy?KB> z15Zv)ewGC9Q>VBCpvwTzf!$SPq14e-KV9#xjnB`%zP{^u1IOIyt+HN$OD~s|m2C&e z%T{OMN!X3<6iE*-&d&t}1yv^|CSFQPO6pSf85tQPnwy&^b#-+uz$Ou&Irs!Ay`mC} z`d(C26jLXwQ_0;?Tj1&G>0_Ikno4j?usW0AH)J1C_w$gp%kT>&{sx?0 zfm$bc6?x`)>J+!Y*Q{AHvaPM{1OekMNWP{gmgnW=)mh{NVpCI7Bih^BO9}FbCD8fc z;NbcD_U*f7k^A^s1+p#^O9I}K$UfT!c);=;)>L(4NeHa7MY4<-wOQCvesh6s`z zp4KIUKM@)ly6(2=@cnWm*uc;{sb!Ro{1l9VO6CH2@#4iygbyo)P{!Scfu1S!cztb4 zPJl0^$|j~tWOa3Qiee1lvdOS9$g*TAot>Q@v*revgu}!X78YLT?Z0bDw^^`?%m9Ay z;6XVsMOut}aGb-Amu-~~7tb5tORu{&H}FJ(b*bbxfYaLUgU`^uRWjC1Z#w-7rw=Q8!V*$d$`g0Bab@Hca~sm*RHA!Gmt)iOV=&++)!V z8SCfgX95o9$-aP-UCLEB)Kn=3gJ#v%De`AJIy!!#_mg$`P_;*m8udM~d$joB(&Kk) zf_4`F295XF6hC^ufMde9;GgHwa1a#E$4BB1p# zK9>hL&I8EhcV~2T^daVe?d?Sm*jt-i4mw8WVGwO)ZU7&lI1=0THu_?m%dyX)nvT8j z3Fs24s;aKB`V_*K4dgRKN=nL1I^+dFClU7n;<*a^Na^uD7aSbL#l=6+qcb?$tcBl7 zm79`Y!jWx4u!-wgBM)%`{wE&#JoEyzo)xQ^+QOvcg?9OIX^8~nRD7mlBXt_`2tBB^ zZ3~=Wu6HAjGIWGGhr%ZtH*UIr@T5P>y^|9Kk zBM)M--?3vy3%luuOO`B&#QAx8^C)!C#3~tTAnYFs4oG zI=vvTc&LiL5z0~ex?Lh$;7p907H3&KhN_s>ZQkw`yF|9YaXinHyM_zFrq~7Rua3Af zcV;Pna(0J>g_%|nrBl=vxLlnutWd_#2ib~aHGWWsY#?6+ckQ8+?eEvSFzr%b_<7TLO1uj)6WWXIlkuc z0A6oMMpk|R?MsY3#CTN6BnSK!jrZ8>6L2Q!?+lqNOO)jdnZy0Ld&*~H?ya8_Z?zs% za}0Ju@FagZC6K2Lx8JE zCcGGUECleVQwnTk2#)WIt@vQ)?)Id@xuuez)~)8w7G9PQLhHeJ>CVP!LU)=N Wf;{UT^#cF^0000 Date: Tue, 21 Jun 2022 15:10:34 +0400 Subject: [PATCH 397/837] EFRR-1248 added error msg --- ...ollection-manager-subject-right.container.component.ts | 8 ++++++-- ui/src/assets/i18n/en.json | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/ui/src/app/features/collection-manager-subject-right/collection-manager-subject-right.container.component.ts b/ui/src/app/features/collection-manager-subject-right/collection-manager-subject-right.container.component.ts index b855086cdf..f14ae6b34e 100644 --- a/ui/src/app/features/collection-manager-subject-right/collection-manager-subject-right.container.component.ts +++ b/ui/src/app/features/collection-manager-subject-right/collection-manager-subject-right.container.component.ts @@ -22,6 +22,7 @@ import { SubjectModeEnum } from 'src/app/data/enums/subject-mode.enum'; import { CollectionItem } from 'src/app/data/interfaces/collection'; import { tap } from 'rxjs/operators'; import { MaxImageSize } from 'src/app/data/interfaces/size.interface'; +import { SnackBarService } from '../snackbar/snackbar.service'; @Component({ selector: 'app-application-right-container', @@ -58,7 +59,7 @@ export class CollectionManagerSubjectRightContainerComponent implements OnInit, @Output() setDefaultMode = new EventEmitter(); private apiKey: string; - constructor(private collectionRightFacade: CollectionRightFacade) {} + constructor(private collectionRightFacade: CollectionRightFacade, private snackBarService: SnackBarService) {} ngOnInit(): void { this.subject$ = this.collectionRightFacade.subject$; @@ -92,7 +93,10 @@ export class CollectionManagerSubjectRightContainerComponent implements OnInit, this.setDefaultMode.emit(); const fileBodySize = fileList.map(item => item.size).reduce((previousValue, currentValue) => previousValue + currentValue); - if (fileBodySize > this.maxFIleSize) return; + if (fileBodySize > this.maxFIleSize) { + this.snackBarService.openNotification({ messageText: 'face_recognition_container.file_size_error', type: 'error' }); + return; + } this.collectionRightFacade.addImageFilesToCollection(fileList); } diff --git a/ui/src/assets/i18n/en.json b/ui/src/assets/i18n/en.json index 3e2a10faf2..3a84e56c61 100644 --- a/ui/src/assets/i18n/en.json +++ b/ui/src/assets/i18n/en.json @@ -239,7 +239,7 @@ "link": "Try Demo" }, "face_recognition_container": { - "file_size_error": "The photo size is more than 5 Mb", + "file_size_error": "File size it too big", "file_unavailable_extension": "File {{filename}} has an unavailable extension" }, "face_recognition": { From fc65964724cb01fe73407cb542a1268741d3ad6a Mon Sep 17 00:00:00 2001 From: smchedlidze826 Date: Tue, 21 Jun 2022 15:12:21 +0400 Subject: [PATCH 398/837] Removeded docker file from pr --- dev/docker-compose.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dev/docker-compose.yml b/dev/docker-compose.yml index 7d9ea977a8..d7d03b4ff0 100644 --- a/dev/docker-compose.yml +++ b/dev/docker-compose.yml @@ -87,12 +87,12 @@ services: - CLIENT_MAX_BODY_SIZE=${max_request_size} compreface-core: - image: pospielov/compreface-core:1.0.0-mobilenet-2d106 + image: ${registry}compreface-core:${CORE_VERSION} restart: always container_name: "compreface-core" ports: - "3300:3000" - # build: - # context: ../embedding-calculator + build: + context: ../embedding-calculator environment: - ML_PORT=3000 From ae739290450b1ff902bf803cc15eabae8fd9bf5a Mon Sep 17 00:00:00 2001 From: Volodymyr Bushko Date: Fri, 24 Jun 2022 11:00:04 +0300 Subject: [PATCH 399/837] EFRS-1256: Added a database changelog for the model_statistic table --- .../db/changelog/db.changelog-0.2.0.yaml | 56 +++++++++++++++++++ .../db/changelog/db.changelog-master.yaml | 2 + 2 files changed, 58 insertions(+) create mode 100644 java/admin/src/main/resources/db/changelog/db.changelog-0.2.0.yaml diff --git a/java/admin/src/main/resources/db/changelog/db.changelog-0.2.0.yaml b/java/admin/src/main/resources/db/changelog/db.changelog-0.2.0.yaml new file mode 100644 index 0000000000..cf3452eede --- /dev/null +++ b/java/admin/src/main/resources/db/changelog/db.changelog-0.2.0.yaml @@ -0,0 +1,56 @@ +databaseChangeLog: + - changeSet: + id: create-model-statistic-table + author: Volodymyr Bushko + changes: + # model_statistic + - createTable: + tableName: model_statistic + columns: + - column: + name: id + type: bigint + - column: + name: request_count + type: bigint + - column: + name: model_id + type: bigint + - column: + name: created_date + type: timestamp + + - addPrimaryKey: + columnNames: id + constraintName: pk_model_statistic + tableName: model_statistic + + - addForeignKeyConstraint: + baseColumnNames: model_id + baseTableName: model_statistic + referencedColumnNames: id + referencedTableName: model + constraintName: fk_model_id + onDelete: CASCADE + onUpdate: CASCADE + + - addNotNullConstraint: + tableName: model_statistic + columnName: request_count + + - addNotNullConstraint: + tableName: model_statistic + columnName: model_id + + - addNotNullConstraint: + tableName: model_statistic + columnName: created_date + + - addDefaultValue: + tableName: model_statistic + columnDataType: timestamp + columnName: created_date + defaultValueComputed: current_timestamp + + - createSequence: + sequenceName: model_statistic_id_seq diff --git a/java/admin/src/main/resources/db/changelog/db.changelog-master.yaml b/java/admin/src/main/resources/db/changelog/db.changelog-master.yaml index 86032033ac..d99ebeb6de 100644 --- a/java/admin/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/java/admin/src/main/resources/db/changelog/db.changelog-master.yaml @@ -37,3 +37,5 @@ databaseChangeLog: file: db/changelog/db.changelog-0.1.8.yaml - include: file: db/changelog/db.changelog-0.1.9.yaml + - include: + file: db/changelog/db.changelog-0.2.0.yaml From 9e9b34878e034ebc26377ea4ee4fc064faf04558 Mon Sep 17 00:00:00 2001 From: Volodymyr Bushko Date: Fri, 24 Jun 2022 11:59:02 +0300 Subject: [PATCH 400/837] EFRS-1256: Added ModelStatistic entity --- .../frs/commonservice/entity/Model.java | 5 +++ .../commonservice/entity/ModelStatistic.java | 43 +++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 java/common/src/main/java/com/exadel/frs/commonservice/entity/ModelStatistic.java diff --git a/java/common/src/main/java/com/exadel/frs/commonservice/entity/Model.java b/java/common/src/main/java/com/exadel/frs/commonservice/entity/Model.java index dc9ccf80f1..b86117e1d0 100644 --- a/java/common/src/main/java/com/exadel/frs/commonservice/entity/Model.java +++ b/java/common/src/main/java/com/exadel/frs/commonservice/entity/Model.java @@ -68,6 +68,11 @@ public Model(Model model) { @OneToMany(mappedBy = "model", cascade = CascadeType.ALL, orphanRemoval = true) private List appModelAccess = new ArrayList<>(); + @ToString.Exclude + @Builder.Default + @OneToMany(mappedBy = "model", cascade = CascadeType.ALL, orphanRemoval = true) + private List modelStatistics = new ArrayList<>(); + @Column(name = "created_date") private LocalDateTime createdDate; diff --git a/java/common/src/main/java/com/exadel/frs/commonservice/entity/ModelStatistic.java b/java/common/src/main/java/com/exadel/frs/commonservice/entity/ModelStatistic.java new file mode 100644 index 0000000000..1bd39a4454 --- /dev/null +++ b/java/common/src/main/java/com/exadel/frs/commonservice/entity/ModelStatistic.java @@ -0,0 +1,43 @@ +package com.exadel.frs.commonservice.entity; + +import static javax.persistence.GenerationType.SEQUENCE; +import java.time.LocalDateTime; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.SequenceGenerator; +import javax.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Entity +@Table(name = "model_statistic", schema = "public") +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ModelStatistic { + + @Id + @Column(name = "id") + @GeneratedValue(strategy = SEQUENCE, generator = "model_statistic_id_seq") + @SequenceGenerator(name = "model_statistic_id_seq", sequenceName = "model_statistic_id_seq", allocationSize = 1) + private Long id; + + @Column(name = "request_count") + private Long requestCount; + + @ManyToOne(optional = false) + @JoinColumn(name = "model_id", referencedColumnName = "id") + private Model model; + + @Column(name = "created_date") + private LocalDateTime createdDate; +} From ab82a44fcb35651f1ba04fc27353b2e71c0becca Mon Sep 17 00:00:00 2001 From: IvanKurnosov Date: Fri, 24 Jun 2022 12:27:12 +0300 Subject: [PATCH 401/837] Run gpu containers on windows documentation --- embedding-calculator/README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/embedding-calculator/README.md b/embedding-calculator/README.md index 2e66acc88e..fe14db5575 100644 --- a/embedding-calculator/README.md +++ b/embedding-calculator/README.md @@ -162,7 +162,14 @@ There are two build arguments for optimization: * `INTEL_OPTIMIZATION` - enable Intel MKL optimization (true/false) -##### NVIDIA Runtime +##### GPU Setup (Windows): +1. Install or update Docker Desktop. +2. Make sure that you have Windows version 21H2 or higher. +3. Update your NVIDIA drivers. +4. Install or update WSL2 Linux kernel. +5. Make sure the WSL2 backend is enabled in Docker Desktop. + +##### GPU Setup (Linux): Install the nvidia-docker2 package and dependencies on the host machine: ``` @@ -171,7 +178,7 @@ sudo apt-get install -y nvidia-docker2 sudo systemctl restart docker ``` -Build and run with enabled gpu +##### Build and run with enabled gpu ``` docker build . -t embedding-calculator-cuda -f gpu.Dockerfile docker build . -t embedding-calculator-gpu --build-arg GPU_IDX=0 --build-arg BASE_IMAGE=embedding-calculator-cuda From cb725727b67aa97edb6f782818eff31cd0574f4c Mon Sep 17 00:00:00 2001 From: IvanKurnosov Date: Fri, 24 Jun 2022 15:04:58 +0300 Subject: [PATCH 402/837] All containers works on Windows machine with 3xxx gpu --- embedding-calculator/Dockerfile | 6 +- embedding-calculator/Makefile | 16 +++-- embedding-calculator/gpu.Dockerfile | 69 ++++--------------- embedding-calculator/requirements.txt | 2 +- embedding-calculator/src/constants.py | 4 +- embedding-calculator/src/init_runtime.py | 4 +- .../services/facescan/plugins/dependencies.py | 4 +- .../facescan/plugins/facenet/__init__.py | 2 +- .../plugins/insightface/facemask/facemask.py | 20 +++--- .../facescan/plugins/insightface/helpers.py | 9 ++- .../plugins/insightface/insightface.py | 32 +++++---- 11 files changed, 71 insertions(+), 97 deletions(-) diff --git a/embedding-calculator/Dockerfile b/embedding-calculator/Dockerfile index 2fb58e9bd3..8df072b079 100644 --- a/embedding-calculator/Dockerfile +++ b/embedding-calculator/Dockerfile @@ -3,7 +3,7 @@ FROM ${BASE_IMAGE:-python:3.7-slim} RUN apt-get update && apt-get install -y build-essential cmake git wget unzip \ curl yasm pkg-config libswscale-dev libtbb2 libtbb-dev libjpeg-dev \ - libpng-dev libtiff-dev libavformat-dev libpq-dev libfreeimage3 \ + libpng-dev libtiff-dev libavformat-dev libpq-dev libfreeimage3 python3-opencv \ && rm -rf /var/lib/apt/lists/* # install common python packages @@ -44,9 +44,11 @@ ARG SKIP_TESTS COPY pytest.ini . RUN if [ -z $SKIP_TESTS ]; then pytest -m "not performance" /app/ml/src; fi +RUN rm -rf /usr/lib/x86_64-linux-gnu/libcuda.so.1 + EXPOSE 3000 COPY uwsgi.ini . ENV UWSGI_PROCESSES=${UWSGI_PROCESSES:-2} ENV UWSGI_THREADS=1 -CMD ["uwsgi", "--ini", "uwsgi.ini"] +CMD ["uwsgi", "--ini", "uwsgi.ini"] \ No newline at end of file diff --git a/embedding-calculator/Makefile b/embedding-calculator/Makefile index 524403209a..44b72845de 100755 --- a/embedding-calculator/Makefile +++ b/embedding-calculator/Makefile @@ -2,13 +2,14 @@ SHELL := /bin/bash .DEFAULT_GOAL := default .PHONY := default up build-images build-cuda -IMAGE := ${DOCKER_REGISTRY}compreface-core -CUDA_IMAGE = $(IMAGE)-base:base-cuda100-py37 +VERSION := 1.0.0 +IMAGE := pospielov/compreface-core +CUDA_IMAGE = $(IMAGE)-base:base-cuda112-py37 APPERY_ARG := --build-arg APPERY_API_KEY=${APPERY_API_KEY} MOBILENET_BUILD_ARGS := --build-arg FACE_DETECTION_PLUGIN=insightface.FaceDetector@retinaface_mnet025_v1 \ --build-arg CALCULATION_PLUGIN=insightface.Calculator@arcface_mobilefacenet \ - --build-arg EXTRA_PLUGINS=insightface.LandmarksDetector,insightface.GenderDetector,insightface.AgeDetector,insightface.facemask.MaskDetector \ + --build-arg EXTRA_PLUGINS=insightface.Landmarks2d106Detector,insightface.GenderDetector,insightface.AgeDetector,insightface.facemask.MaskDetector \ $(APPERY_ARG) ARCFACE_r100_BUILD_ARGS := --build-arg FACE_DETECTION_PLUGIN=insightface.FaceDetector@retinaface_r50_v1 \ @@ -55,11 +56,12 @@ build-images: build-cuda build-images-cpu: docker build . -t $(IMAGE):$(VERSION)-facenet docker build . -t $(IMAGE):$(VERSION)-mobilenet $(MOBILENET_BUILD_ARGS) - docker build . -t $(IMAGE):$(VERSION)-arcface-r100 $(ARCFACE_r100_BUILD_ARGS) + +build-images-2d106: + docker build . -t $(IMAGE):$(VERSION)-mobilenet-2d106 $(MOBILENET_BUILD_ARGS) build-images-gpu: build-cuda - docker build . -t $(IMAGE):$(VERSION)-mobilenet-gpu $(MOBILENET_BUILD_ARGS) $(GPU_ARGS) - docker build . -t $(IMAGE):$(VERSION)-arcface-r100-gpu $(ARCFACE_r100_BUILD_ARGS) $(GPU_ARGS) + docker build . -t $(IMAGE):$(VERSION)-mobilenet-3xxx $(MOBILENET_BUILD_ARGS) $(GPU_ARGS) up: docker run -p3000:3000 embedding-calculator @@ -69,4 +71,4 @@ tools/tmp: tools/benchmark_detection/tmp: $(call get_from_remote_tgz,http://tamaraberg.com/faceDataset/originalPics.tar.gz,tools/benchmark_detection/tmp/originalPics) - $(call get_from_remote_tgz,http://vis-www.cs.umass.edu/fddb/FDDB-folds.tgz,tools/benchmark_detection/tmp) + $(call get_from_remote_tgz,http://vis-www.cs.umass.edu/fddb/FDDB-folds.tgz,tools/benchmark_detection/tmp) \ No newline at end of file diff --git a/embedding-calculator/gpu.Dockerfile b/embedding-calculator/gpu.Dockerfile index b43511f5a4..60b71557cc 100644 --- a/embedding-calculator/gpu.Dockerfile +++ b/embedding-calculator/gpu.Dockerfile @@ -1,70 +1,27 @@ -ARG UBUNTU_VERSION=18.04 +ARG BASE_IMAGE +FROM ${BASE_IMAGE:-nvidia/cuda:11.2.2-cudnn8-runtime-ubuntu20.04} -ARG ARCH= -ARG CUDA=10.0 -FROM nvidia/cuda${ARCH:+-$ARCH}:${CUDA}-base-ubuntu${UBUNTU_VERSION} as base -# ARCH and CUDA are specified again because the FROM directive resets ARGs -# (but their default value is retained if set previously) -ARG ARCH -ARG CUDA -ARG CUDNN=7.6.4.38-1 -ARG CUDNN_MAJOR_VERSION=7 -ARG LIB_DIR_PREFIX=x86_64 -ARG LIBNVINFER=6.0.1-1 -ARG LIBNVINFER_MAJOR_VERSION=6 -ENV CUDA=$CUDA +ENV DEBIAN_FRONTEND=noninteractive +ENV CUDA=11.2 -# Needed for string substitution -SHELL ["/bin/bash", "-c"] -# Pick up some TF dependencies RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential \ - cuda-command-line-tools-${CUDA/./-} \ - # There appears to be a regression in libcublas10=10.2.2.89-1 which - # prevents cublas from initializing in TF. See - # https://github.com/tensorflow/tensorflow/issues/9489#issuecomment-562394257 - libcublas10=10.2.1.243-1 \ - cuda-nvrtc-${CUDA/./-} \ - cuda-cufft-${CUDA/./-} \ - cuda-curand-${CUDA/./-} \ - cuda-cusolver-${CUDA/./-} \ - cuda-cusparse-${CUDA/./-} \ - curl \ - libcudnn7=${CUDNN}+cuda${CUDA} \ - libfreetype6-dev \ - libhdf5-serial-dev \ - libzmq3-dev \ - pkg-config \ software-properties-common \ - unzip - -# Install TensorRT if not building for PowerPC -RUN [[ "${ARCH}" = "ppc64le" ]] || { apt-get update && \ - apt-get install -y --no-install-recommends libnvinfer${LIBNVINFER_MAJOR_VERSION}=${LIBNVINFER}+cuda${CUDA} \ - libnvinfer-plugin${LIBNVINFER_MAJOR_VERSION}=${LIBNVINFER}+cuda${CUDA} \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/*; } - -# For CUDA profiling, TensorFlow requires CUPTI. -ENV LD_LIBRARY_PATH /usr/local/cuda/extras/CUPTI/lib64:/usr/local/cuda/lib64:$LD_LIBRARY_PATH - -# Link the libcuda stub to the location where tensorflow is searching for it and reconfigure -# dynamic linker run-time bindings -RUN ln -s /usr/local/cuda/lib64/stubs/libcuda.so /usr/local/cuda/lib64/stubs/libcuda.so.1 \ - && echo "/usr/local/cuda/lib64/stubs" > /etc/ld.so.conf.d/z-cuda-stubs.conf \ - && ldconfig + curl \ + pkg-config \ + unzip \ + python3-dev \ + python3-distutils \ + && rm -rf /var/lib/apt/lists/* # See http://bugs.python.org/issue19846 ENV LANG C.UTF-8 -ARG PYTHON=python3.7 -RUN add-apt-repository ppa:deadsnakes/ppa && apt-get update && apt-get install -y ${PYTHON} libpython3.7-dev libgl1-mesa-glx -RUN curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py && ${PYTHON} get-pip.py -RUN ${PYTHON} -m pip --no-cache-dir install --upgrade pip setuptools +RUN curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py && python3 get-pip.py +RUN python3 -m pip --no-cache-dir install --upgrade pip setuptools # Some TF tools expect a "python" binary -RUN ln -s $(which $PYTHON) /usr/local/bin/python - +RUN ln -s $(which python3) /usr/local/bin/python # Variables for Tensorflow ENV TF_FORCE_GPU_ALLOW_GROWTH=true diff --git a/embedding-calculator/requirements.txt b/embedding-calculator/requirements.txt index e27d959ccc..94c6b38082 100644 --- a/embedding-calculator/requirements.txt +++ b/embedding-calculator/requirements.txt @@ -5,7 +5,7 @@ flasgger==0.9.5 Flask==1.1.2 itsdangerous==2.0.1 # itsdangerous is a Flask dependency, update it with Flask jinja2<3.1.0 # jinja2 is a Flask dependency, update it with Flask -gdown~=4.3.1 +gdown~=4.3.0 Werkzeug==1.0.1 PyYAML==5.4.1 diff --git a/embedding-calculator/src/constants.py b/embedding-calculator/src/constants.py index ad4f5dfd38..73a484e617 100644 --- a/embedding-calculator/src/constants.py +++ b/embedding-calculator/src/constants.py @@ -34,6 +34,8 @@ class ENV(Constants): GPU_IDX = int(get_env('GPU_IDX', '-1')) INTEL_OPTIMIZATION = get_env_bool('INTEL_OPTIMIZATION') + RUN_MODE = get_env_bool('RUN_MODE', False) + LOGGING_LEVEL = logging._nameToLevel[ENV.LOGGING_LEVEL_NAME] -ENV_MAIN = ENV +ENV_MAIN = ENV \ No newline at end of file diff --git a/embedding-calculator/src/init_runtime.py b/embedding-calculator/src/init_runtime.py index 7b8d1597ac..e124beb3d4 100644 --- a/embedding-calculator/src/init_runtime.py +++ b/embedding-calculator/src/init_runtime.py @@ -19,6 +19,7 @@ from PIL import ImageFile from src._logging import init_logging +from src.constants import ENV def _check_ci_build_args(): @@ -33,4 +34,5 @@ def init_runtime(logging_level): assert sys.version_info >= (3, 7) ImageFile.LOAD_TRUNCATED_IMAGES = True _check_ci_build_args() - init_logging(logging_level) + ENV.RUN_MODE = True + init_logging(logging_level) \ No newline at end of file diff --git a/embedding-calculator/src/services/facescan/plugins/dependencies.py b/embedding-calculator/src/services/facescan/plugins/dependencies.py index afb3f25f34..f5ff371f1b 100644 --- a/embedding-calculator/src/services/facescan/plugins/dependencies.py +++ b/embedding-calculator/src/services/facescan/plugins/dependencies.py @@ -32,7 +32,5 @@ def get_mxnet() -> Tuple[str, ...]: mxnet_lib = 'mxnet-' if ENV.GPU_IDX > -1 and cuda_version: mxnet_lib += f"cu{cuda_version}" - if ENV.INTEL_OPTIMIZATION: - mxnet_lib += 'mkl' mxnet_lib = mxnet_lib.rstrip('-') - return (f'{mxnet_lib}<1.7',) + return (f'{mxnet_lib}==1.9.0',) \ No newline at end of file diff --git a/embedding-calculator/src/services/facescan/plugins/facenet/__init__.py b/embedding-calculator/src/services/facescan/plugins/facenet/__init__.py index 43a483dc42..0171a996e6 100644 --- a/embedding-calculator/src/services/facescan/plugins/facenet/__init__.py +++ b/embedding-calculator/src/services/facescan/plugins/facenet/__init__.py @@ -14,4 +14,4 @@ from src.services.facescan.plugins.dependencies import get_tensorflow -requirements = get_tensorflow() # + ('mtcnn~=0.1.0',) +requirements = ('protobuf~=3.20.1',) + get_tensorflow() # + ('mtcnn~=0.1.0',) diff --git a/embedding-calculator/src/services/facescan/plugins/insightface/facemask/facemask.py b/embedding-calculator/src/services/facescan/plugins/insightface/facemask/facemask.py index 1477c7b47f..ac15964bdd 100644 --- a/embedding-calculator/src/services/facescan/plugins/insightface/facemask/facemask.py +++ b/embedding-calculator/src/services/facescan/plugins/insightface/facemask/facemask.py @@ -18,14 +18,17 @@ from cached_property import cached_property import numpy as np -import mxnet as mx -from mxnet.gluon.model_zoo import vision -from mxnet.gluon.data.vision import transforms from src.services.dto import plugin_result from src.services.imgtools.types import Array3D from src.services.facescan.plugins import base from src.services.facescan.plugins.insightface.insightface import InsightFaceMixin +from src.constants import ENV + +if ENV.RUN_MODE: + import mxnet as mx + from mxnet.gluon.model_zoo import vision + from mxnet.gluon.data.vision import transforms class MaskDetector(InsightFaceMixin, base.BasePlugin): @@ -35,11 +38,12 @@ class MaskDetector(InsightFaceMixin, base.BasePlugin): ('mobilenet_v2_on_mafa_kaggle123', '1DYUIroNXkuYKQypYtCxQvAItLnrTTt5E'), ('resnet18_on_mafa_kaggle123', '1A3fNrvgrJqMw54cWRj47LNFNnFvTjmdj') ) - img_transforms = transforms.Compose([ - transforms.Resize(224), - transforms.ToTensor(), - transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)) - ]) + if ENV.RUN_MODE: + img_transforms = transforms.Compose([ + transforms.Resize(224), + transforms.ToTensor(), + transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)) + ]) @property def input_image_size(self) -> Tuple[int, int]: diff --git a/embedding-calculator/src/services/facescan/plugins/insightface/helpers.py b/embedding-calculator/src/services/facescan/plugins/insightface/helpers.py index 34ddea4cec..9a78e8675a 100644 --- a/embedding-calculator/src/services/facescan/plugins/insightface/helpers.py +++ b/embedding-calculator/src/services/facescan/plugins/insightface/helpers.py @@ -13,11 +13,16 @@ # permissions and limitations under the License. from typing import Tuple -import mxnet as mx import numpy as np import cv2 from skimage import transform as trans +from src.constants import ENV + + +if ENV.RUN_MODE: + import mxnet as mx + def predict_landmark2d106(model, img, crop_size: Tuple[int, int], @@ -66,4 +71,4 @@ def trans_points2d(pts, M): new_pt = np.dot(M, new_pt) #print('new_pt', new_pt.shape, new_pt) new_pts[i] = new_pt[0:2] - return new_pts + return new_pts \ No newline at end of file diff --git a/embedding-calculator/src/services/facescan/plugins/insightface/insightface.py b/embedding-calculator/src/services/facescan/plugins/insightface/insightface.py index 5f872d5c8b..5b2f9c2c63 100644 --- a/embedding-calculator/src/services/facescan/plugins/insightface/insightface.py +++ b/embedding-calculator/src/services/facescan/plugins/insightface/insightface.py @@ -17,12 +17,7 @@ from typing import List, Tuple import attr import numpy as np -import mxnet as mx from cached_property import cached_property -from insightface.app import FaceAnalysis -from insightface.model_zoo import (model_store, face_detection, - face_recognition, face_genderage) -from insightface.utils import face_align from src.constants import ENV from src.services.dto.bounding_box import BoundingBoxDTO @@ -37,6 +32,22 @@ logger = logging.getLogger(__name__) +if ENV.RUN_MODE: + import mxnet as mx + + from insightface.app import FaceAnalysis + from insightface.model_zoo import (model_store, face_detection, + face_recognition, face_genderage) + from insightface.utils import face_align + + class DetectionOnlyFaceAnalysis(FaceAnalysis): + rec_model = None + ga_model = None + + def __init__(self, file): + self.det_model = face_detection.FaceDetector(file, 'net3') + + class InsightFaceMixin: _CTX_ID = ENV.GPU_IDX _NMS = 0.4 @@ -48,14 +59,6 @@ def get_model_file(self, ml_model: base.MLModel): return model_store.find_params_file(ml_model.path) -class DetectionOnlyFaceAnalysis(FaceAnalysis): - rec_model = None - ga_model = None - - def __init__(self, file): - self.det_model = face_detection.FaceDetector(file, 'net3') - - class FaceDetector(InsightFaceMixin, mixins.FaceDetectorMixin, base.BasePlugin): ml_models = ( ('retinaface_mnet025_v1', '1ggNFFqpe0abWz6V1A82rnxD6fyxB8W2c'), @@ -179,7 +182,6 @@ class LandmarksDetector(mixins.LandmarksDetectorMixin, base.BasePlugin): class Landmarks2d106DTO(plugin_result.LandmarksDTO): """ 106-points facial landmarks - Points mark-up - https://github.com/deepinsight/insightface/tree/master/alignment/coordinateReg#visualization """ NOSE_POSITION = 86 @@ -211,4 +213,4 @@ def _landmark_model(self): model.bind(for_training=False, data_shapes=[('data', (1, 3, *self.CROP_SIZE))]) model.set_params(arg_params, aux_params) - return model + return model \ No newline at end of file From cbe8e0dd7a0b19395404bf11abd8df52972e5794 Mon Sep 17 00:00:00 2001 From: IvanKurnosov Date: Mon, 27 Jun 2022 08:25:49 +0300 Subject: [PATCH 403/837] Makefile Dockerfile refactoring --- embedding-calculator/Dockerfile | 2 -- embedding-calculator/Makefile | 14 +++++++------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/embedding-calculator/Dockerfile b/embedding-calculator/Dockerfile index 8df072b079..cc740aa3d6 100644 --- a/embedding-calculator/Dockerfile +++ b/embedding-calculator/Dockerfile @@ -44,8 +44,6 @@ ARG SKIP_TESTS COPY pytest.ini . RUN if [ -z $SKIP_TESTS ]; then pytest -m "not performance" /app/ml/src; fi -RUN rm -rf /usr/lib/x86_64-linux-gnu/libcuda.so.1 - EXPOSE 3000 COPY uwsgi.ini . diff --git a/embedding-calculator/Makefile b/embedding-calculator/Makefile index 44b72845de..87979dc070 100755 --- a/embedding-calculator/Makefile +++ b/embedding-calculator/Makefile @@ -3,13 +3,13 @@ SHELL := /bin/bash .PHONY := default up build-images build-cuda VERSION := 1.0.0 -IMAGE := pospielov/compreface-core +IMAGE := ${DOCKER_REGISTRY}compreface-core CUDA_IMAGE = $(IMAGE)-base:base-cuda112-py37 APPERY_ARG := --build-arg APPERY_API_KEY=${APPERY_API_KEY} MOBILENET_BUILD_ARGS := --build-arg FACE_DETECTION_PLUGIN=insightface.FaceDetector@retinaface_mnet025_v1 \ --build-arg CALCULATION_PLUGIN=insightface.Calculator@arcface_mobilefacenet \ - --build-arg EXTRA_PLUGINS=insightface.Landmarks2d106Detector,insightface.GenderDetector,insightface.AgeDetector,insightface.facemask.MaskDetector \ + --build-arg EXTRA_PLUGINS=insightface.LandmarksDetector,insightface.GenderDetector,insightface.AgeDetector,insightface.facemask.MaskDetector \ $(APPERY_ARG) ARCFACE_r100_BUILD_ARGS := --build-arg FACE_DETECTION_PLUGIN=insightface.FaceDetector@retinaface_r50_v1 \ @@ -56,12 +56,11 @@ build-images: build-cuda build-images-cpu: docker build . -t $(IMAGE):$(VERSION)-facenet docker build . -t $(IMAGE):$(VERSION)-mobilenet $(MOBILENET_BUILD_ARGS) - -build-images-2d106: - docker build . -t $(IMAGE):$(VERSION)-mobilenet-2d106 $(MOBILENET_BUILD_ARGS) + docker build . -t $(IMAGE):$(VERSION)-arcface-r100 $(ARCFACE_r100_BUILD_ARGS) build-images-gpu: build-cuda - docker build . -t $(IMAGE):$(VERSION)-mobilenet-3xxx $(MOBILENET_BUILD_ARGS) $(GPU_ARGS) + docker build . -t $(IMAGE):$(VERSION)-mobilenet-gpu $(MOBILENET_BUILD_ARGS) $(GPU_ARGS) + docker build . -t $(IMAGE):$(VERSION)-arcface-r100-gpu $(ARCFACE_r100_BUILD_ARGS) $(GPU_ARGS) up: docker run -p3000:3000 embedding-calculator @@ -71,4 +70,5 @@ tools/tmp: tools/benchmark_detection/tmp: $(call get_from_remote_tgz,http://tamaraberg.com/faceDataset/originalPics.tar.gz,tools/benchmark_detection/tmp/originalPics) - $(call get_from_remote_tgz,http://vis-www.cs.umass.edu/fddb/FDDB-folds.tgz,tools/benchmark_detection/tmp) \ No newline at end of file + $(call get_from_remote_tgz,http://vis-www.cs.umass.edu/fddb/FDDB-folds.tgz,tools/benchmark_detection/tmp) + \ No newline at end of file From a2fe1663d3a1d39e2b083f32a680d3cb4affd68f Mon Sep 17 00:00:00 2001 From: smchedlidze826 Date: Tue, 28 Jun 2022 01:40:00 +0400 Subject: [PATCH 404/837] EFRS-1247/BugFix user name and surname lenght validation added --- ...tion-manager-subject-right.container.component.ts | 12 ++++++------ .../edit-user-info-dialog.component.html | 4 ++-- .../edit-user-info-dialog.component.ts | 5 +++-- ui/src/app/store/manage-collectiom/effects.ts | 2 +- ui/src/assets/i18n/en.json | 2 +- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/ui/src/app/features/collection-manager-subject-right/collection-manager-subject-right.container.component.ts b/ui/src/app/features/collection-manager-subject-right/collection-manager-subject-right.container.component.ts index f14ae6b34e..3b65d6c37f 100644 --- a/ui/src/app/features/collection-manager-subject-right/collection-manager-subject-right.container.component.ts +++ b/ui/src/app/features/collection-manager-subject-right/collection-manager-subject-right.container.component.ts @@ -53,8 +53,8 @@ export class CollectionManagerSubjectRightContainerComponent implements OnInit, collectionItems$: Observable; mode$: Observable; apiKeyInitSubscription: Subscription; - maxFIleSizeSubs: Subscription; - maxFIleSize: number; + maxFileSizeSubs: Subscription; + maxFileSize: number; @Output() setDefaultMode = new EventEmitter(); private apiKey: string; @@ -72,8 +72,8 @@ export class CollectionManagerSubjectRightContainerComponent implements OnInit, this.defaultSubject$ = this.collectionRightFacade.defaultSubject$.pipe( tap(subject => this.collectionRightFacade.loadSubjectMedia(subject)) ); - this.maxFIleSizeSubs = this.collectionRightFacade.maxBodySize$ - .pipe(tap((size: MaxImageSize) => (this.maxFIleSize = size.clientMaxBodySize))) + this.maxFileSizeSubs = this.collectionRightFacade.maxBodySize$ + .pipe(tap((size: MaxImageSize) => (this.maxFileSize = size.clientMaxBodySize))) .subscribe(); } @@ -93,7 +93,7 @@ export class CollectionManagerSubjectRightContainerComponent implements OnInit, this.setDefaultMode.emit(); const fileBodySize = fileList.map(item => item.size).reduce((previousValue, currentValue) => previousValue + currentValue); - if (fileBodySize > this.maxFIleSize) { + if (this.maxFileSize && fileBodySize > this.maxFileSize) { this.snackBarService.openNotification({ messageText: 'face_recognition_container.file_size_error', type: 'error' }); return; } @@ -121,6 +121,6 @@ export class CollectionManagerSubjectRightContainerComponent implements OnInit, ngOnDestroy(): void { this.collectionRightFacade.resetSubjectExamples(); - this.maxFIleSizeSubs.unsubscribe(); + this.maxFileSizeSubs.unsubscribe(); } } diff --git a/ui/src/app/features/edit-user-info-dialog/edit-user-info-dialog.component.html b/ui/src/app/features/edit-user-info-dialog/edit-user-info-dialog.component.html index 7ba5bdb558..625c251a9e 100644 --- a/ui/src/app/features/edit-user-info-dialog/edit-user-info-dialog.component.html +++ b/ui/src/app/features/edit-user-info-dialog/edit-user-info-dialog.component.html @@ -19,7 +19,7 @@

{{ 'toolbar.user_info_change' | translate }}

-
+
{{ 'registration.name_restriction' | translate }}
@@ -35,7 +35,7 @@

{{ 'toolbar.user_info_change' | translate }}

- diff --git a/ui/src/app/features/edit-user-info-dialog/edit-user-info-dialog.component.ts b/ui/src/app/features/edit-user-info-dialog/edit-user-info-dialog.component.ts index 190c4e92a6..7b9e2188ac 100644 --- a/ui/src/app/features/edit-user-info-dialog/edit-user-info-dialog.component.ts +++ b/ui/src/app/features/edit-user-info-dialog/edit-user-info-dialog.component.ts @@ -27,6 +27,7 @@ export class EditUserInfoDialogComponent implements OnInit { form: FormGroup; firstName: string; lastName: string; + maxLength = 63; constructor( public dialogRef: MatDialogRef, @@ -38,8 +39,8 @@ export class EditUserInfoDialogComponent implements OnInit { this.firstName = this.data?.firstName; this.lastName = this.data?.lastName; this.form = this.formBuilder.group({ - firstName: new FormControl(this.firstName, [Validators.required]), - lastName: new FormControl(this.lastName, [Validators.required]), + firstName: new FormControl(this.firstName, [Validators.required, Validators.maxLength(this.maxLength)]), + lastName: new FormControl(this.lastName, [Validators.required, Validators.maxLength(this.maxLength)]), }); } diff --git a/ui/src/app/store/manage-collectiom/effects.ts b/ui/src/app/store/manage-collectiom/effects.ts index 583fc9d594..cf60f381e3 100644 --- a/ui/src/app/store/manage-collectiom/effects.ts +++ b/ui/src/app/store/manage-collectiom/effects.ts @@ -246,7 +246,7 @@ export class CollectionEffects { const ext = /(\.jpg|\.jpeg|\.webp|\.png)$/i; const type = /(\/jpg|\/jpeg|\/webp|\/png)$/i; - if (file.size > sizeInBytes) { + if (sizeInBytes && file.size > sizeInBytes) { return of(uploadImageFail({ error: `Invalid File Size ! \n File Name: ${file.name}`, item, continueUpload })); } if (!ext.exec(file.name) || !type.exec(file.type)) { diff --git a/ui/src/assets/i18n/en.json b/ui/src/assets/i18n/en.json index 3a84e56c61..2f9144b24f 100644 --- a/ui/src/assets/i18n/en.json +++ b/ui/src/assets/i18n/en.json @@ -41,7 +41,7 @@ "last_name": "Last Name", "email": "Email", "password": "Password", - "confirm_password": "Confirm password", + "confirm_password": "Confirm Password", "password_restriction": "Minimum 8 characters", "password_dont_match": "Passwords don't match", "name_restriction": "First name is required", From fc3b0b2e9ea2f3d1ef42f096ef1b67b15f481b94 Mon Sep 17 00:00:00 2001 From: smchedlidze826 Date: Tue, 28 Jun 2022 02:08:50 +0400 Subject: [PATCH 405/837] EFRS-1255/Bugfix case 2 --- .../collection-manager-subject-left.component.html | 2 +- .../collection-manager-subject-left.component.ts | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/ui/src/app/features/collection-manager-subject-left/collection-manager-subject-left/collection-manager-subject-left.component.html b/ui/src/app/features/collection-manager-subject-left/collection-manager-subject-left/collection-manager-subject-left.component.html index d28cc720f3..1003c1d2b8 100644 --- a/ui/src/app/features/collection-manager-subject-left/collection-manager-subject-left/collection-manager-subject-left.component.html +++ b/ui/src/app/features/collection-manager-subject-left/collection-manager-subject-left/collection-manager-subject-left.component.html @@ -19,7 +19,7 @@

{{ 'manage_collection.left_side.name' | translate }}