From aad9c65cf0d455c514fe5366577c5483d915e658 Mon Sep 17 00:00:00 2001 From: brenault Date: Thu, 2 Mar 2023 13:35:32 +0100 Subject: [PATCH] export Confiance - WIP --- cli/pixano-cli.js | 2 +- config/minio.json | 8 - config/minio_old.json | 9 - frontend/src/actions/media.js | 8 + frontend/src/views/app-dashboard-admin.js | 2 +- frontend/src/views/app-datasets-manager.js | 2 +- frontend/src/views/app-label.js | 16 +- frontend/src/views/app-project-manager.js | 21 +- package.json | 1 + server/router.js | 4 + server/routes/minio_plugin.js | 169 ++++++++------ server/routes/tasks.js | 245 +++++++++++++++++++-- 12 files changed, 381 insertions(+), 106 deletions(-) delete mode 100644 config/minio.json delete mode 100644 config/minio_old.json diff --git a/cli/pixano-cli.js b/cli/pixano-cli.js index ca24c4a..9825b18 100644 --- a/cli/pixano-cli.js +++ b/cli/pixano-cli.js @@ -41,7 +41,7 @@ export function cli(argv) { .option('minio-endpoint', 'Minio endpoint', 'minio-storage.apps.confianceai-public.irtsysx.fr') .option('minio-accessKey', 'Minio accessKey', "developer") .option('minio-secretKey', 'Minio secretKey', "password") - .option('data-provider', 'Data Provider url', 'http://localhost:3011') // 'http://192.168.102.130:3010') + .option('data-provider', 'Data Provider url', 'http://localhost:3010') // 'http://192.168.102.130:3010') //.option('data-provider', 'Data Provider url', ' http://debiai-data-provider-os:3011') //version interne Kubernetes, ecrasé par YAML .example('pixano /path/to/workspace','The most common way to use Pixano:') .example('pixano --workspace /path/to/workspace --port 5001','Run on a specific port:') diff --git a/config/minio.json b/config/minio.json deleted file mode 100644 index b484a08..0000000 --- a/config/minio.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "fake": false, - "endPoint": "minio", - "port": 9000, - "useSSL": false, - "accessKey": "bwR5Xts5PBSIj8lM", - "secretKey": "F0J4f9MaFwXXI9UiVIm0zDxZbbgoDnvi" -} diff --git a/config/minio_old.json b/config/minio_old.json deleted file mode 100644 index 78f2a73..0000000 --- a/config/minio_old.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "fake": false, - "endPoint": "minio", - "port": 9000, - "useSSL": false, - "accessKey": "developer", - "secretKey": "password", - "bucket_name": "pixanoimagesselection" -} diff --git a/frontend/src/actions/media.js b/frontend/src/actions/media.js index c3879fb..6be78a3 100644 --- a/frontend/src/actions/media.js +++ b/frontend/src/actions/media.js @@ -84,6 +84,14 @@ export const importFromKafka = () => (dispatch) => { return GET('/api/v1/datasets/import_from_kafka', dispatch); } +/** + * partial (by image) export to Data Provider (Confiance). + */ +export const partialExporttoDP = (task_name, media_id) => (dispatch) => { + //console.log("partialExporttoDP"); + return GET('/api/v1/dataprovider/partial_export_to_dataprovider/'+task_name+'/'+media_id, dispatch); +} + /** * get project list from Data Provider (Confiance). */ diff --git a/frontend/src/views/app-dashboard-admin.js b/frontend/src/views/app-dashboard-admin.js index 0a4d5bc..c6a71aa 100644 --- a/frontend/src/views/app-dashboard-admin.js +++ b/frontend/src/views/app-dashboard-admin.js @@ -705,7 +705,7 @@ class AppDashboardAdmin extends TemplatePage { return html`

Select a task:

- { + { if (tasks[e.detail.index] && tasks[e.detail.index].name !== taskName) { store.dispatch(updateTaskName(tasks[e.detail.index].name)); this.refreshGrid(); diff --git a/frontend/src/views/app-datasets-manager.js b/frontend/src/views/app-datasets-manager.js index cfdc784..3be4210 100644 --- a/frontend/src/views/app-datasets-manager.js +++ b/frontend/src/views/app-datasets-manager.js @@ -571,7 +571,7 @@ class AppDatasetsManager extends connect(store)(TemplatePage) { return html`

Select a dataset:

- { + { if (this.datasets[e.detail.index] && this.datasets[e.detail.index].id !== datasetId) { this.datasetIdx = e.detail.index; this.refreshGrid(); diff --git a/frontend/src/views/app-label.js b/frontend/src/views/app-label.js index 4f52d18..024c569 100644 --- a/frontend/src/views/app-label.js +++ b/frontend/src/views/app-label.js @@ -13,6 +13,8 @@ import '@material/mwc-icon-button'; import '@material/mwc-snackbar'; import { AppExplore } from './app-explore'; +import { partialExporttoDP } from '../actions/media'; + class AppLabel extends AppExplore { static get properties() { @@ -114,6 +116,18 @@ class AppLabel extends AppExplore { console.log('_submissionHelper'); await store.dispatch(putJob(objective)); await store.dispatch(putLabels()); + + //Confiance DP export + //TODO: test si on est dans Confiance (mais pour le moment on va dire que oui) + //TODO: export sur Submit only (+validate/Reject)? sur skip too ? + if(objective !== 'skip') { + const taskName = getState().application.taskName; + const media_id = getState('media').info.id; + await store.dispatch(partialExporttoDP(taskName, media_id)) + .then(ret => { console.log("Export OK"+ ret); }) + .catch(err => { console.log("ERROR Export", err); this.errorPopup("EXPORT ERROR\n" + err.message); }) + } + } // Job has either been reassigned to someone else or is dead. catch (err) { console.log('err1', err); this.errorPopup(err.message); } @@ -152,7 +166,7 @@ class AppLabel extends AppExplore { } /** - * Submit job. + * Skip job. */ skip() { this._submissionHelper('skip'); diff --git a/frontend/src/views/app-project-manager.js b/frontend/src/views/app-project-manager.js index 0345518..6d37168 100644 --- a/frontend/src/views/app-project-manager.js +++ b/frontend/src/views/app-project-manager.js @@ -111,6 +111,16 @@ class AppProjectManager extends connect(store)(TemplatePage) { browserElem.open = true; } + /** + * Fired when exporting a project to Confiance DB + */ + onExportToDP() { + const browserElem = this.shadowRoot.getElementById('dialog-import-export-path'); + this.importExportText = 'export'; + browserElem.mode = 'export'; + browserElem.open = true; + } + /** * Fired when importing a project */ @@ -407,10 +417,14 @@ class AppProjectManager extends connect(store)(TemplatePage) { type="button" title="Copy database with annotation and their status into an archive" @click="${() => snapshotProject()}">Snapshot - Export + Export to Confiance DB html``)}" return html`
${this.taskHeader}
- ${this.tasks.map((t) => html``)} - New task + ${this.tasks.map((t) => html``)}
diff --git a/package.json b/package.json index bf3c6ab..cc72da8 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "fs": "0.0.1-security", "git-rev-sync": "^3.0.2", "glob": "^7.1.6", + "google-palette": "^1.1.0", "image-thumbnail": "1.0.14", "jsonwebtoken": "^8.5.1", "kafkajs": "^1.15.0", diff --git a/server/router.js b/server/router.js index e4aca76..0d40117 100644 --- a/server/router.js +++ b/server/router.js @@ -40,6 +40,8 @@ const { get_tasks, projects_from_dataprovider, selections_from_dataprovider, id_list_from_dataprovider, + partial_export_to_dataprovider, + export_tasks_to_dataprovider, export_tasks } = require('./routes/tasks'); const { snapshot_project } = require('./routes/project'); const { get_results, @@ -77,6 +79,8 @@ router.delete('/users/:username', middleware.checkToken, delete_user); router.get('/profile', middleware.checkToken, get_profile); //for Confiance data provider +router.get('/dataprovider/export_tasks_to_dataprovider/:task_name', middleware.checkToken, export_tasks_to_dataprovider); +router.get('/dataprovider/partial_export_to_dataprovider/:task_name/:media_id', middleware.checkToken, partial_export_to_dataprovider); router.get('/dataprovider/projects_from_dataprovider', middleware.checkToken, projects_from_dataprovider); router.get('/dataprovider/selections_from_dataprovider/:project_name', middleware.checkToken, selections_from_dataprovider); router.get('/dataprovider/id_list_from_dataprovider/:project_name/:sel_id/:sel_name', middleware.checkToken, id_list_from_dataprovider); diff --git a/server/routes/minio_plugin.js b/server/routes/minio_plugin.js index 033a32c..016f4bf 100644 --- a/server/routes/minio_plugin.js +++ b/server/routes/minio_plugin.js @@ -14,15 +14,21 @@ function waitFor(conditionFunction) {//rajouter un timeout /** * Download files form minio - * @param ["id1","id2",...]: a list of ids to search for in the bucket + * @param minio_files {path0_full: [{bucket: bucket name, path: path0, file: file_id }, ...], ..., pathN: [{...}, ...]}: a struct of bucket paths and ids to search for + * pathX_full is the full "key", aka bucket_name/path, used to group files for seeking * @param workspace: Pixano's current workspace (images will be copied inside of it) * @return ["url1","url2",...]: returns the list of the corresponding URLs * @doc https://docs.min.io/docs/javascript-client-api-reference */ -const downloadFilesFromMinio = async (listIds,workspace,selection_name, bucket_name, bucket_path) => { - console.log("downloadFilesFromMinio (",selection_name,") - nbSamples", listIds.length); +const downloadFilesFromMinio = async (minio_files, workspace, selection_name) => { + //TODO : wrong, need to iterate and sum in each array + let num_ids = 0; + for (const k in minio_files) { + num_ids += minio_files[k].length; + } + console.log("downloadFilesFromMinio (",selection_name,") - nbSamples", num_ids); - const pixano_local_save_image_directory = workspace+'/minio_saved_images/'+'importedFromDebiai/'+selection_name+'/';//... TODO + const pixano_local_save_image_directory = workspace+'/minio_saved_images/importedFromDebiai/'+selection_name+'/';//... TODO var listOfURLs = []; // Instantiate the minio client with the endpoint @@ -35,83 +41,112 @@ const downloadFilesFromMinio = async (listIds,workspace,selection_name, bucket_n accessKey: options.minioAccessKey, secretKey: options.minioSecretKey }}); - // console.log("Minio config:", minio_config); + //console.log("Minio config:", minio_config); + const minioClient = new Client(minio_config); //console.log("Minio :", minioClient); + /** test: list availble buckets + minioClient.listBuckets(function(err, buckets) { + if (err) return console.log(err); + console.log('available buckets :', buckets); + }) + */ - - // check if bucket exists/can be accessed - // var bucket_name = CONFIG.bucket_name; - // if (project_name==='Valeo') bucket_name = 'pixanovaleousecase';// special case : different bucket - - var exists = await minioClient.bucketExists(bucket_name).catch((e) => {throw "Minio: Bucket does not exist\n"+e;}); - if (!exists) throw "Minio: Bucket "+bucket_name+" does not exist"; - console.log(`Bucket ${bucket_name} exists.`); + // check if bucket(s) exists/can be accessed + const unique_buckets = new Set() + for (const k in minio_files) { + for(const f of minio_files[k]) { + unique_buckets.add(f['bucket']); + } + } + console.log('Buckets', unique_buckets); + for (const bucket_name of unique_buckets) { + var exists = await minioClient.bucketExists(bucket_name).catch((e) => {throw "Minio: Bucket does not exist\n"+e;}); + if (!exists) throw "Minio: Bucket "+bucket_name+" does not exist"; + console.log(`Bucket ${bucket_name} exists.`); + } // Extract the list of image from the bucket - var data = []; - var doneData = 0; - console.log(`Seek in ${bucket_name} path: ${bucket_path}`); const bar = new cliProgress.SingleBar({ format: 'Image retrieval from Minio | {bar} | {percentage}% || {value}/{total} jobs' }); - bar.start(listIds.length, 0); - var stream = minioClient.listObjects(bucket_name, bucket_path, true); - stream.on('error', function (e) { throw(e); }); - stream.on('data', function (obj) { data.push(obj); }); - stream.on('end', function () { - if (data.length===0) throw "Minio: no data found in bucket "+bucket_name; - //for (var i=0; i {//search for urls that correspond to the input list and get them - //const obj = data[i]; - if ('name' in obj) { - // console.log("name in obj"); - var corresponding = false; - // FORMAT of sample_ids : - // "sample_ids": [ - // { - // "dataset": "not_labeled", - // "subject": "c34", - // "relative_path": "not_labeled/c34/", - // "url": "", - // "type": "image", - // "id": "191003-2237_2953236_ - C101_OK.jpgImage" - // }, ... ] - var sample; - for (var i=0; i {//search for urls that correspond to the input list and get them + //const obj = data[i]; + if ('name' in obj) { + // console.log("name in obj"); + var corresponding = false; + // FORMAT of sample_ids : + // "sample_ids": [ + // { + // "dataset": "not_labeled", + // "subject": "c34", + // "relative_path": "not_labeled/c34/", + // "url": "", + // "type": "image", + // "id": "191003-2237_2953236_ - C101_OK.jpgImage" + // }, ... ] + var sample; + for (var i=0; i { console.log("test",doneData,data.length); if (data.length>0) return(doneData === data.length); }); + console.log("waitFor",doneData,data.length); + await waitFor(() => { console.log("test",doneData,data.length); if (data.length>0) return(doneData === data.length); }); + } //console.log("listOfURLs=",listOfURLs); - console.info("Minio: got "+listOfURLs.length+" images over "+listIds.length+" in the input list"); + console.info("Minio: got "+listOfURLs.length+" images over "+num_ids+" in the input list"); bar.stop(); if (listOfURLs.length===0) throw "Minio: no corresponding data found in bucket "+bucket_name; diff --git a/server/routes/tasks.js b/server/routes/tasks.js index eaf7a91..6ed68ea 100644 --- a/server/routes/tasks.js +++ b/server/routes/tasks.js @@ -13,6 +13,7 @@ const { getOrcreateDataset, getAllDataFromDataset, const { downloadFilesFromMinio } = require('./minio_plugin').default; const { createJob } = require('./jobs'); const fetch = require("node-fetch"); +const palette = require('google-palette'); const annotation_format_version = "0.9"; @@ -382,13 +383,16 @@ async function process_selection(project_name, sel_name, selections) { dp_res = await get_dp_minio_uris(project_name, selections) .then(res => {return res}) .catch(err=>{ throw "Error in get_dp_minio_uris "+ err}); - console.log("dp_res FULL", dp_res); + //console.log("dp_res FULL", dp_res); + let minio_files = {} + /* bucket_names = [] bucket_paths = [] filenames = [] + */ if (!dp_res || dp_res.length == 0) { throw "ERROR empty data for selection"; } Object.keys(dp_res).forEach(key => { - console.log("dp_res[", key, "]", dp_res[key].storage); + //console.log("dp_res[", key, "].storage", dp_res[key].storage); //console.log("dp_res[", key, "].annotations", dp_res[key].annotations); if(!dp_res[key].storage) { throw "No storage info in selection, import aborted";} if(!dp_res[key].storage.minio) { throw "No minio storage info in selection, import aborted";} @@ -403,15 +407,22 @@ async function process_selection(project_name, sel_name, selections) { bpath = dp_res[key].storage.minio.basepath; if (bpath.startsWith(bname)) { //slice parce que le bucket est mis en prefixe du path !! - bpath = bpath.slice(bname.length); + bpath_clean = bpath.slice(bname.length); } + if (!(bpath in minio_files)) { minio_files[bpath] = [] } + minio_files[bpath].push({'bucket': bname, 'path': bpath_clean, 'file': dp_res[key].storage.filename}) + /* bucket_names.push(bname); bucket_paths.push(bpath); filenames.push(dp_res[key].storage.filename); + */ }); + /* console.log(" - buckets:", bucket_names); console.log(" - paths:", bucket_paths); console.log(" - filenames:", filenames); + */ + console.log("XXXXX - buckets:", minio_files); console.log('# 1) Create a new dataset'); console.log('# 1.1) get/set members'); @@ -423,7 +434,7 @@ async function process_selection(project_name, sel_name, selections) { dataset.data_type = 'image' // TODO: gérer autres cas... console.log("dataset=",dataset); console.log('# 1.2) getPathFromIds'); - dataset.urlList = await downloadFilesFromMinio(filenames, workspace, sel_name, bucket_names[0], bucket_paths[0]).catch((e) => { + dataset.urlList = await downloadFilesFromMinio(minio_files, workspace, sel_name).catch((e) => { console.error('Error in Minio import\n'+e); throw 'Error in Minio import\n'+e; }) @@ -447,16 +458,15 @@ async function createTasksFromDPImport(dp_res, dataset) { const task_types = new Set(); const class_list = new Set(); const class_defs = {}; - console.log("DSQFS", dp_res); + //console.log("DP_RES FULL", dp_res); Object.keys(dp_res).forEach(key => { Object.keys(dp_res[key].annotations).forEach(ann_key => { // TMP we take [0] because it's a list for several annotator, we take the first (???) - console.log("dsqd", dp_res[key].annotations[ann_key]); + //console.log("dp_res annotation", dp_res[key].annotations[ann_key]); let ann = dp_res[key].annotations[ann_key]; if(Array.isArray(ann)) { ann = ann[0]; //case of different annotators, we take first(last?) one only for now } - console.log("ZAEER", ann); let task_type = String(ann.type).toLowerCase(); //tmp if (!task_type) { @@ -484,7 +494,6 @@ async function createTasksFromDPImport(dp_res, dataset) { const geom_types = new Set(); for(it of ann.items) { class_list.add(it['category']); - console.log("AZAZA", it, it['geometry']); class_defs[it['category']] = it['geometry'].type; //keep it because we need to differentiate polygon/mpolygon // mapping geometry -> plugin geom = it['geometry'].type @@ -549,14 +558,16 @@ async function createTasksFromDPImport(dp_res, dataset) { }; } else { //cas non classif let cats = []; + let icol = 0; + const col_seq = palette('mpn65', class_list.size); for (let classe of class_list) { cats.push({ name: classe, - color: "black", + color: "#"+col_seq[icol], properties: [] }); + icol++; } - console.log("DEDE", cats); objdetect_label_value = { category: cats, @@ -591,15 +602,13 @@ async function createTasksFromDPImport(dp_res, dataset) { // task with this updated name does not exist, use this name task.name = newTaskName; } - //console.log("AAAAAAAAA", task); - console.log('# 2) Push the new task + dataset'); // Task does not exist create it - const newTask = {name: task_name, dataset_id: dataset.id, spec_id: spec.id} + const newTask = {name: newTaskName, dataset_id: dataset.id, spec_id: spec.id} const bm = new batchManager.BatchManager(db); await bm.add({ type: 'put', key: dbkeys.keyForTask(newTask.name), value: newTask }) //why ??? - //await db.put(dbkeys.keyForTask(newTask.name), newTask); + await db.put(dbkeys.keyForTask(newTask.name), newTask); // Generate first job list await generateJobResultAndLabelsLists(newTask); // Send back the created task @@ -613,7 +622,7 @@ async function createTasksFromDPImport(dp_res, dataset) { data_id: dp.id, annotations: dp.anns }; - console.log("Label to add", newLabels, newLabels.annotations); + //console.log("Label to add", newLabels, newLabels.annotations); await bm.add({ type: 'put', key: dbkeys.keyForLabels(newTask.name, newLabels.data_id), value: newLabels }); /** TMP On ne gere pas encore le statut (to_validate, to_annotate, ...) if (ann.data.status) {//if existing, get status back @@ -632,6 +641,210 @@ async function createTasksFromDPImport(dp_res, dataset) { +/**********************************************************************************/ +/***************************** DATA PROVIDER EXPORT ****************************/ +/**********************************************************************************/ + +async function patch_dp(project_id, ann) { + const dp_host = await db.get(dbkeys.keyForCliOptions).then((options) => { return options.dataProvider }); + console.log('AAA', dp_host); + return await fetch(dp_host + "/project/"+project_id+"/annotations", { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(ann) + }) + .then(res => { + console.log('BBB', res); + if (res.statusText == 'OK') { + console.log('BBB1'); + return res.json().then(data => { + console.log('BBB1.1'); + return Promise.resolve(data) + }).catch(e2 => {console.log('BBB1.2', e2); }); + } else { + console.log('BBB2'); + return Promise.reject(res); + } + }) + .catch(async err => { + return Promise.reject(err); + }); +} + +/** + * @api {post} /tasks/partial_export_to_dataprovider Export annotations for an image (/sequence?) to Confiance DP + * @apiName PostPartialExportTasksToDP + * @apiGroup Tasks + * @apiPermission admin + * + * @apiParam Task name + * + * @apiSuccessExample Success-Response: + * HTTP/1.1 200 OK + * + * @apiErrorExample Error-Response: + * HTTP/1.1 400 Failed to create export folder + */ +async function partial_export_to_dataprovider(req, res) { + checkAdmin(req, async () => { + console.log("partial_export_to_dataprovider", req.params); + const task_name = req.params.task_name; + const media_id = req.params.media_id + //get projects list from DP to select project corresponding with current task + + const projs = await get_dp("/debiai/projects").then(res => res) + .catch(err => {throw "Unable to get project list for matching with task, aborting export" + err;}) + //console.log("projs", Object.keys(projs)); + const projects_name = Object.keys(projs).filter((p) => task_name.startsWith(p)); + let project_name = "" + if (projects_name.length == 1) { + project_name = projects_name[0]; + } else if (projects_name.length > 1) { + console.log("Warning, several projects match this task name, choosing first one amongst", projects_name); + project_name = projects_name[0]; + } else { + throw "Error, no projects match this task name, aborting export"; + } + //TMP pendant wiping OS + //project_name = "welding_v5_test"; + //console.log("proj", project_name); + + const task = await db.get(dbkeys.keyForTask(task_name)); + const spec = await db.get(dbkeys.keyForSpec(task.spec_id)); + delete spec.id; + const dataset = await db.get(dbkeys.keyForDataset(task.dataset_id)); + const datasetId = dataset.id; + delete dataset.id; + const taskJson = { name: task.name, version: annotation_format_version, spec, dataset }; + //console.log("task", taskJson); + + + // Write annotations + let ann = {}; + const streamLabels = utils.iterateOnDB(db, dbkeys.keyForLabels(task.name), false, true); + //BR + //console.log("BR streamLabels arg = ", dbkeys.keyForLabels(task.name)); + //console.log("BR streamLabels = ", streamLabels); + + //NB: "false" loop, as we will export only one "labels" od the stream + for await (const labels of streamLabels) { + //TODO: streamLabels is a stream, I haven't found a better way to filter than + //loop all stream... There is probably more clever ways... + if(labels.data_id !== media_id) continue; + + //console.log("BR label = ", labels); + /* + resultData = await db.get(dbkeys.keyForResult(task.name, labels.data_id));//get the status for this data + const data = await getDataDetails(datasetId, labels.data_id, true); + console.log("BR data = ", data); + delete data.id; + delete data.dataset_id; + delete data.thumbnail; + data.status = resultData.status;//add the status + let path = data.path; + path = Array.isArray(path) ? path[0] : path; + path = path.replace(dataset.path, '') + const filename = utils.pathToFilename(path); + + let labelsJson = { ...labels, data }; + */ + //TMP ?? pas besoin data pour cas welding + let labelsJson = { ...labels }; + + // EXPORT task json + /* REF: cas export to json file * + const err = utils.writeJSON(labelsJson, `${taskFolder}/${filename}.json`); + if (err) { + return res.status(400).json({ + error: 'cannot_write', + message: `Cannot write json file ${taskFolder}/${filename}.json` + }); + } + */ + // wanted output: + /* + { + "actorId": "string", + "actorType": "string", + "samplesAnnotations": [ + { + "id": "string", + "name": "blurred", + "value": [ + "true", + [ + 0, + 1, + 2 + ] + ] + } + ] + } + */ + const labelsJson_confiance = { + actorId: "pixano", + actorType: 'annotator', + samplesAnnotations: [] + } + for (const annotation of labelsJson.annotations) { + //for ref, when VDP + //category: annotation.category, + //geometry: annotation.geometry, + //options: annotation.options + const cls_name = Object.keys(annotation.options)[0]; + const cls_val = annotation.options[cls_name]; + labelsJson_confiance.samplesAnnotations.push({ + id: labelsJson.data_id, + name: cls_name, + value: [`${cls_val}`] + }); + } + //console.log("labelsJson_confiance=", JSON.stringify(labelsJson_confiance)); + + ann = labelsJson_confiance; + } //end labels + + console.log("jsonified ann=", JSON.stringify(ann)); + + patch_dp(project_name, ann) + .then(res => { + if (res.ok) return res.json(); + else { + throw new Error(res);//we have to throw ourself because fetch only throw on network errors, not on 4xx or 5xx errors + } + }).catch(async (err) => { + const err_txt = await err.text(); + console.log("ERROR partial export :", err_txt); + return res.status(400).json({message: `ERROR while exporting to Confiance DB.\n${err_txt}`}); + }) + }) + return "Done"; +} + + + +/** + * @api {post} /tasks/export_tasks_to_dataprovider Export annotations to Confiance DP + * @apiName PostExportTasksToDP + * @apiGroup Tasks + * @apiPermission admin + * + * @apiParam {string} [path] Relative path to tasks folder OR [url] destination URL for online export + * + * @apiSuccessExample Success-Response: + * HTTP/1.1 200 OK + * + * @apiErrorExample Error-Response: + * HTTP/1.1 400 Failed to create export folder + */ +async function export_tasks_to_dataprovider(req, res) { + checkAdmin(req, async () => { + //TODO + // on va d'abord faire l'export incrémental (sur chaque "submit") + }) +} + /** * @api {post} /tasks/export Export annotations to json format @@ -1226,6 +1439,8 @@ module.exports = { put_task, delete_task, import_tasks, + partial_export_to_dataprovider, + export_tasks_to_dataprovider, projects_from_dataprovider, selections_from_dataprovider, id_list_from_dataprovider,