diff --git a/client/src/Annotator/index.jsx b/client/src/Annotator/index.jsx
index 514640b..da795d9 100644
--- a/client/src/Annotator/index.jsx
+++ b/client/src/Annotator/index.jsx
@@ -15,7 +15,7 @@ import PropTypes from "prop-types"
import noopReducer from "./reducers/noop-reducer.js"
import {useTranslation} from "react-i18next"
import getActiveImage from "./reducers/get-active-image.js"
-import { saveActiveImage } from "../utils/send-data-to-server"
+import { saveActiveImage, saveData, splitRegionData, getImageData} from "../utils/send-data-to-server"
import { useSnackbar} from "../SnackbarContext/index.jsx"
export const Annotator = ({
images,
@@ -124,21 +124,55 @@ export const Annotator = ({
showSnackbar(error.message, 'error');
});
}
-
- const dispatch = useEventCallback((action) => {
+ const preprocessDataBeforeSend = async (output) => {
+ const selectedImageIndex = output.selectedImage;
+ let _image = output.images[selectedImageIndex];
+ let regions = _image['regions'] || [];
+ let imageData = getImageData(_image);
+
+ imageData['regions'] = [];
+ for (let regionNum = 0; regionNum < regions.length; regionNum++) {
+ imageData['regions'].push(splitRegionData(regions[regionNum]));
+ }
+
+ try {
+ const response = await saveData(imageData);
+ showSnackbar(response.message, 'success');
+ return imageData['regions'];
+ } catch (error) {
+ showSnackbar(error.message, 'error');
+ return [];
+ }
+ };
+
+ const dispatch = useEventCallback(async (action) => {
if (action.type === "HEADER_BUTTON_CLICKED") {
if (["Exit", "Done", "Save", "Complete"].includes(action.buttonName)) {
- return onExit(without(state, "history"))
+ // save the current data
+ if (action.buttonName === "Save") {
+ const result = await preprocessDataBeforeSend(without(state, "history"));
+ dispatchToReducer({
+ type: "SAVE_LAST_REGIONS",
+ payload: result
+ });
+ dispatchToReducer({
+ type: "ENABLE_DOWNLOAD",
+ payload: result
+ });
+ return null;
+ } else {
+ return onExit(without(state, "history"));
+ }
} else if (action.buttonName === "Next" && onNextImage) {
- saveCurrentData(getActiveImage(state).activeImage)
- return onNextImage(without(state, "history"))
+ saveCurrentData(getActiveImage(state).activeImage);
+ return onNextImage(without(state, "history"));
} else if (action.buttonName === "Prev" && onPrevImage) {
- saveCurrentData(getActiveImage(state).activeImage)
- return onPrevImage(without(state, "history"))
+ saveCurrentData(getActiveImage(state).activeImage);
+ return onPrevImage(without(state, "history"));
}
}
- dispatchToReducer(action)
- })
+ dispatchToReducer(action);
+ });
const onRegionClassAdded = useEventCallback((cls) => {
dispatchToReducer({
diff --git a/client/src/DemoSite/index.jsx b/client/src/DemoSite/index.jsx
index 4a8edd4..c27bfc6 100644
--- a/client/src/DemoSite/index.jsx
+++ b/client/src/DemoSite/index.jsx
@@ -1,31 +1,50 @@
import Annotator from "../Annotator"
import React, { useEffect, useState } from "react"
-import {saveData, splitRegionData, getImageData} from '../utils/send-data-to-server'
import SetupPage from "../SetupPage";
-import { useSnackbar } from '../SnackbarContext';
import { useSettings } from "../SettingsProvider";
+import {setIn} from "seamless-immutable"
+
+const extractRelevantProps = (region) => ({
+ cls: region.cls,
+ comment: region.comment,
+ id: region.id,
+});
const userReducer = (state, action) => {
switch (action.type) {
- // case "SELECT_CLASSIFICATION": {
- // switch (action.cls) {
- // case "One": {
- // return setIn(state, ["selectedTool"], "create-box");
- // }
- // case "Two": {
- // return setIn(state, ["selectedTool"], "create-polygon");
- // }
- // }
- // }
+ case "CLOSE_REGION_EDITOR":
+ case "DELETE_REGION": {
+ const { images, selectedImage } = state;
+ const lastRegions = state.lastRegions || [];
+ if (selectedImage != null && lastRegions) {
+ const currentImage = images[selectedImage];
+ const regions = currentImage ? (currentImage.regions || []) : [];
+ if (
+ regions.length !== lastRegions.length ||
+ !regions.every((region, index) => {
+ const lastRegion = lastRegions[index] || [];
+ const currentProps = extractRelevantProps(region);
+ const lastProps = extractRelevantProps(lastRegion);
+ return JSON.stringify(currentProps) === JSON.stringify(lastProps);
+ })
+ ) {
+ return setIn(state, ["hasNewChange"], true);
+ } else {
+ return setIn(state, ["hasNewChange"], false);
+ }
+ }
+ }
+ case "SAVE_LAST_REGIONS": {
+ return setIn(state, ["lastRegions"], action.payload);
+ }
+ case "ENABLE_DOWNLOAD": {
+ return setIn(state, ["enabledDownload"], true);
+ }
}
-
return state;
};
-
-
export default () => {
- const { showSnackbar } = useSnackbar();
const [selectedImageIndex, changeSelectedImageIndex] = useState(0)
const [showLabel, setShowLabel] = useState(false)
const [imageNames, setImageNames] = useState([])
@@ -42,23 +61,6 @@ export default () => {
}
})
- const preprocessDataBeforeSend = (output) => {
- const selectedImageIndex = output.selectedImage;
- let _image = output.images[selectedImageIndex]
- let regions = _image['regions'] || []
- let imageData = getImageData(_image)
-
- imageData['regions'] = []
- for (let regionNum = 0; regionNum < regions.length; regionNum++){
- imageData['regions'].push(splitRegionData(regions[regionNum]))
- }
- saveData(imageData).then(response => {
- showSnackbar(response.message, 'success');
- })
- .catch(error => {
- showSnackbar(error.message, 'error');
- });
- }
const [loading, setLoading] = useState(true); // Add loading state
const onSelectJumpHandle = (selectedImageName) => {
@@ -144,7 +146,7 @@ export default () => {
enabledRegionProps= {["class", "comment"]}
userReducer= {userReducer}
onExit={(output) => {
- preprocessDataBeforeSend(output)
+ console.log("Exiting!")
}}
settings={settings}
onSelectJump={onSelectJumpHandle}
diff --git a/client/src/MainLayout/index.jsx b/client/src/MainLayout/index.jsx
index c59ebbf..d0243ac 100644
--- a/client/src/MainLayout/index.jsx
+++ b/client/src/MainLayout/index.jsx
@@ -224,6 +224,7 @@ export const MainLayout = ({
) : null,
].filter(Boolean)}
headerItems={[
+ { name: "Download", label:t("btn.download"), disabled: !state.enabledDownload, hasInnerMenu: true},
!hidePrev && {name: "Prev", label: t("btn.previous"), disabled: disabledNextAndPrev},
!hideNext && {name: "Next", label: t("btn.next"), disabled: disabledNextAndPrev},
state.annotationType !== "video"
@@ -231,10 +232,10 @@ export const MainLayout = ({
: !state.videoPlaying
? {name: "Play", label: t("btn.play")}
: {name: "Pause", label: t("btn.pause")},
- !hideClone &&
+ !hideClone && state.hasNewChange &&
!nextImageHasRegions &&
activeImage.regions && {name: "Clone", label: t("btn.clone")},
- !hideSave && {name: "Save", label:t("btn.save"), icon: },
+ !hideSave && {name: "Save", label:t("btn.save"), disabled: !state.hasNewChange, icon: },
!hideSettings && {name: "Settings", label: t("btn.settings")},
{name: "Exit", label:t("btn.exit"), icon: }
].filter(Boolean)}
diff --git a/client/src/SetupPage/index.jsx b/client/src/SetupPage/index.jsx
index 57601aa..bef8ff1 100644
--- a/client/src/SetupPage/index.jsx
+++ b/client/src/SetupPage/index.jsx
@@ -125,7 +125,7 @@ export const SetupPage = ({setConfiguration, settings, setShowLabel}) => {
{settings.taskChoice === "image_classification" && (
<>
-
+
@@ -136,7 +136,7 @@ export const SetupPage = ({setConfiguration, settings, setShowLabel}) => {
{settings.taskChoice === "image_segmentation" && (
<>
-
+
diff --git a/client/src/workspace/DownloadButton/index.jsx b/client/src/workspace/DownloadButton/index.jsx
index 56160ab..43952f9 100644
--- a/client/src/workspace/DownloadButton/index.jsx
+++ b/client/src/workspace/DownloadButton/index.jsx
@@ -12,7 +12,7 @@ import { hexToRgbTuple } from "../../utils/color-utils.js";
import HeaderButton from "../HeaderButton/index.jsx";
import { useTranslation } from "react-i18next"
-const DownloadButton = ({selectedImageName, classList, hideHeaderText}) => {
+const DownloadButton = ({selectedImageName, classList, hideHeaderText, disabled}) => {
const [anchorEl, setAnchorEl] = useState(null);
const { showSnackbar } = useSnackbar();
const {t} = useTranslation();
@@ -80,6 +80,7 @@ const DownloadButton = ({selectedImageName, classList, hideHeaderText}) => {
name={"Download"}
label={t("btn.download")}
onClick={handleClick}
+ disabled={disabled}
icon={}
/>