Services/Calibration.js

/**Copyright (c) 2009-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.**/
"use strict";const BadRequestError=require("../Errors/BadRequestError"),deepcopy=require("deepcopy"),Database=require("../Utils/Database"),MessageBroker=require("../Utils/MessageBroker"),filterTemplate=require("../queryTemplates/filter.json"),Validator=require("../Utils/Validator"),calibrationSchema=require("../ajv-schemas/calibration.json"),imageMetadataSchema=require("../ajv-schemas/imageMetadata.json"),InternalServerError=require("../Errors/InternalServerError"),InvalidInputError=require("../Errors/InvalidInputError"),Elasticsearch=require("../Utils/Elasticsearch"),NotificationManager=require("./NotificationManager"),Utils=require("../Utils/Utils");
/** 
 * Class which defines Calibration
 * @memberof mdxWebApiCore.Services
 * */
class Calibration{async#e(e,a){const t=e.getClient(),r={properties:{"calibration.sensors.origin.lng":{type:"double"},"calibration.sensors.origin.lat":{type:"double"},"calibration.sensors.geoLocation.lng":{type:"double"},"calibration.sensors.geoLocation.lat":{type:"double"},"calibration.sensors.coordinates.x":{type:"double"},"calibration.sensors.coordinates.y":{type:"double"},"calibration.sensors.coordinates.z":{type:"double"},"calibration.sensors.translationToGlobalCoordinates.x":{type:"double"},"calibration.sensors.translationToGlobalCoordinates.y":{type:"double"},"calibration.sensors.translationToGlobalCoordinates.z":{type:"double"},"calibration.sensors.scaleFactor":{type:"double"},"calibration.sensors.attributes.value":{type:"text",index:!1},"calibration.sensors.imageCoordinates.x":{type:"double"},"calibration.sensors.imageCoordinates.y":{type:"double"},"calibration.sensors.globalCoordinates.x":{type:"double"},"calibration.sensors.globalCoordinates.y":{type:"double"},"calibration.sensors.rois.roiCoordinates.x":{type:"double"},"calibration.sensors.intrinsicMatrix":{type:"nested",enabled:!1,properties:{inner_array:{type:"double"}}},"calibration.sensors.extrinsicMatrix":{type:"nested",enabled:!1,properties:{inner_array:{type:"double"}}},"calibration.sensors.cameraMatrix":{type:"nested",enabled:!1,properties:{inner_array:{type:"double"}}},"calibration.sensors.homography":{type:"nested",enabled:!1,properties:{inner_array:{type:"double"}}},"calibration.sensors.rois.roiCoordinates.y":{type:"double"},"calibration.sensors.tripwires.wire.p1.x":{type:"double"},"calibration.sensors.tripwires.wire.p1.y":{type:"double"},"calibration.sensors.tripwires.wire.p2.x":{type:"double"},"calibration.sensors.tripwires.wire.p2.y":{type:"double"},"calibration.sensors.tripwires.direction.p1.x":{type:"double"},"calibration.sensors.tripwires.direction.p1.y":{type:"double"},"calibration.sensors.tripwires.direction.p2.x":{type:"double"},"calibration.sensors.tripwires.direction.p2.y":{type:"double"},"calibration.sensorGroupings.groups.attributes.value":{type:"keyword"}}};return(await t.indices.existsTemplate({name:`${a}-template`})).body||await t.indices.putTemplate({name:`${a}-template`,body:{index_patterns:[a],mappings:r}}),{success:!0}}async#a(e,a){const t=`${e.getConfigs().get("indexPrefix")}${Elasticsearch.getIndex("calibration")}`;await this.#e(e,t);const r=e.getClient(),i="timestamp",s="addInsertTimestamp-field-timestamp";(await r.ingest.getPipeline()).body.hasOwnProperty(s)||await Elasticsearch.initTimestampIngestPipeline(r,i);let n={index:t,body:{calibration:a},pipeline:s,id:"calibration"},l=await r.index(n);return await r.indices.refresh({index:t}),l}async#t(e,{calibration:a,timestamp:t,eventType:r}){const i=`${e.getConfigs().get("indexPrefix")}${Elasticsearch.getIndex("calibrationAudit")}`;await this.#e(e,i);const s=e.getClient();let n={index:i,body:{calibration:a,timestamp:t,eventType:r}},l=await s.index(n);return await s.indices.refresh({index:i}),l}async#r(e){const a=`${e.getConfigs().get("indexPrefix")}${Elasticsearch.getIndex("calibration")}`,t=e.getClient();let r={index:a,body:{query:{ids:{values:["calibration"]}}},size:1},i=await Elasticsearch.getSearchResults(t,r,!1),s=null;return i.indexAbsent||(i=Elasticsearch.searchResultFormatter(i.body),i.length>0&&(s=i[0])),s}#i(e=null){let a={version:"1.0",osmURL:"",calibrationType:"",sensors:[]};return null!=e&&a.sensors.push({type:"",id:e,origin:{lng:0,lat:0},geoLocation:{lng:0,lat:0},coordinates:{x:0,y:0},scaleFactor:0,attributes:[],place:[],imageCoordinates:[],globalCoordinates:[],tripwires:[],rois:[]}),a}
/** 
     * returns an object containing calibration and the timestamp associated with it.
     * @public
     * @async
     * @param {Database} documentDb - Database Object
     * @param {Object} [input={}] - Input object.
     * @param {?string} [input.sensorId=null]
     * @returns {Promise<Object>} Calibration Object along with timestamp is returned
     * @example
     * const mdx = require("@nvidia-mdx/web-api-core");
     * const elastic = new mdx.Utils.Elasticsearch({node: "elasticsearch-url"},databaseConfigMap);
     * let calibrationObject = new mdx.Services.Calibration();
     * let {calibration, timestamp} = await calibrationObject.getCalibration(elastic);
     */async getCalibration(e,a={}){let t=Validator.validateJsonSchema(a,{type:"object",additionalProperties:{not:!0,errorMessage:"Invalid additional Input ${0#}."},properties:{sensorId:{type:["string","null"],default:null,minLength:1,errorMessage:{minLength:"sensorId should have atleast 1 character."}}}});if(!t.valid)throw new BadRequestError(t.reason);let r=null;const i=(new Date).toISOString();if("Elasticsearch"!==e.getName())throw new InternalServerError(`Invalid database: ${e.getName()}.`);if(r=await this.#r(e),null!=r){if(null!=a.sensorId){let{calibration:e,timestamp:t}=r,s=null;for(let t of e.sensors)if(t.id===a.sensorId){s=t;break}if(null==s){s=this.#i(a.sensorId).sensors[0],t=i}return e.sensors=[s],{calibration:e,timestamp:t}}return r}return{calibration:this.#i(a.sensorId),timestamp:i}}#s(e){let a=new Map;for(let t of e.sensors)a.set(t.id,t);return a}
/** 
     * returns a success message once the calibration file is uploaded and kafka message is sent.
     * @public
     * @async
     * @param {Database} documentDb - Database Object
     * @param {MessageBroker} messageBroker - MessageBroker Object
     * @param {Object} [input={}] - Input object.
     * @param {?Object} [input.fileDetails=null]
     * @param {?string} [input.fieldName=null]
     * @returns {Promise<Object>} A success message is returned
     * @example
     * const mdx = require("@nvidia-mdx/web-api-core");
     * const elastic = new mdx.Utils.Elasticsearch({node: "elasticsearch-url"},databaseConfigMap);
     * const kafka = new mdx.Utils.Kafka({brokers: ["kafka-broker-url"]}, kafkaConfigMap);
     * let calibrationObject = new mdx.Services.Calibration();
     * let result = await calibrationObject.upload(elastic,kafka,{fileDetails:req.files, fieldName:"configFiles"});
     */async upload(e,a,{fileDetails:t=null,fieldName:r=null}={}){if(null==r)throw new BadRequestError("fieldName is required to access the uploaded files.");if(null==t||!(r in t)||0==t[r].length){throw new BadRequestError("No file has been uploaded. Please upload the calibration file.")}let i=require(t[r][0].path);if(await Utils.deleteFiles([t[r][0].path]),!Validator.validateJsonSchema(i,calibrationSchema,!1).valid)throw new BadRequestError("Uploaded file doesn't follow calibration schema.");if(0==i.sensors.length)throw new InvalidInputError("There should be atleast one sensor in calibration.");if("Elasticsearch"===e.getName()){await this.#a(e,i);let t=await this.#r(e);if(null!=t){let{timestamp:r}=t,s={calibration:i,timestamp:r,eventType:"upsert-all"};if(null==a)await this.#t(e,s);else{let t=new NotificationManager;await Promise.all([this.#t(e,s),t.produceCalibrationNotification(a,s)])}return{success:!0}}throw new InternalServerError("Insertion of calibration config has failed. Couldn't find config before audit index insertion.")}throw new InternalServerError(`Invalid database: ${e.getName()}.`)}
/** 
     * returns a success message once the input calibration is updated/inserted and kafka message is sent.
     * @public
     * @async
     * @param {Database} documentDb - Database Object
     * @param {MessageBroker} messageBroker - MessageBroker Object
     * @param {Object} inputCalibration
     * @returns {Promise<Object>} A success message is returned
     * @example
     * const mdx = require("@nvidia-mdx/web-api-core");
     * const elastic = new mdx.Utils.Elasticsearch({node: "elasticsearch-url"},databaseConfigMap);
     * const kafka = new mdx.Utils.Kafka({brokers: ["kafka-broker-url"]}, kafkaConfigMap);
     * let calibrationObject = new mdx.Services.Calibration();
     * let result = await calibrationObject.upsert(elastic,kafka,inputCalibration);
     */async upsert(e,a,t){if(!Validator.validateJsonSchema(t,calibrationSchema,!1).valid)throw new BadRequestError("Upserted data doesn't follow calibration schema.");if(0==t.sensors.length)throw new InvalidInputError("There should be atleast one sensor in calibration.");let{calibration:r,timestamp:i}=await this.getCalibration(e);if(r.version!==t.version)throw new InvalidInputError("Input's 'version' doesn't match the current calibration.");if(""!==r.calibrationType&&r.calibrationType!==t.calibrationType)throw new InvalidInputError("Input's 'calibrationType' doesn't match the current calibration.");if(""!==r.calibrationType&&r.osmURL!==t.osmURL)throw new InvalidInputError("Input's 'osmURL' doesn't match the current calibration.");r.version=t.version,r.calibrationType=t.calibrationType,r.osmURL=t.osmURL;let s=this.#s(t),n=new Set;for(let e=0;e<r.sensors.length;e++){let a=r.sensors[e];s.has(a.id)&&(r.sensors[e]=s.get(a.id),n.add(a.id))}let l=Utils.setDifference(new Set(s.keys()),n);for(let e of l)r.sensors.push(s.get(e));if("Elasticsearch"===e.getName()){await this.#a(e,r);let s=await this.#r(e);if(null!=s){let{timestamp:r}=s;if(Utils.tsCompare(r,"<=",i))throw new InternalServerError("Insertion of calibration config has failed. Couldn't find config before audit index insertion.");let n={calibration:t,timestamp:r,eventType:"upsert"};if(null==a)await this.#t(e,n);else{let t=new NotificationManager;await Promise.all([this.#t(e,n),t.produceCalibrationNotification(a,n)])}return{success:!0}}throw new InternalServerError("Insertion of calibration config has failed. Couldn't find config before audit index insertion.")}throw new InternalServerError(`Invalid database: ${e.getName()}.`)}
/** 
     * returns a success message along with invalid input and deleted sensors once the sensors in calibration have been deleted and kafka message is sent.
     * @public
     * @async
     * @param {Database} documentDb - Database Object
     * @param {MessageBroker} messageBroker - MessageBroker Object
     * @param {Object} input - Input object.
     * @param {Array<string>} input.sensorIds
     * @returns {Promise<Object>} A success message is returned
     * @example
     * const mdx = require("@nvidia-mdx/web-api-core");
     * const elastic = new mdx.Utils.Elasticsearch({node: "elasticsearch-url"},databaseConfigMap);
     * const kafka = new mdx.Utils.Kafka({brokers: ["kafka-broker-url"]}, kafkaConfigMap);
     * let calibrationObject = new mdx.Services.Calibration();
     * let result = await calibrationObject.deleteSensors(elastic,kafka,{sensorIds:["abc","xyz"]});
     */async deleteSensors(e,a,t){let r=Validator.validateJsonSchema(t,{type:"object",additionalProperties:{not:!0,errorMessage:"Invalid additional Input ${0#}."},properties:{sensorIds:{type:"array",minItems:1,items:{type:"string",minLength:1,errorMessage:{minLength:"sensorId should have atleast 1 character."}},errorMessage:{minItems:"sensorIds should have atleast 1 item."}}},required:["sensorIds"],errorMessage:{required:"Input should have required property 'sensorIds'."}},!1);if(!r.valid)throw new BadRequestError(r.reason);let{calibration:i,timestamp:s}=await this.getCalibration(e),n=this.#s(i),l=new Array,o=new Array,d=new Set(t.sensorIds);for(let e of d)n.has(e)?(l.push(n.get(e)),n.delete(e)):o.push(e);if(o.length==d.size)return{success:{partial:!1,complete:!1},invalidSensors:o,deletedCalibration:null};let c={version:i.version,osmURL:i.osmURL,calibrationType:i.calibrationType,sensors:l};if(i.sensors=Array.from(n.values()),"Elasticsearch"===e.getName()){await this.#a(e,i);let t=await this.#r(e);if(null!=t){let{timestamp:r}=t;if(Utils.tsCompare(r,"<=",s))throw new InternalServerError("Insertion of calibration config has failed. Couldn't find config before audit index insertion.");let i={calibration:c,timestamp:r,eventType:"delete"};if(null==a)await this.#t(e,i);else{let t=new NotificationManager;await Promise.all([this.#t(e,i),t.produceCalibrationNotification(a,i)])}return 0==o.length?{success:{partial:!1,complete:!0},invalidSensors:null,deletedCalibration:c}:{success:{partial:!0,complete:!1},invalidSensors:o,deletedCalibration:c}}throw new InternalServerError("Insertion of calibration config has failed. Couldn't find config before audit index insertion.")}throw new InternalServerError(`Invalid database: ${e.getName()}.`)}async#n(e,a){let t=new Array;if("Elasticsearch"===e.getName()){const r=`${e.getConfigs().get("indexPrefix")}${Elasticsearch.getIndex("calibrationImages")}`,i=e.getClient();let s={index:r,body:{query:{ids:{values:a}}},size:a.length},n=await Elasticsearch.getSearchResults(i,s,!1);return n.indexAbsent||(t=Elasticsearch.searchResultFormatter(n.body),t=t.filter((e=>!e.deleted))),t}throw new InternalServerError(`Invalid database: ${e.getName()}.`)}
/** 
     * returns an object containing calibration maps.
     * @public
     * @param {Object} calibration
     * @returns {Promise<Object>} An object containing calibration maps is returned
     * @example
     * const mdx = require("@nvidia-mdx/web-api-core");
     * let calibrationObject = new mdx.Services.Calibration();
     * let calibrationMaps = calibrationObject.getCalibrationMaps(calibration);
     */getCalibrationMaps(e){let a={placeHierarchyMap:new Map,sensorPlaceMap:new Map};for(let t of e.sensors){let e=null;for(let r of t.place)if(null==e)e=`${r.name}=${r.value}`,a.placeHierarchyMap.has(e)||a.placeHierarchyMap.set(e,{places:null,sensors:null});else{null==a.placeHierarchyMap.get(e).places&&(a.placeHierarchyMap.get(e).places=new Set);let t=`${e}/${r.name}=${r.value}`;a.placeHierarchyMap.get(e).places.add(t),a.placeHierarchyMap.has(t)||a.placeHierarchyMap.set(t,{places:null,sensors:null}),e=t}null!=e&&(null==a.placeHierarchyMap.get(e).sensors&&(a.placeHierarchyMap.get(e).sensors=new Set),a.placeHierarchyMap.get(e).sensors.add(t.id)),a.sensorPlaceMap.set(t.id,e)}return a}#l(e){let a=new Map;for(let t of e.images)if(a.has(t.fileName)){let e=a.get(t.fileName);e.push(t),a.set(t.fileName,e)}else a.set(t.fileName,[t]);return a}async#o(e,a){let t=new Map;if("Elasticsearch"===e.getName()){let r=deepcopy(filterTemplate);const i=`${e.getConfigs().get("indexPrefix")}${Elasticsearch.getIndex("calibrationImages")}`,s=e.getClient();let n=new Array;for(let e of a)n.push({term:{"fileName.keyword":e}});r.query.bool.must.push({bool:{should:n,minimum_should_match:1}}),r.aggs={fileNames:{terms:{field:"fileName.keyword",size:a.length}}};let l={index:i,body:r,size:0},o=await Elasticsearch.getSearchResults(s,l,!1);if(!o.indexAbsent)for(let e of o.body.aggregations.fileNames.buckets)t.set(e.key,e.doc_count);return t}throw new InternalServerError(`Invalid database: ${e.getName()}.`)}async#d(e,a){if("Elasticsearch"===e.getName()){const t=`${e.getConfigs().get("indexPrefix")}${Elasticsearch.getIndex("calibrationImages")}`,r=e.getClient(),i="timestamp",s=`addInsertTimestamp-field-${i}`;(await r.ingest.getPipeline()).body.hasOwnProperty(s)||await Elasticsearch.initTimestampIngestPipeline(r,i);let n=a.flatMap((e=>[{index:{_index:t,_id:e.id}},e.body]));return await r.bulk({refresh:!0,body:n,pipeline:s}),{success:!0}}throw new InternalServerError(`Invalid database: ${e.getName()}.`)}
/** 
     * returns a success message once the calibration images are uploaded.
     * @public
     * @async
     * @param {Database} documentDb - Database Object
     * @param {Object} [input={}] - Input object.
     * @param {?Object} [input.fileDetails=null]
     * @param {?string} [input.imageFieldName=null]
     * @param {?string} [input.metadataFieldName=null]
     * @returns {Promise<Object>} A success message is returned
     * @example
     * const mdx = require("@nvidia-mdx/web-api-core");
     * const elastic = new mdx.Utils.Elasticsearch({node: "elasticsearch-url"},databaseConfigMap);
     * let calibrationObject = new mdx.Services.Calibration();
     * let result = await calibrationObject.uploadImages(elastic,{fileDetails:req.files, imageFieldName:"images", metadataFieldName: "imageMetadata"});
     */async uploadImages(e,{fileDetails:a=null,imageFieldName:t=null,metadataFieldName:r=null}={}){if(null==t)throw new BadRequestError("imageFieldName is required to access the uploaded files.");if(null==r)throw new BadRequestError("metadataFieldName is required to access the uploaded files.");if(null==a){throw new BadRequestError("No file has been uploaded. Please upload images and imageMetadata file.")}if(!(t in a)||0==a[t].length){throw new BadRequestError("No images have been uploaded. Please upload images.")}if(!(r in a)||0==a[r].length){throw new BadRequestError("imageMetadata file has not been uploaded. Please upload the image metadata file.")}let i=new Set,s=new Array,n=require(a[r][0].path);if(i.add(a[r][0].path),!Validator.validateJsonSchema(n,imageMetadataSchema,!1).valid)throw new BadRequestError("Uploaded file doesn't follow imageMetadata schema.");let l=this.#l(n),o=new Map;for(let e of a[t])l.has(e.originalname)?o.set(e.originalname,e):(i.add(e.path),s.push(e.originalname));if(s.length==a[t].length)return await Utils.deleteFiles(Array.from(i)),{success:{partial:!1,complete:!1},invalidImages:s};let d=new Array;for(let[e,a]of l.entries())if(o.has(e))for(let e of a)e.hasOwnProperty("sensorId")?d.push(`sensorId-${e.sensorId}-${e.view}`):d.push(`place-${e.place}-${e.view}`);let c=await this.#n(e,d);if(c.length>0){let a=new Map;for(let e of c)if(a.has(e.fileName)){let t=a.get(e.fileName);a.count+=1,a.set(e.fileName,t)}else a.set(e.fileName,{path:e.path,count:1});let t=await this.#o(e,Array.from(a.keys()));for(let[e,r]of t.entries()){let t=a.get(e);r==t.count&&i.add(t.path)}}await Utils.deleteFiles(Array.from(i));let p=new Array;for(let[e,a]of o.entries()){let t=l.get(e);for(let e of t){let t={view:e.view,fileName:a.filename,path:a.path,deleted:!1};e.hasOwnProperty("sensorId")?(t.sensorId=e.sensorId,t.id=`sensorId-${t.sensorId}-${t.view}`):(t.place=e.place,t.id=`place-${t.place}-${t.view}`),p.push({id:t.id,body:t})}}return await this.#d(e,p),0==s.length?{success:{complete:!0,partial:!1},invalidImages:null}:{success:{complete:!1,partial:!0},invalidImages:s}}async#c(e,{sensorId:a,place:t,view:r}){let i=null;if("Elasticsearch"===e.getName()){const s=`${e.getConfigs().get("indexPrefix")}${Elasticsearch.getIndex("calibrationImages")}`,n=null!=a?`sensorId-${a}-${r}`:`place-${t}-${r}`,l=e.getClient();let o={index:s,body:{query:{ids:{values:[n]}}},size:1},d=await Elasticsearch.getSearchResults(l,o,!1);return d.indexAbsent||(d=Elasticsearch.searchResultFormatter(d.body),d.length>0&&(i=d[0],i.deleted&&(i=null))),i}throw new InternalServerError(`Invalid database: ${e.getName()}.`)}
/** 
     * returns the path of calibration image.
     * @public
     * @async
     * @param {Database} documentDb - Database Object
     * @param {Object} input - Input object.
     * @param {string} [input.sensorId] - Either sensorId or place should be present.
     * @param {string} [input.place] - Either sensorId or place should be present.
     * @param {("camera-view"|"warped-camera-view"|"plan-view")} input.view
     * @returns {Promise<string>} Path of calibration image is returned
     * @example
     * const mdx = require("@nvidia-mdx/web-api-core");
     * const elastic = new mdx.Utils.Elasticsearch({node: "elasticsearch-url"},databaseConfigMap);
     * let calibrationObject = new mdx.Services.Calibration();
     * let result = await calibrationObject.getImage(elastic,{sensorId:"abc", view:"warped-camera-view"});
     */async getImage(e,a){let t=Validator.validateJsonSchema(a,{type:"object",additionalProperties:!1,properties:{sensorId:{type:["string","null"],default:null,minLength:1,errorMessage:{minLength:"sensorId should have atleast 1 character."}},place:{type:["string","null"],default:null,minLength:1,errorMessage:{minLength:"place should have atleast 1 character."}},view:{type:"string",enum:["camera-view","warped-camera-view","plan-view"]}},required:["view"],oneOf:[{required:["sensorId"]},{required:["place"]}],errorMessage:{required:"Input should have required property 'view'.",oneOf:"Input can either have 'sensorId' or 'place'."}});if(!t.valid)throw new BadRequestError(t.reason);let r=await this.#c(e,a);if(null==r)throw new InvalidInputError("Image doesn't exist for the given input params.");return r.path}async#p(e,{sensorId:a,place:t,view:r}){const i=`${e.getConfigs().get("indexPrefix")}${Elasticsearch.getIndex("calibrationImages")}`;let s=deepcopy(filterTemplate);null!=a?s.query.bool.must.push({term:{"sensorId.keyword":a}}):null!=t&&s.query.bool.must.push({term:{"place.keyword":t}}),null!=r&&s.query.bool.must.push({term:{"view.keyword":r}}),s.query.bool.must.push({term:{deleted:!1}});const n=e.getClient();let l={index:i,body:s,size:1e4,_sourceIncludes:["view","place","sensorId"]},o=await Elasticsearch.getSearchResults(n,l,!1);return o.indexAbsent||(o=Elasticsearch.searchResultFormatter(o.body)),o}
/** 
     * returns the calibration image metadata.
     * @public
     * @async
     * @param {Database} documentDb - Database Object
     * @param {Object} [input={}] - Input object.
     * @param {?string} [input.sensorId=null] - sensorId is an optional param. Either sensorId or place can be present.
     * @param {?string} [input.place=null] - place is an optional param. Either sensorId or place can be present.
     * @param {(null|"camera-view"|"warped-camera-view"|"plan-view")} [input.view=null]
     * @returns {Promise<Object>} Object containing image metadata is returned
     * @example
     * const mdx = require("@nvidia-mdx/web-api-core");
     * const elastic = new mdx.Utils.Elasticsearch({node: "elasticsearch-url"},databaseConfigMap);
     * let calibrationObject = new mdx.Services.Calibration();
     * let result = await calibrationObject.getImageMetadata(elastic,{sensorId:"abc"});
     */async getImageMetadata(e,a={}){let t=Validator.validateJsonSchema(a,{type:"object",additionalProperties:{not:!0,errorMessage:"Invalid additional Input ${0#}."},properties:{sensorId:{type:["string","null"],default:null,minLength:1,errorMessage:{minLength:"sensorId should have atleast 1 character."}},place:{type:["string","null"],default:null,minLength:1,errorMessage:{minLength:"place should have atleast 1 character."}},view:{type:["string","null"],enum:[null,"camera-view","warped-camera-view","plan-view"],default:null,errorMessage:{enum:"view is an optional input, but when present it must have one of the following values: 'camera-view', 'warped-camera-view' or 'plan-view'."}}},if:{not:{properties:{place:{const:null}}}},then:{properties:{sensorId:{const:null}}},errorMessage:{if:"Input can have either 'sensorId' or 'place'. Both of them are optional inputs, but can't be present together."}});if(!t.valid)throw new BadRequestError(t.reason);let r=new Array;if("Elasticsearch"===e.getName())return r=await this.#p(e,a),{imageMetadata:r};throw new InternalServerError(`Invalid database: ${e.getName()}.`)}async#u(e,{calibrationImages:a}){let t=new Map;for(let{sensorId:e,place:r,view:i}of a){const a=null!=e?`sensorId-${e}-${i}`:`place-${r}-${i}`;t.set(a,{sensorId:e,place:r,view:i})}let r=await this.#n(e,Array.from(t.keys())),i=null,s=null;if(0==r.length)i=new Set(t.keys());else{s=new Set;let a=new Map,n=new Array;for(let e of r){if(s.add(e.id),a.has(e.fileName)){let t=a.get(e.fileName);t.deletionCount+=1,a.set(e.fileName,t)}else a.set(e.fileName,{deletionCount:1,path:e.path});e.path=null,e.fileName=null,e.deleted=!0,n.push({id:e.id,body:e})}i=s.size==t.size?null:Utils.setDifference(new Set(t.keys()),s);let l=await this.#o(e,Array.from(a.keys())),o=new Set;for(let[e,t]of l.entries()){let r=a.get(e);t==r.deletionCount&&o.add(r.path)}await Promise.all([this.#d(e,n),Utils.deleteFiles(Array.from(o))])}let n=null==s?null:new Array,l=null==i?null:new Array;if(null!=s)for(let e of s){let{sensorId:a,place:r,view:i}=t.get(e);null!=a?n.push({sensorId:a,view:i}):n.push({place:r,view:i})}if(null!=i)for(let e of i){let{sensorId:a,place:r,view:i}=t.get(e);null!=a?l.push({sensorId:a,view:i}):l.push({place:r,view:i})}let o={invalidInput:l,deletedCalibrationImages:n};return o.success=null==i?{complete:!0,partial:!1}:null==s?{complete:!1,partial:!1}:{complete:!1,partial:!0},o}
/** 
     * returns a success message along with invalid input and deleted calibration images.
     * @public
     * @async
     * @param {Database} documentDb - Database Object
     * @param {Object} input - Input object.
     * @param {Array<{sensorId:?string,place:?string,view:("camera-view"|"warped-camera-view"|"plan-view")}>} input.calibrationImages - Each item in the array can contain either sensorId or place
     * @returns {Promise<Object>} A success message is returned
     * @example
     * const mdx = require("@nvidia-mdx/web-api-core");
     * const elastic = new mdx.Utils.Elasticsearch({node: "elasticsearch-url"},databaseConfigMap);
     * let calibrationObject = new mdx.Services.Calibration();
     * let result = await calibrationObject.deleteCalibrationImages(elastic,{calibrationImages:[{sensorId:"abc", view:"warped-camera-view"}]});
     */async deleteCalibrationImages(e,a){let t=Validator.validateJsonSchema(a,{type:"object",additionalProperties:{not:!0,errorMessage:"Invalid additional Input ${0#}."},properties:{calibrationImages:{type:"array",items:{type:"object",additionalProperties:{not:!0,errorMessage:"Invalid additional Input ${0#}."},properties:{sensorId:{type:["string","null"],default:null,minLength:1,errorMessage:{minLength:"sensorId should have atleast 1 character."}},place:{type:["string","null"],default:null,minLength:1,errorMessage:{minLength:"place should have atleast 1 character."}},view:{type:"string",enum:["camera-view","warped-camera-view","plan-view"],errorMessage:{enum:"view must have one of the following values: 'camera-view', 'warped-camera-view' or 'plan-view'."}}},required:["view"],oneOf:[{required:["sensorId"]},{required:["place"]}],errorMessage:{required:"Object in calibrationImages array should have required property 'view'.",oneOf:"Input should have either 'sensorId' or 'place'."}}}},required:["calibrationImages"],errorMessage:{required:"Input should have required property 'calibrationImages'."}},!1);if(!t.valid)throw new BadRequestError(t.reason);if("Elasticsearch"===e.getName()){return await this.#u(e,a)}throw new InternalServerError(`Invalid database: ${e.getName()}.`)}}module.exports=Calibration;