diff --git a/README.md b/README.md index f883d8f0..f06a0a57 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,15 @@ Andrew Ng ## Sneak Peek

- bbox + alfa-demo +

+ +## Advanced AI functionalities + +[makesense.ai][1] strives to significantly reduce the time we have to spend on labeling photos. To achieve this, we are going to use many different AI models that will be able to give you recommendations as well as automate repetitive and tedious activities. The first step on this journey is to use a [SSD model][8] pretrained on the [COCO dataset][9], which will do some of the work for you in drawing bboxes on photos and - in future versions of the application - will also suggest a label. We also plan to add, among other things, models that classify photos, detect characteristic features of faces, whole faces, and also human pose. The engine that drives our AI functionalities is [TensorFlow.js][10] - JS version of the most popular framework for training neural networks. This choice allows us not only to speed up your work but also to care about the privacy of your data, because unlike with other commercial and open source tools, your photos do not have to be transferred to the server. This time AI comes to your device! + +

+ ai-demo

## Set Up the Project Locally @@ -95,17 +103,6 @@ Feel free to file [issues](https://github.com/SkalskiP/make-sense/issues) or [pu } ``` -## Citation - -``` -@MISC{make-sense, - author = {Piotr Skalski}, - title = {{Make Sense}}, - howpublished = "\url{https://github.com/SkalskiP/make-sense/}", - year = {2019}, -} -``` - ## License This project is licensed under the GPL-3.0 License - see the [LICENSE][2] file for details @@ -119,3 +116,6 @@ Copyright (c) 2019-present, Piotr Skalski [5]: https://gitter.im/make-sense-ai/community?utm_source=share-link&utm_medium=link&utm_campaign=share-link [6]: https://github.com/SkalskiP/make-sense/wiki/Road-Map [7]: https://github.com/SkalskiP/make-sense/wiki/Supported-Output-Formats +[8]: https://arxiv.org/abs/1512.02325 +[9]: http://cocodataset.org +[10]: https://www.tensorflow.org/js diff --git a/examples/ai-demo.gif b/examples/ai-demo.gif new file mode 100644 index 00000000..d3d008ee Binary files /dev/null and b/examples/ai-demo.gif differ diff --git a/package-lock.json b/package-lock.json index 2f53e8f7..2742fd81 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1452,6 +1452,68 @@ "loader-utils": "^1.1.0" } }, + "@tensorflow-models/coco-ssd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tensorflow-models/coco-ssd/-/coco-ssd-2.0.0.tgz", + "integrity": "sha512-JexMswea9a5k8//sdImcxdVaofejkzUzJA8cpDBOZSauBkBM34M3c1YoEUrFuJSa5sKxgMu5TwMPWbJ59A7IVA==" + }, + "@tensorflow/tfjs": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs/-/tfjs-1.2.9.tgz", + "integrity": "sha512-9UAQnSp638FyM5eedYEM+j2R7VcNajiFmkeT5EXtf7YIurmMFNEm1sbajKJx7/ckz31YcYrVoUPc/iLhhDQl2A==", + "requires": { + "@tensorflow/tfjs-converter": "1.2.9", + "@tensorflow/tfjs-core": "1.2.9", + "@tensorflow/tfjs-data": "1.2.9", + "@tensorflow/tfjs-layers": "1.2.9" + } + }, + "@tensorflow/tfjs-converter": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-1.2.9.tgz", + "integrity": "sha512-OKmiuZicIgadT3Bv9BvM+oom7wRz9eC5rTglQnuv7VN9H0syFVuhf5oD1Ff70tGDhJjJgL+cPz01fZRxTXjRWA==" + }, + "@tensorflow/tfjs-core": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-1.2.9.tgz", + "integrity": "sha512-s0hHZSx6rGTlkkB8u8gs5n7sIPv1GXDNHmISRy+kqGzmlpkfI2kr6WXqOWQy6wFgjzopRD8cJQjBZ9USPZnYTQ==", + "requires": { + "@types/offscreencanvas": "~2019.3.0", + "@types/seedrandom": "2.4.27", + "@types/webgl-ext": "0.0.30", + "@types/webgl2": "0.0.4", + "node-fetch": "~2.1.2", + "seedrandom": "2.4.3" + }, + "dependencies": { + "node-fetch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz", + "integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U=" + } + } + }, + "@tensorflow/tfjs-data": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-data/-/tfjs-data-1.2.9.tgz", + "integrity": "sha512-Ti9Cj3pte9butuEsK5OPq0Lcqdi4wVUdtQXm0o7iYOZ0umseRzfbIb6zbdqucc2MQzOMTnRoxN+FL7LZmncsHg==", + "requires": { + "@types/node-fetch": "^2.1.2", + "node-fetch": "~2.1.2" + }, + "dependencies": { + "node-fetch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz", + "integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U=" + } + } + }, + "@tensorflow/tfjs-layers": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-layers/-/tfjs-layers-1.2.9.tgz", + "integrity": "sha512-OlXYaIb1rCk5dYmpaNsPEkO7R+T0oxfS3vQGIztNJB+YxrN8mwCu3hqgpbdKhAITiP+jxO0o+7bny8MsOCkOSQ==" + }, "@types/babel__core": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.2.tgz", @@ -1575,6 +1637,19 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.8.tgz", "integrity": "sha512-b8bbUOTwzIY3V5vDTY1fIJ+ePKDUBqt2hC2woVGotdQQhG/2Sh62HOKHrT7ab+VerXAcPyAiTEipPu/FsreUtg==" }, + "@types/node-fetch": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.0.tgz", + "integrity": "sha512-TLFRywthBgL68auWj+ziWu+vnmmcHCDFC/sqCOQf1xTz4hRq8cu79z8CtHU9lncExGBsB8fXA4TiLDLt6xvMzw==", + "requires": { + "@types/node": "*" + } + }, + "@types/offscreencanvas": { + "version": "2019.3.0", + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.3.0.tgz", + "integrity": "sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q==" + }, "@types/prop-types": { "version": "15.7.1", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.1.tgz", @@ -1640,6 +1715,11 @@ "@types/react": "*" } }, + "@types/seedrandom": { + "version": "2.4.27", + "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.27.tgz", + "integrity": "sha1-nbVjk33YaRX2kJK8QyWdL0hXjkE=" + }, "@types/stack-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", @@ -1678,6 +1758,16 @@ "@types/unist": "*" } }, + "@types/webgl-ext": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/webgl-ext/-/webgl-ext-0.0.30.tgz", + "integrity": "sha512-LKVgNmBxN0BbljJrVUwkxwRYqzsAEPcZOe6S2T6ZaBDIrFp0qu4FNlpc5sM1tGbXUYFgdVQIoeLk1Y1UoblyEg==" + }, + "@types/webgl2": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@types/webgl2/-/webgl2-0.0.4.tgz", + "integrity": "sha512-PACt1xdErJbMUOUweSrbVM7gSIYm1vTncW2hF6Os/EeWi6TXYAYMPp+8v6rzHmypE5gHrxaxZNXgMkJVIdZpHw==" + }, "@types/yargs": { "version": "12.0.12", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-12.0.12.tgz", @@ -12124,6 +12214,11 @@ } } }, + "seedrandom": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-2.4.3.tgz", + "integrity": "sha1-JDhQTa0zkXMUv/GKxNeU8W1qrsw=" + }, "select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", diff --git a/package.json b/package.json index 710dbe08..dbb9edc1 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,8 @@ "private": true, "dependencies": { "@material-ui/core": "^4.1.1", + "@tensorflow-models/coco-ssd": "^2.0.0", + "@tensorflow/tfjs": "^1.2.9", "@types/jest": "24.0.14", "@types/node": "12.0.8", "@types/react": "16.8.20", @@ -29,7 +31,7 @@ "scripts": { "start": "react-scripts start", "build": "react-scripts build", - "test": "CI=true react-scripts test --env=jsdom", + "test": "CI=true react-scripts test --env=./src/__test__/custom-test-env.js", "test:coverage": "npm test -- --coverage", "eject": "react-scripts eject" }, diff --git a/public/ico/checkbox-checked-color.png b/public/ico/checkbox-checked-color.png deleted file mode 100644 index c6f96d1e..00000000 Binary files a/public/ico/checkbox-checked-color.png and /dev/null differ diff --git a/public/ico/ok.png b/public/ico/ok.png new file mode 100644 index 00000000..adc32168 Binary files /dev/null and b/public/ico/ok.png differ diff --git a/public/img/robot.png b/public/img/robot.png new file mode 100644 index 00000000..80a13977 Binary files /dev/null and b/public/img/robot.png differ diff --git a/src/App.scss b/src/App.scss index f4f3df7f..31c72cfa 100644 --- a/src/App.scss +++ b/src/App.scss @@ -1,8 +1,11 @@ @import './settings/_Settings'; -//.App { -// height: 100vh; -// width: 100vw; -// margin: 0; -// padding: 0; -//} \ No newline at end of file +.App { + --leading-color: #{$secondaryColor}; + --hue-value: 172deg; +} + +.App.AI { + --leading-color: #{$primaryColor}; + --hue-value: 120deg; +} \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index cfce5b29..d9de591d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -11,13 +11,15 @@ import {ISize} from "./interfaces/ISize"; import {Settings} from "./settings/Settings"; import {SizeItUpView} from "./views/SizeItUpView/SizeItUpView"; import {PlatformModel} from "./staticModels/PlatformModel"; +import classNames from "classnames"; interface IProps { projectType: ProjectType; windowSize: ISize; + AIMode: boolean; } -const App: React.FC = ({projectType, windowSize}) => { +const App: React.FC = ({projectType, windowSize, AIMode}) => { const selectRoute = () => { if (!!PlatformModel.mobileDeviceData.manufacturer && !!PlatformModel.mobileDeviceData.os) return ; @@ -33,7 +35,7 @@ const App: React.FC = ({projectType, windowSize}) => { }; return ( -
{selectRoute()} @@ -43,8 +45,9 @@ const App: React.FC = ({projectType, windowSize}) => { }; const mapStateToProps = (state: AppState) => ({ - projectType: state.editor.projectType, - windowSize: state.general.windowSize + projectType: state.general.projectData.type, + windowSize: state.general.windowSize, + AIMode: state.ai.isObjectDetectorLoaded }); export default connect( diff --git a/src/__test__/custom-test-env.js b/src/__test__/custom-test-env.js new file mode 100644 index 00000000..771878f8 --- /dev/null +++ b/src/__test__/custom-test-env.js @@ -0,0 +1,12 @@ +const Environment = require('jest-environment-jsdom'); + +/** + * A custom environment to set the TextEncoder that is required by TensorFlow.js. + */ +module.exports = class CustomTestEnvironment extends Environment { + async setup() { + await super.setup(); + const { TextEncoder } = require('util'); + this.global.TextEncoder = TextEncoder; + } +} \ No newline at end of file diff --git a/src/ai/ObjectDetector.ts b/src/ai/ObjectDetector.ts new file mode 100644 index 00000000..2a16c483 --- /dev/null +++ b/src/ai/ObjectDetector.ts @@ -0,0 +1,39 @@ +import * as cocoSsd from '@tensorflow-models/coco-ssd'; +import {ObjectDetection} from "@tensorflow-models/coco-ssd"; +import {DetectedObject} from "@tensorflow-models/coco-ssd"; +import {store} from "../index"; +import {updateObjectDetectorStatus} from "../store/ai/actionCreators"; +import {AIActions} from "../logic/actions/AIActions"; + +export class ObjectDetector { + private static model: ObjectDetection; + + public static loadModel(callback?: () => any) { + cocoSsd + .load() + .then((model: ObjectDetection) => { + ObjectDetector.model = model; + store.dispatch(updateObjectDetectorStatus(true)); + AIActions.detectRectsForActiveImage(); + callback && callback(); + }) + .catch((error) => { + // TODO + throw new Error(error); + }) + } + + public static predict(image: HTMLImageElement, callback?: (predictions: DetectedObject[]) => any) { + if (!ObjectDetector.model) return; + + ObjectDetector.model + .detect(image) + .then((predictions: DetectedObject[]) => { + callback && callback(predictions) + }) + .catch((error) => { + // TODO + throw new Error(error); + }) + } +} \ No newline at end of file diff --git a/src/data/enums/LabelStatus.ts b/src/data/enums/LabelStatus.ts new file mode 100644 index 00000000..de06ee29 --- /dev/null +++ b/src/data/enums/LabelStatus.ts @@ -0,0 +1,5 @@ +export enum LabelStatus { + ACCEPTED = "ACCEPTED", + REJECTED = "REJECTED", + UNDECIDED = "UNDECIDED" +} \ No newline at end of file diff --git a/src/data/enums/PopupWindowType.ts b/src/data/enums/PopupWindowType.ts index 2265b875..af9c8822 100644 --- a/src/data/enums/PopupWindowType.ts +++ b/src/data/enums/PopupWindowType.ts @@ -1,6 +1,7 @@ export enum PopupWindowType { LOAD_LABEL_NAMES = "LOAD_LABEL_NAMES", LOAD_IMAGES = "LOAD_IMAGES", + LOAD_AI_MODEL = "LOAD_AI_MODEL", EXPORT_LABELS = "EXPORT_LABELS", INSERT_LABEL_NAMES = 'INSERT_LABEL_NAMES', EXIT_PROJECT = 'EXIT_PROJECT' diff --git a/src/data/info/EditorFeatureData.ts b/src/data/info/EditorFeatureData.ts index d840c388..9894ab88 100644 --- a/src/data/info/EditorFeatureData.ts +++ b/src/data/info/EditorFeatureData.ts @@ -31,8 +31,8 @@ export const EditorFeatureData: IEditorFeature[] = [ imageAlt: "file", }, { - displayText: "Support basic image operations like crop and resize", - imageSrc: "img/crop.png", - imageAlt: "crop", + displayText: "Use AI to make your work more productive", + imageSrc: "img/robot.png", + imageAlt: "robot", }, ]; \ No newline at end of file diff --git a/src/logic/actions/AIActions.ts b/src/logic/actions/AIActions.ts new file mode 100644 index 00000000..f72ceea4 --- /dev/null +++ b/src/logic/actions/AIActions.ts @@ -0,0 +1,53 @@ +import {DetectedObject} from "@tensorflow-models/coco-ssd"; +import {ImageData, LabelRect} from "../../store/labels/types"; +import {LabelsSelector} from "../../store/selectors/LabelsSelector"; +import uuidv1 from 'uuid/v1'; +import {store} from "../../index"; +import {updateImageDataById} from "../../store/labels/actionCreators"; +import {ObjectDetector} from "../../ai/ObjectDetector"; +import {ImageRepository} from "../imageRepository/ImageRepository"; +import {LabelStatus} from "../../data/enums/LabelStatus"; + +export class AIActions { + public static detectRectsForActiveImage(): void { + const activeImageData: ImageData = LabelsSelector.getActiveImageData(); + AIActions.detectRects(activeImageData.id, ImageRepository.getById(activeImageData.id)) + } + + public static detectRects(imageId: string, image: HTMLImageElement): void { + if (LabelsSelector.getImageDataById(imageId).isVisitedByObjectDetector) + return; + + ObjectDetector.predict(image, (predictions: DetectedObject[]) => { + AIActions.savePredictions(imageId, predictions); + }) + } + + public static savePredictions(imageId: string, predictions: DetectedObject[]) { + const imageData: ImageData = LabelsSelector.getImageDataById(imageId); + const predictedLabels: LabelRect[] = AIActions.mapPredictionsToRectLabels(predictions); + const nextImageData: ImageData = { + ...imageData, + labelRects: imageData.labelRects.concat(predictedLabels), + isVisitedByObjectDetector: true + }; + store.dispatch(updateImageDataById(imageData.id, nextImageData)); + } + + public static mapPredictionsToRectLabels(predictions: DetectedObject[]): LabelRect[] { + return predictions.map((prediction: DetectedObject) => { + return { + id: uuidv1(), + labelIndex: null, + rect: { + x: prediction.bbox[0], + y: prediction.bbox[1], + width: prediction.bbox[2], + height: prediction.bbox[3], + }, + isCreatedByAI: true, + status: LabelStatus.UNDECIDED + } + }) + } +} \ No newline at end of file diff --git a/src/logic/actions/EditorActions.ts b/src/logic/actions/EditorActions.ts index 681ef438..bbcff600 100644 --- a/src/logic/actions/EditorActions.ts +++ b/src/logic/actions/EditorActions.ts @@ -88,7 +88,7 @@ export class EditorActions { viewPortContentSize: CanvasUtil.getSize(EditorModel.canvas), activeKeyCombo: ContextManager.getActiveCombo(), event: event, - zoom: EditorModel.zoom, + zoom: GeneralSelector.getZoom(), viewPortSize: EditorModel.viewPortSize, defaultRenderImageRect: EditorModel.defaultRenderImageRect, viewPortContentImageRect: ViewPortActions.calculateViewPortContentImageRect(), diff --git a/src/logic/actions/ImageActions.ts b/src/logic/actions/ImageActions.ts index 9e883866..55b1abc4 100644 --- a/src/logic/actions/ImageActions.ts +++ b/src/logic/actions/ImageActions.ts @@ -1,24 +1,24 @@ -import {EditorSelector} from "../../store/selectors/EditorSelector"; +import {LabelsSelector} from "../../store/selectors/LabelsSelector"; import {store} from "../../index"; -import {updateActiveImageIndex, updateActiveLabelId} from "../../store/editor/actionCreators"; +import {updateActiveImageIndex, updateActiveLabelId} from "../../store/labels/actionCreators"; import {ViewPortActions} from "./ViewPortActions"; import {EditorModel} from "../../staticModels/EditorModel"; export class ImageActions { public static getPreviousImage(): void { - const currentImageIndex: number = EditorSelector.getActiveImageIndex(); + const currentImageIndex: number = LabelsSelector.getActiveImageIndex(); ImageActions.getImageByIndex(currentImageIndex - 1); } public static getNextImage(): void { - const currentImageIndex: number = EditorSelector.getActiveImageIndex(); + const currentImageIndex: number = LabelsSelector.getActiveImageIndex(); ImageActions.getImageByIndex(currentImageIndex + 1); } public static getImageByIndex(index: number): void { if (EditorModel.viewPortActionsDisabled) return; - const imageCount: number = EditorSelector.getImagesData().length; + const imageCount: number = LabelsSelector.getImagesData().length; if (index < 0 || index > imageCount - 1) { return; diff --git a/src/logic/actions/LabelActions.ts b/src/logic/actions/LabelActions.ts index 86600dc8..bd9122bf 100644 --- a/src/logic/actions/LabelActions.ts +++ b/src/logic/actions/LabelActions.ts @@ -1,19 +1,19 @@ -import {EditorSelector} from "../../store/selectors/EditorSelector"; -import {ImageData, LabelPoint, LabelPolygon, LabelRect} from "../../store/editor/types"; +import {LabelsSelector} from "../../store/selectors/LabelsSelector"; +import {ImageData, LabelPoint, LabelPolygon, LabelRect} from "../../store/labels/types"; import * as _ from "lodash"; import {store} from "../../index"; -import {updateImageDataById} from "../../store/editor/actionCreators"; +import {updateImageDataById} from "../../store/labels/actionCreators"; import {LabelType} from "../../data/enums/LabelType"; export class LabelActions { public static deleteActiveLabel() { - const activeImageData: ImageData = EditorSelector.getActiveImageData(); - const activeLabelId: string = EditorSelector.getActiveLabelId(); + const activeImageData: ImageData = LabelsSelector.getActiveImageData(); + const activeLabelId: string = LabelsSelector.getActiveLabelId(); LabelActions.deleteImageLabelById(activeImageData.id, activeLabelId); } public static deleteImageLabelById(imageId: string, labelId: string) { - switch (EditorSelector.getActiveLabelType()) { + switch (LabelsSelector.getActiveLabelType()) { case LabelType.POINT: LabelActions.deletePointLabelById(imageId, labelId); break; @@ -27,7 +27,7 @@ export class LabelActions { } public static deleteRectLabelById(imageId: string, labelRectId: string) { - const imageData: ImageData = EditorSelector.getImageDataById(imageId); + const imageData: ImageData = LabelsSelector.getImageDataById(imageId); const newImageData = { ...imageData, labelRects: _.filter(imageData.labelRects, (currentLabel: LabelRect) => { @@ -38,7 +38,7 @@ export class LabelActions { } public static deletePointLabelById(imageId: string, labelPointId: string) { - const imageData: ImageData = EditorSelector.getImageDataById(imageId); + const imageData: ImageData = LabelsSelector.getImageDataById(imageId); const newImageData = { ...imageData, labelPoints: _.filter(imageData.labelPoints, (currentLabel: LabelPoint) => { @@ -49,7 +49,7 @@ export class LabelActions { } public static deletePolygonLabelById(imageId: string, labelPolygonId: string) { - const imageData: ImageData = EditorSelector.getImageDataById(imageId); + const imageData: ImageData = LabelsSelector.getImageDataById(imageId); const newImageData = { ...imageData, labelPolygons: _.filter(imageData.labelPolygons, (currentLabel: LabelPolygon) => { diff --git a/src/logic/actions/ViewPortActions.ts b/src/logic/actions/ViewPortActions.ts index 89c2c341..8ed67a9f 100644 --- a/src/logic/actions/ViewPortActions.ts +++ b/src/logic/actions/ViewPortActions.ts @@ -11,6 +11,9 @@ import {SizeUtil} from "../../utils/SizeUtil"; import {EditorActions} from "./EditorActions"; import {Direction} from "../../data/enums/Direction"; import {DirectionUtil} from "../../utils/DirectionUtil"; +import {GeneralSelector} from "../../store/selectors/GeneralSelector"; +import {store} from "../../index"; +import {updateZoom} from "../../store/general/actionCreators"; export class ViewPortActions { public static updateViewPortSize() { @@ -35,7 +38,7 @@ export class ViewPortActions { public static calculateViewPortContentSize(): ISize { if (!!EditorModel.viewPortSize && !!EditorModel.image) { const defaultViewPortImageRect: IRect = EditorModel.defaultRenderImageRect; - const scaledImageSize: ISize = SizeUtil.scale(EditorModel.defaultRenderImageRect, EditorModel.zoom); + const scaledImageSize: ISize = SizeUtil.scale(EditorModel.defaultRenderImageRect, GeneralSelector.getZoom()); return { width: scaledImageSize.width + 2 * defaultViewPortImageRect.x, height: scaledImageSize.height + 2 * defaultViewPortImageRect.y @@ -123,7 +126,7 @@ export class ViewPortActions { public static zoomIn() { if (EditorModel.viewPortActionsDisabled) return; - const currentZoom: number = EditorModel.zoom; + const currentZoom: number = GeneralSelector.getZoom(); const currentRelativeScrollPosition: IPoint = ViewPortActions.getRelativeScrollPosition(); const nextRelativeScrollPosition = currentZoom === 1 ? {x: 0.5, y: 0.5} : currentRelativeScrollPosition; ViewPortActions.setZoom(currentZoom + ViewPointSettings.ZOOM_STEP); @@ -135,7 +138,7 @@ export class ViewPortActions { public static zoomOut() { if (EditorModel.viewPortActionsDisabled) return; - const currentZoom: number = EditorModel.zoom; + const currentZoom: number = GeneralSelector.getZoom(); const currentRelativeScrollPosition: IPoint = ViewPortActions.getRelativeScrollPosition(); ViewPortActions.setZoom(currentZoom - ViewPointSettings.ZOOM_STEP); ViewPortActions.resizeViewPortContent(); @@ -152,7 +155,7 @@ export class ViewPortActions { } public static setOneForOneZoom() { - const currentZoom: number = EditorModel.zoom; + const currentZoom: number = GeneralSelector.getZoom(); const currentRelativeScrollPosition: IPoint = ViewPortActions.getRelativeScrollPosition(); const nextRelativeScrollPosition = currentZoom === 1 ? {x: 0.5, y: 0.5} : currentRelativeScrollPosition; const nextZoom: number = EditorModel.image.width / EditorModel.defaultRenderImageRect.width @@ -163,12 +166,12 @@ export class ViewPortActions { } public static setZoom(value: number) { - const currentZoom: number = EditorModel.zoom; + const currentZoom: number = GeneralSelector.getZoom(); const isNewValueValid: boolean = NumberUtil.isValueInRange( value, ViewPointSettings.MIN_ZOOM, ViewPointSettings.MAX_ZOOM); if (isNewValueValid && value !== currentZoom) { - EditorModel.zoom = value; + store.dispatch(updateZoom(value)); } } } \ No newline at end of file diff --git a/src/logic/export/PointLabelsExport.ts b/src/logic/export/PointLabelsExport.ts index 4acd4782..a5feaa37 100644 --- a/src/logic/export/PointLabelsExport.ts +++ b/src/logic/export/PointLabelsExport.ts @@ -1,8 +1,8 @@ import {ExportFormatType} from "../../data/enums/ExportFormatType"; -import {ImageData, LabelPoint} from "../../store/editor/types"; +import {ImageData, LabelPoint} from "../../store/labels/types"; import {saveAs} from "file-saver"; import {ImageRepository} from "../imageRepository/ImageRepository"; -import {EditorSelector} from "../../store/selectors/EditorSelector"; +import {LabelsSelector} from "../../store/selectors/LabelsSelector"; import {ExporterUtil} from "../../utils/ExporterUtil"; export class PointLabelsExporter { @@ -17,7 +17,7 @@ export class PointLabelsExporter { } private static exportAsCSV(): void { - const content: string = EditorSelector.getImagesData() + const content: string = LabelsSelector.getImagesData() .map((imageData: ImageData) => { return PointLabelsExporter.wrapRectLabelsIntoCSV(imageData)}) .filter((imageLabelData: string) => { @@ -38,7 +38,7 @@ export class PointLabelsExporter { return null; const image: HTMLImageElement = ImageRepository.getById(imageData.id); - const labelNamesList: string[] = EditorSelector.getLabelNames(); + const labelNamesList: string[] = LabelsSelector.getLabelNames(); const labelRectsString: string[] = imageData.labelPoints.map((labelPoint: LabelPoint) => { const labelFields = [ labelNamesList[labelPoint.labelIndex], diff --git a/src/logic/export/PolygonLabelsExporter.ts b/src/logic/export/PolygonLabelsExporter.ts index 23e0afe1..a38d66cd 100644 --- a/src/logic/export/PolygonLabelsExporter.ts +++ b/src/logic/export/PolygonLabelsExporter.ts @@ -1,8 +1,8 @@ import {ExportFormatType} from "../../data/enums/ExportFormatType"; import {IPoint} from "../../interfaces/IPoint"; import {VGGFileData, VGGObject, VGGPolygon, VGGRegionsData} from "../../data/VGG/IVGG"; -import {ImageData, LabelPolygon} from "../../store/editor/types"; -import {EditorSelector} from "../../store/selectors/EditorSelector"; +import {ImageData, LabelPolygon} from "../../store/labels/types"; +import {LabelsSelector} from "../../store/selectors/LabelsSelector"; import {saveAs} from "file-saver"; import {ExporterUtil} from "../../utils/ExporterUtil"; @@ -18,8 +18,8 @@ export class PolygonLabelsExporter { } private static exportAsVGGJson(): void { - const imagesData: ImageData[] = EditorSelector.getImagesData(); - const labelNames: string[] = EditorSelector.getLabelNames(); + const imagesData: ImageData[] = LabelsSelector.getImagesData(); + const labelNames: string[] = LabelsSelector.getLabelNames(); const content: string = JSON.stringify(PolygonLabelsExporter.mapImagesDataToVGGObject(imagesData, labelNames)); const blob = new Blob([content], {type: "text/plain;charset=utf-8"}); try { diff --git a/src/logic/export/RectLabelsExporter.ts b/src/logic/export/RectLabelsExporter.ts index 1238e617..b2d7f5a7 100644 --- a/src/logic/export/RectLabelsExporter.ts +++ b/src/logic/export/RectLabelsExporter.ts @@ -1,11 +1,12 @@ import {ExportFormatType} from "../../data/enums/ExportFormatType"; -import {ImageData, LabelRect} from "../../store/editor/types"; +import {ImageData, LabelRect} from "../../store/labels/types"; import {ImageRepository} from "../imageRepository/ImageRepository"; import JSZip from 'jszip'; import { saveAs } from 'file-saver'; -import {EditorSelector} from "../../store/selectors/EditorSelector"; +import {LabelsSelector} from "../../store/selectors/LabelsSelector"; import {XMLSanitizerUtil} from "../../utils/XMLSanitizerUtil"; import {ExporterUtil} from "../../utils/ExporterUtil"; +import {GeneralSelector} from "../../store/selectors/GeneralSelector"; export class RectLabelsExporter { public static export(exportFormatType: ExportFormatType): void { @@ -26,7 +27,7 @@ export class RectLabelsExporter { private static exportAsYOLO(): void { let zip = new JSZip(); - EditorSelector.getImagesData() + LabelsSelector.getImagesData() .forEach((imageData: ImageData) => { const fileContent: string = RectLabelsExporter.wrapRectLabelsIntoYOLO(imageData); if (fileContent) { @@ -72,7 +73,7 @@ export class RectLabelsExporter { private static exportAsVOC(): void { let zip = new JSZip(); - EditorSelector.getImagesData().forEach((imageData: ImageData) => { + LabelsSelector.getImagesData().forEach((imageData: ImageData) => { const fileContent: string = RectLabelsExporter.wrapImageIntoVOC(imageData); if (fileContent) { const fileName : string = imageData.fileData.name.replace(/\.[^/.]+$/, ".xml"); @@ -100,7 +101,7 @@ export class RectLabelsExporter { if (imageData.labelRects.length === 0 || !imageData.loadStatus) return null; - const labelNamesList: string[] = EditorSelector.getLabelNames(); + const labelNamesList: string[] = LabelsSelector.getLabelNames(); const labelRectsString: string[] = imageData.labelRects.map((labelRect: LabelRect) => { const labelFields = [ `\t`, @@ -123,7 +124,7 @@ export class RectLabelsExporter { private static wrapImageIntoVOC(imageData: ImageData): string { const labels: string = RectLabelsExporter.wrapRectLabelsIntoVOC(imageData); - const projectName: string = XMLSanitizerUtil.sanitize(EditorSelector.getProjectName()); + const projectName: string = XMLSanitizerUtil.sanitize(GeneralSelector.getProjectName()); if (labels) { const image: HTMLImageElement = ImageRepository.getById(imageData.id); @@ -149,7 +150,7 @@ export class RectLabelsExporter { private static exportAsCSV(): void { - const content: string = EditorSelector.getImagesData() + const content: string = LabelsSelector.getImagesData() .map((imageData: ImageData) => { return RectLabelsExporter.wrapRectLabelsIntoCSV(imageData)}) .filter((imageLabelData: string) => { @@ -170,7 +171,7 @@ export class RectLabelsExporter { return null; const image: HTMLImageElement = ImageRepository.getById(imageData.id); - const labelNamesList: string[] = EditorSelector.getLabelNames(); + const labelNamesList: string[] = LabelsSelector.getLabelNames(); const labelRectsString: string[] = imageData.labelRects.map((labelRect: LabelRect) => { const labelFields = [ labelNamesList[labelRect.labelIndex], diff --git a/src/logic/export/__tests__/PolygonLabelsExporter.test.ts b/src/logic/export/__tests__/PolygonLabelsExporter.test.ts index 0a391574..44657fcb 100644 --- a/src/logic/export/__tests__/PolygonLabelsExporter.test.ts +++ b/src/logic/export/__tests__/PolygonLabelsExporter.test.ts @@ -1,7 +1,7 @@ import {IPoint} from "../../../interfaces/IPoint"; import {PolygonLabelsExporter} from "../PolygonLabelsExporter"; import {VGGPolygon, VGGRegionsData} from "../../../data/VGG/IVGG"; -import {ImageData} from "../../../store/editor/types"; +import {ImageData} from "../../../store/labels/types"; describe('PolygonLabelsExporter mapPolygonToVGG method', () => { it('should return correct VGGPolygon object', () => { diff --git a/src/logic/helpers/CSSHelper.ts b/src/logic/helpers/CSSHelper.ts new file mode 100644 index 00000000..26c50094 --- /dev/null +++ b/src/logic/helpers/CSSHelper.ts @@ -0,0 +1,8 @@ +import {Settings} from "../../settings/Settings"; +import {AISelector} from "../../store/selectors/AISelector"; + +export class CSSHelper { + public static getLeadingColor(): string { + return AISelector.isAiModelLoaded() ? Settings.PRIMARY_COLOR : Settings.SECONDARY_COLOR; + } +} \ No newline at end of file diff --git a/src/logic/initializer/AppInitializer.ts b/src/logic/initializer/AppInitializer.ts index d89ff00a..a9ba600b 100644 --- a/src/logic/initializer/AppInitializer.ts +++ b/src/logic/initializer/AppInitializer.ts @@ -11,8 +11,8 @@ export class AppInitializer { AppInitializer.detectDeviceParams(); window.addEventListener(EventType.RESIZE, AppInitializer.handleResize); window.addEventListener(EventType.MOUSE_WHEEL, AppInitializer.disableGenericScrollZoom); - window.addEventListener(EventType.KEY_DOWN, AppInitializer.disableGenericKeyBordZoom); - window.addEventListener(EventType.KEY_PRESS, AppInitializer.disableGenericKeyBordZoom); + window.addEventListener(EventType.KEY_DOWN, AppInitializer.disableUnwantedKeyBoardBehaviour); + window.addEventListener(EventType.KEY_PRESS, AppInitializer.disableUnwantedKeyBoardBehaviour); ContextManager.init(); } @@ -23,7 +23,7 @@ export class AppInitializer { })); }; - public static disableGenericKeyBordZoom = (event: KeyboardEvent) => { + private static disableUnwantedKeyBoardBehaviour = (event: KeyboardEvent) => { if (PlatformModel.isMac && event.metaKey) { event.preventDefault(); } diff --git a/src/logic/render/PointRenderEngine.ts b/src/logic/render/PointRenderEngine.ts index 18077aa1..7ab7ed74 100644 --- a/src/logic/render/PointRenderEngine.ts +++ b/src/logic/render/PointRenderEngine.ts @@ -3,19 +3,19 @@ import {RenderEngineConfig} from "../../settings/RenderEngineConfig"; import {IPoint} from "../../interfaces/IPoint"; import {CanvasUtil} from "../../utils/CanvasUtil"; import {store} from "../../index"; -import {ImageData, LabelPoint} from "../../store/editor/types"; +import {ImageData, LabelPoint} from "../../store/labels/types"; import uuidv1 from 'uuid/v1'; import { updateActiveLabelId, updateFirstLabelCreatedFlag, updateHighlightedLabelId, updateImageDataById -} from "../../store/editor/actionCreators"; +} from "../../store/labels/actionCreators"; import {RectUtil} from "../../utils/RectUtil"; import {DrawUtil} from "../../utils/DrawUtil"; import {updateCustomCursorStyle} from "../../store/general/actionCreators"; import {CustomCursorStyle} from "../../data/enums/CustomCursorStyle"; -import {EditorSelector} from "../../store/selectors/EditorSelector"; +import {LabelsSelector} from "../../store/selectors/LabelsSelector"; import {EditorData} from "../../data/EditorData"; import {BaseRenderEngine} from "./BaseRenderEngine"; import {RenderEngineUtil} from "../../utils/RenderEngineUtil"; @@ -68,10 +68,10 @@ export class PointRenderEngine extends BaseRenderEngine { public mouseUpHandler(data: EditorData): void { if (this.isInProgress()) { - const activeLabelPoint: LabelPoint = EditorSelector.getActivePointLabel(); + const activeLabelPoint: LabelPoint = LabelsSelector.getActivePointLabel(); const pointSnapped: IPoint = RectUtil.snapPointToRect(data.mousePositionOnViewPortContent, data.viewPortContentImageRect); const pointOnImage: IPoint = RenderEngineUtil.transferPointFromViewPortContentToImage(pointSnapped, data); - const imageData = EditorSelector.getActiveImageData(); + const imageData = LabelsSelector.getActiveImageData(); imageData.labelPoints = imageData.labelPoints.map((labelPoint: LabelPoint) => { if (labelPoint.id === activeLabelPoint.id) { @@ -92,11 +92,11 @@ export class PointRenderEngine extends BaseRenderEngine { if (isOverImage) { const labelPoint: LabelPoint = this.getLabelPointUnderMouse(data.mousePositionOnViewPortContent, data); if (!!labelPoint) { - if (EditorSelector.getHighlightedLabelId() !== labelPoint.id) { + if (LabelsSelector.getHighlightedLabelId() !== labelPoint.id) { store.dispatch(updateHighlightedLabelId(labelPoint.id)) } } else { - if (EditorSelector.getHighlightedLabelId() !== null) { + if (LabelsSelector.getHighlightedLabelId() !== null) { store.dispatch(updateHighlightedLabelId(null)) } } @@ -108,9 +108,9 @@ export class PointRenderEngine extends BaseRenderEngine { // ================================================================================================================= public render(data: EditorData): void { - const activeLabelId: string = EditorSelector.getActiveLabelId(); - const highlightedLabelId: string = EditorSelector.getHighlightedLabelId(); - const imageData: ImageData = EditorSelector.getActiveImageData(); + const activeLabelId: string = LabelsSelector.getActiveLabelId(); + const highlightedLabelId: string = LabelsSelector.getHighlightedLabelId(); + const imageData: ImageData = LabelsSelector.getActiveImageData(); if (imageData) { imageData.labelPoints.forEach((labelPoint: LabelPoint) => { if (labelPoint.id === activeLabelId) { @@ -172,7 +172,7 @@ export class PointRenderEngine extends BaseRenderEngine { } private getLabelPointUnderMouse(mousePosition: IPoint, data: EditorData): LabelPoint { - const labelPoints: LabelPoint[] = EditorSelector.getActiveImageData().labelPoints; + const labelPoints: LabelPoint[] = LabelsSelector.getActiveImageData().labelPoints; for (let i = 0; i < labelPoints.length; i++) { const pointOnCanvas: IPoint = RenderEngineUtil.transferPointFromImageToViewPortContent(labelPoints[i].point, data); const handleRect: IRect = RectUtil.getRectWithCenterAndSize(pointOnCanvas, this.config.anchorHoverSize); @@ -184,8 +184,8 @@ export class PointRenderEngine extends BaseRenderEngine { } private addPointLabel = (point: IPoint) => { - const activeLabelIndex = EditorSelector.getActiveLabelNameIndex(); - const imageData: ImageData = EditorSelector.getActiveImageData(); + const activeLabelIndex = LabelsSelector.getActiveLabelNameIndex(); + const imageData: ImageData = LabelsSelector.getActiveImageData(); const labelPoint: LabelPoint = { id: uuidv1(), labelIndex: activeLabelIndex, diff --git a/src/logic/render/PolygonRenderEngine.ts b/src/logic/render/PolygonRenderEngine.ts index b7223794..563da697 100644 --- a/src/logic/render/PolygonRenderEngine.ts +++ b/src/logic/render/PolygonRenderEngine.ts @@ -9,15 +9,15 @@ import {IPoint} from "../../interfaces/IPoint"; import {ILine} from "../../interfaces/ILine"; import {DrawUtil} from "../../utils/DrawUtil"; import {IRect} from "../../interfaces/IRect"; -import {ImageData, LabelPolygon} from "../../store/editor/types"; -import {EditorSelector} from "../../store/selectors/EditorSelector"; +import {ImageData, LabelPolygon} from "../../store/labels/types"; +import {LabelsSelector} from "../../store/selectors/LabelsSelector"; import uuidv1 from 'uuid/v1'; import { updateActiveLabelId, updateFirstLabelCreatedFlag, updateHighlightedLabelId, updateImageDataById -} from "../../store/editor/actionCreators"; +} from "../../store/labels/actionCreators"; import {LineUtil} from "../../utils/LineUtil"; import {MouseEventUtil} from "../../utils/MouseEventUtil"; import {EventType} from "../../data/enums/EventType"; @@ -116,7 +116,7 @@ export class PolygonRenderEngine extends BaseRenderEngine { if (isOverImage && !this.isCreationInProgress()) { const labelPolygon: LabelPolygon = this.getPolygonUnderMouse(data); if (!!labelPolygon && !this.isResizeInProgress()) { - if (EditorSelector.getHighlightedLabelId() !== labelPolygon.id) { + if (LabelsSelector.getHighlightedLabelId() !== labelPolygon.id) { store.dispatch(updateHighlightedLabelId(labelPolygon.id)) } const pathOnCanvas: IPoint[] = RenderEngineUtil.transferPolygonFromImageToViewPortContent(labelPolygon.vertices, data); @@ -130,7 +130,7 @@ export class PolygonRenderEngine extends BaseRenderEngine { } } } else { - if (EditorSelector.getHighlightedLabelId() !== null) { + if (LabelsSelector.getHighlightedLabelId() !== null) { store.dispatch(updateHighlightedLabelId(null)); this.discardSuggestedPoint(); } @@ -144,7 +144,7 @@ export class PolygonRenderEngine extends BaseRenderEngine { // ================================================================================================================= public render(data: EditorData): void { - const imageData: ImageData = EditorSelector.getActiveImageData(); + const imageData: ImageData = LabelsSelector.getActiveImageData(); if (imageData) { this.drawExistingLabels(data); this.drawActivelyCreatedLabel(data); @@ -199,7 +199,7 @@ export class PolygonRenderEngine extends BaseRenderEngine { } private drawActivelyResizeLabel(data: EditorData) { - const activeLabelPolygon: LabelPolygon = EditorSelector.getActivePolygonLabel(); + const activeLabelPolygon: LabelPolygon = LabelsSelector.getActivePolygonLabel(); if (!!activeLabelPolygon && this.isResizeInProgress()) { const snappedMousePosition: IPoint = RectUtil.snapPointToRect(data.mousePositionOnViewPortContent, data.viewPortContentImageRect); const polygonOnCanvas: IPoint[] = activeLabelPolygon.vertices.map((point: IPoint, index: number) => { @@ -210,9 +210,9 @@ export class PolygonRenderEngine extends BaseRenderEngine { } private drawExistingLabels(data: EditorData) { - const activeLabelId: string = EditorSelector.getActiveLabelId(); - const highlightedLabelId: string = EditorSelector.getHighlightedLabelId(); - const imageData: ImageData = EditorSelector.getActiveImageData(); + const activeLabelId: string = LabelsSelector.getActiveLabelId(); + const highlightedLabelId: string = LabelsSelector.getHighlightedLabelId(); + const imageData: ImageData = LabelsSelector.getActiveImageData(); imageData.labelPolygons.forEach((labelPolygon: LabelPolygon) => { const isActive: boolean = labelPolygon.id === activeLabelId || labelPolygon.id === highlightedLabelId; const pathOnCanvas: IPoint[] = RenderEngineUtil.transferPolygonFromImageToViewPortContent(labelPolygon.vertices, data); @@ -286,8 +286,8 @@ export class PolygonRenderEngine extends BaseRenderEngine { } private addPolygonLabel(polygon: IPoint[]) { - const activeLabelIndex = EditorSelector.getActiveLabelNameIndex(); - const imageData: ImageData = EditorSelector.getActiveImageData(); + const activeLabelIndex = LabelsSelector.getActiveLabelNameIndex(); + const imageData: ImageData = LabelsSelector.getActiveImageData(); const labelPolygon: LabelPolygon = { id: uuidv1(), labelIndex: activeLabelIndex, @@ -316,8 +316,8 @@ export class PolygonRenderEngine extends BaseRenderEngine { } private applyResizeToPolygonLabel(data: EditorData) { - const imageData: ImageData = EditorSelector.getActiveImageData(); - const activeLabel: LabelPolygon = EditorSelector.getActivePolygonLabel(); + const imageData: ImageData = LabelsSelector.getActiveImageData(); + const activeLabel: LabelPolygon = LabelsSelector.getActivePolygonLabel(); imageData.labelPolygons = imageData.labelPolygons.map((polygon: LabelPolygon) => { if (polygon.id !== activeLabel.id) { return polygon @@ -350,8 +350,8 @@ export class PolygonRenderEngine extends BaseRenderEngine { // ================================================================================================================= private addSuggestedAnchorToPolygonLabel(data: EditorData) { - const imageData: ImageData = EditorSelector.getActiveImageData(); - const activeLabel: LabelPolygon = EditorSelector.getActivePolygonLabel(); + const imageData: ImageData = LabelsSelector.getActiveImageData(); + const activeLabel: LabelPolygon = LabelsSelector.getActivePolygonLabel(); const newAnchorPositionOnImage: IPoint = RenderEngineUtil.transferPointFromViewPortContentToImage(this.suggestedAnchorPositionOnCanvas, data); const insert = (arr, index, newItem) => [...arr.slice(0, index), newItem, ...arr.slice(index)]; @@ -429,7 +429,7 @@ export class PolygonRenderEngine extends BaseRenderEngine { // ================================================================================================================= private getPolygonUnderMouse(data: EditorData): LabelPolygon { - const labelPolygons: LabelPolygon[] = EditorSelector.getActiveImageData().labelPolygons; + const labelPolygons: LabelPolygon[] = LabelsSelector.getActiveImageData().labelPolygons; for (let i = 0; i < labelPolygons.length; i++) { const pathOnCanvas: IPoint[] = RenderEngineUtil.transferPolygonFromImageToViewPortContent(labelPolygons[i].vertices, data); const linesOnCanvas: ILine[] = this.mapPointsToLines(pathOnCanvas.concat(pathOnCanvas[0])); @@ -447,7 +447,7 @@ export class PolygonRenderEngine extends BaseRenderEngine { } private getAnchorUnderMouse(data: EditorData): IPoint { - const labelPolygons: LabelPolygon[] = EditorSelector.getActiveImageData().labelPolygons; + const labelPolygons: LabelPolygon[] = LabelsSelector.getActiveImageData().labelPolygons; for (let i = 0; i < labelPolygons.length; i++) { const pathOnCanvas: IPoint[] = RenderEngineUtil.transferPolygonFromImageToViewPortContent(labelPolygons[i].vertices, data); for (let j = 0; j < pathOnCanvas.length; j ++) { diff --git a/src/logic/render/RectRenderEngine.ts b/src/logic/render/RectRenderEngine.ts index 27457f0f..c7e41b2a 100644 --- a/src/logic/render/RectRenderEngine.ts +++ b/src/logic/render/RectRenderEngine.ts @@ -3,26 +3,27 @@ import {IRect} from "../../interfaces/IRect"; import {RectUtil} from "../../utils/RectUtil"; import {DrawUtil} from "../../utils/DrawUtil"; import {store} from "../.."; -import {ImageData, LabelRect} from "../../store/editor/types"; +import {ImageData, LabelRect} from "../../store/labels/types"; import uuidv1 from 'uuid/v1'; import { updateActiveLabelId, updateFirstLabelCreatedFlag, updateHighlightedLabelId, updateImageDataById -} from "../../store/editor/actionCreators"; +} from "../../store/labels/actionCreators"; import {PointUtil} from "../../utils/PointUtil"; import {RectAnchor} from "../../data/RectAnchor"; import {RenderEngineConfig} from "../../settings/RenderEngineConfig"; import {updateCustomCursorStyle} from "../../store/general/actionCreators"; import {CustomCursorStyle} from "../../data/enums/CustomCursorStyle"; -import {EditorSelector} from "../../store/selectors/EditorSelector"; +import {LabelsSelector} from "../../store/selectors/LabelsSelector"; import {EditorData} from "../../data/EditorData"; import {BaseRenderEngine} from "./BaseRenderEngine"; import {RenderEngineUtil} from "../../utils/RenderEngineUtil"; import {LabelType} from "../../data/enums/LabelType"; import {EditorActions} from "../actions/EditorActions"; import {GeneralSelector} from "../../store/selectors/GeneralSelector"; +import {LabelStatus} from "../../data/enums/LabelStatus"; export class RectRenderEngine extends BaseRenderEngine { private config: RenderEngineConfig = new RenderEngineConfig(); @@ -51,12 +52,12 @@ export class RectRenderEngine extends BaseRenderEngine { if (!!rectUnderMouse) { const rect: IRect = this.calculateRectRelativeToActiveImage(rectUnderMouse.rect, data); const anchorUnderMouse: RectAnchor = this.getAnchorUnderMouseByRect(rect, data.mousePositionOnViewPortContent, data.viewPortContentImageRect); - if (!!anchorUnderMouse) { + if (!!anchorUnderMouse && rectUnderMouse.status === LabelStatus.ACCEPTED) { store.dispatch(updateActiveLabelId(rectUnderMouse.id)); this.startRectResize(anchorUnderMouse); } else { - if (!!EditorSelector.getHighlightedLabelId()) - store.dispatch(updateActiveLabelId(EditorSelector.getHighlightedLabelId())); + if (!!LabelsSelector.getHighlightedLabelId()) + store.dispatch(updateActiveLabelId(LabelsSelector.getHighlightedLabelId())); else this.startRectCreation(data.mousePositionOnViewPortContent); } @@ -70,7 +71,7 @@ export class RectRenderEngine extends BaseRenderEngine { public mouseUpHandler = (data: EditorData) => { if (!!data.viewPortContentImageRect) { const mousePositionSnapped: IPoint = RectUtil.snapPointToRect(data.mousePositionOnViewPortContent, data.viewPortContentImageRect); - const activeLabelRect: LabelRect = EditorSelector.getActiveRectLabel(); + const activeLabelRect: LabelRect = LabelsSelector.getActiveRectLabel(); if (!!this.startCreateRectPoint && !PointUtil.equals(this.startCreateRectPoint, mousePositionSnapped)) { @@ -92,7 +93,7 @@ export class RectRenderEngine extends BaseRenderEngine { const scale: number = RenderEngineUtil.calculateImageScale(data); const scaledRect: IRect = RectUtil.scaleRect(resizeRect, scale); - const imageData = EditorSelector.getActiveImageData(); + const imageData = LabelsSelector.getActiveImageData(); imageData.labelRects = imageData.labelRects.map((labelRect: LabelRect) => { if (labelRect.id === activeLabelRect.id) { return { @@ -114,11 +115,11 @@ export class RectRenderEngine extends BaseRenderEngine { if (isOverImage && !this.startResizeRectAnchor) { const labelRect: LabelRect = this.getRectUnderMouse(data); if (!!labelRect && !this.isInProgress()) { - if (EditorSelector.getHighlightedLabelId() !== labelRect.id) { + if (LabelsSelector.getHighlightedLabelId() !== labelRect.id) { store.dispatch(updateHighlightedLabelId(labelRect.id)) } } else { - if (EditorSelector.getHighlightedLabelId() !== null) { + if (LabelsSelector.getHighlightedLabelId() !== null) { store.dispatch(updateHighlightedLabelId(null)) } } @@ -131,12 +132,14 @@ export class RectRenderEngine extends BaseRenderEngine { // ================================================================================================================= public render(data: EditorData) { - const activeLabelId: string = EditorSelector.getActiveLabelId(); - const imageData: ImageData = EditorSelector.getActiveImageData(); + const activeLabelId: string = LabelsSelector.getActiveLabelId(); + const imageData: ImageData = LabelsSelector.getActiveImageData(); if (imageData) { imageData.labelRects.forEach((labelRect: LabelRect) => { - labelRect.id === activeLabelId ? this.drawActiveRect(labelRect, data) : this.drawInactiveRect(labelRect, data); + const displayAsActive: boolean = + labelRect.status === LabelStatus.ACCEPTED && labelRect.id === activeLabelId; + displayAsActive ? this.drawActiveRect(labelRect, data) : this.drawInactiveRect(labelRect, data); }); this.drawCurrentlyCreatedRect(data.mousePositionOnViewPortContent, data.viewPortContentImageRect); this.updateCursorStyle(data); @@ -159,8 +162,9 @@ export class RectRenderEngine extends BaseRenderEngine { private drawInactiveRect(labelRect: LabelRect, data: EditorData) { const rectOnImage: IRect = RenderEngineUtil.transferRectFromViewPortContentToImage(labelRect.rect, data); - const highlightedLabelId: string = EditorSelector.getHighlightedLabelId(); - this.renderRect(rectOnImage, labelRect.id === highlightedLabelId); + const highlightedLabelId: string = LabelsSelector.getHighlightedLabelId(); + const displayAsActive: boolean = labelRect.status === LabelStatus.ACCEPTED && labelRect.id === highlightedLabelId; + this.renderRect(rectOnImage, displayAsActive); } private drawActiveRect(labelRect: LabelRect, data: EditorData) { @@ -191,10 +195,13 @@ export class RectRenderEngine extends BaseRenderEngine { private updateCursorStyle(data: EditorData) { if (!!this.canvas && !!data.mousePositionOnViewPortContent && !GeneralSelector.getImageDragModeStatus()) { - const rectAnchorUnderMouse: RectAnchor = this.getAnchorUnderMouse(data); - if (!!rectAnchorUnderMouse || !!this.startResizeRectAnchor) { - store.dispatch(updateCustomCursorStyle(CustomCursorStyle.MOVE)); - return; + const rectUnderMouse: LabelRect = this.getRectUnderMouse(data); + if (!!rectUnderMouse) { + const rectAnchorUnderMouse: RectAnchor = this.getAnchorUnderMouse(data); + if ((!!rectAnchorUnderMouse && rectUnderMouse.status === LabelStatus.ACCEPTED) || !!this.startResizeRectAnchor) { + store.dispatch(updateCustomCursorStyle(CustomCursorStyle.MOVE)); + return; + } } if (RenderEngineUtil.isMouseOverCanvas(data)) { if (!RenderEngineUtil.isMouseOverImage(data) && !!this.startCreateRectPoint) @@ -222,12 +229,14 @@ export class RectRenderEngine extends BaseRenderEngine { } private addRectLabel = (rect: IRect) => { - const activeLabelIndex = EditorSelector.getActiveLabelNameIndex(); - const imageData: ImageData = EditorSelector.getActiveImageData(); + const activeLabelIndex = LabelsSelector.getActiveLabelNameIndex(); + const imageData: ImageData = LabelsSelector.getActiveImageData(); const labelRect: LabelRect = { id: uuidv1(), labelIndex: activeLabelIndex, - rect + rect, + isCreatedByAI: false, + status: LabelStatus.ACCEPTED }; imageData.labelRects.push(labelRect); store.dispatch(updateImageDataById(imageData.id, imageData)); @@ -236,12 +245,12 @@ export class RectRenderEngine extends BaseRenderEngine { }; private getRectUnderMouse(data: EditorData): LabelRect { - const activeRectLabel: LabelRect = EditorSelector.getActiveRectLabel(); + const activeRectLabel: LabelRect = LabelsSelector.getActiveRectLabel(); if (!!activeRectLabel && this.isMouseOverRectEdges(activeRectLabel.rect, data)) { return activeRectLabel; } - const labelRects: LabelRect[] = EditorSelector.getActiveImageData().labelRects; + const labelRects: LabelRect[] = LabelsSelector.getActiveImageData().labelRects; for (let i = 0; i < labelRects.length; i++) { if (this.isMouseOverRectEdges(labelRects[i].rect, data)) { return labelRects[i]; @@ -282,7 +291,7 @@ export class RectRenderEngine extends BaseRenderEngine { } private getAnchorUnderMouse(data: EditorData): RectAnchor { - const labelRects: LabelRect[] = EditorSelector.getActiveImageData().labelRects; + const labelRects: LabelRect[] = LabelsSelector.getActiveImageData().labelRects; for (let i = 0; i < labelRects.length; i++) { const rect: IRect = this.calculateRectRelativeToActiveImage(labelRects[i].rect, data); const rectAnchor = this.getAnchorUnderMouseByRect(rect, data.mousePositionOnViewPortContent, data.viewPortContentImageRect); diff --git a/src/model/EditorModel.ts b/src/model/EditorModel.ts deleted file mode 100644 index 69c0c0bd..00000000 --- a/src/model/EditorModel.ts +++ /dev/null @@ -1,17 +0,0 @@ -import {PrimaryEditorRenderEngine} from "../logic/render/PrimaryEditorRenderEngine"; -import {BaseRenderEngine} from "../logic/render/BaseRenderEngine"; -import {IRect} from "../interfaces/IRect"; -import {IPoint} from "../interfaces/IPoint"; - -export class EditorModel { - public static canvas: HTMLCanvasElement; - public static mousePositionIndicator: HTMLDivElement; - public static cursor: HTMLDivElement; - public static primaryRenderingEngine: PrimaryEditorRenderEngine; - public static supportRenderingEngine: BaseRenderEngine; - public static image: HTMLImageElement; - public static imageRectOnCanvas: IRect; - public static imageScale: number; // Image / Canvas - public static mousePositionOnCanvas: IPoint; - public static isLoading: boolean = false; -} \ No newline at end of file diff --git a/src/settings/_Settings.scss b/src/settings/_Settings.scss index fe12fa47..bd3a8f36 100644 --- a/src/settings/_Settings.scss +++ b/src/settings/_Settings.scss @@ -3,14 +3,7 @@ $darkThemeSecondColor: #282828; $darkThemeThirdColor: #4c4c4c; $darkThemeForthColor: #262c2f; -//$primaryColor: #33d1e5; -//$secondaryColor: #39f5c2; - -//$primaryColor: #5ee7df; -//$secondaryColor: #b490ca; - $primaryColor: #2af598; -//$primaryColor: #ff3366; $secondaryColor: #009efd; $topNavigationBarHeight: 35px; diff --git a/src/staticModels/EditorModel.ts b/src/staticModels/EditorModel.ts index 20933d7e..ae34ead9 100644 --- a/src/staticModels/EditorModel.ts +++ b/src/staticModels/EditorModel.ts @@ -3,7 +3,6 @@ import {BaseRenderEngine} from "../logic/render/BaseRenderEngine"; import {IRect} from "../interfaces/IRect"; import {IPoint} from "../interfaces/IPoint"; import {ISize} from "../interfaces/ISize"; -import {ViewPointSettings} from "../settings/ViewPointSettings"; import Scrollbars from "react-custom-scrollbars"; import {ViewPortHelper} from "../logic/helpers/ViewPortHelper"; @@ -23,7 +22,6 @@ export class EditorModel { public static isLoading: boolean = false; public static viewPortActionsDisabled: boolean = false; public static mousePositionOnViewPortContent: IPoint; - public static zoom: number = ViewPointSettings.MIN_ZOOM; public static viewPortSize: ISize; // x and y describe the dimension of the margin that remains constant regardless of the scale of the image diff --git a/src/store/Actions.ts b/src/store/Actions.ts index 85eb5d4a..9d2f8a83 100644 --- a/src/store/Actions.ts +++ b/src/store/Actions.ts @@ -1,6 +1,5 @@ export enum Action { - UPDATE_PROJECT_TYPE = '@@UPDATE_PROJECT_TYPE', - UPDATE_PROJECT_NAME = '@@UPDATE_PROJECT_NAME', + UPDATE_PROJECT_DATA = '@@UPDATE_PROJECT_DATA', UPDATE_ACTIVE_IMAGE_INDEX = '@@UPDATE_ACTIVE_IMAGE_INDEX', UPDATE_IMAGE_DATA_BY_ID = '@@UPDATE_IMAGE_DATA_BY_ID', ADD_IMAGES_DATA = '@@ADD_IMAGES_DATA', @@ -16,5 +15,7 @@ export enum Action { UPDATE_CUSTOM_CURSOR_STYLE = '@@UPDATE_CUSTOM_CURSOR_STYLE', UPDATE_PREVENT_CUSTOM_CURSOR_STATUS = '@@UPDATE_PREVENT_CUSTOM_CURSOR_STATUS', UPDATE_IMAGE_DRAG_MODE_STATUS = '@@UPDATE_IMAGE_DRAG_MODE_STATUS', - UPDATE_CONTEXT = '@@UPDATE_CONTEXT' + UPDATE_CONTEXT = '@@UPDATE_CONTEXT', + UPDATE_OBJECT_DETECTOR_STATUS = '@@UPDATE_OBJECT_DETECTOR_STATUS', + UPDATE_ZOOM = '@@UPDATE_ZOOM' } \ No newline at end of file diff --git a/src/store/ai/actionCreators.ts b/src/store/ai/actionCreators.ts new file mode 100644 index 00000000..dcaf6529 --- /dev/null +++ b/src/store/ai/actionCreators.ts @@ -0,0 +1,11 @@ +import {Action} from "../Actions"; +import {AIActionTypes} from "./types"; + +export function updateObjectDetectorStatus(isObjectDetectorLoaded: boolean): AIActionTypes { + return { + type: Action.UPDATE_OBJECT_DETECTOR_STATUS, + payload: { + isObjectDetectorLoaded, + } + } +} \ No newline at end of file diff --git a/src/store/ai/reducer.ts b/src/store/ai/reducer.ts new file mode 100644 index 00000000..69007225 --- /dev/null +++ b/src/store/ai/reducer.ts @@ -0,0 +1,22 @@ +import {AIActionTypes, AIState} from "./types"; +import {Action} from "../Actions"; + +const initialState: AIState = { + isObjectDetectorLoaded: false +}; + +export function aiReducer( + state = initialState, + action: AIActionTypes +): AIState { + switch (action.type) { + case Action.UPDATE_OBJECT_DETECTOR_STATUS: { + return { + ...state, + isObjectDetectorLoaded: action.payload.isObjectDetectorLoaded + } + } + default: + return state; + } +} \ No newline at end of file diff --git a/src/store/ai/types.ts b/src/store/ai/types.ts new file mode 100644 index 00000000..483e19b0 --- /dev/null +++ b/src/store/ai/types.ts @@ -0,0 +1,14 @@ +import {Action} from "../Actions"; + +export type AIState = { + isObjectDetectorLoaded: boolean; +} + +interface UpdateObjectDetectorStatus { + type: typeof Action.UPDATE_OBJECT_DETECTOR_STATUS; + payload: { + isObjectDetectorLoaded: boolean; + } +} + +export type AIActionTypes = UpdateObjectDetectorStatus \ No newline at end of file diff --git a/src/store/general/actionCreators.ts b/src/store/general/actionCreators.ts index f54909cd..c89ee102 100644 --- a/src/store/general/actionCreators.ts +++ b/src/store/general/actionCreators.ts @@ -1,5 +1,5 @@ import {ISize} from "../../interfaces/ISize"; -import {GeneralActionTypes} from "./types"; +import {GeneralActionTypes, ProjectData} from "./types"; import {Action} from "../Actions"; import {PopupWindowType} from "../../data/enums/PopupWindowType"; import {CustomCursorStyle} from "../../data/enums/CustomCursorStyle"; @@ -57,4 +57,22 @@ export function updateImageDragModeStatus(imageDragMode: boolean): GeneralAction imageDragMode, }, }; +} + +export function updateProjectData(projectData: ProjectData): GeneralActionTypes { + return { + type: Action.UPDATE_PROJECT_DATA, + payload: { + projectData, + }, + }; +} + +export function updateZoom(zoom: number): GeneralActionTypes { + return { + type: Action.UPDATE_ZOOM, + payload: { + zoom, + }, + }; } \ No newline at end of file diff --git a/src/store/general/reducer.ts b/src/store/general/reducer.ts index 033076bd..efe1f161 100644 --- a/src/store/general/reducer.ts +++ b/src/store/general/reducer.ts @@ -1,6 +1,7 @@ import {GeneralActionTypes, GeneralState} from "./types"; import {Action} from "../Actions"; import {CustomCursorStyle} from "../../data/enums/CustomCursorStyle"; +import {ViewPointSettings} from "../../settings/ViewPointSettings"; const initialState: GeneralState = { windowSize: null, @@ -8,7 +9,12 @@ const initialState: GeneralState = { customCursorStyle: CustomCursorStyle.DEFAULT, activeContext: null, preventCustomCursor: false, - imageDragMode: false + imageDragMode: false, + projectData: { + type: null, + name: "my-project-name", + }, + zoom: ViewPointSettings.MIN_ZOOM }; export function generalReducer( @@ -52,6 +58,18 @@ export function generalReducer( imageDragMode: action.payload.imageDragMode } } + case Action.UPDATE_PROJECT_DATA: { + return { + ...state, + projectData: action.payload.projectData + } + } + case Action.UPDATE_ZOOM: { + return { + ...state, + zoom: action.payload.zoom + } + } default: return state; } diff --git a/src/store/general/types.ts b/src/store/general/types.ts index a69f9ba6..9da99486 100644 --- a/src/store/general/types.ts +++ b/src/store/general/types.ts @@ -3,6 +3,12 @@ import {Action} from "../Actions"; import {PopupWindowType} from "../../data/enums/PopupWindowType"; import {CustomCursorStyle} from "../../data/enums/CustomCursorStyle"; import {ContextType} from "../../data/enums/ContextType"; +import {ProjectType} from "../../data/enums/ProjectType"; + +export type ProjectData = { + type: ProjectType; + name: string, +} export type GeneralState = { windowSize: ISize; @@ -11,6 +17,15 @@ export type GeneralState = { preventCustomCursor: boolean; imageDragMode: boolean; activeContext: ContextType; + projectData: ProjectData; + zoom: number; +} + +interface UpdateProjectData { + type: typeof Action.UPDATE_PROJECT_DATA; + payload: { + projectData: ProjectData; + } } interface UpdateWindowSize { @@ -55,9 +70,18 @@ interface UpdateImageDragModeStatus { } } -export type GeneralActionTypes = UpdateWindowSize +interface UpdateZoom { + type: typeof Action.UPDATE_ZOOM, + payload: { + zoom: number; + } +} + +export type GeneralActionTypes = UpdateProjectData + | UpdateWindowSize | UpdateActivePopupType | UpdateCustomCursorStyle | UpdateActiveContext | UpdatePreventCustomCursorStatus - | UpdateImageDragModeStatus \ No newline at end of file + | UpdateImageDragModeStatus + | UpdateZoom \ No newline at end of file diff --git a/src/store/index.ts b/src/store/index.ts index b86d4fe3..fba54974 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -1,10 +1,12 @@ import { combineReducers } from 'redux'; -import {editorReducer} from "./editor/reducer"; +import {labelsReducer} from "./labels/reducer"; import {generalReducer} from "./general/reducer"; +import {aiReducer} from "./ai/reducer"; export const rootReducer = combineReducers({ general: generalReducer, - editor: editorReducer + labels: labelsReducer, + ai: aiReducer }); export type AppState = ReturnType; \ No newline at end of file diff --git a/src/store/editor/actionCreators.ts b/src/store/labels/actionCreators.ts similarity index 68% rename from src/store/editor/actionCreators.ts rename to src/store/labels/actionCreators.ts index ae6f72e0..0eebf22a 100644 --- a/src/store/editor/actionCreators.ts +++ b/src/store/labels/actionCreators.ts @@ -1,27 +1,8 @@ -import {ProjectType} from "../../data/enums/ProjectType"; -import {EditorActionTypes, ImageData} from "./types"; +import {LabelsActionTypes, ImageData} from "./types"; import {Action} from "../Actions"; import {LabelType} from "../../data/enums/LabelType"; -export function updateProjectType(projectType: ProjectType): EditorActionTypes { - return { - type: Action.UPDATE_PROJECT_TYPE, - payload: { - projectType, - }, - }; -} - -export function updateProjectName(projectName: string): EditorActionTypes { - return { - type: Action.UPDATE_PROJECT_NAME, - payload: { - projectName, - }, - }; -} - -export function updateActiveImageIndex(activeImageIndex: number): EditorActionTypes { +export function updateActiveImageIndex(activeImageIndex: number): LabelsActionTypes { return { type: Action.UPDATE_ACTIVE_IMAGE_INDEX, payload: { @@ -30,7 +11,7 @@ export function updateActiveImageIndex(activeImageIndex: number): EditorActionTy }; } -export function updateActiveLabelNameIndex(activeLabelNameIndex: number): EditorActionTypes { +export function updateActiveLabelNameIndex(activeLabelNameIndex: number): LabelsActionTypes { return { type: Action.UPDATE_ACTIVE_LABEL_NAME_INDEX, payload: { @@ -39,7 +20,7 @@ export function updateActiveLabelNameIndex(activeLabelNameIndex: number): Editor }; } -export function updateActiveLabelId(activeLabelId: string): EditorActionTypes { +export function updateActiveLabelId(activeLabelId: string): LabelsActionTypes { return { type: Action.UPDATE_ACTIVE_LABEL_ID, payload: { @@ -48,7 +29,7 @@ export function updateActiveLabelId(activeLabelId: string): EditorActionTypes { }; } -export function updateHighlightedLabelId(highlightedLabelId: string): EditorActionTypes { +export function updateHighlightedLabelId(highlightedLabelId: string): LabelsActionTypes { return { type: Action.UPDATE_HIGHLIGHTED_LABEL_ID, payload: { @@ -57,7 +38,7 @@ export function updateHighlightedLabelId(highlightedLabelId: string): EditorActi }; } -export function updateActiveLabelType(activeLabelType: LabelType): EditorActionTypes { +export function updateActiveLabelType(activeLabelType: LabelType): LabelsActionTypes { return { type: Action.UPDATE_ACTIVE_LABEL_TYPE, payload: { @@ -66,7 +47,7 @@ export function updateActiveLabelType(activeLabelType: LabelType): EditorActionT }; } -export function updateImageDataById(id: string, newImageData: ImageData): EditorActionTypes { +export function updateImageDataById(id: string, newImageData: ImageData): LabelsActionTypes { return { type: Action.UPDATE_IMAGE_DATA_BY_ID, payload: { @@ -76,7 +57,7 @@ export function updateImageDataById(id: string, newImageData: ImageData): Editor }; } -export function addImageData(imageData: ImageData[]): EditorActionTypes { +export function addImageData(imageData: ImageData[]): LabelsActionTypes { return { type: Action.ADD_IMAGES_DATA, payload: { @@ -85,7 +66,7 @@ export function addImageData(imageData: ImageData[]): EditorActionTypes { }; } -export function updateImageData(imageData: ImageData[]): EditorActionTypes { +export function updateImageData(imageData: ImageData[]): LabelsActionTypes { return { type: Action.UPDATE_IMAGES_DATA, payload: { diff --git a/src/store/editor/reducer.ts b/src/store/labels/reducer.ts similarity index 80% rename from src/store/editor/reducer.ts rename to src/store/labels/reducer.ts index 804e0227..621a183b 100644 --- a/src/store/editor/reducer.ts +++ b/src/store/labels/reducer.ts @@ -1,36 +1,22 @@ -import {EditorActionTypes, EditorState, ImageData} from "./types"; +import {LabelsActionTypes, LabelsState, ImageData} from "./types"; import {Action} from "../Actions"; -const initialState: EditorState = { +const initialState: LabelsState = { activeImageIndex: null, activeLabelNameIndex: null, activeLabelType: null, activeLabelId: null, highlightedLabelId: null, - projectType: null, - projectName: "my-project-name", imagesData: [], labelNames: [], firstLabelCreatedFlag: false }; -export function editorReducer( +export function labelsReducer( state = initialState, - action: EditorActionTypes -): EditorState { + action: LabelsActionTypes +): LabelsState { switch (action.type) { - case Action.UPDATE_PROJECT_TYPE: { - return { - ...state, - projectType: action.payload.projectType - } - } - case Action.UPDATE_PROJECT_NAME: { - return { - ...state, - projectName: action.payload.projectName - } - } case Action.UPDATE_ACTIVE_IMAGE_INDEX: { return { ...state, diff --git a/src/store/editor/types.ts b/src/store/labels/types.ts similarity index 83% rename from src/store/editor/types.ts rename to src/store/labels/types.ts index dec2f4f9..35e44cb1 100644 --- a/src/store/editor/types.ts +++ b/src/store/labels/types.ts @@ -1,13 +1,15 @@ import {IRect} from "../../interfaces/IRect"; -import {ProjectType} from "../../data/enums/ProjectType"; import {Action} from "../Actions"; import {LabelType} from "../../data/enums/LabelType"; import {IPoint} from "../../interfaces/IPoint"; +import {LabelStatus} from "../../data/enums/LabelStatus"; export type LabelRect = { id: string; labelIndex: number; rect: IRect; + isCreatedByAI: boolean; + status: LabelStatus; } export type LabelPoint = { @@ -29,35 +31,20 @@ export type ImageData = { labelRects: LabelRect[]; labelPoints: LabelPoint[]; labelPolygons: LabelPolygon[]; + isVisitedByObjectDetector: boolean; } -export type EditorState = { +export type LabelsState = { activeImageIndex: number; activeLabelNameIndex: number; activeLabelType: LabelType; activeLabelId: string; highlightedLabelId: string; - projectType: ProjectType; - projectName: string, imagesData: ImageData[]; labelNames: string[]; firstLabelCreatedFlag: boolean; } -interface UpdateProjectType { - type: typeof Action.UPDATE_PROJECT_TYPE; - payload: { - projectType: ProjectType; - } -} - -interface UpdateProjectName { - type: typeof Action.UPDATE_PROJECT_NAME; - payload: { - projectName: string; - } -} - interface UpdateActiveImageIndex { type: typeof Action.UPDATE_ACTIVE_IMAGE_INDEX; payload: { @@ -129,9 +116,7 @@ interface UpdateFirstLabelCreatedFlag { } } -export type EditorActionTypes = UpdateProjectType - | UpdateProjectName - | UpdateActiveImageIndex +export type LabelsActionTypes = UpdateActiveImageIndex | UpdateActiveLabelNameIndex | UpdateActiveLabelType | UpdateImageDataById diff --git a/src/store/selectors/AISelector.ts b/src/store/selectors/AISelector.ts new file mode 100644 index 00000000..11870e9c --- /dev/null +++ b/src/store/selectors/AISelector.ts @@ -0,0 +1,7 @@ +import {store} from "../.."; + +export class AISelector { + public static isAiModelLoaded(): boolean { + return store.getState().ai.isObjectDetectorLoaded; + } +} \ No newline at end of file diff --git a/src/store/selectors/GeneralSelector.ts b/src/store/selectors/GeneralSelector.ts index 850e9f39..88301987 100644 --- a/src/store/selectors/GeneralSelector.ts +++ b/src/store/selectors/GeneralSelector.ts @@ -2,6 +2,7 @@ import {store} from "../.."; import {PopupWindowType} from "../../data/enums/PopupWindowType"; import {ContextType} from "../../data/enums/ContextType"; import {CustomCursorStyle} from "../../data/enums/CustomCursorStyle"; +import {ProjectType} from "../../data/enums/ProjectType"; export class GeneralSelector { public static getActivePopupType(): PopupWindowType { @@ -23,4 +24,16 @@ export class GeneralSelector { public static getCustomCursorStyle(): CustomCursorStyle { return store.getState().general.customCursorStyle; } + + public static getProjectName(): string { + return store.getState().general.projectData.name; + } + + public static getProjectType(): ProjectType { + return store.getState().general.projectData.type; + } + + public static getZoom(): number { + return store.getState().general.zoom; + } } \ No newline at end of file diff --git a/src/store/selectors/EditorSelector.ts b/src/store/selectors/LabelsSelector.ts similarity index 56% rename from src/store/selectors/EditorSelector.ts rename to src/store/selectors/LabelsSelector.ts index 4828cfab..2296033d 100644 --- a/src/store/selectors/EditorSelector.ts +++ b/src/store/selectors/LabelsSelector.ts @@ -1,84 +1,80 @@ import {store} from "../.."; -import {ImageData, LabelPoint, LabelPolygon, LabelRect} from "../editor/types"; +import {ImageData, LabelPoint, LabelPolygon, LabelRect} from "../labels/types"; import _ from "lodash"; import {LabelType} from "../../data/enums/LabelType"; -export class EditorSelector { - public static getProjectName(): string { - return store.getState().editor.projectName; - } - +export class LabelsSelector { public static getLabelNames(): string[] { - return store.getState().editor.labelNames; + return store.getState().labels.labelNames; } public static getActiveLabelNameIndex(): number { - return store.getState().editor.activeLabelNameIndex; + return store.getState().labels.activeLabelNameIndex; } public static getActiveLabelType(): LabelType { - return store.getState().editor.activeLabelType; + return store.getState().labels.activeLabelType; } public static getImagesData(): ImageData[] { - return store.getState().editor.imagesData; + return store.getState().labels.imagesData; } public static getActiveImageIndex(): number { - return store.getState().editor.activeImageIndex; + return store.getState().labels.activeImageIndex; } public static getActiveImageData(): ImageData | null { - const activeImageIndex: number | null = EditorSelector.getActiveImageIndex(); + const activeImageIndex: number | null = LabelsSelector.getActiveImageIndex(); if (activeImageIndex === null) return null; - return EditorSelector.getImageDataByIndex(activeImageIndex); + return LabelsSelector.getImageDataByIndex(activeImageIndex); } public static getImageDataByIndex(index: number): ImageData { - const imagesData: ImageData[] = EditorSelector.getImagesData(); + const imagesData: ImageData[] = LabelsSelector.getImagesData(); return imagesData[index]; } public static getImageDataById(id: string): ImageData { - const imagesData: ImageData[] = EditorSelector.getImagesData(); + const imagesData: ImageData[] = LabelsSelector.getImagesData(); return _.find(imagesData, {id: id}); } public static getActiveLabelId(): string | null { - return store.getState().editor.activeLabelId; + return store.getState().labels.activeLabelId; } public static getHighlightedLabelId(): string | null { - return store.getState().editor.highlightedLabelId; + return store.getState().labels.highlightedLabelId; } public static getActiveRectLabel(): LabelRect | null { - const activeLabelId: string | null = EditorSelector.getActiveLabelId(); + const activeLabelId: string | null = LabelsSelector.getActiveLabelId(); if (activeLabelId === null) return null; - return _.find(EditorSelector.getActiveImageData().labelRects, {id: activeLabelId}); + return _.find(LabelsSelector.getActiveImageData().labelRects, {id: activeLabelId}); } public static getActivePointLabel(): LabelPoint | null { - const activeLabelId: string | null = EditorSelector.getActiveLabelId(); + const activeLabelId: string | null = LabelsSelector.getActiveLabelId(); if (activeLabelId === null) return null; - return _.find(EditorSelector.getActiveImageData().labelPoints, {id: activeLabelId}); + return _.find(LabelsSelector.getActiveImageData().labelPoints, {id: activeLabelId}); } public static getActivePolygonLabel(): LabelPolygon | null { - const activeLabelId: string | null = EditorSelector.getActiveLabelId(); + const activeLabelId: string | null = LabelsSelector.getActiveLabelId(); if (activeLabelId === null) return null; - return _.find(EditorSelector.getActiveImageData().labelPolygons, {id: activeLabelId}); + return _.find(LabelsSelector.getActiveImageData().labelPolygons, {id: activeLabelId}); } } \ No newline at end of file diff --git a/src/utils/ExporterUtil.ts b/src/utils/ExporterUtil.ts index 1a5c1344..a7be4ca3 100644 --- a/src/utils/ExporterUtil.ts +++ b/src/utils/ExporterUtil.ts @@ -1,9 +1,9 @@ -import {EditorSelector} from "../store/selectors/EditorSelector"; import moment from 'moment'; +import {GeneralSelector} from "../store/selectors/GeneralSelector"; export class ExporterUtil { public static getExportFileName(): string { - const projectName: string = EditorSelector.getProjectName(); + const projectName: string = GeneralSelector.getProjectName(); const date: string = moment().format('YYYYMMDDhhmmss'); return `labels_${projectName}_${date}` } diff --git a/src/utils/FileUtil.ts b/src/utils/FileUtil.ts index df05ef91..08d2927b 100644 --- a/src/utils/FileUtil.ts +++ b/src/utils/FileUtil.ts @@ -1,5 +1,5 @@ import uuidv1 from 'uuid/v1'; -import {ImageData} from "../store/editor/types"; +import {ImageData} from "../store/labels/types"; export class FileUtil { public static mapFileDataToImageData(fileData: File): ImageData { @@ -9,11 +9,12 @@ export class FileUtil { loadStatus: false, labelRects: [], labelPoints: [], - labelPolygons: [] + labelPolygons: [], + isVisitedByObjectDetector: false } } - public static loadImage(fileData: File, onSuccess: (image:HTMLImageElement) => any, onFailure: () => any): Promise { + public static loadImage(fileData: File, onSuccess: (image:HTMLImageElement) => any, onFailure: () => any): any { return new Promise((resolve, reject) => { const url = URL.createObjectURL(fileData); const image = new Image(); diff --git a/src/views/Common/TextInput/TextInput.scss b/src/views/Common/TextInput/TextInput.scss index 7b2f2d94..ce5014be 100644 --- a/src/views/Common/TextInput/TextInput.scss +++ b/src/views/Common/TextInput/TextInput.scss @@ -45,7 +45,8 @@ input { ~label { top: 0; font: 700 $width/25; - color: $secondaryColor; + color: $secondaryColor; // fallback if new css variables are not supported by browser + color: var(--leading-color); } ~ .Bar { @@ -70,4 +71,7 @@ input { position: relative; } -::selection {background: rgba($secondaryColor, .3);} \ No newline at end of file +::selection { + background: rgba($secondaryColor, .3); + background: rgba(var(--leading-color), .3); +} \ No newline at end of file diff --git a/src/views/Common/UnderlineTextButton/UnderlineTextButton.scss b/src/views/Common/UnderlineTextButton/UnderlineTextButton.scss index bba35eb0..69a08b6e 100644 --- a/src/views/Common/UnderlineTextButton/UnderlineTextButton.scss +++ b/src/views/Common/UnderlineTextButton/UnderlineTextButton.scss @@ -17,11 +17,13 @@ &:hover, &.active { - color: $secondaryColor; + color: $secondaryColor; // fallback if new css variables are not supported by browser + color: var(--leading-color); } &.over::before { - background: $secondaryColor; + background-color: $secondaryColor; // fallback if new css variables are not supported by browser + background-color: var(--leading-color); width: 100%; position: absolute; top: 0; @@ -32,7 +34,8 @@ } &.under::after { - background: $secondaryColor; + background-color: $secondaryColor; // fallback if new css variables are not supported by browser + background-color: var(--leading-color); width: 100%; position: absolute; bottom: 0; diff --git a/src/views/EditorView/BottomNavigationBar/BottomNavigationBar.scss b/src/views/EditorView/BottomNavigationBar/BottomNavigationBar.scss index 384d9565..0cb48f7c 100644 --- a/src/views/EditorView/BottomNavigationBar/BottomNavigationBar.scss +++ b/src/views/EditorView/BottomNavigationBar/BottomNavigationBar.scss @@ -31,7 +31,8 @@ } &:not(.disabled):hover { - filter: brightness(35%) sepia(100%) hue-rotate(172deg) saturate(1000%); + filter: brightness(35%) sepia(100%) hue-rotate(172deg) saturate(2000%); // fallback if new css variables are not supported by browser + filter: brightness(35%) sepia(100%) hue-rotate(var(--hue-value)) saturate(2000%); &.right { transform: translate(2px); } diff --git a/src/views/EditorView/BottomNavigationBar/BottomNavigationBar.tsx b/src/views/EditorView/BottomNavigationBar/BottomNavigationBar.tsx index 23ce4794..4c9f9e31 100644 --- a/src/views/EditorView/BottomNavigationBar/BottomNavigationBar.tsx +++ b/src/views/EditorView/BottomNavigationBar/BottomNavigationBar.tsx @@ -1,6 +1,6 @@ import React from 'react'; import './BottomNavigationBar.scss'; -import {ImageData} from "../../../store/editor/types"; +import {ImageData} from "../../../store/labels/types"; import {AppState} from "../../../store"; import {connect} from "react-redux"; import {ImageButton} from "../../Common/ImageButton/ImageButton"; @@ -62,7 +62,7 @@ const BottomNavigationBar: React.FC = ({size, imageData, totalImageCount const mapDispatchToProps = {}; const mapStateToProps = (state: AppState) => ({ - activeImageIndex: state.editor.activeImageIndex, + activeImageIndex: state.labels.activeImageIndex, activeContext: state.general.activeContext }); diff --git a/src/views/EditorView/Editor/Editor.tsx b/src/views/EditorView/Editor/Editor.tsx index 2461f033..a1fb8d4d 100644 --- a/src/views/EditorView/Editor/Editor.tsx +++ b/src/views/EditorView/Editor/Editor.tsx @@ -1,11 +1,11 @@ import React from 'react'; import './Editor.scss'; import {ISize} from "../../../interfaces/ISize"; -import {ImageData} from "../../../store/editor/types"; +import {ImageData, LabelRect} from "../../../store/labels/types"; import {FileUtil} from "../../../utils/FileUtil"; import {AppState} from "../../../store"; import {connect} from "react-redux"; -import {updateImageDataById} from "../../../store/editor/actionCreators"; +import {updateImageDataById} from "../../../store/labels/actionCreators"; import {ImageRepository} from "../../../logic/imageRepository/ImageRepository"; import {LabelType} from "../../../data/enums/LabelType"; import {PopupWindowType} from "../../../data/enums/PopupWindowType"; @@ -22,6 +22,11 @@ import {ContextType} from "../../../data/enums/ContextType"; import Scrollbars from 'react-custom-scrollbars'; import {ViewPortActions} from "../../../logic/actions/ViewPortActions"; import {PlatformModel} from "../../../staticModels/PlatformModel"; +import {AIActions} from "../../../logic/actions/AIActions"; +import LabelControlPanel from "../LabelControlPanel/LabelControlPanel"; +import {IPoint} from "../../../interfaces/IPoint"; +import {RenderEngineUtil} from "../../../utils/RenderEngineUtil"; +import {LabelStatus} from "../../../data/enums/LabelStatus"; interface IProps { size: ISize; @@ -32,6 +37,7 @@ interface IProps { activeLabelId: string; customCursorStyle: CustomCursorStyle; imageDragMode: boolean; + zoom: number; } class Editor extends React.Component { @@ -89,6 +95,7 @@ class Editor extends React.Component { private loadImage = async (imageData: ImageData): Promise => { if (imageData.loadStatus) { EditorActions.setActiveImage(ImageRepository.getById(imageData.id)); + AIActions.detectRects(imageData.id, ImageRepository.getById(imageData.id)); this.updateModelAndRender() } else { @@ -105,6 +112,7 @@ class Editor extends React.Component { this.props.updateImageDataById(imageData.id, imageData); ImageRepository.store(imageData.id, image); EditorActions.setActiveImage(image); + AIActions.detectRects(imageData.id, image); EditorActions.setLoadingStatus(false); this.updateModelAndRender() }; @@ -149,6 +157,25 @@ class Editor extends React.Component { } }; + private getOptionsPanels = () => { + if (this.props.activeLabelType !== LabelType.RECTANGLE) + return null; + + const editorData: EditorData = EditorActions.getEditorData(); + return this.props.imageData.labelRects + .filter((labelRect: LabelRect) => labelRect.isCreatedByAI && labelRect.status !== LabelStatus.ACCEPTED) + .map((labelRect: LabelRect) => { + const positionOnImage: IPoint = {x: labelRect.rect.x, y: labelRect.rect.y}; + const positionOnViewPort: IPoint = RenderEngineUtil.transferPointFromImageToViewPortContent(positionOnImage, editorData); + return + }) + }; + public render() { return (
{ draggable={false} onContextMenu={(event: React.MouseEvent) => event.preventDefault()} /> + {this.getOptionsPanels()}
({ - activeLabelType: state.editor.activeLabelType, + activeLabelType: state.labels.activeLabelType, activePopupType: state.general.activePopupType, - activeLabelId: state.editor.activeLabelId, + activeLabelId: state.labels.activeLabelId, customCursorStyle: state.general.customCursorStyle, - imageDragMode: state.general.imageDragMode + imageDragMode: state.general.imageDragMode, + zoom: state.general.zoom }); export default connect( diff --git a/src/views/EditorView/EditorContainer/EditorContainer.tsx b/src/views/EditorView/EditorContainer/EditorContainer.tsx index 61fb6ffd..c78f4f8c 100644 --- a/src/views/EditorView/EditorContainer/EditorContainer.tsx +++ b/src/views/EditorView/EditorContainer/EditorContainer.tsx @@ -4,7 +4,7 @@ import {Direction} from "../../../data/enums/Direction"; import {ISize} from "../../../interfaces/ISize"; import {Settings} from "../../../settings/Settings"; import {AppState} from "../../../store"; -import {ImageData} from "../../../store/editor/types"; +import {ImageData} from "../../../store/labels/types"; import ImagesList from "../SideNavigationBar/ImagesList/ImagesList"; import LabelsToolkit from "../SideNavigationBar/LabelsToolkit/LabelsToolkit"; import {SideNavigationBar} from "../SideNavigationBar/SideNavigationBar"; @@ -132,8 +132,8 @@ const EditorContainer: React.FC = ({windowSize, activeImageIndex, images const mapStateToProps = (state: AppState) => ({ windowSize: state.general.windowSize, - activeImageIndex: state.editor.activeImageIndex, - imagesData: state.editor.imagesData, + activeImageIndex: state.labels.activeImageIndex, + imagesData: state.labels.imagesData, activeContext: state.general.activeContext }); diff --git a/src/views/EditorView/LabelControlPanel/LabelControlPanel.scss b/src/views/EditorView/LabelControlPanel/LabelControlPanel.scss new file mode 100644 index 00000000..a037e1c3 --- /dev/null +++ b/src/views/EditorView/LabelControlPanel/LabelControlPanel.scss @@ -0,0 +1,89 @@ +@import '../../../settings/Settings'; + +.LabelControlPanel { + position: absolute; + cursor: pointer; + + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: center; + align-items: center; + align-content: center; + + .ImageButton { + transition: transform 0.3s; + + img { + filter: brightness(0) invert(1); + user-select: none; + } + + &:hover { + background-color: transparent; + } + + &:not(.disabled):hover { + filter: brightness(35%) sepia(100%) hue-rotate(172deg) saturate(2000%); // fallback if new css variables are not supported by browser + filter: brightness(35%) sepia(100%) hue-rotate(var(--hue-value)) saturate(2000%); + + &.right { + transform: translate(2px); + } + + &.left { + transform: translate(-2px); + } + } + + &.disabled { + img { + filter: invert(1) opacity(25%); + user-select: none; + } + } + } + + $randomNumber: random(5); + + &.is-active { + background-color: $darkThemeSecondColor; + border: solid 1px $primaryColor; + animation: brColor 5s infinite linear; + animation-delay: $randomNumber + s; + transform: translate(-15px, -15px); + border-radius: 3px; + min-width: 30px; + height: 30px; + z-index: 1000; + } + + &:not(.is-active) { + background-color: $primaryColor; + animation: bgColor 5s infinite linear; + animation-delay: $randomNumber + s; + transform: translate(-6px, -6px); + border-radius: 6px; + width: 12px; + height: 12px; + z-index: 1; + } +} + +@keyframes bgColor { + 50% { + background-color: $secondaryColor; + } + 100% { + background-color: $primaryColor; + } +} + +@keyframes brColor { + 50% { + border-color: $secondaryColor; + } + 100% { + border-color: $primaryColor; + } +} \ No newline at end of file diff --git a/src/views/EditorView/LabelControlPanel/LabelControlPanel.tsx b/src/views/EditorView/LabelControlPanel/LabelControlPanel.tsx new file mode 100644 index 00000000..a54334d6 --- /dev/null +++ b/src/views/EditorView/LabelControlPanel/LabelControlPanel.tsx @@ -0,0 +1,111 @@ +import React, {useState} from 'react'; +import './LabelControlPanel.scss'; +import {updatePreventCustomCursorStatus} from "../../../store/general/actionCreators"; +import {AppState} from "../../../store"; +import {connect} from "react-redux"; +import {IPoint} from "../../../interfaces/IPoint"; +import classNames from "classnames"; +import {LabelRect} from "../../../store/labels/types"; +import {ImageButton} from "../../Common/ImageButton/ImageButton"; +import {LabelActions} from "../../../logic/actions/LabelActions"; +import {ImageData} from "../../../store/labels/types"; +import {LabelStatus} from "../../../data/enums/LabelStatus"; +import {updateImageDataById} from "../../../store/labels/actionCreators"; + +interface IProps { + position: IPoint; + updatePreventCustomCursorStatus: (preventCustomCursor: boolean) => any; + activeLabelId: string; + highlightedLabelId: string; + labelData: LabelRect; + imageData: ImageData; + updateImageDataById: (id: string, newImageData: ImageData) => any; +} + +const LabelControlPanel: React.FC = ({position, updatePreventCustomCursorStatus, activeLabelId, highlightedLabelId, labelData, imageData, updateImageDataById}) => { + const [isActive, setIsActiveStatus] = useState(false); + + const onMouseEnter = () => { + updatePreventCustomCursorStatus(true); + setIsActiveStatus(true); + }; + + const onMouseLeave = () => { + updatePreventCustomCursorStatus(false); + setIsActiveStatus(false); + }; + + const onAccept = () => { + const newImageData = { + ...imageData, + labelRects: imageData.labelRects.map((labelRect: LabelRect) => { + if (labelRect.id === labelData.id) { + return { + ...labelRect, + status: LabelStatus.ACCEPTED + } + } else { + return labelRect + } + }) + }; + updateImageDataById(imageData.id, newImageData); + updatePreventCustomCursorStatus(false); + }; + + const onReject = () => { + LabelActions.deleteRectLabelById(imageData.id, labelData.id); + updatePreventCustomCursorStatus(false); + }; + + const getClassName = () => { + return classNames( + "LabelControlPanel", { + "is-active": isPanelActive() + } + ); + }; + + const isPanelActive = () => { + return isActive || labelData.id === activeLabelId || labelData.id === highlightedLabelId + }; + + return
+ {isPanelActive() && <> + + + } +
+}; + +const mapDispatchToProps = { + updatePreventCustomCursorStatus, + updateImageDataById +}; + +const mapStateToProps = (state: AppState) => ({ + activeLabelId: state.labels.activeLabelId, + highlightedLabelId: state.labels.highlightedLabelId, +}); + +export default connect( + mapStateToProps, + mapDispatchToProps +)(LabelControlPanel); \ No newline at end of file diff --git a/src/views/EditorView/SideNavigationBar/EmptyLabelList/EmptyLabelList.tsx b/src/views/EditorView/SideNavigationBar/EmptyLabelList/EmptyLabelList.tsx index e2334c91..db58490e 100644 --- a/src/views/EditorView/SideNavigationBar/EmptyLabelList/EmptyLabelList.tsx +++ b/src/views/EditorView/SideNavigationBar/EmptyLabelList/EmptyLabelList.tsx @@ -36,7 +36,7 @@ const EmptyLabelList: React.FC = ({firstLabelCreatedFlag, labelBefore, l const mapDispatchToProps = {}; const mapStateToProps = (state: AppState) => ({ - firstLabelCreatedFlag: state.editor.firstLabelCreatedFlag + firstLabelCreatedFlag: state.labels.firstLabelCreatedFlag }); export default connect( diff --git a/src/views/EditorView/SideNavigationBar/ImagePreview/ImagePreview.scss b/src/views/EditorView/SideNavigationBar/ImagePreview/ImagePreview.scss index 81dff846..f1161bfa 100644 --- a/src/views/EditorView/SideNavigationBar/ImagePreview/ImagePreview.scss +++ b/src/views/EditorView/SideNavigationBar/ImagePreview/ImagePreview.scss @@ -32,10 +32,12 @@ .CheckBox { position: absolute; z-index: 1000; - max-width: 15px; - max-height: 15px; - bottom: 5px; - left: 5px; + max-width: 20px; + max-height: 20px; + bottom: -10px; + left: -10px; + filter: invert(1) brightness(35%) sepia(100%) hue-rotate(172deg) saturate(2000%); // fallback if new css variables are not supported by browser + filter: invert(1) brightness(35%) sepia(100%) hue-rotate(var(--hue-value)) saturate(2000%); } } @@ -52,7 +54,8 @@ } .Background { transform: translate(2px, -2px); - background-color: $secondaryColor; + background-color: $secondaryColor; // fallback if new css variables are not supported by browser + background-color: var(--leading-color); } } diff --git a/src/views/EditorView/SideNavigationBar/ImagePreview/ImagePreview.tsx b/src/views/EditorView/SideNavigationBar/ImagePreview/ImagePreview.tsx index 2c797563..8f896099 100644 --- a/src/views/EditorView/SideNavigationBar/ImagePreview/ImagePreview.tsx +++ b/src/views/EditorView/SideNavigationBar/ImagePreview/ImagePreview.tsx @@ -6,13 +6,13 @@ import {ImageLoadManager} from "../../../../logic/imageRepository/ImageLoadManag import {IRect} from "../../../../interfaces/IRect"; import {ISize} from "../../../../interfaces/ISize"; import {ImageRepository} from "../../../../logic/imageRepository/ImageRepository"; -import {Settings} from "../../../../settings/Settings"; import {AppState} from "../../../../store"; -import {updateImageDataById} from "../../../../store/editor/actionCreators"; -import {ImageData} from "../../../../store/editor/types"; +import {updateImageDataById} from "../../../../store/labels/actionCreators"; +import {ImageData} from "../../../../store/labels/types"; import {FileUtil} from "../../../../utils/FileUtil"; import {RectUtil} from "../../../../utils/RectUtil"; import './ImagePreview.scss'; +import {CSSHelper} from "../../../../logic/helpers/CSSHelper"; interface IProps { imageData: ImageData; @@ -161,7 +161,7 @@ class ImagePreview extends React.Component { {isChecked && {"checkbox"}}
, @@ -174,7 +174,7 @@ class ImagePreview extends React.Component { } ) diff --git a/src/views/EditorView/SideNavigationBar/ImagesList/ImagesList.tsx b/src/views/EditorView/SideNavigationBar/ImagesList/ImagesList.tsx index bf421de1..59e5c6ec 100644 --- a/src/views/EditorView/SideNavigationBar/ImagesList/ImagesList.tsx +++ b/src/views/EditorView/SideNavigationBar/ImagesList/ImagesList.tsx @@ -3,7 +3,7 @@ import {connect} from "react-redux"; import {LabelType} from "../../../../data/enums/LabelType"; import {ISize} from "../../../../interfaces/ISize"; import {AppState} from "../../../../store"; -import {ImageData} from "../../../../store/editor/types"; +import {ImageData} from "../../../../store/labels/types"; import {VirtualList} from "../../../Common/VirtualList/VirtualList"; import ImagePreview from "../ImagePreview/ImagePreview"; import './ImagesList.scss'; @@ -99,9 +99,9 @@ class ImagesList extends React.Component { const mapDispatchToProps = {}; const mapStateToProps = (state: AppState) => ({ - activeImageIndex: state.editor.activeImageIndex, - imagesData: state.editor.imagesData, - activeLabelType: state.editor.activeLabelType + activeImageIndex: state.labels.activeImageIndex, + imagesData: state.labels.imagesData, + activeLabelType: state.labels.activeLabelType }); export default connect( diff --git a/src/views/EditorView/SideNavigationBar/LabelInputField/LabelInputField.scss b/src/views/EditorView/SideNavigationBar/LabelInputField/LabelInputField.scss index 0046032d..5c98039c 100644 --- a/src/views/EditorView/SideNavigationBar/LabelInputField/LabelInputField.scss +++ b/src/views/EditorView/SideNavigationBar/LabelInputField/LabelInputField.scss @@ -68,7 +68,8 @@ background-color: rgba(0, 0, 0, 0.05); &::after { - background-color: $secondaryColor; + background-color: $secondaryColor; // fallback if new css variables are not supported by browser + background-color: var(--leading-color); } } @@ -131,7 +132,8 @@ } &.trash:not(.disabled):hover { - filter: brightness(35%) sepia(100%) hue-rotate(172deg) saturate(1000%); + filter: brightness(35%) sepia(100%) hue-rotate(172deg) saturate(2000%); // fallback if new css variables are not supported by browser + filter: brightness(35%) sepia(100%) hue-rotate(var(--hue-value)) saturate(2000%); } } } @@ -139,7 +141,8 @@ &:hover { background-color: rgba(0, 0, 0, 0.05); .Marker { - background-color: $secondaryColor; + background-color: $secondaryColor; // fallback if new css variables are not supported by browser + background-color: var(--leading-color); } } } @@ -147,7 +150,8 @@ &.active { background-color: rgba(0, 0, 0, 0.05); .Marker { - background-color: $secondaryColor; + background-color: $secondaryColor; // fallback if new css variables are not supported by browser + background-color: var(--leading-color); } } @@ -160,7 +164,8 @@ &.highlighted { background-color: rgba(0, 0, 0, 0.05); .Marker { - background-color: $secondaryColor; + background-color: $secondaryColor; // fallback if new css variables are not supported by browser + background-color: var(--leading-color); } } } \ No newline at end of file diff --git a/src/views/EditorView/SideNavigationBar/LabelInputField/LabelInputField.tsx b/src/views/EditorView/SideNavigationBar/LabelInputField/LabelInputField.tsx index 6fc12138..5970569e 100644 --- a/src/views/EditorView/SideNavigationBar/LabelInputField/LabelInputField.tsx +++ b/src/views/EditorView/SideNavigationBar/LabelInputField/LabelInputField.tsx @@ -8,7 +8,7 @@ import {IPoint} from "../../../../interfaces/IPoint"; import {RectUtil} from "../../../../utils/RectUtil"; import {AppState} from "../../../../store"; import {connect} from "react-redux"; -import {updateActiveLabelId, updateHighlightedLabelId} from "../../../../store/editor/actionCreators"; +import {updateActiveLabelId, updateHighlightedLabelId} from "../../../../store/labels/actionCreators"; import Scrollbars from 'react-custom-scrollbars'; import {EventType} from "../../../../data/enums/EventType"; diff --git a/src/views/EditorView/SideNavigationBar/LabelsToolkit/LabelsToolkit.scss b/src/views/EditorView/SideNavigationBar/LabelsToolkit/LabelsToolkit.scss index 68e56a4c..5bf899da 100644 --- a/src/views/EditorView/SideNavigationBar/LabelsToolkit/LabelsToolkit.scss +++ b/src/views/EditorView/SideNavigationBar/LabelsToolkit/LabelsToolkit.scss @@ -16,7 +16,6 @@ position: relative; align-self: stretch; box-sizing: border-box; - //border-bottom: solid 1px $darkThemeSecondColor; padding: 0 25px; color: white; font-size: 14px; @@ -74,7 +73,8 @@ background-color: rgba(0, 0, 0, 0.05); .Marker { transform: translate(5px); - background-color: $secondaryColor; + background-color: $secondaryColor; // fallback if new css variables are not supported by browser + background-color: var(--leading-color); } .Arrow { @@ -96,28 +96,6 @@ justify-content: center; align-items: center; align-content: center; - - //&:not(.active) { - // animation-duration: 0.6s; - // animation-name: cssAnimationHide; - // animation-fill-mode: forwards; - //} } } -//@keyframes cssAnimationHide { -// 0% { -// opacity: 1; -// height: 0; -// } -// 20% { -// opacity: 0; -// //width: 400px; -// } -// 100% { -// opacity: 0; -// //width: 0; -// overflow: hidden; -// } -//} - diff --git a/src/views/EditorView/SideNavigationBar/LabelsToolkit/LabelsToolkit.tsx b/src/views/EditorView/SideNavigationBar/LabelsToolkit/LabelsToolkit.tsx index 6b0a5f27..18bf3065 100644 --- a/src/views/EditorView/SideNavigationBar/LabelsToolkit/LabelsToolkit.tsx +++ b/src/views/EditorView/SideNavigationBar/LabelsToolkit/LabelsToolkit.tsx @@ -1,7 +1,7 @@ import React from "react"; import './LabelsToolkit.scss'; -import {ImageData} from "../../../../store/editor/types"; -import {updateActiveLabelId, updateActiveLabelType, updateImageDataById} from "../../../../store/editor/actionCreators"; +import {ImageData} from "../../../../store/labels/types"; +import {updateActiveLabelId, updateActiveLabelType, updateImageDataById} from "../../../../store/labels/actionCreators"; import {AppState} from "../../../../store"; import {connect} from "react-redux"; import {LabelType} from "../../../../data/enums/LabelType"; @@ -182,10 +182,10 @@ const mapDispatchToProps = { }; const mapStateToProps = (state: AppState) => ({ - activeImageIndex: state.editor.activeImageIndex, - activeLabelType: state.editor.activeLabelType, - imagesData: state.editor.imagesData, - projectType: state.editor.projectType, + activeImageIndex: state.labels.activeImageIndex, + activeLabelType: state.labels.activeLabelType, + imagesData: state.labels.imagesData, + projectType: state.general.projectData.type, }); export default connect( diff --git a/src/views/EditorView/SideNavigationBar/PointLabelsList/PointLabelsList.tsx b/src/views/EditorView/SideNavigationBar/PointLabelsList/PointLabelsList.tsx index 522e8bf4..20ef510e 100644 --- a/src/views/EditorView/SideNavigationBar/PointLabelsList/PointLabelsList.tsx +++ b/src/views/EditorView/SideNavigationBar/PointLabelsList/PointLabelsList.tsx @@ -1,13 +1,13 @@ import React from 'react'; import {ISize} from "../../../../interfaces/ISize"; import Scrollbars from 'react-custom-scrollbars'; -import {ImageData, LabelPoint} from "../../../../store/editor/types"; +import {ImageData, LabelPoint} from "../../../../store/labels/types"; import './PointLabelsList.scss'; import { updateActiveLabelId, updateActiveLabelNameIndex, updateImageDataById -} from "../../../../store/editor/actionCreators"; +} from "../../../../store/labels/actionCreators"; import {AppState} from "../../../../store"; import {connect} from "react-redux"; import LabelInputField from "../LabelInputField/LabelInputField"; @@ -112,10 +112,10 @@ const mapDispatchToProps = { }; const mapStateToProps = (state: AppState) => ({ - activeLabelIndex: state.editor.activeLabelNameIndex, - activeLabelId: state.editor.activeLabelId, - highlightedLabelId: state.editor.highlightedLabelId, - labelNames : state.editor.labelNames + activeLabelIndex: state.labels.activeLabelNameIndex, + activeLabelId: state.labels.activeLabelId, + highlightedLabelId: state.labels.highlightedLabelId, + labelNames : state.labels.labelNames }); export default connect( diff --git a/src/views/EditorView/SideNavigationBar/PolygonLabelsList/PolygonLabelsList.tsx b/src/views/EditorView/SideNavigationBar/PolygonLabelsList/PolygonLabelsList.tsx index e3fea0fc..4f08729c 100644 --- a/src/views/EditorView/SideNavigationBar/PolygonLabelsList/PolygonLabelsList.tsx +++ b/src/views/EditorView/SideNavigationBar/PolygonLabelsList/PolygonLabelsList.tsx @@ -1,13 +1,13 @@ import React from 'react'; import {ISize} from "../../../../interfaces/ISize"; import Scrollbars from 'react-custom-scrollbars'; -import {ImageData, LabelPolygon} from "../../../../store/editor/types"; +import {ImageData, LabelPolygon} from "../../../../store/labels/types"; import './PolygonLabelsList.scss'; import { updateActiveLabelId, updateActiveLabelNameIndex, updateImageDataById -} from "../../../../store/editor/actionCreators"; +} from "../../../../store/labels/actionCreators"; import {AppState} from "../../../../store"; import {connect} from "react-redux"; import LabelInputField from "../LabelInputField/LabelInputField"; @@ -112,10 +112,10 @@ const mapDispatchToProps = { }; const mapStateToProps = (state: AppState) => ({ - activeLabelIndex: state.editor.activeLabelNameIndex, - activeLabelId: state.editor.activeLabelId, - highlightedLabelId: state.editor.highlightedLabelId, - labelNames : state.editor.labelNames + activeLabelIndex: state.labels.activeLabelNameIndex, + activeLabelId: state.labels.activeLabelId, + highlightedLabelId: state.labels.highlightedLabelId, + labelNames : state.labels.labelNames }); export default connect( diff --git a/src/views/EditorView/SideNavigationBar/RectLabelsList/RectLabelsList.tsx b/src/views/EditorView/SideNavigationBar/RectLabelsList/RectLabelsList.tsx index 1091809e..c6a83a40 100644 --- a/src/views/EditorView/SideNavigationBar/RectLabelsList/RectLabelsList.tsx +++ b/src/views/EditorView/SideNavigationBar/RectLabelsList/RectLabelsList.tsx @@ -1,18 +1,19 @@ import React from 'react'; import {ISize} from "../../../../interfaces/ISize"; import Scrollbars from 'react-custom-scrollbars'; -import {ImageData, LabelRect} from "../../../../store/editor/types"; +import {ImageData, LabelRect} from "../../../../store/labels/types"; import './RectLabelsList.scss'; import { updateActiveLabelId, updateActiveLabelNameIndex, updateImageDataById -} from "../../../../store/editor/actionCreators"; +} from "../../../../store/labels/actionCreators"; import {AppState} from "../../../../store"; import {connect} from "react-redux"; import LabelInputField from "../LabelInputField/LabelInputField"; import EmptyLabelList from "../EmptyLabelList/EmptyLabelList"; import {LabelActions} from "../../../../logic/actions/LabelActions"; +import {LabelStatus} from "../../../../data/enums/LabelStatus"; interface IProps { size: ISize; @@ -48,7 +49,8 @@ const RectLabelsList: React.FC = ({size, imageData, updateImageDataById, if (labelRect.id === labelRectId) { return { ...labelRect, - labelIndex: labelNameIndex + labelIndex: labelNameIndex, + status: LabelStatus.ACCEPTED } } else { return labelRect @@ -113,10 +115,10 @@ const mapDispatchToProps = { }; const mapStateToProps = (state: AppState) => ({ - activeLabelIndex: state.editor.activeLabelNameIndex, - activeLabelId: state.editor.activeLabelId, - highlightedLabelId: state.editor.highlightedLabelId, - labelNames : state.editor.labelNames + activeLabelIndex: state.labels.activeLabelNameIndex, + activeLabelId: state.labels.activeLabelId, + highlightedLabelId: state.labels.highlightedLabelId, + labelNames : state.labels.labelNames }); export default connect( diff --git a/src/views/EditorView/StateBar/StateBar.scss b/src/views/EditorView/StateBar/StateBar.scss index 72c40b52..89ea9338 100644 --- a/src/views/EditorView/StateBar/StateBar.scss +++ b/src/views/EditorView/StateBar/StateBar.scss @@ -14,8 +14,8 @@ .done { align-self: stretch; - //width: 30%; - background-color: $secondaryColor; + background-color: $secondaryColor; // fallback if new css variables are not supported by browser + background-color: var(--leading-color); transition: width 0.4s ease-out; } } \ No newline at end of file diff --git a/src/views/EditorView/StateBar/StateBar.tsx b/src/views/EditorView/StateBar/StateBar.tsx index 3ab6fb31..1a13c04f 100644 --- a/src/views/EditorView/StateBar/StateBar.tsx +++ b/src/views/EditorView/StateBar/StateBar.tsx @@ -1,6 +1,6 @@ import React from 'react'; import './StateBar.scss'; -import {ImageData} from "../../../store/editor/types"; +import {ImageData} from "../../../store/labels/types"; import {AppState} from "../../../store"; import {connect} from "react-redux"; import {LabelType} from "../../../data/enums/LabelType"; @@ -50,8 +50,8 @@ const StateBar: React.FC = ({imagesData, activeLabelType}) => { const mapDispatchToProps = {}; const mapStateToProps = (state: AppState) => ({ - imagesData: state.editor.imagesData, - activeLabelType: state.editor.activeLabelType + imagesData: state.labels.imagesData, + activeLabelType: state.labels.activeLabelType }); export default connect( diff --git a/src/views/EditorView/ToolBox/ToolBox.tsx b/src/views/EditorView/ToolBox/ToolBox.tsx index f8fd8d7d..590cd903 100644 --- a/src/views/EditorView/ToolBox/ToolBox.tsx +++ b/src/views/EditorView/ToolBox/ToolBox.tsx @@ -10,8 +10,8 @@ import {ViewPortActions} from "../../../logic/actions/ViewPortActions"; import {ISize} from "../../../interfaces/ISize"; import {updateImageDragModeStatus} from "../../../store/general/actionCreators"; import ImageButtonDropDown from "./ImageButtonDropDown/ImageButtonDropDown"; -import {EditorModel} from "../../../staticModels/EditorModel"; import {ViewPointSettings} from "../../../settings/ViewPointSettings"; +import {GeneralSelector} from "../../../store/selectors/GeneralSelector"; interface IProps { activeContext: ContextType; @@ -46,7 +46,10 @@ const ToolBox: React.FC = ({activeContext, updateImageDragModeStatus, im ]; const imageDragOnClick = () => { - if (EditorModel.zoom !== ViewPointSettings.MIN_ZOOM) { + if (imageDragMode) { + updateImageDragModeStatus(!imageDragMode); + } + else if (GeneralSelector.getZoom() !== ViewPointSettings.MIN_ZOOM) { updateImageDragModeStatus(!imageDragMode); } }; diff --git a/src/views/EditorView/TopNavigationBar/TopNavigationBar.scss b/src/views/EditorView/TopNavigationBar/TopNavigationBar.scss index e610206b..b09b0af2 100644 --- a/src/views/EditorView/TopNavigationBar/TopNavigationBar.scss +++ b/src/views/EditorView/TopNavigationBar/TopNavigationBar.scss @@ -55,7 +55,8 @@ } &:not(.disabled):hover { - filter: brightness(35%) sepia(100%) hue-rotate(172deg) saturate(1000%); + filter: brightness(35%) sepia(100%) hue-rotate(172deg) saturate(2000%); // fallback if new css variables are not supported by browser + filter: brightness(35%) sepia(100%) hue-rotate(var(--hue-value)) saturate(2000%); } } @@ -72,10 +73,12 @@ &:focus { outline: none; - color: $secondaryColor; + color: $secondaryColor; // fallback if new css variables are not supported by browser + color: var(--leading-color); ~ .Bar { - background-color: $secondaryColor; + background-color: $secondaryColor; // fallback if new css variables are not supported by browser + background-color: var(--leading-color); } } } diff --git a/src/views/EditorView/TopNavigationBar/TopNavigationBar.tsx b/src/views/EditorView/TopNavigationBar/TopNavigationBar.tsx index d5aad8d1..36c79f5a 100644 --- a/src/views/EditorView/TopNavigationBar/TopNavigationBar.tsx +++ b/src/views/EditorView/TopNavigationBar/TopNavigationBar.tsx @@ -5,19 +5,19 @@ import {UnderlineTextButton} from "../../Common/UnderlineTextButton/UnderlineTex import {PopupWindowType} from "../../../data/enums/PopupWindowType"; import {AppState} from "../../../store"; import {connect} from "react-redux"; -import {updateActivePopupType} from "../../../store/general/actionCreators"; +import {updateActivePopupType, updateProjectData} from "../../../store/general/actionCreators"; import TextInput from "../../Common/TextInput/TextInput"; -import {updateProjectName} from "../../../store/editor/actionCreators"; import {ImageButton} from "../../Common/ImageButton/ImageButton"; import {Settings} from "../../../settings/Settings"; +import {ProjectData} from "../../../store/general/types"; interface IProps { updateActivePopupType: (activePopupType: PopupWindowType) => any; - updateProjectName: (projectName: string) => any; - projectName: string; + updateProjectData: (projectData: ProjectData) => any; + projectData: ProjectData; } -const TopNavigationBar: React.FC = ({updateActivePopupType, updateProjectName, projectName}) => { +const TopNavigationBar: React.FC = ({updateActivePopupType, updateProjectData, projectData}) => { const onFocus = (event: React.FocusEvent) => { event.target.setSelectionRange(0, event.target.value.length); }; @@ -26,7 +26,11 @@ const TopNavigationBar: React.FC = ({updateActivePopupType, updateProjec const value = event.target.value .toLowerCase() .replace(' ', '-'); - updateProjectName(value) + + updateProjectData({ + ...projectData, + name: value + }) }; return ( @@ -51,7 +55,7 @@ const TopNavigationBar: React.FC = ({updateActivePopupType, updateProjec @@ -81,11 +85,11 @@ const TopNavigationBar: React.FC = ({updateActivePopupType, updateProjec const mapDispatchToProps = { updateActivePopupType, - updateProjectName + updateProjectData }; const mapStateToProps = (state: AppState) => ({ - projectName: state.editor.projectName + projectData: state.general.projectData }); export default connect( diff --git a/src/views/EditorView/VerticalEditorButton/VerticalEditorButton.scss b/src/views/EditorView/VerticalEditorButton/VerticalEditorButton.scss index 6d9739b8..c5811dff 100644 --- a/src/views/EditorView/VerticalEditorButton/VerticalEditorButton.scss +++ b/src/views/EditorView/VerticalEditorButton/VerticalEditorButton.scss @@ -30,10 +30,12 @@ } &:hover { - background-color: $secondaryColor; + background-color: $secondaryColor; // fallback if new css variables are not supported by browser + background-color: var(--leading-color); } &.active { - background-color: $secondaryColor; + background-color: $secondaryColor; // fallback if new css variables are not supported by browser + background-color: var(--leading-color); } } \ No newline at end of file diff --git a/src/views/MainView/ImagesDropZone/ImagesDropZone.tsx b/src/views/MainView/ImagesDropZone/ImagesDropZone.tsx index b609dd82..ff339070 100644 --- a/src/views/MainView/ImagesDropZone/ImagesDropZone.tsx +++ b/src/views/MainView/ImagesDropZone/ImagesDropZone.tsx @@ -2,31 +2,36 @@ import React from "react"; import './ImagesDropZone.scss'; import {useDropzone} from "react-dropzone"; import {TextButton} from "../../Common/TextButton/TextButton"; -import {ImageData} from "../../../store/editor/types"; +import {ImageData} from "../../../store/labels/types"; import {connect} from "react-redux"; -import {addImageData, updateActiveImageIndex, updateProjectType} from "../../../store/editor/actionCreators"; +import {addImageData, updateActiveImageIndex} from "../../../store/labels/actionCreators"; import {AppState} from "../../../store"; import {ProjectType} from "../../../data/enums/ProjectType"; import {FileUtil} from "../../../utils/FileUtil"; import {PopupWindowType} from "../../../data/enums/PopupWindowType"; -import {updateActivePopupType} from "../../../store/general/actionCreators"; +import {updateActivePopupType, updateProjectData} from "../../../store/general/actionCreators"; import {AcceptedFileType} from "../../../data/enums/AcceptedFileType"; +import {ProjectData} from "../../../store/general/types"; interface IProps { updateActiveImageIndex: (activeImageIndex: number) => any; addImageData: (imageData: ImageData[]) => any; - updateProjectType: (projectType: ProjectType) => any; + updateProjectData: (projectData: ProjectData) => any; updateActivePopupType: (activePopupType: PopupWindowType) => any; + projectData: ProjectData; } -const ImagesDropZone: React.FC = ({updateActiveImageIndex, addImageData, updateProjectType, updateActivePopupType}) => { +const ImagesDropZone: React.FC = ({updateActiveImageIndex, addImageData, updateProjectData, updateActivePopupType, projectData}) => { const {acceptedFiles, getRootProps, getInputProps} = useDropzone({ accept: AcceptedFileType.IMAGE }); const startEditor = (projectType: ProjectType) => { if (acceptedFiles.length > 0) { - updateProjectType(projectType); + updateProjectData({ + ...projectData, + type: projectType + }); updateActiveImageIndex(0); addImageData(acceptedFiles.map((fileData:File) => FileUtil.mapFileDataToImageData(fileData))); updateActivePopupType(PopupWindowType.INSERT_LABEL_NAMES); @@ -92,11 +97,13 @@ const ImagesDropZone: React.FC = ({updateActiveImageIndex, addImageData, const mapDispatchToProps = { updateActiveImageIndex, addImageData, - updateProjectType, + updateProjectData, updateActivePopupType }; -const mapStateToProps = (state: AppState) => ({}); +const mapStateToProps = (state: AppState) => ({ + projectData: state.general.projectData +}); export default connect( mapStateToProps, diff --git a/src/views/PopupView/ExitProjectPopup/ExitProjectPopup.tsx b/src/views/PopupView/ExitProjectPopup/ExitProjectPopup.tsx index 1ed620a1..b6c18ffb 100644 --- a/src/views/PopupView/ExitProjectPopup/ExitProjectPopup.tsx +++ b/src/views/PopupView/ExitProjectPopup/ExitProjectPopup.tsx @@ -6,32 +6,32 @@ import { updateActiveLabelNameIndex, updateFirstLabelCreatedFlag, updateImageData, - updateLabelNamesList, - updateProjectType -} from "../../../store/editor/actionCreators"; + updateLabelNamesList +} from "../../../store/labels/actionCreators"; import {AppState} from "../../../store"; import {connect} from "react-redux"; -import {ProjectType} from "../../../data/enums/ProjectType"; -import {ImageData} from "../../../store/editor/types"; +import {ImageData} from "../../../store/labels/types"; import {PopupActions} from "../../../logic/actions/PopupActions"; +import {ProjectData} from "../../../store/general/types"; +import {updateProjectData} from "../../../store/general/actionCreators"; interface IProps { updateActiveImageIndex: (activeImageIndex: number) => any; - updateProjectType: (projectType: ProjectType) => any; updateActiveLabelNameIndex: (activeLabelIndex: number) => any; updateLabelNamesList: (labelNames: string[]) => any; updateImageData: (imageData: ImageData[]) => any; updateFirstLabelCreatedFlag: (firstLabelCreatedFlag: boolean) => any; + updateProjectData: (projectData: ProjectData) => any; } const ExitProjectPopup: React.FC = (props) => { const { updateActiveLabelNameIndex, updateLabelNamesList, - updateProjectType, updateActiveImageIndex, updateImageData, - updateFirstLabelCreatedFlag + updateFirstLabelCreatedFlag, + updateProjectData } = props; const renderContent = () => { @@ -47,7 +47,7 @@ const ExitProjectPopup: React.FC = (props) => { const onAccept = () => { updateActiveLabelNameIndex(null); updateLabelNamesList([]); - updateProjectType(null); + updateProjectData({type: null, name: "my-project-name"}); updateActiveImageIndex(null); updateImageData([]); updateFirstLabelCreatedFlag(false); @@ -72,7 +72,7 @@ const ExitProjectPopup: React.FC = (props) => { const mapDispatchToProps = { updateActiveLabelNameIndex, updateLabelNamesList, - updateProjectType, + updateProjectData, updateActiveImageIndex, updateImageData, updateFirstLabelCreatedFlag diff --git a/src/views/PopupView/ExportLabelsPopup/ExportLabelPopup.tsx b/src/views/PopupView/ExportLabelsPopup/ExportLabelPopup.tsx index b36b0a94..cf2a4216 100644 --- a/src/views/PopupView/ExportLabelsPopup/ExportLabelPopup.tsx +++ b/src/views/PopupView/ExportLabelsPopup/ExportLabelPopup.tsx @@ -131,7 +131,7 @@ const ExportLabelPopup: React.FC = () => { const mapDispatchToProps = {}; const mapStateToProps = (state: AppState) => ({ - imagesData: state.editor.imagesData + imagesData: state.labels.imagesData }); export default connect( diff --git a/src/views/PopupView/GenericYesNoPopup/GenericYesNoPopup.scss b/src/views/PopupView/GenericYesNoPopup/GenericYesNoPopup.scss index 6ecd1356..4c999725 100644 --- a/src/views/PopupView/GenericYesNoPopup/GenericYesNoPopup.scss +++ b/src/views/PopupView/GenericYesNoPopup/GenericYesNoPopup.scss @@ -60,13 +60,16 @@ .TextButton { margin-left: 20px; - box-shadow: $secondaryColor 0 0 0 2px inset; - background-color: $secondaryColor; + box-shadow: $secondaryColor 0 0 0 2px inset; // fallback if new css variables are not supported by browser + box-shadow: var(--leading-color) 0 0 0 2px inset; + background-color: $secondaryColor; // fallback if new css variables are not supported by browser + background-color: var(--leading-color); color: white; &:hover { background-color: transparent; - color: $secondaryColor; + color: $secondaryColor; // fallback if new css variables are not supported by browser + color: var(--leading-color); } &.disabled { diff --git a/src/views/PopupView/InsertLabelNamesPopup/InsertLabelNamesPopup.tsx b/src/views/PopupView/InsertLabelNamesPopup/InsertLabelNamesPopup.tsx index 40249a64..ac97f0b9 100644 --- a/src/views/PopupView/InsertLabelNamesPopup/InsertLabelNamesPopup.tsx +++ b/src/views/PopupView/InsertLabelNamesPopup/InsertLabelNamesPopup.tsx @@ -2,7 +2,7 @@ import React, {useState} from 'react' import './InsertLabelNamesPopup.scss' import {GenericYesNoPopup} from "../GenericYesNoPopup/GenericYesNoPopup"; import {PopupWindowType} from "../../../data/enums/PopupWindowType"; -import {updateActiveLabelNameIndex, updateLabelNamesList} from "../../../store/editor/actionCreators"; +import {updateActiveLabelNameIndex, updateLabelNamesList} from "../../../store/labels/actionCreators"; import {updateActivePopupType} from "../../../store/general/actionCreators"; import {AppState} from "../../../store"; import {connect} from "react-redux"; @@ -10,7 +10,6 @@ import Scrollbars from 'react-custom-scrollbars'; import TextInput from "../../Common/TextInput/TextInput"; import {ImageButton} from "../../Common/ImageButton/ImageButton"; import uuidv1 from 'uuid/v1'; -import {PopupActions} from "../../../logic/actions/PopupActions"; interface IProps { updateActiveLabelNameIndex: (activeLabelIndex: number) => any; @@ -58,7 +57,7 @@ const InsertLabelNamesPopup: React.FC = ({updateActiveLabelNameIndex, up const labelNamesList: string[] = extractLabelNamesList(); if (labelNamesList.length > 0) { updateLabelNamesList(labelNamesList); - PopupActions.close(); + updateActivePopupType(PopupWindowType.LOAD_AI_MODEL); } }; diff --git a/src/views/PopupView/LoadLabelNamesPopup/LoadLabelNamesPopup.tsx b/src/views/PopupView/LoadLabelNamesPopup/LoadLabelNamesPopup.tsx index 46abd1ec..2188088e 100644 --- a/src/views/PopupView/LoadLabelNamesPopup/LoadLabelNamesPopup.tsx +++ b/src/views/PopupView/LoadLabelNamesPopup/LoadLabelNamesPopup.tsx @@ -2,14 +2,13 @@ import React, {useState} from 'react' import './LoadLabelNamesPopup.scss' import {AppState} from "../../../store"; import {connect} from "react-redux"; -import {updateActiveLabelNameIndex, updateLabelNamesList} from "../../../store/editor/actionCreators"; +import {updateActiveLabelNameIndex, updateLabelNamesList} from "../../../store/labels/actionCreators"; import {GenericYesNoPopup} from "../GenericYesNoPopup/GenericYesNoPopup"; import {PopupWindowType} from "../../../data/enums/PopupWindowType"; import {updateActivePopupType} from "../../../store/general/actionCreators"; import {useDropzone} from "react-dropzone"; import {FileUtil} from "../../../utils/FileUtil"; import {AcceptedFileType} from "../../../data/enums/AcceptedFileType"; -import {PopupActions} from "../../../logic/actions/PopupActions"; interface IProps { updateActiveLabelNameIndex: (activeLabelIndex: number) => any; @@ -44,7 +43,7 @@ const LoadLabelNamesPopup: React.FC = ({updateActiveLabelNameIndex, upda if (labelsList.length > 0) { updateActiveLabelNameIndex(0); updateLabelNamesList(labelsList); - PopupActions.close(); + updateActivePopupType(PopupWindowType.LOAD_AI_MODEL); } }; diff --git a/src/views/PopupView/LoadModelPopup/LoadModelPopup.scss b/src/views/PopupView/LoadModelPopup/LoadModelPopup.scss new file mode 100644 index 00000000..8b4255b4 --- /dev/null +++ b/src/views/PopupView/LoadModelPopup/LoadModelPopup.scss @@ -0,0 +1,38 @@ +@import '../../../settings/Settings'; + +.LoadModelPopupContent { + display: flex; + flex-direction: column; + flex-wrap: nowrap; + justify-content: center; + align-items: center; + align-content: center; + padding-top: 30px; + flex: 1; + + .Message { + align-self: stretch; + color: white; + font-size: 15px; + padding: 0 40px 30px 40px; + } + + .Companion { + align-self: stretch; + padding-bottom: 30px; + display: flex; + flex-direction: column; + flex-wrap: nowrap; + justify-content: center; + align-items: center; + align-content: center; + height: 150px; + + > img { + filter: brightness(0) invert(1); + max-width: 60px; + max-height: 60px; + user-select: none; + } + } +} \ No newline at end of file diff --git a/src/views/PopupView/LoadModelPopup/LoadModelPopup.tsx b/src/views/PopupView/LoadModelPopup/LoadModelPopup.tsx new file mode 100644 index 00000000..fdcf3a14 --- /dev/null +++ b/src/views/PopupView/LoadModelPopup/LoadModelPopup.tsx @@ -0,0 +1,61 @@ +import React, {useState} from "react"; +import {PopupActions} from "../../../logic/actions/PopupActions"; +import {GenericYesNoPopup} from "../GenericYesNoPopup/GenericYesNoPopup"; +import {ObjectDetector} from "../../../ai/ObjectDetector"; +import './LoadModelPopup.scss' +import {Settings} from "../../../settings/Settings"; +import {ClipLoader} from "react-spinners"; + +export const LoadModelPopup: React.FC = () => { + const [modelIsLoadingStatus, setModelIsLoadingStatus] = useState(false); + + const onAccept = () => { + setModelIsLoadingStatus(true); + ObjectDetector.loadModel(() => { + PopupActions.close(); + }); + }; + + const onReject = () => { + PopupActions.close(); + }; + + const renderContent = () => { + return
+
+ To speed up your work, you can use our AI, which will try to mark objects on your images. Don't worry, + your photos are still safe. To take care of your privacy, we decided not to send your images to the + server, but instead send our AI to you. When accepting, make sure that you have a fast and stable + connection - it may take a few minutes to load the model. +
+
+ {modelIsLoadingStatus ? + : + {"robot"} + } +
+
+ }; + + return( + + ); +}; \ No newline at end of file diff --git a/src/views/PopupView/LoadMoreImagesPopup/LoadMoreImagesPopup.tsx b/src/views/PopupView/LoadMoreImagesPopup/LoadMoreImagesPopup.tsx index de60324d..dbf44f3b 100644 --- a/src/views/PopupView/LoadMoreImagesPopup/LoadMoreImagesPopup.tsx +++ b/src/views/PopupView/LoadMoreImagesPopup/LoadMoreImagesPopup.tsx @@ -2,11 +2,11 @@ import React from 'react' import './LoadMoreImagesPopup.scss' import {AppState} from "../../../store"; import {connect} from "react-redux"; -import {addImageData} from "../../../store/editor/actionCreators"; +import {addImageData} from "../../../store/labels/actionCreators"; import {GenericYesNoPopup} from "../GenericYesNoPopup/GenericYesNoPopup"; import {useDropzone} from "react-dropzone"; import {FileUtil} from "../../../utils/FileUtil"; -import {ImageData} from "../../../store/editor/types"; +import {ImageData} from "../../../store/labels/types"; import {AcceptedFileType} from "../../../data/enums/AcceptedFileType"; import {PopupActions} from "../../../logic/actions/PopupActions"; diff --git a/src/views/PopupView/PopupView.tsx b/src/views/PopupView/PopupView.tsx index c4d0087e..5f66bd56 100644 --- a/src/views/PopupView/PopupView.tsx +++ b/src/views/PopupView/PopupView.tsx @@ -8,6 +8,7 @@ import ExportLabelPopup from "./ExportLabelsPopup/ExportLabelPopup"; import InsertLabelNamesPopup from "./InsertLabelNamesPopup/InsertLabelNamesPopup"; import ExitProjectPopup from "./ExitProjectPopup/ExitProjectPopup"; import LoadMoreImagesPopup from "./LoadMoreImagesPopup/LoadMoreImagesPopup"; +import {LoadModelPopup} from "./LoadModelPopup/LoadModelPopup"; interface IProps { activePopupType: PopupWindowType; @@ -27,6 +28,8 @@ const PopupView: React.FC = ({activePopupType}) => { return ; case PopupWindowType.LOAD_IMAGES: return ; + case PopupWindowType.LOAD_AI_MODEL: + return ; default: return null; }