From 13e76e33e817cb713e5cce9da0be19b8e99332ba Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Sun, 21 Feb 2021 04:47:34 -0500 Subject: [PATCH] :zap: Add text detection operation to AWS Rekognition (#1417) --- .../Aws/Rekognition/AwsRekognition.node.ts | 211 ++++++++++++++---- .../nodes/Aws/Rekognition/GenericFunctions.ts | 12 + 2 files changed, 179 insertions(+), 44 deletions(-) diff --git a/packages/nodes-base/nodes/Aws/Rekognition/AwsRekognition.node.ts b/packages/nodes-base/nodes/Aws/Rekognition/AwsRekognition.node.ts index 72a5806c6c..ad6618232c 100644 --- a/packages/nodes-base/nodes/Aws/Rekognition/AwsRekognition.node.ts +++ b/packages/nodes-base/nodes/Aws/Rekognition/AwsRekognition.node.ts @@ -12,6 +12,7 @@ import { import { awsApiRequestREST, + keysTPascalCase, } from './GenericFunctions'; export class AwsRekognition implements INodeType { @@ -66,6 +67,16 @@ export class AwsRekognition implements INodeType { displayName: 'Type', name: 'type', type: 'options', + displayOptions: { + show: { + operation: [ + 'analyze', + ], + resource: [ + 'image', + ], + }, + }, options: [ { name: 'Detect Faces', @@ -79,6 +90,10 @@ export class AwsRekognition implements INodeType { name: 'Detect Moderation Labels', value: 'detectModerationLabels', }, + { + name: 'Detect Text', + value: 'detectText', + }, { name: 'Recognize Celebrity', value: 'recognizeCelebrity', @@ -185,6 +200,59 @@ export class AwsRekognition implements INodeType { }, default: {}, options: [ + { + displayName: 'Regions of Interest', + name: 'regionsOfInterestUi', + type: 'fixedCollection', + default: '', + placeholder: 'Add Region of Interest', + displayOptions: { + show: { + '/type': [ + 'detectText', + ], + }, + }, + typeOptions: { + multipleValues: true, + }, + options: [ + { + name: 'regionsOfInterestValues', + displayName: 'Region of Interest', + values: [ + { + displayName: 'Height', + name: 'height', + type: 'number', + description: 'Height of the bounding box as a ratio of the overall image height.', + default: 0, + }, + { + displayName: 'Left', + name: 'left', + type: 'number', + description: 'Left coordinate of the bounding box as a ratio of overall image width.', + default: 0, + }, + { + displayName: 'Top', + name: 'top', + type: 'number', + description: 'Top coordinate of the bounding box as a ratio of overall image height.', + default: 0, + }, + { + displayName: 'Width', + name: 'Width', + type: 'number', + description: 'Width of the bounding box as a ratio of the overall image width.', + default: 0, + }, + ], + }, + ], + }, { displayName: 'Version', name: 'version', @@ -199,6 +267,46 @@ export class AwsRekognition implements INodeType { default: '', description: 'If the bucket is versioning enabled, you can specify the object version', }, + { + displayName: 'Word Filter', + name: 'wordFilterUi', + type: 'collection', + default: '', + placeholder: 'Add Word Filter', + displayOptions: { + show: { + '/type': [ + 'detectText', + ], + }, + }, + typeOptions: { + multipleValues: false, + }, + options: [ + { + displayName: 'Min Bounding Box Height', + name: 'MinBoundingBoxHeight', + type: 'number', + description: 'Sets the minimum height of the word bounding box. Words with bounding box heights lesser than this value will be excluded from the result. Value is relative to the video frame height.', + default: 0, + }, + { + displayName: 'Min Bounding Box Width', + name: 'MinBoundingBoxWidth', + type: 'number', + description: 'Sets the minimum width of the word bounding box. Words with bounding boxes widths lesser than this value will be excluded from the result. Value is relative to the video frame width.', + default: 0, + }, + { + displayName: 'Min Confidence', + name: 'MinConfidence', + type: 'number', + description: 'Sets the confidence of word detection. Words with detection confidence below this will be excluded from the result. Values should be between 50 and 100 as Text in Video will not return any result below 50.', + default: 0, + }, + ], + }, { displayName: 'Max Labels', name: 'maxLabels', @@ -280,7 +388,7 @@ export class AwsRekognition implements INodeType { let action = undefined; - let body: IDataObject = {}; + const body: IDataObject = {}; const type = this.getNodeParameter('type', 0) as string; @@ -324,57 +432,72 @@ export class AwsRekognition implements INodeType { action = 'RekognitionService.RecognizeCelebrities'; } - const binaryData = this.getNodeParameter('binaryData', 0) as boolean; + if (type === 'detectText') { + action = 'RekognitionService.DetectText'; - if (binaryData) { + body.Filters = {}; - const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0) as string; + const box = (additionalFields.regionsOfInterestUi as IDataObject || {}).regionsOfInterestValues as IDataObject[] || []; - if (items[i].binary === undefined) { - throw new Error('No binary data exists on item!'); - } - - if ((items[i].binary as IBinaryKeyData)[binaryPropertyName] === undefined) { - throw new Error(`No binary data property "${binaryPropertyName}" does not exists on item!`); - } - - const binaryPropertyData = (items[i].binary as IBinaryKeyData)[binaryPropertyName]; - - body = { - Image: { - Bytes: binaryPropertyData.data, - }, - }; - - } else { - - const bucket = this.getNodeParameter('bucket', i) as string; - - const name = this.getNodeParameter('name', i) as string; - - body = { - Image: { - S3Object: { - Bucket: bucket, - Name: name, - }, - }, - }; - - if (additionalFields.version) { + if (box.length !== 0) { //@ts-ignore - body.Image.S3Object.Version = additionalFields.version as string; + body.Filters.RegionsOfInterest = box.map((box: IDataObject) => { + return { BoundingBox: keysTPascalCase(box) }; + }); } + + const wordFilter = additionalFields.wordFilterUi as IDataObject || {}; + if (Object.keys(wordFilter).length !== 0) { + //@ts-ignore + body.Filters.WordFilter = keysTPascalCase(wordFilter); + } + + const binaryData = this.getNodeParameter('binaryData', 0) as boolean; + + if (binaryData) { + + const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0) as string; + + if (items[i].binary === undefined) { + throw new Error('No binary data exists on item!'); + } + + if ((items[i].binary as IBinaryKeyData)[binaryPropertyName] === undefined) { + throw new Error(`No binary data property "${binaryPropertyName}" does not exists on item!`); + } + + const binaryPropertyData = (items[i].binary as IBinaryKeyData)[binaryPropertyName]; + + Object.assign(body, { + Image: { + Bytes: binaryPropertyData.data, + }, + }); + + } else { + + const bucket = this.getNodeParameter('bucket', i) as string; + + const name = this.getNodeParameter('name', i) as string; + + Object.assign(body, { + Image: { + S3Object: { + Bucket: bucket, + Name: name, + }, + }, + }); + + if (additionalFields.version) { + //@ts-ignore + body.Image.S3Object.Version = additionalFields.version as string; + } + } + responseData = await awsApiRequestREST.call(this, 'rekognition', 'POST', '', JSON.stringify(body), {}, { 'X-Amz-Target': action, 'Content-Type': 'application/x-amz-json-1.1' }); } - - responseData = await awsApiRequestREST.call(this, 'rekognition', 'POST', '', JSON.stringify(body), {}, { 'X-Amz-Target': action, 'Content-Type': 'application/x-amz-json-1.1' }); - - // if (property !== undefined) { - // responseData = responseData[property as string]; - // } } } - if (Array.isArray(responseData)) { returnData.push.apply(returnData, responseData as IDataObject[]); } else { diff --git a/packages/nodes-base/nodes/Aws/Rekognition/GenericFunctions.ts b/packages/nodes-base/nodes/Aws/Rekognition/GenericFunctions.ts index 779f6d1f3d..8afb205f9c 100644 --- a/packages/nodes-base/nodes/Aws/Rekognition/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Aws/Rekognition/GenericFunctions.ts @@ -29,6 +29,10 @@ import { IDataObject, } from 'n8n-workflow'; +import { + pascalCase, +} from 'change-case'; + export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, service: string, method: string, path: string, body?: string | Buffer | IDataObject, query: IDataObject = {}, headers?: object, option: IDataObject = {}, region?: string): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('aws'); if (credentials === undefined) { @@ -128,3 +132,11 @@ export async function awsApiRequestSOAPAllItems(this: IHookFunctions | IExecuteF function queryToString(params: IDataObject) { return Object.keys(params).map(key => key + '=' + params[key]).join('&'); } + +export function keysTPascalCase(object: IDataObject) { + const data: IDataObject = {}; + for (const key of Object.keys(object)) { + data[pascalCase(key as string)] = object[key]; + } + return data; +}