Skip to content

Commit

Permalink
Merge pull request exadel-inc#416 from exadel-inc/feature/EFRS-954-vi…
Browse files Browse the repository at this point in the history
…ew-images-with-extension(.tiff.tif)

EFRS-954 View images with the extension.tiff.tif
  • Loading branch information
pbohdan authored Mar 11, 2021
2 parents ffb9c41 + d6d4973 commit 8a4c629
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 75 deletions.
1 change: 1 addition & 0 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"@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"
},
Expand Down
58 changes: 58 additions & 0 deletions ui/src/app/core/photo-loader/photo-loader.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* 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 { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';

import { LoadingPhotoService } from './photo-loader.service';

describe('LoadingPhotoService', () => {
let service: LoadingPhotoService;
let httpMock: HttpTestingController;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [LoadingPhotoService],
});

service = TestBed.inject(LoadingPhotoService);
httpMock = TestBed.inject(HttpTestingController);
});

it('should be created', () => {
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();
});
});
79 changes: 79 additions & 0 deletions ui/src/app/core/photo-loader/photo-loader.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* 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 { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

import { map, switchMap } from 'rxjs/operators';
import { Observable } from 'rxjs';

import { decode } from 'tiff';
import TiffIfd from 'tiff/lib/tiffIfd';

@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<ImageBitmap> {
return this.http.get(url, { responseType: 'arraybuffer' }).pipe(
map((array: ArrayBuffer) => {
const ifd: TiffIfd = decode(array)[0];
const imageData: ImageData = new ImageData(ifd.width, ifd.height);

for (let i = 0; i < ifd.data.length; i++) {
imageData.data[i] = ifd.data[i];
}

return imageData as ImageData;
}),
switchMap(async (imageData: ImageData) => (await createImageBitmap(imageData)) as ImageBitmap)
);
}

createImage(url: string): Observable<ImageBitmap> {
return this.http
.get(url, { responseType: 'blob' })
.pipe(switchMap(async (blob: Blob) => (await createImageBitmap(blob)) as ImageBitmap));
}

loader(file: File): Observable<ImageBitmap> {
const checkImageType: boolean = this.imageType.includes(file.type);

if (!checkImageType) return;

const url: string = URL.createObjectURL(file);
const type = 'image/tiff';

return file.type === type ? this.tiffConvertor(url) : this.createImage(url);
}
}
4 changes: 4 additions & 0 deletions ui/src/app/data/interfaces/image.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface ImageSize {
width: any;
height: any;
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,23 @@
* permissions and limitations under the License.
*/

import { Component, ElementRef, Input, OnDestroy, ViewChild, OnChanges, SimpleChanges } from '@angular/core';
import { Observable, Subscription } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import { Component, ElementRef, Input, ViewChild, OnChanges, SimpleChanges } from '@angular/core';
import { Observable } from 'rxjs';
import { first, map, tap } from 'rxjs/operators';

import { ServiceTypes } from '../../../../data/enums/service-types.enum';
import {
getImageSize,
ImageSize,
recalculateFaceCoordinate,
resultRecognitionFormatter,
createDefaultImage,
} from '../../face-services.helpers';
import { recalculateFaceCoordinate, resultRecognitionFormatter, createDefaultImage } from '../../face-services.helpers';
import { RequestResult } 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';

@Component({
selector: 'app-recognition-result',
templateUrl: './recognition-result.component.html',
styleUrls: ['./recognition-result.component.scss'],
})
export class RecognitionResultComponent implements OnChanges, OnDestroy {
export class RecognitionResultComponent implements OnChanges {
@Input() file: File;
@Input() requestInfo: RequestInfo;
@Input() printData: RequestResult;
Expand All @@ -45,12 +41,8 @@ export class RecognitionResultComponent implements OnChanges, OnDestroy {
if (canvas) {
this.myCanvas = canvas;

if (this.printSubscription) {
this.printSubscription.unsubscribe();
}

if (this.printData && this.myCanvas) {
this.printSubscription = this.printResult(this.printData).subscribe();
this.printResult(this.printData).pipe(first()).subscribe();
}
}
}
Expand All @@ -59,26 +51,22 @@ export class RecognitionResultComponent implements OnChanges, OnDestroy {
myCanvas: ElementRef;
faceDescriptionHeight = 25;
formattedResult: string;
private imgCanvas: ImageBitmap;

private printSubscription: Subscription;
constructor(private loadingPhotoService: LoadingPhotoService) {}

ngOnChanges(changes: SimpleChanges) {
if (changes?.requestInfo?.currentValue) {
this.formattedResult = resultRecognitionFormatter(this.requestInfo.response);
}
}

ngOnDestroy() {
if (this.printSubscription) {
this.printSubscription.unsubscribe();
}
}

printResult(result: any): Observable<any> {
return getImageSize(this.file).pipe(
tap(({ width, height }) => {
this.canvasSize.height = (height / width) * this.canvasSize.width;
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))
Expand All @@ -88,7 +76,7 @@ export class RecognitionResultComponent implements OnChanges, OnDestroy {
private prepareForDraw(size, rawData): Observable<any> {
return rawData.map(value => ({
box: recalculateFaceCoordinate(value.box, size, this.canvasSize, this.faceDescriptionHeight),
faces: value.faces,
faces: value.subjects,
}));
}

Expand All @@ -99,7 +87,7 @@ export class RecognitionResultComponent implements OnChanges, OnDestroy {
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.face_name, box.x_min + 10, box.y_min - 5);
ctx.fillText(face.subject, box.x_min + 10, box.y_min - 5);
}

private createDetectionImage(ctx, box) {
Expand Down Expand Up @@ -127,28 +115,24 @@ export class RecognitionResultComponent implements OnChanges, OnDestroy {
}
}

createImage(drow) {
const img = new Image();
createImage(draw) {
const ctx: CanvasRenderingContext2D = this.myCanvas.nativeElement.getContext('2d');
img.onload = () => {
ctx.drawImage(img, 0, 0, this.canvasSize.width, this.canvasSize.height);
drow(img, ctx);
};
img.src = URL.createObjectURL(this.file);
ctx.drawImage(this.imgCanvas, 0, 0, this.canvasSize.width, this.canvasSize.height);
draw(ctx);
}

drawRecognitionCanvas(data) {
this.createImage((img, ctx) => {
this.createImage(ctx => {
for (const value of data) {
// eslint-disable-next-line @typescript-eslint/naming-convention
const resultFace = value.faces.length > 0 ? value.faces[0] : { face_name: undefined, similarity: 0 };
const resultFace = value.faces.length > 0 ? value.faces[0] : { subject: undefined, similarity: 0 };
this.createRecognitionImage(ctx, value.box, resultFace);
}
});
}

drawDetectionCanvas(data) {
this.createImage((img, ctx) => {
this.createImage(ctx => {
for (const value of data) {
// eslint-disable-next-line @typescript-eslint/naming-convention
this.createDetectionImage(ctx, value.box);
Expand Down
21 changes: 1 addition & 20 deletions ui/src/app/features/face-services/face-services.helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,8 @@
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
import { Observable } from 'rxjs';

export interface ImageSize {
width: any;
height: any;
}

/**
* Get image size.
*
* @param file File.
*/
export const getImageSize = (file: File): Observable<ImageSize> =>
new Observable(subscriber => {
const img = new Image();
img.onload = () => {
subscriber.next({ width: img.width, height: img.height });
subscriber.complete();
};
img.src = URL.createObjectURL(file);
});
import { ImageSize } from '../../data/interfaces/image';

/**
* Beautify the result JSON format
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,12 @@
import { Component, ElementRef, Input, OnDestroy, ViewChild, OnChanges, SimpleChanges, Output, EventEmitter } from '@angular/core';
import { Observable, Subscription } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import {
getImageSize,
ImageSize,
recalculateFaceCoordinate,
resultRecognitionFormatter,
createDefaultImage,
} from '../../face-services.helpers';
import { recalculateFaceCoordinate, resultRecognitionFormatter, createDefaultImage } from '../../face-services.helpers';
import { RequestResult } 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';

@Component({
selector: 'app-verification-result',
Expand Down Expand Up @@ -59,7 +55,9 @@ export class VerificationResultComponent implements OnChanges, OnDestroy {
private checkFilePrintSub: Subscription;
private processFileCanvasLink: any = null;
private checkFileCanvasLink: any = null;
private imgCanvas: ImageBitmap;

constructor(private loadingPhotoService: LoadingPhotoService) {}
ngOnChanges(changes: SimpleChanges) {
if (changes?.requestInfo?.currentValue) {
this.formattedResult = resultRecognitionFormatter(this.requestInfo.response);
Expand Down Expand Up @@ -102,10 +100,11 @@ export class VerificationResultComponent implements OnChanges, OnDestroy {
}

printResult(canvas: ElementRef, canvasSize: any, file: any, data, key: string): Observable<any> {
return getImageSize(file).pipe(
tap(({ width, height }) => {
canvasSize.height = (height / width) * canvasSize.width;
return this.loadingPhotoService.loader(file).pipe(
tap((bitmap: ImageBitmap) => {
canvasSize.height = (bitmap.height / bitmap.width) * canvasSize.width;
canvas.nativeElement.setAttribute('height', canvasSize.height);
this.imgCanvas = bitmap;
}),
map(imageSize => this.prepareForDraw(imageSize, data, canvasSize, key)),
map(preparedImageData => this.drawCanvas(canvas, preparedImageData, file, canvasSize))
Expand Down Expand Up @@ -141,7 +140,7 @@ export class VerificationResultComponent implements OnChanges, OnDestroy {
* @preparedData prepared box data and faces.
*/
drawCanvas(canvas, data, file, canvasSize) {
this.createImage(canvas, file, canvasSize, (img, ctx) => {
this.createImage(canvas, file, canvasSize, ctx => {
if (!data) return;
for (const value of data) {
// eslint-disable-next-line @typescript-eslint/naming-convention
Expand All @@ -151,12 +150,8 @@ export class VerificationResultComponent implements OnChanges, OnDestroy {
}

createImage(canvas, file, canvasSize, draw) {
const img = new Image();
const ctx: CanvasRenderingContext2D = canvas.nativeElement.getContext('2d');
img.onload = () => {
ctx.drawImage(img, 0, 0, canvasSize.width, canvasSize.height);
draw(img, ctx);
};
img.src = URL.createObjectURL(file);
ctx.drawImage(this.imgCanvas, 0, 0, canvasSize.width, canvasSize.height);
draw(ctx);
}
}

0 comments on commit 8a4c629

Please sign in to comment.