diff --git a/Dockerfile b/Dockerfile
index 4dfb491..0bcfaec 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -15,8 +15,7 @@ COPY server server
COPY .logo-ascii .logo-ascii
# Build frontend and install backend dependencies
-RUN npm run installApp && npm run buildApp && npm install \
- && rm -rf src frontend
+RUN npm i && cd frontend/ && npm i && npm run build && rm -rf src frontend && cd ..
EXPOSE 3000
diff --git a/frontend/package.json b/frontend/package.json
index 71d416c..63664a0 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,6 +1,6 @@
{
"name": "pixano-app-frontend",
- "version": "0.4.7",
+ "version": "0.4.9",
"description": "This is a Pixano app.",
"scripts": {
"copyindex": "shx cp src/index.html ../build",
@@ -23,11 +23,11 @@
"webpack-cli": "^3.3.10"
},
"dependencies": {
- "@babel/core": "^7.7.7",
- "@babel/plugin-transform-runtime": "^7.7.6",
+ "@babel/core": "^7.16.0",
+ "@babel/plugin-transform-runtime": "^7.16.0",
"@babel/polyfill": "^7.7.0",
- "@babel/preset-env": "^7.7.7",
- "@babel/runtime": "^7.7.7",
+ "@babel/preset-env": "^7.16.0",
+ "@babel/runtime": "^7.16.0",
"@material/mwc-button": "0.19.1",
"@material/mwc-checkbox": "0.19.1",
"@material/mwc-circular-progress-four-color": "0.19.1",
@@ -49,13 +49,13 @@
"@material/mwc-tab-bar": "0.19.1",
"@material/mwc-textarea": "0.19.1",
"@material/mwc-textfield": "0.19.1",
- "@pixano/ai": "0.5.17",
- "@pixano/core": "0.5.17",
- "@pixano/graphics-2d": "0.5.17",
- "@pixano/graphics-3d": "0.5.17",
+ "@pixano/ai": "0.6.0",
+ "@pixano/core": "0.6.0",
+ "@pixano/graphics-2d": "0.6.0",
+ "@pixano/graphics-3d": "0.6.0",
"@trystan2k/fleshy-jsoneditor": "3.0.0",
"@webcomponents/webcomponentsjs": "^2.4.0",
- "babel-loader": "^8.0.6",
+ "babel-loader": "^8.2.3",
"copy-webpack-plugin": "^5.1.1",
"css-loader": "^3.4.0",
"file-loader": "^5.0.2",
@@ -65,9 +65,9 @@
"material-design-icons": "^3.0.1",
"node-sass": "^4.13.0",
"pwa-helpers": "^0.9.1",
- "redux": "^4.0.4",
+ "redux": "^4.1.2",
"redux-devtools-extension": "^2.13.8",
- "redux-thunk": "^2.3.0",
+ "redux-thunk": "^2.4.0",
"redux-undo": "^1.0.0",
"sass-loader": "^8.0.0",
"source-map-loader": "^0.2.4",
diff --git a/frontend/src/helpers/attribute-picker.js b/frontend/src/helpers/attribute-picker.js
index bd46be9..33193c3 100644
--- a/frontend/src/helpers/attribute-picker.js
+++ b/frontend/src/helpers/attribute-picker.js
@@ -112,6 +112,14 @@ export class AttributePicker extends LitElement {
}
static get properties () {
+ /**
+ * showDetail: Boolean, rendering mode for the selected category (showing all attributes or only the category)
+ * shortcuts : Array of strings, contains the list of all applicable keyboard shortcuts
+ * schema: shema for this annotation (i.e. category and attributes available for each category in this annotation)
+ * value: {category, options }, contains the value of the current category and its options (i.e. attributes available for this category)
+ * numDone: Number, only used for keypoints-box
+ * numTotal: Number, only used for keypoints-box
+ */
return {
showDetail: { type: Boolean },
shortcuts: { type: Array },
@@ -400,16 +408,6 @@ export class AttributePicker extends LitElement {
${this.renderSimple}
`;
}
- // render(){
- // return html`
- // ${this.shortcutsDialog}
- //
- // ${this.renderDetail}
- // ${this.renderSimple}
- //
keypoints faits ${this.numDone} / ${this.numTotal}
- // `;
- // }
-
}
customElements.define('attribute-picker', AttributePicker);
diff --git a/frontend/src/plugins/keypoints-box.js b/frontend/src/plugins/keypoints-box.js
index d6dbf2b..25aca4e 100644
--- a/frontend/src/plugins/keypoints-box.js
+++ b/frontend/src/plugins/keypoints-box.js
@@ -108,7 +108,7 @@ export class PluginKeypointsBox extends TemplatePluginInstance {
freeBoxes.shift();
this.attributePicker.numDone = this.attributePicker.numTotal - freeBoxes.length;
store.dispatch(createAnnotation(newAnnotation));
- this.element.shapes = freeBoxes.length ? [{...freeBoxes[0], color: this._colorFor(freeBoxes[0].categoryName) }] : [];
+ this.element.shapes = freeBoxes.length ? [{...freeBoxes[0], color: this._colorFor(freeBoxes[0].category) }] : [];
} else {
this.element.shapes = [];
}
@@ -138,7 +138,7 @@ export class PluginKeypointsBox extends TemplatePluginInstance {
.filter((r) => !ids.includes(r.id));
if (freeBoxes.length) {
- this.element.shapes = [{...freeBoxes[0], color: this._colorFor(freeBoxes[0].categoryName) }];
+ this.element.shapes = [{...freeBoxes[0], color: this._colorFor(freeBoxes[0].category) }];
}
}
diff --git a/frontend/src/plugins/segmentation.js b/frontend/src/plugins/segmentation.js
index 6c3b2cb..c6b5209 100644
--- a/frontend/src/plugins/segmentation.js
+++ b/frontend/src/plugins/segmentation.js
@@ -14,7 +14,8 @@ import { store, getState } from '../store';
import '../helpers/attribute-picker';
import { subtract, union } from '../my-icons';
import { setAnnotations } from '../actions/annotations';
-import { TemplatePlugin } from '../templates/template-plugin';
+import { TemplatePluginInstance } from '../templates/template-plugin-instance';
+import { commonJson } from '../helpers/utils';
const EditionMode = {
ADD_TO_INSTANCE: 'add_to_instance',
@@ -27,7 +28,7 @@ const EditionMode = {
* Reads labels as:
* { id: 0, mask: Base64 }
*/
-export class PluginSegmentation extends TemplatePlugin {
+export class PluginSegmentation extends TemplatePluginInstance {
static get properties() {
return {
@@ -39,16 +40,15 @@ export class PluginSegmentation extends TemplatePlugin {
constructor() {
super();
- this.mode = 'edit';
+ this.mode = 'create';
this.maskVisuMode = 'SEMANTIC';
this.currentEditionMode = EditionMode.NEW_INSTANCE;
- this.selectedIds = [0,0,0];
}
get toolDrawer() {
return html`
this.mode = 'edit'}">
@@ -87,7 +87,7 @@ export class PluginSegmentation extends TemplatePlugin {
this.maskVisuMode = this.maskVisuMode === 'INSTANCE' ? 'SEMANTIC': 'INSTANCE'}">
`
@@ -110,53 +110,118 @@ export class PluginSegmentation extends TemplatePlugin {
this.element.targetClass = schema.category.find((c) => c.name === schema.default).idx;
}
- onUpdate() {
- const frame = { mask: this.element.getMask()};
- store.dispatch(setAnnotations({annotations: frame}));
- }
-
- onSelection(evt) {
- this.selectedIds = evt.detail;
- this.updateDisplayOfSelectedProperties();
- }
-
- updateDisplayOfSelectedProperties() {
- if (this.selectedIds && this.selectedIds.length) {
- this.attributePicker.setAttributesIdx(this.selectedIds[2]);
- } else {
- this.attributePicker.setAttributesIdx();
- }
- }
-
- onAttributeChanged() {
- const value = this.attributePicker.selectedCategory;
- this.element.targetClass = value.idx;
- if (this.selectedIds && this.selectedIds.length
- && (this.selectedIds[0] != 0 || this.selectedIds[1] != 0 || this.selectedIds[2] != 0)) {
- this.element.fillSelectionWithClass(value.idx);
- this.onUpdate();
- }
- }
-
- get propertyPanel() {
- return html`
-
- `
- }
-
- refresh() {
- if (!this.element) {
- return;
- }
- if (!this.annotations.mask) {
- this.element.setEmpty();
- return;
- }
- const mask = this.annotations.mask;
- if (mask != this.element.getMask()) {
- this.element.setMask(mask);
- }
- }
+ /**
+ * Invoked on instance selection in the canvas.
+ * @param {CustomEvent} evt
+ */
+ onSelection(evt) {
+ this.selectedIds = evt.detail;
+ if (this.selectedIds) {//only one id at a time for segmentation
+ const annot = this.annotations.filter((a) => JSON.stringify(this.selectedIds)===(a.id));// search the corresponding id
+ const common = commonJson(annot);
+ this.attributePicker.setAttributes(common);
+ } else {
+ // if null, nothing is selected
+ this.selectedIds = [];
+ }
+ }
+
+ /**
+ * Invoked when a new instance is updated (created = updated for segmentation)
+ * @param {CustomEvent} evt
+ */
+ onUpdate(evt) {
+ const updatedIds = evt.detail;
+ let frame = this.annotations;
+ // 1) update the mask (always id 0)
+ let mask = frame.find((l) => l.id === 0);
+ if (!mask) {
+ mask = {id: 0, mask: this.element.getMask()};//if the mask already exists => just overwrite the previous mask
+ frame.push(mask);//otherwise(first time), create it
+ } else {
+ mask.mask = this.element.getMask();
+ }
+ // 2) update annotation info when needed
+ let label = frame.find((l) => l.id === JSON.stringify(updatedIds));// search the corresponding id
+ if (label) {//id exists in the database, update information
+ // nothing to do for annotation infos, only the mask has changed
+ } else {// this is a new id
+ // create the new label
+ label = {...this.attributePicker.defaultValue};
+ // store the stringified values
+ const value = this.attributePicker.value;
+ Object.keys(label).forEach((key) => {
+ label[key] = JSON.parse(JSON.stringify(value[key]));
+ });
+ label.id = JSON.stringify(updatedIds);
+ frame.push(label)
+ }
+ // 3) store the new annotation structure
+ store.dispatch(setAnnotations({annotations: frame}));
+ // selectedId has also changed, update it
+ this.selectedIds = updatedIds;
+ }
+
+ /**
+ * Invoked on attribute change from property panel.
+ */
+ onAttributeChanged() {
+ if (!this.selectedIds.length) {//nothing is selected
+ // only set the category acordingly to the selected attribute
+ const category = this.attributePicker.selectedCategory;
+ this.element.targetClass = category.idx;
+ return;
+ }
+ let frame = this.annotations;
+ // 1) update the mask (always id 0)
+ // change category in element
+ const category = this.attributePicker.selectedCategory;
+ this.element.targetClass = category.idx;
+ this.element.fillSelectionWithClass(category.idx);
+ // get the new mask and store it
+ let mask = frame.find((l) => l.id === 0);
+ mask.mask = this.element.getMask();//just overwrite the previous mask
+ // 2) update annotation info from attributes
+ const value = this.attributePicker.value;
+ let label = frame.find((l) => l.id === JSON.stringify(this.selectedIds));// search the corresponding id
+ Object.keys(value).forEach((key) => {
+ label[key] = JSON.parse(JSON.stringify(value[key]));
+ });
+ // category has changed => selectedId has also changed, update it
+ const updatedIds = this.element.selectedId;
+ label.id = JSON.stringify(updatedIds);
+ this.selectedIds = updatedIds;
+ // 3) store the new annotation structure
+ store.dispatch(setAnnotations({annotations: frame}));
+ }
+
+ /**
+ * Invoked on instance removal
+ * @param {CustomEvent} evt
+ */
+ onDelete(evt) {
+ const ids = evt.detail;
+ let frame = this.annotations;
+ // 1) update the mask (always id 0)
+ // get the new mask and store it
+ let mask = frame.find((l) => l.id === 0);
+ mask.mask = this.element.getMask();//just overwrite the previous mask
+ // 2) update annotation info (= delete corresponding id)
+ frame = frame.filter((l) => l.id !== JSON.stringify(ids))
+ // 3) store the new annotation structure
+ store.dispatch(setAnnotations({annotations: frame}));
+ }
+
+ refresh() {//get back annotation into element
+ console.log("refresh")
+ if (!this.element) {
+ return;
+ }
+ // 1) get back the mask into element
+ let mask = this.annotations.find((l) => l.id === 0);
+ if (!mask) this.element.setEmpty();
+ else this.element.setMask(mask.mask);
+ }
getEditionMode() {
if (this.element) return this.element.editionMode;
@@ -173,87 +238,13 @@ export class PluginSegmentation extends TemplatePlugin {
maskVisuMode=${this.maskVisuMode}
@update=${this.onUpdate}
@selection=${this.onSelection}
- @mode=${this.onModeChange}>`;
+ @delete=${this.onDelete}
+ @mode=${this.onModeChange}>`;//onCreate never really called for segmentation : the mask is updated
}
+ collect() {
+ console.log("should not be called")
+ }
+
}
customElements.define('plugin-segmentation', PluginSegmentation);
-
-
-// Code to handle attributes for segments
-// /**
-// * Invoked on instance selection in the canvas.
-// * @param {CustomEvent} evt
-// */
-// onSelection(evt) {
-// this.selectedIds = evt.detail;
-// if (this.selectedIds) {//only one id at a time for segmentation
-// const annot = this.annotations.filter((a) => JSON.stringify(this.selectedIds)===(a.id));// search the corresponding id
-// const common = commonJson(annot);
-// this.attributePicker.setAttributes(common);
-// } else {
-// // if null, nothing is selected
-// this.selectedIds = [];
-// }
-// }
-
-// /**
-// * Invoked when a new instance is updated (created = updated for segmentation)
-// * @param {CustomEvent} evt
-// */
-// onUpdate(evt) {
-// // 1) update annotation info when needed
-// const updatedIds = evt.detail;
-// const label = this.annotations.find((l) => l.id === JSON.stringify(updatedIds));// search the corresponding id
-// if (label) {//id exists in the database, update information
-// // nothing to do for annotation infos, only the mask has changed
-// } else {// this is a new id
-// // create the new label
-// let label = {...this.attributePicker.defaultValue};
-// // store the stringified values
-// const value = this.attributePicker.value;
-// Object.keys(label).forEach((key) => {
-// label[key] = JSON.parse(JSON.stringify(value[key]));
-// });
-// label.id = JSON.stringify(updatedIds);
-// store.dispatch(createAnnotation(label));
-// }
-// // 2) update the mask (always id 0)
-// const curr = this.annotations.find((l) => l.id === 0);
-// const im = this.element.getMask();
-// const fn = curr ? updateAnnotation : createAnnotation;
-// store.dispatch(fn({ id: 0, mask: im }));
-// this.selectedIds = updatedIds;
-// }
-
-// /**
-// * Invoked on attribute change from property panel.
-// */
-// onAttributeChanged() {
-// if (!this.selectedIds.length) {//nothing is selected
-// // only set the category acordingly to the selected attribute
-// const category = this.attributePicker.selectedCategory;
-// this.element.targetClass = category.idx;
-// return;
-// }
-// // 2) update the mask (always id 0)
-// // change category in element
-// const category = this.attributePicker.selectedCategory;
-// this.element.targetClass = category.idx;
-// this.element.fillSelectionWithClass(category.idx);
-// // get the new mask and store it
-// const im = this.element.getMask();
-// store.dispatch(updateAnnotation({ id: 0, mask: im }));
-// // 1) update annotation info from attributes
-// const value = this.attributePicker.value;
-// const label = this.annotations.find((l) => l.id === JSON.stringify(this.selectedIds));// search the corresponding id
-// Object.keys(value).forEach((key) => {
-// label[key] = JSON.parse(JSON.stringify(value[key]));
-// });
-// // category has changed => selectedId has also changed, update it
-// const updatedIds = this.element.selectedId;
-// label.id = JSON.stringify(updatedIds);
-// this.selectedIds = updatedIds;
-// // update store
-// store.dispatch(updateAnnotation(label));
-// }
diff --git a/frontend/src/plugins/smart-segmentation.js b/frontend/src/plugins/smart-segmentation.js
index 81cc8a5..c212867 100644
--- a/frontend/src/plugins/smart-segmentation.js
+++ b/frontend/src/plugins/smart-segmentation.js
@@ -10,6 +10,7 @@ import '@material/mwc-icon-button';
import '@material/mwc-icon-button-toggle';
import '@material/mwc-icon';
import { PluginSegmentation } from './segmentation';
+import { getState } from '../store';
/**
* Plugin segmentation.
@@ -20,12 +21,8 @@ export class PluginSmartSegmentation extends PluginSegmentation {
initDisplay() {
super.initDisplay();
- const tasks = this.info.tasks;
- const taskName = this.info.taskName;
- const task = tasks.find((t) => t.name === taskName);
- if (!task) {
- return;
- }
+ const taskName = getState('application').taskName;
+ const task = getState('application').tasks.find((t) => t.name === taskName);
if (task.spec.settings && task.spec.settings.model) {
this.element.model = task.spec.settings.model;
}
@@ -39,7 +36,6 @@ export class PluginSmartSegmentation extends PluginSegmentation {
title="Smart create"
@click="${() => this.mode = 'smart-create'}">
-
`
}
@@ -49,7 +45,8 @@ export class PluginSmartSegmentation extends PluginSegmentation {
maskVisuMode=${this.maskVisuMode}
@update=${this.onUpdate}
@selection=${this.onSelection}
- @mode=${this.onModeChange}>`;
+ @delete=${this.onDelete}
+ @mode=${this.onModeChange}>`;//onCreate never really called for segmentation : the mask is updated
}
}
diff --git a/frontend/src/templates/template-plugin-instance.js b/frontend/src/templates/template-plugin-instance.js
index 366d286..621e7d8 100644
--- a/frontend/src/templates/template-plugin-instance.js
+++ b/frontend/src/templates/template-plugin-instance.js
@@ -25,7 +25,7 @@ export class TemplatePluginInstance extends TemplatePlugin {
constructor(){
super();
- this.mode = 'edit';
+ this.mode = 'create';
this.selectedIds = [];
}
@@ -138,7 +138,7 @@ export class TemplatePluginInstance extends TemplatePlugin {
get toolDrawer() {
return html`
this.mode = 'edit'}">
diff --git a/frontend/src/views/app-dashboard-admin.js b/frontend/src/views/app-dashboard-admin.js
index 1414d2d..03cae5a 100644
--- a/frontend/src/views/app-dashboard-admin.js
+++ b/frontend/src/views/app-dashboard-admin.js
@@ -467,7 +467,7 @@ class AppDashboardAdmin extends TemplatePage {
/**
* Display table row
- * Status | Data Id | Annotator | Validator | State | Time | Launch
+ * Status | Data Id | Annotator | Validator | State | Time | Thumbnail | Launch
*/
listitem(item) {
const v = this.statusMap.get(item.status);
@@ -528,7 +528,10 @@ class AppDashboardAdmin extends TemplatePage {
-
+
+
+
+
`;
}
diff --git a/frontend/src/views/app-user-manager.js b/frontend/src/views/app-user-manager.js
index f14c258..0406d63 100644
--- a/frontend/src/views/app-user-manager.js
+++ b/frontend/src/views/app-user-manager.js
@@ -8,6 +8,7 @@ import { html, css } from 'lit-element';
import { connect } from 'pwa-helpers/connect-mixin.js';
import TemplatePage from '../templates/template-page';
import { store, getState } from '../store';
+import { getValue } from '../helpers/utils';
import { logout, signup,
getUsers,
deleteUser,
@@ -90,10 +91,15 @@ class AppUserManager extends connect(store)(TemplatePage) {
});
}
- onSaveUser(user) {
- store.dispatch(updateUser(user));
- this.enabledUsername = '';
- }
+ onPasswordChanged(e) {
+ this.passwordElement.value = getValue(e);
+ }
+
+ onSaveUser(user) {
+ user.password = this.passwordElement.value;
+ store.dispatch(updateUser(user));
+ this.enabledUsername = '';
+ }
onCancel() {
this.enabledUsername = '';
@@ -227,32 +233,33 @@ class AppUserManager extends connect(store)(TemplatePage) {
`;
}
- listitem(user) {
- return html`
-
-
${user.username}
-
-
-
-
- user.role = this.dropdownValues['role'][e.detail.index]}>
- ${this.dropdownValues['role'].map((v) => html`${v}`)}
-
-
-
- user.preferences.theme = this.dropdownValues['preferences.theme'][e.detail.index]}>
- ${this.dropdownValues['preferences.theme'].map((v) => html`${v}`)}
-
-
- ${this.editionCell(user)}
-
- `;
- }
+ listitem(user) {
+ return html`
+
+
${user.username}
+
+
+
+
+ user.role = this.dropdownValues['role'][e.detail.index]}>
+ ${this.dropdownValues['role'].map((v) => html`${v}`)}
+
+
+
+ user.preferences.theme = this.dropdownValues['preferences.theme'][e.detail.index]}>
+ ${this.dropdownValues['preferences.theme'].map((v) => html`${v}`)}
+
+
+ ${this.editionCell(user)}
+
+ `;
+ }
get userSection() {
diff --git a/package.json b/package.json
index aab2f46..f8ec583 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "pixano-app",
- "version": "0.4.7",
+ "version": "0.4.9",
"description": "This is a Pixano app.",
"keywords": [],
"license": "CECILL-C",
@@ -29,6 +29,7 @@
"object-sizeof": "^1.5.3",
"path": "^0.12.7",
"save": "^2.4.0",
+ "semver": "^7.3.5",
"short-uuid": "^3.1.1",
"tmp": "^0.1.0"
}
diff --git a/server/config/parser.js b/server/config/parser.js
index ee70274..48f6fcd 100644
--- a/server/config/parser.js
+++ b/server/config/parser.js
@@ -45,7 +45,7 @@ async function parse(databasePath) {
return {
id: a.id,
name: a.name,
- category: a.categoryName,
+ category: a.category,
geometry: a.geometry,
options: {
...a.options,
diff --git a/server/helpers/data-populator.js b/server/helpers/data-populator.js
index 4d3bb57..2be8d46 100644
--- a/server/helpers/data-populator.js
+++ b/server/helpers/data-populator.js
@@ -69,7 +69,8 @@ async function populateSimple(db, mediaRelativePath, hostWorkspacePath, datasetI
for await (const f of files) {
const id = generateKey();
const url = workspaceToMount(hostWorkspacePath, f);
- const value = { id, dataset_id: datasetId, type: dataType, path: url, children: '', thumbnail: await imageThumbnail(f, {responseType: 'base64'})};
+ let value = { id, dataset_id: datasetId, type: dataType, path: url, children: ''}
+ if (dataType=='image') value.thumbnail = await imageThumbnail(f, {responseType: 'base64', height: 100});
await bm.add({ type: 'put', key: dbkeys.keyForData(datasetId, id), value: value});
bar1.increment();
}
diff --git a/server/routes/users.js b/server/routes/users.js
index c2e8bb1..91984d0 100644
--- a/server/routes/users.js
+++ b/server/routes/users.js
@@ -254,7 +254,7 @@ async function put_user(req, res) {
const user = await db.get(dbkeys.keyForUser(username));
return user.password === password;
} catch (err) {
- console.log(username, password, 'does not exist.');
+ console.log(username, 'does not exist.');
return false;
}
}