├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── docker └── Dockerfile ├── examples ├── ai-demo.gif ├── alfa-demo.gif ├── bbox-demo.gif ├── demo-base.gif ├── demo-base.mp4 ├── demo-posenet.gif ├── demo-posenet.mp4 ├── demo-ssd.gif ├── demo-ssd.mp4 ├── object_detection_basketball.gif ├── points-demo.gif └── polygon-demo.gif ├── package-lock.json ├── package.json ├── public ├── ico │ ├── accept-all.png │ ├── actions.png │ ├── add.png │ ├── ai.png │ ├── box-closed.png │ ├── box-opened.png │ ├── bug.png │ ├── camera.png │ ├── cancel.png │ ├── checkbox-checked.png │ ├── checkbox-unchecked.png │ ├── close.png │ ├── cross-hair.png │ ├── delete.png │ ├── documentation.png │ ├── down.png │ ├── export-labels.png │ ├── file.png │ ├── files.png │ ├── github-logo.png │ ├── hand-fill-grab.png │ ├── hand-fill.png │ ├── hand.png │ ├── import-labels.png │ ├── labels.png │ ├── labels_list.png │ ├── labels_list_empty.png │ ├── left.png │ ├── line.png │ ├── main-image-color.png │ ├── main-image-dark.png │ ├── main-image-dark_alter.png │ ├── medium-logo.png │ ├── more.png │ ├── move.png │ ├── new_labels.png │ ├── object.png │ ├── ok.png │ ├── online.png │ ├── open-source.png │ ├── patreon-logo.png │ ├── pictures.png │ ├── plus.png │ ├── point.png │ ├── polygon.png │ ├── polyline.png │ ├── private.png │ ├── rectangle.png │ ├── reject-all.png │ ├── remove.png │ ├── resize.png │ ├── right.png │ ├── robot.png │ ├── rocket.png │ ├── small_window.png │ ├── tags.png │ ├── take-off.png │ ├── trash.png │ ├── type-writer.png │ ├── upload.png │ ├── zoom-fit.png │ ├── zoom-in.png │ ├── zoom-max.png │ └── zoom-out.png ├── index.html ├── make-sense-ico-transparent.png ├── make-sense-ico.png └── manifest.json ├── src ├── App.scss ├── App.tsx ├── __test__ │ └── custom-test-env.js ├── ai │ ├── ObjectDetector.ts │ └── PoseDetector.ts ├── configureStore.ts ├── data │ ├── EditorData.ts │ ├── ExportFormatData.ts │ ├── HotKeyAction.ts │ ├── ImportFormatData.ts │ ├── ImporterSpecData.ts │ ├── MobileDeviceData.ts │ ├── RectAnchor.ts │ ├── enums │ │ ├── AIModel.ts │ │ ├── AcceptedFileType.ts │ │ ├── AnnotationFormatType.ts │ │ ├── ContextType.ts │ │ ├── CustomCursorStyle.ts │ │ ├── Direction.ts │ │ ├── EventType.ts │ │ ├── LabelStatus.ts │ │ ├── LabelType.ts │ │ ├── LineAnchorType.ts │ │ ├── PopupWindowType.ts │ │ └── ProjectType.ts │ ├── info │ │ ├── DropDownMenuData.ts │ │ ├── EditorFeatureData.ts │ │ ├── LabelToolkitData.ts │ │ └── SocialMediaData.ts │ └── labels │ │ ├── COCO.ts │ │ └── VGG.ts ├── index.scss ├── index.tsx ├── interfaces │ ├── ILabelFormatData.ts │ ├── ILine.ts │ ├── IPoint.ts │ ├── IRect.ts │ └── ISize.ts ├── logic │ ├── __tests__ │ │ ├── actions │ │ │ ├── AIActions.test.ts │ │ │ └── AIObjectDetectionActions.test.ts │ │ ├── export │ │ │ └── polygon │ │ │ │ ├── COCOExporter.test.ts │ │ │ │ └── VGGExporter.test.ts │ │ └── import │ │ │ ├── coco │ │ │ └── COCOUtils.tests.ts │ │ │ └── yolo │ │ │ ├── YOLOImporter.test.ts │ │ │ └── YOLOUtils.test.ts │ ├── actions │ │ ├── AIActions.ts │ │ ├── AIObjectDetectionActions.ts │ │ ├── AIPoseDetectionActions.ts │ │ ├── EditorActions.ts │ │ ├── ImageActions.ts │ │ ├── LabelActions.ts │ │ ├── PopupActions.ts │ │ └── ViewPortActions.ts │ ├── context │ │ ├── BaseContext.ts │ │ ├── ContextManager.ts │ │ ├── EditorContext.ts │ │ └── PopupContext.ts │ ├── export │ │ ├── LineLabelExport.ts │ │ ├── PointLabelsExport.ts │ │ ├── RectLabelsExporter.ts │ │ ├── TagLabelsExport.ts │ │ └── polygon │ │ │ ├── COCOExporter.ts │ │ │ ├── PolygonLabelsExporter.ts │ │ │ └── VGGExporter.ts │ ├── helpers │ │ ├── CSSHelper.ts │ │ └── ViewPortHelper.ts │ ├── imageRepository │ │ ├── ImageLoadManager.ts │ │ └── ImageRepository.ts │ ├── import │ │ ├── AnnotationImporter.ts │ │ ├── coco │ │ │ ├── COCOErrors.ts │ │ │ ├── COCOImporter.ts │ │ │ └── COCOUtils.ts │ │ └── yolo │ │ │ ├── YOLOErrors.ts │ │ │ ├── YOLOImporter.ts │ │ │ └── YOLOUtils.ts │ ├── initializer │ │ └── AppInitializer.ts │ └── render │ │ ├── BaseRenderEngine.ts │ │ ├── LineRenderEngine.ts │ │ ├── PointRenderEngine.ts │ │ ├── PolygonRenderEngine.ts │ │ ├── PrimaryEditorRenderEngine.ts │ │ └── RectRenderEngine.ts ├── react-app-env.d.ts ├── serviceWorker.ts ├── settings │ ├── RenderEngineConfig.ts │ ├── Settings.ts │ ├── ViewPointSettings.ts │ └── _Settings.scss ├── staticModels │ ├── EditorModel.ts │ └── PlatformModel.ts ├── store │ ├── Actions.ts │ ├── ai │ │ ├── actionCreators.ts │ │ ├── reducer.ts │ │ └── types.ts │ ├── general │ │ ├── actionCreators.ts │ │ ├── reducer.ts │ │ └── types.ts │ ├── index.ts │ ├── labels │ │ ├── actionCreators.ts │ │ ├── reducer.ts │ │ └── types.ts │ └── selectors │ │ ├── AISelector.ts │ │ ├── GeneralSelector.ts │ │ └── LabelsSelector.ts ├── utils │ ├── ArrayUtil.ts │ ├── CanvasUtil.ts │ ├── DirectionUtil.ts │ ├── DrawUtil.ts │ ├── EditorUtil.ts │ ├── EnvironmentUtil.ts │ ├── ExporterUtil.ts │ ├── FileUtil.ts │ ├── ImageDataUtil.ts │ ├── ImageUtil.ts │ ├── LabelUtil.ts │ ├── LineUtil.ts │ ├── MouseEventUtil.ts │ ├── NumberUtil.ts │ ├── PlatformUtil.ts │ ├── PointUtil.ts │ ├── RectUtil.ts │ ├── RenderEngineUtil.ts │ ├── SizeUtil.ts │ ├── UnitUtil.ts │ ├── VirtualListUtil.ts │ ├── XMLSanitizerUtil.ts │ └── __tests__ │ │ ├── ArrayUtil.test.ts │ │ ├── FileUtil.test.ts │ │ ├── ImageDataUtil.test.ts │ │ ├── LineUtil.test.ts │ │ └── RectUtil.test.ts └── views │ ├── Common │ ├── ImageButton │ │ ├── ImageButton.scss │ │ └── ImageButton.tsx │ ├── TextButton │ │ ├── TextButton.scss │ │ └── TextButton.tsx │ ├── TextInput │ │ ├── TextInput.scss │ │ └── TextInput.tsx │ ├── UnderlineTextButton │ │ ├── UnderlineTextButton.scss │ │ └── UnderlineTextButton.tsx │ └── VirtualList │ │ └── VirtualList.tsx │ ├── EditorView │ ├── Editor │ │ ├── Editor.scss │ │ └── Editor.tsx │ ├── EditorBottomNavigationBar │ │ ├── EditorBottomNavigationBar.scss │ │ └── EditorBottomNavigationBar.tsx │ ├── EditorContainer │ │ ├── EditorContainer.scss │ │ └── EditorContainer.tsx │ ├── EditorTopNavigationBar │ │ ├── EditorTopNavigationBar.scss │ │ └── EditorTopNavigationBar.tsx │ ├── EditorView.scss │ ├── EditorView.tsx │ ├── FeatureInProgress │ │ ├── FeatureInProgress.scss │ │ └── FeatureInProgress.tsx │ ├── LabelControlPanel │ │ ├── LabelControlPanel.scss │ │ └── LabelControlPanel.tsx │ ├── SideNavigationBar │ │ ├── EmptyLabelList │ │ │ ├── EmptyLabelList.scss │ │ │ └── EmptyLabelList.tsx │ │ ├── ImagePreview │ │ │ ├── ImagePreview.scss │ │ │ └── ImagePreview.tsx │ │ ├── ImagesList │ │ │ ├── ImagesList.scss │ │ │ └── ImagesList.tsx │ │ ├── LabelInputField │ │ │ ├── LabelInputField.scss │ │ │ └── LabelInputField.tsx │ │ ├── LabelsToolkit │ │ │ ├── LabelsToolkit.scss │ │ │ └── LabelsToolkit.tsx │ │ ├── LineLabelsList │ │ │ ├── LineLabelsList.scss │ │ │ └── LineLabelsList.tsx │ │ ├── PointLabelsList │ │ │ ├── PointLabelsList.scss │ │ │ └── PointLabelsList.tsx │ │ ├── PolygonLabelsList │ │ │ ├── PolygonLabelsList.scss │ │ │ └── PolygonLabelsList.tsx │ │ ├── RectLabelsList │ │ │ ├── RectLabelsList.scss │ │ │ └── RectLabelsList.tsx │ │ ├── SideNavigationBar.scss │ │ ├── SideNavigationBar.tsx │ │ └── TagLabelsList │ │ │ ├── TagLabelsList.scss │ │ │ └── TagLabelsList.tsx │ ├── StateBar │ │ ├── StateBar.scss │ │ └── StateBar.tsx │ ├── TopNavigationBar │ │ ├── DropDownMenu │ │ │ ├── DropDownMenu.scss │ │ │ └── DropDownMenu.tsx │ │ ├── TopNavigationBar.scss │ │ └── TopNavigationBar.tsx │ └── VerticalEditorButton │ │ ├── VerticalEditorButton.scss │ │ └── VerticalEditorButton.tsx │ ├── MainView │ ├── ImagesDropZone │ │ ├── ImagesDropZone.scss │ │ └── ImagesDropZone.tsx │ ├── MainView.scss │ └── MainView.tsx │ ├── MobileMainView │ ├── MobileMainView.scss │ └── MobileMainView.tsx │ ├── PopupView │ ├── ExitProjectPopup │ │ ├── ExitProjectPopup.scss │ │ └── ExitProjectPopup.tsx │ ├── ExportLabelsPopup │ │ ├── ExportLabelPopup.scss │ │ └── ExportLabelPopup.tsx │ ├── GenericLabelTypePopup │ │ ├── GenericLabelTypePopup.scss │ │ └── GenericLabelTypePopup.tsx │ ├── GenericYesNoPopup │ │ ├── GenericYesNoPopup.scss │ │ └── GenericYesNoPopup.tsx │ ├── ImportLabelPopup │ │ ├── ImportLabelPopup.scss │ │ └── ImportLabelPopup.tsx │ ├── InsertLabelNamesPopup │ │ ├── InsertLabelNamesPopup.scss │ │ └── InsertLabelNamesPopup.tsx │ ├── LoadLabelNamesPopup │ │ ├── LoadLabelNamesPopup.scss │ │ └── LoadLabelNamesPopup.tsx │ ├── LoadModelPopup │ │ ├── LoadModelPopup.scss │ │ └── LoadModelPopup.tsx │ ├── LoadMoreImagesPopup │ │ ├── LoadMoreImagesPopup.scss │ │ └── LoadMoreImagesPopup.tsx │ ├── PopupView.scss │ ├── PopupView.tsx │ └── SuggestLabelNamesPopup │ │ ├── SuggestLabelNamesPopup.scss │ │ └── SuggestLabelNamesPopup.tsx │ └── SizeItUpView │ ├── SizeItUpView.scss │ └── SizeItUpView.tsx └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # INTELIIJ 2 | 3 | .idea/ 4 | 5 | # CREATE REACT APP 6 | 7 | # dependencies 8 | /node_modules 9 | /.pnp 10 | .pnp.js 11 | 12 | # testing 13 | /coverage 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | .env.local 21 | .env.development.local 22 | .env.test.local 23 | .env.production.local 24 | 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "stable" 4 | cache: 5 | directories: 6 | - node_modules 7 | script: 8 | - npm test 9 | - npm run test:coverage 10 | - npm run build -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:11.10-slim 2 | 3 | RUN apt-get update && apt-get -y install git && rm -rf /var/lib/apt/lists/* 4 | 5 | RUN mkdir /workspace && \ 6 | cd /workspace && \ 7 | git clone https://github.com/SkalskiP/make-sense.git && \ 8 | cd make-sense && \ 9 | npm install 10 | 11 | WORKDIR /workspace/make-sense 12 | 13 | ENTRYPOINT ["npm", "start"] 14 | -------------------------------------------------------------------------------- /examples/ai-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/examples/ai-demo.gif -------------------------------------------------------------------------------- /examples/alfa-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/examples/alfa-demo.gif -------------------------------------------------------------------------------- /examples/bbox-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/examples/bbox-demo.gif -------------------------------------------------------------------------------- /examples/demo-base.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/examples/demo-base.gif -------------------------------------------------------------------------------- /examples/demo-base.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/examples/demo-base.mp4 -------------------------------------------------------------------------------- /examples/demo-posenet.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/examples/demo-posenet.gif -------------------------------------------------------------------------------- /examples/demo-posenet.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/examples/demo-posenet.mp4 -------------------------------------------------------------------------------- /examples/demo-ssd.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/examples/demo-ssd.gif -------------------------------------------------------------------------------- /examples/demo-ssd.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/examples/demo-ssd.mp4 -------------------------------------------------------------------------------- /examples/object_detection_basketball.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/examples/object_detection_basketball.gif -------------------------------------------------------------------------------- /examples/points-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/examples/points-demo.gif -------------------------------------------------------------------------------- /examples/polygon-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/examples/polygon-demo.gif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "make-sense", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@material-ui/core": "^4.1.1", 7 | "@tensorflow-models/coco-ssd": "^2.0.0", 8 | "@tensorflow-models/posenet": "^2.1.3", 9 | "@tensorflow/tfjs": "^1.2.9", 10 | "@types/jest": "24.0.14", 11 | "@types/node": "12.0.8", 12 | "@types/react": "16.8.20", 13 | "@types/react-dom": "16.8.4", 14 | "classnames": "^2.2.6", 15 | "file-saver": "^2.0.2", 16 | "jszip": "^3.2.2", 17 | "lodash": "^4.17.20", 18 | "mobile-detect": "^1.4.3", 19 | "moment": "^2.24.0", 20 | "node-sass": "^4.14.1", 21 | "react": "^16.8.6", 22 | "react-custom-scrollbars": "^4.2.1", 23 | "react-dom": "^16.8.6", 24 | "react-dropzone": "^10.1.5", 25 | "react-redux": "^7.1.0", 26 | "react-scripts": "^3.4.3", 27 | "react-spinners": "^0.9.0", 28 | "redux": "^4.0.4", 29 | "typescript": "3.5.2", 30 | "uuid": "^3.3.2" 31 | }, 32 | "scripts": { 33 | "start": "react-scripts start", 34 | "build": "react-scripts build", 35 | "test": "CI=true react-scripts test --env=./src/__test__/custom-test-env.js", 36 | "test:coverage": "npm test -- --coverage", 37 | "eject": "react-scripts eject" 38 | }, 39 | "eslintConfig": { 40 | "extends": "react-app" 41 | }, 42 | "browserslist": { 43 | "production": [ 44 | ">0.2%", 45 | "not dead", 46 | "not op_mini all" 47 | ], 48 | "development": [ 49 | "last 1 chrome version", 50 | "last 1 firefox version", 51 | "last 1 safari version" 52 | ] 53 | }, 54 | "devDependencies": { 55 | "@types/classnames": "^2.2.8", 56 | "@types/file-saver": "^2.0.1", 57 | "@types/jszip": "^3.1.6", 58 | "@types/lodash": "^4.14.135", 59 | "@types/moment": "^2.13.0", 60 | "@types/react-custom-scrollbars": "^4.0.5", 61 | "@types/react-dropzone": "^4.2.2", 62 | "@types/react-redux": "^7.1.0", 63 | "@types/uuid": "^3.4.4", 64 | "prettier": "^1.17.1", 65 | "redux-devtools": "^3.5.0" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /public/ico/accept-all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/accept-all.png -------------------------------------------------------------------------------- /public/ico/actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/actions.png -------------------------------------------------------------------------------- /public/ico/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/add.png -------------------------------------------------------------------------------- /public/ico/ai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/ai.png -------------------------------------------------------------------------------- /public/ico/box-closed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/box-closed.png -------------------------------------------------------------------------------- /public/ico/box-opened.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/box-opened.png -------------------------------------------------------------------------------- /public/ico/bug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/bug.png -------------------------------------------------------------------------------- /public/ico/camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/camera.png -------------------------------------------------------------------------------- /public/ico/cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/cancel.png -------------------------------------------------------------------------------- /public/ico/checkbox-checked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/checkbox-checked.png -------------------------------------------------------------------------------- /public/ico/checkbox-unchecked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/checkbox-unchecked.png -------------------------------------------------------------------------------- /public/ico/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/close.png -------------------------------------------------------------------------------- /public/ico/cross-hair.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/cross-hair.png -------------------------------------------------------------------------------- /public/ico/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/delete.png -------------------------------------------------------------------------------- /public/ico/documentation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/documentation.png -------------------------------------------------------------------------------- /public/ico/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/down.png -------------------------------------------------------------------------------- /public/ico/export-labels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/export-labels.png -------------------------------------------------------------------------------- /public/ico/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/file.png -------------------------------------------------------------------------------- /public/ico/files.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/files.png -------------------------------------------------------------------------------- /public/ico/github-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/github-logo.png -------------------------------------------------------------------------------- /public/ico/hand-fill-grab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/hand-fill-grab.png -------------------------------------------------------------------------------- /public/ico/hand-fill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/hand-fill.png -------------------------------------------------------------------------------- /public/ico/hand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/hand.png -------------------------------------------------------------------------------- /public/ico/import-labels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/import-labels.png -------------------------------------------------------------------------------- /public/ico/labels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/labels.png -------------------------------------------------------------------------------- /public/ico/labels_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/labels_list.png -------------------------------------------------------------------------------- /public/ico/labels_list_empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/labels_list_empty.png -------------------------------------------------------------------------------- /public/ico/left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/left.png -------------------------------------------------------------------------------- /public/ico/line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/line.png -------------------------------------------------------------------------------- /public/ico/main-image-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/main-image-color.png -------------------------------------------------------------------------------- /public/ico/main-image-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/main-image-dark.png -------------------------------------------------------------------------------- /public/ico/main-image-dark_alter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/main-image-dark_alter.png -------------------------------------------------------------------------------- /public/ico/medium-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/medium-logo.png -------------------------------------------------------------------------------- /public/ico/more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/more.png -------------------------------------------------------------------------------- /public/ico/move.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/move.png -------------------------------------------------------------------------------- /public/ico/new_labels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/new_labels.png -------------------------------------------------------------------------------- /public/ico/object.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/object.png -------------------------------------------------------------------------------- /public/ico/ok.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/ok.png -------------------------------------------------------------------------------- /public/ico/online.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/online.png -------------------------------------------------------------------------------- /public/ico/open-source.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/open-source.png -------------------------------------------------------------------------------- /public/ico/patreon-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/patreon-logo.png -------------------------------------------------------------------------------- /public/ico/pictures.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/pictures.png -------------------------------------------------------------------------------- /public/ico/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/plus.png -------------------------------------------------------------------------------- /public/ico/point.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/point.png -------------------------------------------------------------------------------- /public/ico/polygon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/polygon.png -------------------------------------------------------------------------------- /public/ico/polyline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/polyline.png -------------------------------------------------------------------------------- /public/ico/private.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/private.png -------------------------------------------------------------------------------- /public/ico/rectangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/rectangle.png -------------------------------------------------------------------------------- /public/ico/reject-all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/reject-all.png -------------------------------------------------------------------------------- /public/ico/remove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/remove.png -------------------------------------------------------------------------------- /public/ico/resize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/resize.png -------------------------------------------------------------------------------- /public/ico/right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/right.png -------------------------------------------------------------------------------- /public/ico/robot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/robot.png -------------------------------------------------------------------------------- /public/ico/rocket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/rocket.png -------------------------------------------------------------------------------- /public/ico/small_window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/small_window.png -------------------------------------------------------------------------------- /public/ico/tags.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/tags.png -------------------------------------------------------------------------------- /public/ico/take-off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/take-off.png -------------------------------------------------------------------------------- /public/ico/trash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/trash.png -------------------------------------------------------------------------------- /public/ico/type-writer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/type-writer.png -------------------------------------------------------------------------------- /public/ico/upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/upload.png -------------------------------------------------------------------------------- /public/ico/zoom-fit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/zoom-fit.png -------------------------------------------------------------------------------- /public/ico/zoom-in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/zoom-in.png -------------------------------------------------------------------------------- /public/ico/zoom-max.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/zoom-max.png -------------------------------------------------------------------------------- /public/ico/zoom-out.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/ico/zoom-out.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 23 | 24 | 33 | Make Sense 34 | 35 | 36 | 37 |
38 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /public/make-sense-ico-transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/make-sense-ico-transparent.png -------------------------------------------------------------------------------- /public/make-sense-ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peng-zhihui/Make-Sense/1a92b5a6c2ccb5bb035bc0dadee919d663cdd24d/public/make-sense-ico.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "makesense.ai", 3 | "name": "makesense.ai - free to use online tool for labelling photos", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/App.scss: -------------------------------------------------------------------------------- 1 | @import './settings/_Settings'; 2 | 3 | .App { 4 | --leading-color: #{$secondaryColor}; 5 | --button-text-color: #{white}; 6 | --hue-value: 172deg; 7 | } 8 | 9 | .App.AI { 10 | --leading-color: #{$primaryColor}; 11 | --button-text-color: #{$darkThemeSecondColor}; 12 | --hue-value: 120deg; 13 | } -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './App.scss'; 3 | import EditorView from "./views/EditorView/EditorView"; 4 | import MainView from "./views/MainView/MainView"; 5 | import {ProjectType} from "./data/enums/ProjectType"; 6 | import {AppState} from "./store"; 7 | import {connect} from "react-redux"; 8 | import PopupView from "./views/PopupView/PopupView"; 9 | import MobileMainView from "./views/MobileMainView/MobileMainView"; 10 | import {ISize} from "./interfaces/ISize"; 11 | import {Settings} from "./settings/Settings"; 12 | import {SizeItUpView} from "./views/SizeItUpView/SizeItUpView"; 13 | import {PlatformModel} from "./staticModels/PlatformModel"; 14 | import classNames from "classnames"; 15 | 16 | interface IProps { 17 | projectType: ProjectType; 18 | windowSize: ISize; 19 | ObjectDetectorLoaded: boolean; 20 | PoseDetectionLoaded: boolean; 21 | } 22 | 23 | const App: React.FC = ({projectType, windowSize, ObjectDetectorLoaded, PoseDetectionLoaded}) => { 24 | const selectRoute = () => { 25 | if (!!PlatformModel.mobileDeviceData.manufacturer && !!PlatformModel.mobileDeviceData.os) 26 | return ; 27 | if (!projectType) 28 | return ; 29 | else { 30 | if (windowSize.height < Settings.EDITOR_MIN_HEIGHT || windowSize.width < Settings.EDITOR_MIN_WIDTH) { 31 | return ; 32 | } else { 33 | return ; 34 | } 35 | } 36 | }; 37 | 38 | return ( 39 |
42 | {selectRoute()} 43 | 44 |
45 | ); 46 | }; 47 | 48 | const mapStateToProps = (state: AppState) => ({ 49 | projectType: state.general.projectData.type, 50 | windowSize: state.general.windowSize, 51 | ObjectDetectorLoaded: state.ai.isObjectDetectorLoaded, 52 | PoseDetectionLoaded: state.ai.isPoseDetectorLoaded 53 | }); 54 | 55 | export default connect( 56 | mapStateToProps 57 | )(App); 58 | -------------------------------------------------------------------------------- /src/__test__/custom-test-env.js: -------------------------------------------------------------------------------- 1 | const Environment = require('jest-environment-jsdom'); 2 | 3 | /** 4 | * A custom environment to set the TextEncoder that is required by TensorFlow.js. 5 | */ 6 | module.exports = class CustomTestEnvironment extends Environment { 7 | async setup() { 8 | await super.setup(); 9 | const { TextEncoder } = require('util'); 10 | this.global.TextEncoder = TextEncoder; 11 | } 12 | } -------------------------------------------------------------------------------- /src/ai/ObjectDetector.ts: -------------------------------------------------------------------------------- 1 | import * as cocoSsd from '@tensorflow-models/coco-ssd'; 2 | import {DetectedObject, ObjectDetection} from '@tensorflow-models/coco-ssd'; 3 | import {store} from "../index"; 4 | import {updateObjectDetectorStatus} from "../store/ai/actionCreators"; 5 | import {LabelType} from "../data/enums/LabelType"; 6 | import {LabelsSelector} from "../store/selectors/LabelsSelector"; 7 | import {AIObjectDetectionActions} from "../logic/actions/AIObjectDetectionActions"; 8 | import {updateActiveLabelType} from "../store/labels/actionCreators"; 9 | 10 | export class ObjectDetector { 11 | private static model: ObjectDetection; 12 | 13 | public static loadModel(callback?: () => any) { 14 | cocoSsd 15 | .load() 16 | .then((model: ObjectDetection) => { 17 | ObjectDetector.model = model; 18 | store.dispatch(updateObjectDetectorStatus(true)); 19 | store.dispatch(updateActiveLabelType(LabelType.RECT)); 20 | const activeLabelType: LabelType = LabelsSelector.getActiveLabelType(); 21 | activeLabelType === LabelType.RECT && AIObjectDetectionActions.detectRectsForActiveImage(); 22 | callback && callback(); 23 | }) 24 | .catch((error) => { 25 | // TODO 26 | throw new Error(error); 27 | }) 28 | } 29 | 30 | public static predict(image: HTMLImageElement, callback?: (predictions: DetectedObject[]) => any) { 31 | if (!ObjectDetector.model) return; 32 | 33 | ObjectDetector.model 34 | .detect(image) 35 | .then((predictions: DetectedObject[]) => { 36 | callback && callback(predictions) 37 | }) 38 | .catch((error) => { 39 | // TODO 40 | throw new Error(error); 41 | }) 42 | } 43 | } -------------------------------------------------------------------------------- /src/ai/PoseDetector.ts: -------------------------------------------------------------------------------- 1 | import * as posenet from '@tensorflow-models/posenet'; 2 | import {PoseNet} from "@tensorflow-models/posenet"; 3 | import {Pose} from "@tensorflow-models/posenet"; 4 | import {store} from "../index"; 5 | import {updatePoseDetectorStatus} from "../store/ai/actionCreators"; 6 | import {AIPoseDetectionActions} from "../logic/actions/AIPoseDetectionActions"; 7 | import {LabelType} from "../data/enums/LabelType"; 8 | import {LabelsSelector} from "../store/selectors/LabelsSelector"; 9 | import {updateActiveLabelType} from "../store/labels/actionCreators"; 10 | 11 | export class PoseDetector { 12 | private static model: PoseNet; 13 | 14 | public static loadModel(callback?: () => any) { 15 | posenet 16 | .load({ 17 | architecture: 'ResNet50', 18 | outputStride: 32, 19 | inputResolution: 257, 20 | quantBytes: 2 21 | }) 22 | .then((model: PoseNet) => { 23 | PoseDetector.model = model; 24 | store.dispatch(updatePoseDetectorStatus(true)); 25 | store.dispatch(updateActiveLabelType(LabelType.POINT)); 26 | const activeLabelType: LabelType = LabelsSelector.getActiveLabelType(); 27 | activeLabelType === LabelType.POINT && AIPoseDetectionActions.detectPoseForActiveImage(); 28 | callback && callback(); 29 | }) 30 | .catch((error) => { 31 | // TODO 32 | throw new Error(error); 33 | }) 34 | } 35 | 36 | public static predict(image: HTMLImageElement, callback?: (predictions: Pose[]) => any) { 37 | if (!PoseDetector.model) return; 38 | 39 | PoseDetector.model 40 | .estimateMultiplePoses(image) 41 | .then((predictions: Pose[]) => { 42 | callback && callback(predictions) 43 | }) 44 | .catch((error) => { 45 | // TODO 46 | throw new Error(error); 47 | }) 48 | } 49 | } -------------------------------------------------------------------------------- /src/configureStore.ts: -------------------------------------------------------------------------------- 1 | import { createStore } from 'redux'; 2 | import { rootReducer } from './store'; 3 | 4 | export default function configureStore() { 5 | return createStore( 6 | rootReducer, 7 | // @ts-ignore 8 | window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() 9 | ); 10 | } -------------------------------------------------------------------------------- /src/data/EditorData.ts: -------------------------------------------------------------------------------- 1 | import {IPoint} from "../interfaces/IPoint"; 2 | import {IRect} from "../interfaces/IRect"; 3 | import {ISize} from "../interfaces/ISize"; 4 | 5 | export interface EditorData { 6 | viewPortContentSize: ISize, 7 | mousePositionOnViewPortContent: IPoint, 8 | activeKeyCombo: string[], 9 | event?: Event 10 | zoom: number, 11 | viewPortSize: ISize, 12 | defaultRenderImageRect: IRect, 13 | realImageSize: ISize, 14 | viewPortContentImageRect: IRect, 15 | absoluteViewPortContentScrollPosition: IPoint 16 | } -------------------------------------------------------------------------------- /src/data/ExportFormatData.ts: -------------------------------------------------------------------------------- 1 | import {ILabelFormatData} from "../interfaces/ILabelFormatData"; 2 | import {LabelType} from "./enums/LabelType"; 3 | import {AnnotationFormatType} from "./enums/AnnotationFormatType"; 4 | 5 | export type ExportFormatDataMap = { [s in LabelType]: ILabelFormatData[]; }; 6 | 7 | export const ExportFormatData: ExportFormatDataMap = { 8 | "RECT": [ 9 | { 10 | type: AnnotationFormatType.YOLO, 11 | label: "A .zip package containing files in YOLO format." 12 | }, 13 | { 14 | type: AnnotationFormatType.VOC, 15 | label: "A .zip package containing files in VOC XML format." 16 | }, 17 | { 18 | type: AnnotationFormatType.CSV, 19 | label: "Single CSV file." 20 | } 21 | ], 22 | "POINT": [ 23 | { 24 | type: AnnotationFormatType.CSV, 25 | label: "Single CSV file." 26 | } 27 | ], 28 | "LINE": [ 29 | { 30 | type: AnnotationFormatType.CSV, 31 | label: "Single CSV file." 32 | } 33 | ], 34 | "POLYGON": [ 35 | { 36 | type: AnnotationFormatType.VGG, 37 | label: "Single file in VGG JSON format." 38 | }, 39 | { 40 | type: AnnotationFormatType.COCO, 41 | label: "Single file in COCO JSON format." 42 | } 43 | ], 44 | "IMAGE RECOGNITION": [ 45 | { 46 | type: AnnotationFormatType.CSV, 47 | label: "Single CSV file." 48 | }, 49 | { 50 | type: AnnotationFormatType.JSON, 51 | label: "Single JSON file." 52 | } 53 | ] 54 | } -------------------------------------------------------------------------------- /src/data/HotKeyAction.ts: -------------------------------------------------------------------------------- 1 | export type HotKeyAction = { 2 | keyCombo: string[]; 3 | action: (event: KeyboardEvent) => any; 4 | } -------------------------------------------------------------------------------- /src/data/ImportFormatData.ts: -------------------------------------------------------------------------------- 1 | import {LabelType} from "./enums/LabelType"; 2 | import {ILabelFormatData} from "../interfaces/ILabelFormatData"; 3 | import {AnnotationFormatType} from "./enums/AnnotationFormatType"; 4 | 5 | export type ImportFormatDataMap = Record 6 | 7 | export const ImportFormatData: ImportFormatDataMap = { 8 | "RECT": [ 9 | { 10 | type: AnnotationFormatType.COCO, 11 | label: "Single file in COCO JSON format." 12 | }, 13 | { 14 | type: AnnotationFormatType.YOLO, 15 | label: "Multiple files in YOLO format along with labels names definition - labels.txt file." 16 | } 17 | ], 18 | "POINT": [], 19 | "LINE": [], 20 | "POLYGON": [ 21 | { 22 | type: AnnotationFormatType.COCO, 23 | label: "Single file in COCO JSON format." 24 | } 25 | ], 26 | "IMAGE RECOGNITION": [] 27 | } -------------------------------------------------------------------------------- /src/data/ImporterSpecData.ts: -------------------------------------------------------------------------------- 1 | import {AnnotationFormatType} from "./enums/AnnotationFormatType"; 2 | import {AnnotationImporter} from "../logic/import/AnnotationImporter"; 3 | import {COCOImporter} from "../logic/import/coco/COCOImporter"; 4 | import {YOLOImporter} from "../logic/import/yolo/YOLOImporter"; 5 | 6 | export type ImporterSpecDataMap = { [s in AnnotationFormatType]: typeof AnnotationImporter; }; 7 | 8 | 9 | export const ImporterSpecData: ImporterSpecDataMap = { 10 | COCO: COCOImporter, 11 | CSV: undefined, 12 | JSON: undefined, 13 | VGG: undefined, 14 | VOC: undefined, 15 | YOLO: YOLOImporter 16 | } -------------------------------------------------------------------------------- /src/data/MobileDeviceData.ts: -------------------------------------------------------------------------------- 1 | export interface MobileDeviceData { 2 | manufacturer: string, 3 | browser: string, 4 | os: string 5 | } -------------------------------------------------------------------------------- /src/data/RectAnchor.ts: -------------------------------------------------------------------------------- 1 | import {IPoint} from "../interfaces/IPoint"; 2 | import {Direction} from "./enums/Direction"; 3 | 4 | export interface RectAnchor { 5 | type: Direction, 6 | position: IPoint 7 | } -------------------------------------------------------------------------------- /src/data/enums/AIModel.ts: -------------------------------------------------------------------------------- 1 | export enum AIModel { 2 | OBJECT_DETECTION = "OBJECT_DETECTION", 3 | POSE_DETECTION = "POSE_DETECTION" 4 | } -------------------------------------------------------------------------------- /src/data/enums/AcceptedFileType.ts: -------------------------------------------------------------------------------- 1 | export enum AcceptedFileType { 2 | IMAGE = 'image/jpeg, image/png', 3 | TEXT = 'text/plain', 4 | JSON = 'application/json' 5 | } -------------------------------------------------------------------------------- /src/data/enums/AnnotationFormatType.ts: -------------------------------------------------------------------------------- 1 | export enum AnnotationFormatType { 2 | YOLO = "YOLO", 3 | COCO = "COCO", 4 | CSV = "CSV", 5 | JSON = "JSON", 6 | VOC = "VOC", 7 | VGG = "VGG" 8 | } -------------------------------------------------------------------------------- /src/data/enums/ContextType.ts: -------------------------------------------------------------------------------- 1 | export enum ContextType { 2 | EDITOR = "EDITOR", 3 | LEFT_NAVBAR = "LEFT_NAVBAR", 4 | RIGHT_NAVBAR = "RIGHT_NAVBAR", 5 | POPUP = "POPUP", 6 | DROPDOWN = "DROPDOWN" 7 | } -------------------------------------------------------------------------------- /src/data/enums/CustomCursorStyle.ts: -------------------------------------------------------------------------------- 1 | export enum CustomCursorStyle { 2 | DEFAULT = "DEFAULT", 3 | MOVE = "MOVE", 4 | RESIZE = "RESIZE", 5 | ADD = "ADD", 6 | CANCEL = "CANCEL", 7 | CLOSE = "CLOSE", 8 | GRAB = "GRAB", 9 | GRABBING = "GRABBING" 10 | } -------------------------------------------------------------------------------- /src/data/enums/Direction.ts: -------------------------------------------------------------------------------- 1 | export enum Direction { 2 | TOP = "TOP", 3 | BOTTOM = "BOTTOM", 4 | LEFT = "LEFT", 5 | RIGHT = "RIGHT", 6 | TOP_RIGHT = "TOP_RIGHT", 7 | TOP_LEFT = "TOP_LEFT", 8 | BOTTOM_RIGHT = "BOTTOM_RIGHT", 9 | BOTTOM_LEFT = "BOTTOM_LEFT", 10 | CENTER = "CENTER" 11 | } -------------------------------------------------------------------------------- /src/data/enums/EventType.ts: -------------------------------------------------------------------------------- 1 | export enum EventType { 2 | RESIZE = "resize", 3 | MOUSE_UP = "mouseup", 4 | MOUSE_DOWN = "mousedown", 5 | MOUSE_MOVE = "mousemove", 6 | MOUSE_WHEEL = "wheel", 7 | KEY_DOWN = "keydown", 8 | KEY_PRESS = "keypress", 9 | KEY_UP = "keyup", 10 | FOCUS = "focus" 11 | } -------------------------------------------------------------------------------- /src/data/enums/LabelStatus.ts: -------------------------------------------------------------------------------- 1 | export enum LabelStatus { 2 | ACCEPTED = "ACCEPTED", 3 | REJECTED = "REJECTED", 4 | UNDECIDED = "UNDECIDED" 5 | } -------------------------------------------------------------------------------- /src/data/enums/LabelType.ts: -------------------------------------------------------------------------------- 1 | export enum LabelType { 2 | IMAGE_RECOGNITION = "IMAGE RECOGNITION", 3 | POINT = "POINT", 4 | RECT = "RECT", 5 | POLYGON = "POLYGON", 6 | LINE = "LINE" 7 | } -------------------------------------------------------------------------------- /src/data/enums/LineAnchorType.ts: -------------------------------------------------------------------------------- 1 | export enum LineAnchorType { 2 | START = "START", 3 | END = "END" 4 | } -------------------------------------------------------------------------------- /src/data/enums/PopupWindowType.ts: -------------------------------------------------------------------------------- 1 | export enum PopupWindowType { 2 | LOAD_LABEL_NAMES = "LOAD_LABEL_NAMES", 3 | UPDATE_LABEL = "UPDATE_LABEL", 4 | SUGGEST_LABEL_NAMES = "SUGGEST_LABEL_NAMES", 5 | IMPORT_IMAGES = "IMPORT_IMAGES", 6 | LOAD_AI_MODEL = "LOAD_AI_MODEL", 7 | EXPORT_ANNOTATIONS = "EXPORT_ANNOTATIONS", 8 | IMPORT_ANNOTATIONS = "IMPORT_ANNOTATIONS", 9 | INSERT_LABEL_NAMES = 'INSERT_LABEL_NAMES', 10 | EXIT_PROJECT = 'EXIT_PROJECT', 11 | LOADER = 'LOADER' 12 | } -------------------------------------------------------------------------------- /src/data/enums/ProjectType.ts: -------------------------------------------------------------------------------- 1 | export enum ProjectType { 2 | IMAGE_RECOGNITION = "IMAGE_RECOGNITION", 3 | OBJECT_DETECTION = "OBJECT_DETECTION" 4 | } -------------------------------------------------------------------------------- /src/data/info/EditorFeatureData.ts: -------------------------------------------------------------------------------- 1 | export interface IEditorFeature { 2 | displayText:string; 3 | imageSrc:string; 4 | imageAlt:string; 5 | } 6 | 7 | export const EditorFeatureData: IEditorFeature[] = [ 8 | { 9 | displayText: "Open source and free to use under GPLv3 license", 10 | imageSrc: "ico/open-source.png", 11 | imageAlt: "open-source", 12 | }, 13 | { 14 | displayText: "No advanced installation required, just open up your browser", 15 | imageSrc: "ico/online.png", 16 | imageAlt: "online", 17 | }, 18 | { 19 | displayText: "We don't store your images, because we don't send them anywhere", 20 | imageSrc: "ico/private.png", 21 | imageAlt: "private", 22 | }, 23 | { 24 | displayText: "Support multiple label types - rects, lines, points and polygons", 25 | imageSrc: "ico/labels.png", 26 | imageAlt: "labels", 27 | }, 28 | { 29 | displayText: "Support output file formats like YOLO, VOC XML, VGG JSON, CSV", 30 | imageSrc: "ico/file.png", 31 | imageAlt: "file", 32 | }, 33 | { 34 | displayText: "Use AI to make your work more productive", 35 | imageSrc: "ico/robot.png", 36 | imageAlt: "robot", 37 | }, 38 | ]; -------------------------------------------------------------------------------- /src/data/info/LabelToolkitData.ts: -------------------------------------------------------------------------------- 1 | import {LabelType} from "../enums/LabelType"; 2 | import {ProjectType} from "../enums/ProjectType"; 3 | 4 | export interface ILabelToolkit { 5 | labelType: LabelType; 6 | headerText: string; 7 | imageSrc: string; 8 | imageAlt: string; 9 | projectType: ProjectType; 10 | } 11 | 12 | export const LabelToolkitData: ILabelToolkit[] = [ 13 | { 14 | labelType: LabelType.IMAGE_RECOGNITION, 15 | headerText: "Image recognition", 16 | imageSrc: "ico/object.png", 17 | imageAlt: "object", 18 | projectType: ProjectType.IMAGE_RECOGNITION, 19 | }, 20 | { 21 | labelType: LabelType.RECT, 22 | headerText: "Rect", 23 | imageSrc: "ico/rectangle.png", 24 | imageAlt: "rectangle", 25 | projectType: ProjectType.OBJECT_DETECTION, 26 | }, 27 | { 28 | labelType: LabelType.POINT, 29 | headerText: "Point", 30 | imageSrc: "ico/point.png", 31 | imageAlt: "point", 32 | projectType: ProjectType.OBJECT_DETECTION, 33 | }, 34 | { 35 | labelType: LabelType.LINE, 36 | headerText: "Line", 37 | imageSrc: "ico/line.png", 38 | imageAlt: "line", 39 | projectType: ProjectType.OBJECT_DETECTION, 40 | }, 41 | { 42 | labelType: LabelType.POLYGON, 43 | headerText: "Polygon", 44 | imageSrc: "ico/polygon.png", 45 | imageAlt: "polygon", 46 | projectType: ProjectType.OBJECT_DETECTION, 47 | }, 48 | ]; -------------------------------------------------------------------------------- /src/data/info/SocialMediaData.ts: -------------------------------------------------------------------------------- 1 | import {Settings} from "../../settings/Settings"; 2 | 3 | export interface ISocialMedia { 4 | displayName:string; 5 | imageSrc:string; 6 | imageAlt:string; 7 | href:string; 8 | tooltipMessage:string; 9 | } 10 | 11 | export const SocialMediaData: ISocialMedia[] = [ 12 | { 13 | displayName: "Github", 14 | imageSrc: "/ico/github-logo.png", 15 | imageAlt: "GitHub Logo", 16 | href: Settings.GITHUB_URL, 17 | tooltipMessage: "Show me some love on GitHub", 18 | }, 19 | { 20 | displayName: "Medium", 21 | imageSrc: "/ico/medium-logo.png", 22 | imageAlt: "Medium Logo", 23 | href: Settings.MEDIUM_URL, 24 | tooltipMessage: "Read my AI content on Medium", 25 | }, 26 | { 27 | displayName: "Patreon", 28 | imageSrc: "/ico/patreon-logo.png", 29 | imageAlt: "Patreon Logo", 30 | href: Settings.PATREON_URL, 31 | tooltipMessage: "Support MakeSense on Patreon and help it grow" 32 | }, 33 | ]; -------------------------------------------------------------------------------- /src/data/labels/COCO.ts: -------------------------------------------------------------------------------- 1 | export type COCOSegmentation = number[][] 2 | export type COCOBBox = [number, number, number, number] 3 | 4 | export type COCOInfo = { 5 | description: string; 6 | } 7 | 8 | export type COCOImage = { 9 | id: number; 10 | width: number; 11 | height: number; 12 | file_name: string; 13 | } 14 | 15 | export type COCOCategory = { 16 | id: number; 17 | name: string; 18 | } 19 | 20 | export type COCOAnnotation = { 21 | id: number; 22 | category_id: number; 23 | iscrowd: number; 24 | segmentation: COCOSegmentation; 25 | image_id: number; 26 | area: number; 27 | bbox: COCOBBox; 28 | } 29 | 30 | export type COCOObject = { 31 | info: COCOInfo, 32 | images: COCOImage[], 33 | annotations: COCOAnnotation[], 34 | categories: COCOCategory[] 35 | } -------------------------------------------------------------------------------- /src/data/labels/VGG.ts: -------------------------------------------------------------------------------- 1 | export interface VGGShape {} 2 | 3 | export interface VGGRect extends VGGShape { 4 | name: string, 5 | x: number, 6 | y: number, 7 | width: number, 8 | height: number 9 | } 10 | 11 | export interface VGGPolygon extends VGGShape { 12 | name: string, 13 | all_points_x: number[], 14 | all_points_y: number[] 15 | } 16 | 17 | export interface VGGRegion { 18 | shape_attributes: VGGShape, 19 | region_attributes: { [key:string]:string; } 20 | } 21 | 22 | export type VGGRegionsData = { [key: string]: VGGRegion; } 23 | 24 | export type VGGFileData = { 25 | fileref: string; 26 | size: number; 27 | filename: string; 28 | base64_img_data: string; 29 | file_attributes: object; 30 | regions: VGGRegionsData; 31 | } 32 | 33 | export type VGGObject = { [key: string]: VGGFileData; } -------------------------------------------------------------------------------- /src/index.scss: -------------------------------------------------------------------------------- 1 | body { 2 | height: 100vh; 3 | width: 100vw; 4 | margin: 0; 5 | padding: 0; 6 | 7 | font-family: 'Saira Semi Condensed', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 8 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 9 | sans-serif; 10 | -webkit-font-smoothing: antialiased; 11 | -moz-osx-font-smoothing: grayscale; 12 | 13 | overflow-x: hidden; 14 | overflow-y: hidden; 15 | } 16 | 17 | #root { 18 | width: 100%; 19 | height: 100%; 20 | } 21 | 22 | code { 23 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 24 | monospace; 25 | } -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.scss'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | import configureStore from "./configureStore"; 7 | import {Provider} from "react-redux"; 8 | import {AppInitializer} from "./logic/initializer/AppInitializer"; 9 | 10 | export const store = configureStore(); 11 | AppInitializer.inti(); 12 | 13 | ReactDOM.render( 14 | ( 15 | 16 | ), 17 | document.getElementById('root') || document.createElement('div') // fix for testing purposes 18 | ); 19 | 20 | serviceWorker.unregister(); 21 | -------------------------------------------------------------------------------- /src/interfaces/ILabelFormatData.ts: -------------------------------------------------------------------------------- 1 | import {AnnotationFormatType} from "../data/enums/AnnotationFormatType"; 2 | 3 | export interface ILabelFormatData { 4 | type: AnnotationFormatType, 5 | label: string 6 | } -------------------------------------------------------------------------------- /src/interfaces/ILine.ts: -------------------------------------------------------------------------------- 1 | import {IPoint} from "./IPoint"; 2 | 3 | export interface ILine { 4 | start: IPoint, 5 | end: IPoint 6 | } -------------------------------------------------------------------------------- /src/interfaces/IPoint.ts: -------------------------------------------------------------------------------- 1 | export interface IPoint { 2 | x:number, 3 | y:number 4 | } -------------------------------------------------------------------------------- /src/interfaces/IRect.ts: -------------------------------------------------------------------------------- 1 | export interface IRect { 2 | x:number, 3 | y:number, 4 | height:number, 5 | width:number 6 | } -------------------------------------------------------------------------------- /src/interfaces/ISize.ts: -------------------------------------------------------------------------------- 1 | export interface ISize { 2 | width:number, 3 | height:number 4 | } -------------------------------------------------------------------------------- /src/logic/__tests__/actions/AIActions.test.ts: -------------------------------------------------------------------------------- 1 | import {AIActions} from "../../actions/AIActions"; 2 | 3 | describe('AIActions excludeRejectedLabelNames method', () => { 4 | it('should return list with correct values', () => { 5 | // GIVEN 6 | const suggestedLabels: string[] = [ 7 | "label_1", 8 | "label_2", 9 | "label_3", 10 | "label_4", 11 | ]; 12 | 13 | const rejectedLabels: string[] = [ 14 | "label_3", 15 | "label_4", 16 | "label_5", 17 | ]; 18 | 19 | // WHEN 20 | const excludedLabels: string[] = AIActions.excludeRejectedLabelNames(suggestedLabels, rejectedLabels); 21 | 22 | // THEN 23 | const expectedLabels: string[] = [ 24 | "label_1", 25 | "label_2", 26 | ]; 27 | expect(excludedLabels.toString()).toBe(expectedLabels.toString()); 28 | }); 29 | }); -------------------------------------------------------------------------------- /src/logic/__tests__/actions/AIObjectDetectionActions.test.ts: -------------------------------------------------------------------------------- 1 | import {LabelName} from "../../../store/labels/types"; 2 | import {DetectedObject} from "@tensorflow-models/coco-ssd"; 3 | import {AIObjectDetectionActions} from "../../actions/AIObjectDetectionActions"; 4 | 5 | describe('AIObjectDetectionActions extractNewSuggestedLabelNames method', () => { 6 | const mockLabelNames: LabelName[] = [ 7 | { 8 | id: "id_1", 9 | name: "label_1" 10 | }, 11 | { 12 | id: "id_2", 13 | name: "label_2" 14 | }, 15 | { 16 | id: "id_3", 17 | name: "label_3" 18 | } 19 | ]; 20 | 21 | it('should return list with correct values', () => { 22 | // GIVEN 23 | const labelNames: LabelName[] = mockLabelNames; 24 | const predictions: DetectedObject[] = [ 25 | { 26 | bbox: [1, 2, 3 , 4], 27 | class: "label_3", 28 | score: 0 29 | }, 30 | { 31 | bbox: [1, 2, 3 , 4], 32 | class: "label_4", 33 | score: 0 34 | }, 35 | { 36 | bbox: [1, 2, 3 , 4], 37 | class: "label_5", 38 | score: 0 39 | } 40 | ]; 41 | 42 | // WHEN 43 | const suggestedLabels: string[] = AIObjectDetectionActions.extractNewSuggestedLabelNames(labelNames, predictions); 44 | 45 | // THEN 46 | expect(suggestedLabels.toString()).toBe(["label_4", "label_5"].toString()); 47 | }); 48 | 49 | it('should return empty list', () => { 50 | // GIVEN 51 | const labelNames: LabelName[] = mockLabelNames; 52 | const predictions: DetectedObject[] = [ 53 | { 54 | bbox: [1, 2, 3 , 4], 55 | class: "label_3", 56 | score: 0 57 | }, 58 | { 59 | bbox: [1, 2, 3 , 4], 60 | class: "label_1", 61 | score: 0 62 | } 63 | ]; 64 | 65 | // WHEN 66 | const suggestedLabels: string[] = AIObjectDetectionActions.extractNewSuggestedLabelNames(labelNames, predictions); 67 | 68 | // THEN 69 | expect(suggestedLabels.toString()).toBe([].toString()); 70 | }); 71 | }); -------------------------------------------------------------------------------- /src/logic/__tests__/export/polygon/COCOExporter.test.ts: -------------------------------------------------------------------------------- 1 | import {COCOCategory, COCOInfo} from "../../../../data/labels/COCO"; 2 | import {COCOExporter} from "../../../export/polygon/COCOExporter"; 3 | import {LabelName} from "../../../../store/labels/types"; 4 | 5 | describe('COCOExporter produces correct COCO label', () => { 6 | it('should produce correct info component', () => { 7 | const givenDescription: string = "lorem ipsum"; 8 | const expectedCOCOInfo: COCOInfo = { 9 | "description": "lorem ipsum" 10 | } 11 | expect(COCOExporter.getInfoComponent(givenDescription)).toEqual(expectedCOCOInfo); 12 | }); 13 | 14 | it('should produce correct categories component', () => { 15 | const givenLabelNames: LabelName[] = [ 16 | { 17 | "id": "id_1", 18 | "name": "label_1" 19 | }, 20 | { 21 | "id": "id_2", 22 | "name": "label_2" 23 | }, 24 | { 25 | "id": "id_3", 26 | "name": "label_3" 27 | } 28 | ] 29 | const expectedCOCOCategories: COCOCategory[] = [ 30 | { 31 | "id": 1, 32 | "name": "label_1" 33 | }, 34 | { 35 | "id": 2, 36 | "name": "label_2" 37 | }, 38 | { 39 | "id": 3, 40 | "name": "label_3" 41 | } 42 | ] 43 | expect(COCOExporter.getCategoriesComponent(givenLabelNames)).toEqual(expectedCOCOCategories); 44 | }); 45 | }) -------------------------------------------------------------------------------- /src/logic/__tests__/import/coco/COCOUtils.tests.ts: -------------------------------------------------------------------------------- 1 | import {COCOUtils} from "../../../import/coco/COCOUtils"; 2 | import {isEqual} from "lodash"; 3 | 4 | describe('COCOUtils bbox2rect method', () => { 5 | it('should return valid IRect', () => { 6 | // given 7 | const x = 10, y = 20, width= 30, height = 40; 8 | const bbox: [number, number, number, number] = [x, y, width, height] 9 | 10 | // when 11 | const result = COCOUtils.bbox2rect(bbox); 12 | 13 | // then 14 | const expectedResult = { 15 | x: x, 16 | y: y, 17 | width: width, 18 | height: height 19 | } 20 | expect(result).toEqual(expectedResult); 21 | }); 22 | }); 23 | 24 | describe('COCOUtils segmentation2vertices method', () => { 25 | it('should return valid array of polygon vertices', () => { 26 | // given 27 | const p1x = 10, p1y = 20, p2x = 30, p2y = 40, p3x = 50, p3y = 60; 28 | const segmentation: number[][] = [[p1x, p1y, p2x, p2y, p3x, p3y]]; 29 | 30 | // when 31 | const result = COCOUtils.segmentation2vertices(segmentation); 32 | 33 | // then 34 | const expectedResult = [[ 35 | {x: p1x, y: p1y}, 36 | {x: p2x, y: p2y}, 37 | {x: p3x, y: p3y} 38 | ]] 39 | expect(isEqual(result, expectedResult)).toBe(true); 40 | }); 41 | }); -------------------------------------------------------------------------------- /src/logic/__tests__/import/yolo/YOLOImporter.test.ts: -------------------------------------------------------------------------------- 1 | import {ImageData} from "../../../../store/labels/types"; 2 | import {AcceptedFileType} from "../../../../data/enums/AcceptedFileType"; 3 | import uuidv4 from 'uuid/v4'; 4 | import {YOLOImporter} from "../../../import/yolo/YOLOImporter"; 5 | import {isEqual} from "lodash"; 6 | 7 | const getDummyImageData = (fileName: string): ImageData => { 8 | return { 9 | id: uuidv4(), 10 | fileData: new File([""], fileName, { type: AcceptedFileType.IMAGE }), 11 | loadStatus: true, 12 | labelRects: [], 13 | labelPoints: [], 14 | labelLines: [], 15 | labelPolygons: [], 16 | labelNameIds: [], 17 | isVisitedByObjectDetector: false, 18 | isVisitedByPoseDetector: false 19 | } 20 | } 21 | 22 | const getDummyFileData = (fileName: string): File => { 23 | return new File([""], fileName, { type: AcceptedFileType.TEXT }) 24 | } 25 | 26 | describe('YOLOImporter filterFilesData method', () => { 27 | it('should return correct fileData partition', () => { 28 | // given 29 | const imageFileNames = [ 30 | "00000.png", 31 | "00001.png", 32 | "00002.png", 33 | "00003.png", 34 | "00004.png" 35 | ] 36 | const imagesData: ImageData[] = imageFileNames.map((fileName: string) => getDummyImageData(fileName)); 37 | const annotationFileNames = [ 38 | "00002.txt", 39 | "00003.txt", 40 | "00004.txt", 41 | "00005.txt", 42 | "00006.txt" 43 | ] 44 | const annotationFiles: File[] = annotationFileNames.map((fileName: string) => getDummyFileData(fileName)); 45 | const labelFileNames = [ 46 | "labels.txt" 47 | ] 48 | const labelFiles : File[] = labelFileNames.map((fileName: string) => getDummyFileData(fileName)); 49 | const filesData: File[] = [...annotationFiles, ...labelFiles]; 50 | const expectedAnnotationFileNames = [ 51 | "00002.txt", 52 | "00003.txt", 53 | "00004.txt" 54 | ] 55 | 56 | // when 57 | const result = YOLOImporter.filterFilesData(filesData, imagesData); 58 | 59 | // then 60 | const resultNames = result.annotationFiles.map((item: File) => item.name); 61 | expect(result.labelNameFile.name).toEqual("labels.txt"); 62 | expect(result.annotationFiles.length).toEqual(3); 63 | expect(isEqual(resultNames, expectedAnnotationFileNames)).toBe(true); 64 | }); 65 | }); -------------------------------------------------------------------------------- /src/logic/actions/AIActions.ts: -------------------------------------------------------------------------------- 1 | import {LabelType} from "../../data/enums/LabelType"; 2 | import {LabelsSelector} from "../../store/selectors/LabelsSelector"; 3 | import {AIObjectDetectionActions} from "./AIObjectDetectionActions"; 4 | import {AIPoseDetectionActions} from "./AIPoseDetectionActions"; 5 | import {ImageData} from "../../store/labels/types"; 6 | 7 | export class AIActions { 8 | public static excludeRejectedLabelNames(suggestedLabels: string[], rejectedLabels: string[]): string[] { 9 | return suggestedLabels.reduce((acc: string[], label: string) => { 10 | if (!rejectedLabels.includes(label)) { 11 | acc.push(label) 12 | } 13 | return acc; 14 | }, []) 15 | } 16 | 17 | public static detect(imageId: string, image: HTMLImageElement): void { 18 | const activeLabelType: LabelType = LabelsSelector.getActiveLabelType(); 19 | 20 | switch (activeLabelType) { 21 | case LabelType.RECT: 22 | AIObjectDetectionActions.detectRects(imageId, image); 23 | break; 24 | case LabelType.POINT: 25 | AIPoseDetectionActions.detectPoses(imageId, image); 26 | break; 27 | } 28 | } 29 | 30 | public static rejectAllSuggestedLabels(imageData: ImageData) { 31 | const activeLabelType: LabelType = LabelsSelector.getActiveLabelType(); 32 | 33 | switch (activeLabelType) { 34 | case LabelType.RECT: 35 | AIObjectDetectionActions.rejectAllSuggestedRectLabels(imageData); 36 | break; 37 | case LabelType.POINT: 38 | AIPoseDetectionActions.rejectAllSuggestedPointLabels(imageData); 39 | break; 40 | } 41 | } 42 | 43 | public static acceptAllSuggestedLabels(imageData: ImageData) { 44 | const activeLabelType: LabelType = LabelsSelector.getActiveLabelType(); 45 | switch (activeLabelType) { 46 | case LabelType.RECT: 47 | AIObjectDetectionActions.acceptAllSuggestedRectLabels(imageData); 48 | break; 49 | case LabelType.POINT: 50 | AIPoseDetectionActions.acceptAllSuggestedPointLabels(imageData); 51 | break; 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /src/logic/actions/PopupActions.ts: -------------------------------------------------------------------------------- 1 | import {ContextManager} from "../context/ContextManager"; 2 | import {store} from "../../index"; 3 | import {updateActivePopupType} from "../../store/general/actionCreators"; 4 | 5 | export class PopupActions { 6 | public static close() { 7 | store.dispatch(updateActivePopupType(null)); 8 | ContextManager.restoreCtx(); 9 | } 10 | } -------------------------------------------------------------------------------- /src/logic/context/BaseContext.ts: -------------------------------------------------------------------------------- 1 | import {HotKeyAction} from "../../data/HotKeyAction"; 2 | 3 | export class BaseContext { 4 | public static actions: HotKeyAction[] = []; 5 | 6 | public static getActions(): HotKeyAction[] { 7 | return this.actions; 8 | } 9 | } -------------------------------------------------------------------------------- /src/logic/context/PopupContext.ts: -------------------------------------------------------------------------------- 1 | import {HotKeyAction} from "../../data/HotKeyAction"; 2 | import {PopupWindowType} from "../../data/enums/PopupWindowType"; 3 | import {GeneralSelector} from "../../store/selectors/GeneralSelector"; 4 | import {BaseContext} from "./BaseContext"; 5 | import {PopupActions} from "../actions/PopupActions"; 6 | import {Settings} from "../../settings/Settings"; 7 | 8 | export class PopupContext extends BaseContext { 9 | public static actions: HotKeyAction[] = [ 10 | { 11 | keyCombo: ["Escape"], 12 | action: (event: KeyboardEvent) => { 13 | const popupType: PopupWindowType = GeneralSelector.getActivePopupType(); 14 | const canBeClosed: boolean = Settings.CLOSEABLE_POPUPS.includes(popupType); 15 | if (canBeClosed) { 16 | PopupActions.close(); 17 | } 18 | } 19 | } 20 | ]; 21 | } -------------------------------------------------------------------------------- /src/logic/export/LineLabelExport.ts: -------------------------------------------------------------------------------- 1 | import {AnnotationFormatType} from "../../data/enums/AnnotationFormatType"; 2 | import {LabelsSelector} from "../../store/selectors/LabelsSelector"; 3 | import {ImageData, LabelLine, LabelName} from "../../store/labels/types"; 4 | import {ExporterUtil} from "../../utils/ExporterUtil"; 5 | import {ImageRepository} from "../imageRepository/ImageRepository"; 6 | import {findLast} from "lodash"; 7 | 8 | export class LineLabelsExporter { 9 | public static export(exportFormatType: AnnotationFormatType): void { 10 | switch (exportFormatType) { 11 | case AnnotationFormatType.CSV: 12 | LineLabelsExporter.exportAsCSV(); 13 | break; 14 | default: 15 | return; 16 | } 17 | } 18 | 19 | private static exportAsCSV(): void { 20 | const content: string = LabelsSelector.getImagesData() 21 | .map((imageData: ImageData) => { 22 | return LineLabelsExporter.wrapLineLabelsIntoCSV(imageData)}) 23 | .filter((imageLabelData: string) => { 24 | return !!imageLabelData}) 25 | .join("\n"); 26 | const fileName: string = `${ExporterUtil.getExportFileName()}.csv`; 27 | ExporterUtil.saveAs(content, fileName); 28 | } 29 | 30 | private static wrapLineLabelsIntoCSV(imageData: ImageData): string { 31 | if (imageData.labelLines.length === 0 || !imageData.loadStatus) 32 | return null; 33 | 34 | const image: HTMLImageElement = ImageRepository.getById(imageData.id); 35 | const labelNames: LabelName[] = LabelsSelector.getLabelNames(); 36 | const labelLinesString: string[] = imageData.labelLines.map((labelLine: LabelLine) => { 37 | const labelName: LabelName = findLast(labelNames, {id: labelLine.labelId}); 38 | const labelFields = !!labelName ? [ 39 | labelName.name, 40 | Math.round(labelLine.line.start.x).toString(), 41 | Math.round(labelLine.line.start.y).toString(), 42 | Math.round(labelLine.line.end.x).toString(), 43 | Math.round(labelLine.line.end.y).toString(), 44 | imageData.fileData.name, 45 | image.width.toString(), 46 | image.height.toString() 47 | ] : []; 48 | return labelFields.join(",") 49 | }); 50 | return labelLinesString.join("\n"); 51 | } 52 | } -------------------------------------------------------------------------------- /src/logic/export/PointLabelsExport.ts: -------------------------------------------------------------------------------- 1 | import {AnnotationFormatType} from "../../data/enums/AnnotationFormatType"; 2 | import {ImageData, LabelName, LabelPoint} from "../../store/labels/types"; 3 | import {ImageRepository} from "../imageRepository/ImageRepository"; 4 | import {LabelsSelector} from "../../store/selectors/LabelsSelector"; 5 | import {ExporterUtil} from "../../utils/ExporterUtil"; 6 | import {findLast} from "lodash"; 7 | 8 | export class PointLabelsExporter { 9 | public static export(exportFormatType: AnnotationFormatType): void { 10 | switch (exportFormatType) { 11 | case AnnotationFormatType.CSV: 12 | PointLabelsExporter.exportAsCSV(); 13 | break; 14 | default: 15 | return; 16 | } 17 | } 18 | 19 | private static exportAsCSV(): void { 20 | const content: string = LabelsSelector.getImagesData() 21 | .map((imageData: ImageData) => { 22 | return PointLabelsExporter.wrapRectLabelsIntoCSV(imageData)}) 23 | .filter((imageLabelData: string) => { 24 | return !!imageLabelData}) 25 | .join("\n"); 26 | const fileName: string = `${ExporterUtil.getExportFileName()}.csv`; 27 | ExporterUtil.saveAs(content, fileName); 28 | } 29 | 30 | private static wrapRectLabelsIntoCSV(imageData: ImageData): string { 31 | if (imageData.labelPoints.length === 0 || !imageData.loadStatus) 32 | return null; 33 | 34 | const image: HTMLImageElement = ImageRepository.getById(imageData.id); 35 | const labelNames: LabelName[] = LabelsSelector.getLabelNames(); 36 | const labelRectsString: string[] = imageData.labelPoints.map((labelPoint: LabelPoint) => { 37 | const labelName: LabelName = findLast(labelNames, {id: labelPoint.labelId}); 38 | const labelFields = !!labelName ? [ 39 | labelName.name, 40 | Math.round(labelPoint.point.x).toString(), 41 | Math.round(labelPoint.point.y).toString(), 42 | imageData.fileData.name, 43 | image.width.toString(), 44 | image.height.toString() 45 | ] : []; 46 | return labelFields.join(",") 47 | }); 48 | return labelRectsString.join("\n"); 49 | } 50 | } -------------------------------------------------------------------------------- /src/logic/export/polygon/PolygonLabelsExporter.ts: -------------------------------------------------------------------------------- 1 | import {AnnotationFormatType} from "../../../data/enums/AnnotationFormatType"; 2 | import {VGGExporter} from "./VGGExporter"; 3 | import {COCOExporter} from "./COCOExporter"; 4 | 5 | export class PolygonLabelsExporter { 6 | public static export(exportFormatType: AnnotationFormatType): void { 7 | switch (exportFormatType) { 8 | case AnnotationFormatType.VGG: 9 | VGGExporter.export(); 10 | break; 11 | case AnnotationFormatType.COCO: 12 | COCOExporter.export(); 13 | break; 14 | default: 15 | return; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/logic/helpers/CSSHelper.ts: -------------------------------------------------------------------------------- 1 | import {Settings} from "../../settings/Settings"; 2 | import {AISelector} from "../../store/selectors/AISelector"; 3 | 4 | export class CSSHelper { 5 | public static getLeadingColor(): string { 6 | return AISelector.isAIObjectDetectorModelLoaded() || 7 | AISelector.isAIPoseDetectorModelLoaded() ? Settings.PRIMARY_COLOR : Settings.SECONDARY_COLOR; 8 | } 9 | } -------------------------------------------------------------------------------- /src/logic/helpers/ViewPortHelper.ts: -------------------------------------------------------------------------------- 1 | import {EditorData} from "../../data/EditorData"; 2 | import {MouseEventUtil} from "../../utils/MouseEventUtil"; 3 | import {EventType} from "../../data/enums/EventType"; 4 | import {store} from "../../index"; 5 | import {updateCustomCursorStyle} from "../../store/general/actionCreators"; 6 | import {CustomCursorStyle} from "../../data/enums/CustomCursorStyle"; 7 | import {EditorModel} from "../../staticModels/EditorModel"; 8 | import {IPoint} from "../../interfaces/IPoint"; 9 | import {PointUtil} from "../../utils/PointUtil"; 10 | import {ViewPortActions} from "../actions/ViewPortActions"; 11 | 12 | export class ViewPortHelper { 13 | private startScrollPosition: IPoint; 14 | private mouseStartPosition: IPoint; 15 | 16 | public update(data: EditorData): void { 17 | if (!!data.event) { 18 | switch (MouseEventUtil.getEventType(data.event)) { 19 | case EventType.MOUSE_MOVE: 20 | this.mouseMoveHandler(data); 21 | break; 22 | case EventType.MOUSE_UP: 23 | this.mouseUpHandler(data); 24 | break; 25 | case EventType.MOUSE_DOWN: 26 | this.mouseDownHandler(data); 27 | break; 28 | default: 29 | break; 30 | } 31 | } 32 | } 33 | 34 | private mouseDownHandler(data: EditorData) { 35 | const event = data.event as MouseEvent; 36 | this.startScrollPosition = data.absoluteViewPortContentScrollPosition; 37 | this.mouseStartPosition = {x: event.screenX, y: event.screenY}; 38 | 39 | store.dispatch(updateCustomCursorStyle(CustomCursorStyle.GRABBING)); 40 | EditorModel.canvas.style.cursor = "none"; 41 | } 42 | 43 | private mouseUpHandler(data: EditorData) { 44 | this.startScrollPosition = null; 45 | this.mouseStartPosition = null; 46 | store.dispatch(updateCustomCursorStyle(CustomCursorStyle.GRAB)); 47 | EditorModel.canvas.style.cursor = "none"; 48 | } 49 | 50 | private mouseMoveHandler(data: EditorData) { 51 | if (!!this.startScrollPosition && !!this.mouseStartPosition) { 52 | const event = data.event as MouseEvent; 53 | const currentMousePosition: IPoint = {x: event.screenX, y: event.screenY}; 54 | const mousePositionDelta: IPoint = PointUtil.subtract(currentMousePosition, this.mouseStartPosition); 55 | const nextScrollPosition = PointUtil.subtract(this.startScrollPosition, mousePositionDelta); 56 | ViewPortActions.setScrollPosition(nextScrollPosition); 57 | store.dispatch(updateCustomCursorStyle(CustomCursorStyle.GRABBING)); 58 | } else { 59 | store.dispatch(updateCustomCursorStyle(CustomCursorStyle.GRAB)); 60 | } 61 | EditorModel.canvas.style.cursor = "none"; 62 | } 63 | } -------------------------------------------------------------------------------- /src/logic/imageRepository/ImageLoadManager.ts: -------------------------------------------------------------------------------- 1 | export class ImageLoadManager { 2 | 3 | private static queue: (() => Promise)[] = []; 4 | private static isRunning: boolean = false; 5 | 6 | public static add(fx: Promise) { 7 | ImageLoadManager.queue.push(async () => await fx); 8 | } 9 | 10 | public static run() { 11 | setTimeout(() => ImageLoadManager.runQueue(), 10); 12 | } 13 | 14 | public static addAndRun(fx: Promise) { 15 | ImageLoadManager.add(fx); 16 | ImageLoadManager.run(); 17 | } 18 | 19 | public static async runQueue() { 20 | if (!ImageLoadManager.isRunning) { 21 | ImageLoadManager.isRunning = true; 22 | await ImageLoadManager.runTasks(); 23 | ImageLoadManager.isRunning = false; 24 | } 25 | } 26 | 27 | private static async runTasks() { 28 | while (ImageLoadManager.queue.length > 0) { 29 | const fx = ImageLoadManager.queue.shift(); 30 | await fx(); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/logic/imageRepository/ImageRepository.ts: -------------------------------------------------------------------------------- 1 | import {zip} from "lodash"; 2 | 3 | export type ImageMap = { [s: string]: HTMLImageElement; }; 4 | 5 | export class ImageRepository { 6 | private static repository: ImageMap = {}; 7 | 8 | public static storeImage(id: string, image: HTMLImageElement) { 9 | ImageRepository.repository[id] = image; 10 | } 11 | 12 | public static storeImages(ids: string[], images: HTMLImageElement[]) { 13 | zip(ids, images).forEach((pair: [string, HTMLImageElement]) => { 14 | ImageRepository.storeImage(...pair); 15 | }) 16 | } 17 | 18 | public static getById(uuid: string): HTMLImageElement { 19 | return ImageRepository.repository[uuid]; 20 | } 21 | } -------------------------------------------------------------------------------- /src/logic/import/AnnotationImporter.ts: -------------------------------------------------------------------------------- 1 | import {ImageData, LabelName} from "../../store/labels/types"; 2 | import {LabelType} from "../../data/enums/LabelType"; 3 | 4 | export type ImportResult = { 5 | imagesData: ImageData[] 6 | labelNames: LabelName[] 7 | } 8 | 9 | export class AnnotationImporter { 10 | public labelType: LabelType[] 11 | 12 | constructor(labelType: LabelType[]) { 13 | this.labelType = labelType; 14 | } 15 | 16 | public import( 17 | filesData: File[], 18 | onSuccess: (imagesData: ImageData[], labelNames: LabelName[]) => any, 19 | onFailure: (error?:Error) => any 20 | ): void { 21 | throw new Error("Method not implemented."); 22 | } 23 | } -------------------------------------------------------------------------------- /src/logic/import/coco/COCOErrors.ts: -------------------------------------------------------------------------------- 1 | export class COCOAnnotationsLoadingError extends Error { 2 | constructor(message) { 3 | super(message); 4 | this.name = "COCOAnnotationsLoadingError"; 5 | } 6 | } 7 | 8 | export class COCOFormatValidationError extends COCOAnnotationsLoadingError { 9 | constructor(message) { 10 | super(message); 11 | this.name = "COCOFormatValidationError"; 12 | } 13 | } 14 | 15 | export class COCOAnnotationReadingError extends COCOAnnotationsLoadingError { 16 | constructor() { 17 | super("Unexpected error occurred during reading annotations from file"); 18 | this.name = "COCOAnnotationReadingError"; 19 | } 20 | } 21 | 22 | export class COCOAnnotationDeserializationError extends COCOAnnotationsLoadingError { 23 | constructor() { 24 | super("COCO annotation file need to be in JSON format"); 25 | this.name = "COCOAnnotationDeserializationError"; 26 | } 27 | } 28 | 29 | export class COCOAnnotationFileCountError extends COCOAnnotationsLoadingError { 30 | constructor() { 31 | super("COCO annotation requires single file but multiple were given"); 32 | this.name = "COCOAnnotationFileCountError"; 33 | } 34 | } -------------------------------------------------------------------------------- /src/logic/import/coco/COCOUtils.ts: -------------------------------------------------------------------------------- 1 | import {COCOBBox, COCOSegmentation} from "../../../data/labels/COCO"; 2 | import {IRect} from "../../../interfaces/IRect"; 3 | import {IPoint} from "../../../interfaces/IPoint"; 4 | import {chunk} from "lodash"; 5 | 6 | export class COCOUtils { 7 | public static bbox2rect(bbox: COCOBBox): IRect { 8 | return { 9 | x: bbox[0], 10 | y: bbox[1], 11 | width: bbox[2], 12 | height: bbox[3] 13 | } 14 | } 15 | 16 | public static segmentation2vertices(segmentation: COCOSegmentation): IPoint[][] { 17 | return segmentation.map((segment: number[]) => { 18 | return chunk(segment, 2).map((pair: number[]) => { 19 | return {x: pair[0], y: pair[1]} 20 | }) 21 | }) 22 | } 23 | } -------------------------------------------------------------------------------- /src/logic/import/yolo/YOLOErrors.ts: -------------------------------------------------------------------------------- 1 | export class YOLOAnnotationsLoadingError extends Error { 2 | constructor(message) { 3 | super(message); 4 | this.name = "YOLOAnnotationsLoadingError"; 5 | } 6 | } 7 | 8 | export class YOLOLabelsReadingError extends YOLOAnnotationsLoadingError { 9 | constructor() { 10 | super("Unexpected error occurred during reading label names from labels.txt file"); 11 | this.name = "YOLOLabelsLoadingError"; 12 | } 13 | } 14 | 15 | export class NoLabelNamesFileProvidedError extends YOLOAnnotationsLoadingError { 16 | constructor() { 17 | super("For YOLO labels to be loaded correctly, labels.txt file is required"); 18 | this.name = "NoLabelNamesFileProvidedError"; 19 | } 20 | } 21 | 22 | export class LabelNamesNotUniqueError extends YOLOAnnotationsLoadingError { 23 | constructor() { 24 | super("Label names listed in labels.txt file should be unique"); 25 | this.name = "LabelNamesNotUniqueError"; 26 | } 27 | } 28 | 29 | export class AnnotationsParsingError extends YOLOAnnotationsLoadingError { 30 | constructor(imageName: string) { 31 | super(`Unexpected error occurred during parsing of ${imageName} annotations file`); 32 | this.name = "AnnotationsParsingError"; 33 | } 34 | } -------------------------------------------------------------------------------- /src/logic/initializer/AppInitializer.ts: -------------------------------------------------------------------------------- 1 | import {updateWindowSize} from "../../store/general/actionCreators"; 2 | import {ContextManager} from "../context/ContextManager"; 3 | import {store} from "../../index"; 4 | import {PlatformUtil} from "../../utils/PlatformUtil"; 5 | import {PlatformModel} from "../../staticModels/PlatformModel"; 6 | import {EventType} from "../../data/enums/EventType"; 7 | import {GeneralSelector} from "../../store/selectors/GeneralSelector"; 8 | import {EnvironmentUtil} from "../../utils/EnvironmentUtil"; 9 | 10 | export class AppInitializer { 11 | public static inti():void { 12 | AppInitializer.handleResize(); 13 | AppInitializer.detectDeviceParams(); 14 | AppInitializer.handleAccidentalPageExit(); 15 | window.addEventListener(EventType.RESIZE, AppInitializer.handleResize); 16 | window.addEventListener(EventType.MOUSE_WHEEL, AppInitializer.disableGenericScrollZoom,{passive:false}); 17 | window.addEventListener(EventType.KEY_DOWN, AppInitializer.disableUnwantedKeyBoardBehaviour); 18 | window.addEventListener(EventType.KEY_PRESS, AppInitializer.disableUnwantedKeyBoardBehaviour); 19 | ContextManager.init(); 20 | } 21 | 22 | private static handleAccidentalPageExit = () => { 23 | window.onbeforeunload = (event) => { 24 | const projectType = GeneralSelector.getProjectType(); 25 | if (projectType != null && EnvironmentUtil.isProd()) { 26 | event.preventDefault(); 27 | event.returnValue = ''; 28 | } 29 | } 30 | }; 31 | 32 | private static handleResize = () => { 33 | store.dispatch(updateWindowSize({ 34 | width: window.innerWidth, 35 | height: window.innerHeight 36 | })); 37 | }; 38 | 39 | private static disableUnwantedKeyBoardBehaviour = (event: KeyboardEvent) => { 40 | if (PlatformModel.isMac && event.metaKey) { 41 | event.preventDefault(); 42 | } 43 | 44 | if (["=", "+", "-"].includes(event.key)) { 45 | if (event.ctrlKey || (PlatformModel.isMac && event.metaKey)) { 46 | event.preventDefault(); 47 | } 48 | } 49 | }; 50 | 51 | private static disableGenericScrollZoom = (event: MouseEvent) => { 52 | if (event.ctrlKey || (PlatformModel.isMac && event.metaKey)) { 53 | event.preventDefault(); 54 | } 55 | }; 56 | 57 | private static detectDeviceParams = () => { 58 | const userAgent: string = window.navigator.userAgent; 59 | PlatformModel.mobileDeviceData = PlatformUtil.getMobileDeviceData(userAgent); 60 | PlatformModel.isMac = PlatformUtil.isMac(userAgent); 61 | PlatformModel.isSafari = PlatformUtil.isSafari(userAgent); 62 | PlatformModel.isFirefox = PlatformUtil.isFirefox(userAgent); 63 | }; 64 | } -------------------------------------------------------------------------------- /src/logic/render/BaseRenderEngine.ts: -------------------------------------------------------------------------------- 1 | import {EditorData} from "../../data/EditorData"; 2 | import {MouseEventUtil} from "../../utils/MouseEventUtil"; 3 | import {EventType} from "../../data/enums/EventType"; 4 | import {LabelType} from "../../data/enums/LabelType"; 5 | 6 | export abstract class BaseRenderEngine { 7 | protected readonly canvas: HTMLCanvasElement; 8 | public labelType: LabelType; 9 | 10 | protected constructor(canvas: HTMLCanvasElement) { 11 | this.canvas = canvas; 12 | } 13 | 14 | public update(data: EditorData): void { 15 | if (!!data.event) { 16 | switch (MouseEventUtil.getEventType(data.event)) { 17 | case EventType.MOUSE_MOVE: 18 | this.mouseMoveHandler(data); 19 | break; 20 | case EventType.MOUSE_UP: 21 | this.mouseUpHandler(data); 22 | break; 23 | case EventType.MOUSE_DOWN: 24 | this.mouseDownHandler(data); 25 | break; 26 | default: 27 | break; 28 | } 29 | } 30 | } 31 | 32 | protected abstract mouseDownHandler(data: EditorData): void; 33 | protected abstract mouseMoveHandler(data: EditorData): void; 34 | protected abstract mouseUpHandler(data: EditorData): void; 35 | 36 | abstract render(data: EditorData): void; 37 | 38 | abstract isInProgress(): boolean; 39 | } -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/settings/RenderEngineConfig.ts: -------------------------------------------------------------------------------- 1 | import {ISize} from "../interfaces/ISize"; 2 | import {Settings} from "./Settings"; 3 | 4 | export class RenderEngineConfig { 5 | public readonly lineThickness: number = 2; 6 | public readonly lineActiveColor: string = Settings.PRIMARY_COLOR; 7 | public readonly lineInactiveColor: string = "#fff"; 8 | public readonly crossHairLineColor: string = "#fff"; 9 | public readonly crossHairPadding: number = 25; 10 | public readonly anchorSize: ISize = { 11 | width: Settings.RESIZE_HANDLE_DIMENSION_PX, 12 | height: Settings.RESIZE_HANDLE_DIMENSION_PX 13 | }; 14 | public readonly anchorHoverSize: ISize = { 15 | width: Settings.RESIZE_HANDLE_HOVER_DIMENSION_PX, 16 | height: Settings.RESIZE_HANDLE_HOVER_DIMENSION_PX 17 | }; 18 | public readonly suggestedAnchorDetectionSize: ISize = { 19 | width: 100, 20 | height: 100 21 | }; 22 | public readonly activeAnchorColor: string = Settings.SECONDARY_COLOR; 23 | public readonly inactiveAnchorColor: string = Settings.DARK_THEME_SECOND_COLOR; 24 | } -------------------------------------------------------------------------------- /src/settings/Settings.ts: -------------------------------------------------------------------------------- 1 | import {PopupWindowType} from "../data/enums/PopupWindowType"; 2 | 3 | export class Settings { 4 | public static readonly GITHUB_URL: string = "https://github.com/SkalskiP"; 5 | public static readonly MEDIUM_URL: string = "https://medium.com/@piotr.skalski92"; 6 | public static readonly PATREON_URL: string = "https://www.patreon.com/make_sense"; 7 | 8 | public static readonly TOP_NAVIGATION_BAR_HEIGHT_PX: number = 35; 9 | public static readonly EDITOR_BOTTOM_NAVIGATION_BAR_HEIGHT_PX: number = 40 + 1; 10 | public static readonly EDITOR_TOP_NAVIGATION_BAR_HEIGHT_PX: number = 40 + 1; 11 | public static readonly SIDE_NAVIGATION_BAR_WIDTH_CLOSED_PX: number = 23 + 1; 12 | public static readonly SIDE_NAVIGATION_BAR_WIDTH_OPEN_PX: number = Settings.SIDE_NAVIGATION_BAR_WIDTH_CLOSED_PX + 300 + 1; 13 | public static readonly TOOLKIT_TAB_HEIGHT_PX: number = 40; 14 | public static readonly TOOLBOX_PANEL_WIDTH_PX: number = 50 + 1; 15 | 16 | public static readonly EDITOR_MIN_WIDTH: number = 900; 17 | public static readonly EDITOR_MIN_HEIGHT: number = 500; 18 | 19 | public static readonly PRIMARY_COLOR: string = "#2af598"; 20 | public static readonly SECONDARY_COLOR: string = "#009efd"; 21 | 22 | public static readonly DARK_THEME_FIRST_COLOR: string = "#171717"; 23 | public static readonly DARK_THEME_SECOND_COLOR: string = "#282828"; 24 | public static readonly DARK_THEME_THIRD_COLOR: string = "#4c4c4c"; 25 | public static readonly DARK_THEME_FORTH_COLOR: string = "#262c2f"; 26 | 27 | public static readonly CROSS_HAIR_THICKNESS_PX: number = 1; 28 | public static readonly CROSS_HAIR_COLOR: string = "#fff"; 29 | 30 | public static readonly RESIZE_HANDLE_DIMENSION_PX: number = 8; 31 | public static readonly RESIZE_HANDLE_HOVER_DIMENSION_PX = 16; 32 | 33 | public static readonly CLOSEABLE_POPUPS: PopupWindowType[] = [ 34 | PopupWindowType.IMPORT_IMAGES, 35 | PopupWindowType.EXPORT_ANNOTATIONS, 36 | PopupWindowType.IMPORT_ANNOTATIONS, 37 | PopupWindowType.EXIT_PROJECT, 38 | PopupWindowType.UPDATE_LABEL 39 | ]; 40 | } -------------------------------------------------------------------------------- /src/settings/ViewPointSettings.ts: -------------------------------------------------------------------------------- 1 | export class ViewPointSettings { 2 | public static readonly CANVAS_MIN_MARGIN_PX: number = 20; 3 | public static readonly MIN_ZOOM: number = 1; 4 | public static readonly MAX_ZOOM: number = 4; 5 | public static readonly ZOOM_STEP: number = 0.1; 6 | public static readonly TRANSLATION_STEP_PX: number = 20; 7 | } -------------------------------------------------------------------------------- /src/settings/_Settings.scss: -------------------------------------------------------------------------------- 1 | $darkThemeFirstColor: #171717; 2 | $darkThemeSecondColor: #282828; 3 | $darkThemeThirdColor: #4c4c4c; 4 | $darkThemeForthColor: #262c2f; 5 | 6 | $primaryColor: #2af598; 7 | $secondaryColor: #009efd; 8 | 9 | $topNavigationBarHeight: 35px; 10 | $stateBarHeight: 2px; 11 | $sideNavigationBarCompanionWidth: 23px; 12 | $sideNavigationBarContentWidth: 300px; 13 | $editorBottomNavigationBarHeight: 40px; 14 | $editorTopNavigationBarHeight: 40px; 15 | $toolboxWidth: 50px; -------------------------------------------------------------------------------- /src/staticModels/EditorModel.ts: -------------------------------------------------------------------------------- 1 | import {PrimaryEditorRenderEngine} from "../logic/render/PrimaryEditorRenderEngine"; 2 | import {BaseRenderEngine} from "../logic/render/BaseRenderEngine"; 3 | import {IRect} from "../interfaces/IRect"; 4 | import {IPoint} from "../interfaces/IPoint"; 5 | import {ISize} from "../interfaces/ISize"; 6 | import Scrollbars from "react-custom-scrollbars"; 7 | import {ViewPortHelper} from "../logic/helpers/ViewPortHelper"; 8 | 9 | export class EditorModel { 10 | public static editor: HTMLDivElement; 11 | public static canvas: HTMLCanvasElement; 12 | public static mousePositionIndicator: HTMLDivElement; 13 | public static cursor: HTMLDivElement; 14 | public static viewPortScrollbars: Scrollbars; 15 | public static image: HTMLImageElement; 16 | 17 | public static primaryRenderingEngine: PrimaryEditorRenderEngine; 18 | public static supportRenderingEngine: BaseRenderEngine; 19 | 20 | public static viewPortHelper: ViewPortHelper; 21 | 22 | public static isLoading: boolean = false; 23 | public static viewPortActionsDisabled: boolean = false; 24 | public static mousePositionOnViewPortContent: IPoint; 25 | public static viewPortSize: ISize; 26 | 27 | // x and y describe the dimension of the margin that remains constant regardless of the scale of the image 28 | // width and height describes the render image size for 100% scale 29 | public static defaultRenderImageRect: IRect; 30 | } -------------------------------------------------------------------------------- /src/staticModels/PlatformModel.ts: -------------------------------------------------------------------------------- 1 | import {MobileDeviceData} from "../data/MobileDeviceData"; 2 | 3 | export class PlatformModel { 4 | public static mobileDeviceData: MobileDeviceData; 5 | public static isMac: boolean; 6 | public static isSafari: boolean; 7 | public static isFirefox: boolean; 8 | } -------------------------------------------------------------------------------- /src/store/Actions.ts: -------------------------------------------------------------------------------- 1 | export enum Action { 2 | // AI 3 | UPDATE_SUGGESTED_LABEL_LIST = "@@UPDATE_SUGGESTED_LABEL_LIST", 4 | UPDATE_REJECTED_SUGGESTED_LABEL_LIST = "@@UPDATE_REJECTED_SUGGESTED_LABEL_LIST", 5 | UPDATE_OBJECT_DETECTOR_STATUS = '@@UPDATE_OBJECT_DETECTOR_STATUS', 6 | UPDATE_POSE_DETECTOR_STATUS = '@@UPDATE_POSE_DETECTOR_STATUS', 7 | UPDATE_DISABLED_AI_FLAG = "@@UPDATE_DISABLED_AI_FLAG", 8 | 9 | // GENERAL 10 | UPDATE_PROJECT_DATA = '@@UPDATE_PROJECT_DATA', 11 | UPDATE_WINDOW_SIZE = '@@UPDATE_WINDOW_SIZE', 12 | UPDATE_ACTIVE_POPUP_TYPE = '@@UPDATE_ACTIVE_POPUP_TYPE', 13 | UPDATE_CUSTOM_CURSOR_STYLE = '@@UPDATE_CUSTOM_CURSOR_STYLE', 14 | UPDATE_CONTEXT = '@@UPDATE_CONTEXT', 15 | UPDATE_PREVENT_CUSTOM_CURSOR_STATUS = '@@UPDATE_PREVENT_CUSTOM_CURSOR_STATUS', 16 | UPDATE_IMAGE_DRAG_MODE_STATUS = '@@UPDATE_IMAGE_DRAG_MODE_STATUS', 17 | UPDATE_CROSS_HAIR_VISIBLE_STATUS = '@@UPDATE_CROSS_HAIR_VISIBLE_STATUS', 18 | UPDATE_ZOOM = '@@UPDATE_ZOOM', 19 | 20 | // LABELS 21 | UPDATE_ACTIVE_IMAGE_INDEX = '@@UPDATE_ACTIVE_IMAGE_INDEX', 22 | UPDATE_IMAGE_DATA_BY_ID = '@@UPDATE_IMAGE_DATA_BY_ID', 23 | ADD_IMAGES_DATA = '@@ADD_IMAGES_DATA', 24 | UPDATE_IMAGES_DATA = '@@UPDATE_IMAGES_DATA', 25 | UPDATE_ACTIVE_LABEL_NAME_ID = '@@UPDATE_ACTIVE_LABEL_NAME_ID', 26 | UPDATE_ACTIVE_LABEL_TYPE = '@@UPDATE_ACTIVE_LABEL_TYPE', 27 | UPDATE_ACTIVE_LABEL_ID = '@@UPDATE_ACTIVE_LABEL_ID', 28 | UPDATE_HIGHLIGHTED_LABEL_ID = '@@UPDATE_HIGHLIGHTED_LABEL_ID', 29 | UPDATE_LABEL_NAMES = '@@UPDATE_LABEL_NAMES', 30 | UPDATE_FIRST_LABEL_CREATED_FLAG = '@@UPDATE_FIRST_LABEL_CREATED_FLAG', 31 | } -------------------------------------------------------------------------------- /src/store/ai/actionCreators.ts: -------------------------------------------------------------------------------- 1 | import {Action} from "../Actions"; 2 | import {AIActionTypes} from "./types"; 3 | 4 | export function updateSuggestedLabelList(labelList: string[]): AIActionTypes { 5 | return { 6 | type: Action.UPDATE_SUGGESTED_LABEL_LIST, 7 | payload: { 8 | labelList, 9 | } 10 | } 11 | } 12 | 13 | export function updateRejectedSuggestedLabelList(labelList: string[]): AIActionTypes { 14 | return { 15 | type: Action.UPDATE_REJECTED_SUGGESTED_LABEL_LIST, 16 | payload: { 17 | labelList, 18 | } 19 | } 20 | } 21 | 22 | export function updateObjectDetectorStatus(isObjectDetectorLoaded: boolean): AIActionTypes { 23 | return { 24 | type: Action.UPDATE_OBJECT_DETECTOR_STATUS, 25 | payload: { 26 | isObjectDetectorLoaded, 27 | } 28 | } 29 | } 30 | 31 | export function updatePoseDetectorStatus(isPoseDetectorLoaded: boolean): AIActionTypes { 32 | return { 33 | type: Action.UPDATE_POSE_DETECTOR_STATUS, 34 | payload: { 35 | isPoseDetectorLoaded, 36 | } 37 | } 38 | } 39 | 40 | export function updateDisabledAIFlag(isAIDisabled: boolean): AIActionTypes { 41 | return { 42 | type: Action.UPDATE_DISABLED_AI_FLAG, 43 | payload: { 44 | isAIDisabled, 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /src/store/ai/reducer.ts: -------------------------------------------------------------------------------- 1 | import {AIActionTypes, AIState} from "./types"; 2 | import {Action} from "../Actions"; 3 | 4 | const initialState: AIState = { 5 | suggestedLabelList: [], 6 | rejectedSuggestedLabelList: [], 7 | isObjectDetectorLoaded: false, 8 | isPoseDetectorLoaded: false, 9 | isAIDisabled: false 10 | }; 11 | 12 | export function aiReducer( 13 | state = initialState, 14 | action: AIActionTypes 15 | ): AIState { 16 | switch (action.type) { 17 | case Action.UPDATE_SUGGESTED_LABEL_LIST: { 18 | return { 19 | ...state, 20 | suggestedLabelList: action.payload.labelList 21 | } 22 | } 23 | case Action.UPDATE_REJECTED_SUGGESTED_LABEL_LIST: { 24 | return { 25 | ...state, 26 | rejectedSuggestedLabelList: action.payload.labelList 27 | } 28 | } 29 | case Action.UPDATE_OBJECT_DETECTOR_STATUS: { 30 | return { 31 | ...state, 32 | isObjectDetectorLoaded: action.payload.isObjectDetectorLoaded 33 | } 34 | } 35 | case Action.UPDATE_POSE_DETECTOR_STATUS: { 36 | return { 37 | ...state, 38 | isPoseDetectorLoaded: action.payload.isPoseDetectorLoaded 39 | } 40 | } 41 | case Action.UPDATE_DISABLED_AI_FLAG: { 42 | return { 43 | ...state, 44 | isAIDisabled: action.payload.isAIDisabled 45 | } 46 | } 47 | default: 48 | return state; 49 | } 50 | } -------------------------------------------------------------------------------- /src/store/ai/types.ts: -------------------------------------------------------------------------------- 1 | import {Action} from "../Actions"; 2 | 3 | export type AIState = { 4 | // SSD 5 | isObjectDetectorLoaded: boolean; 6 | 7 | // POSE NET 8 | isPoseDetectorLoaded: boolean; 9 | 10 | // GENERAL 11 | suggestedLabelList: string[]; 12 | rejectedSuggestedLabelList: string[]; 13 | isAIDisabled: boolean; 14 | } 15 | 16 | interface UpdateSuggestedLabelList { 17 | type: typeof Action.UPDATE_SUGGESTED_LABEL_LIST; 18 | payload: { 19 | labelList: string[]; 20 | } 21 | } 22 | 23 | interface UpdateRejectedSuggestedLabelList { 24 | type: typeof Action.UPDATE_REJECTED_SUGGESTED_LABEL_LIST; 25 | payload: { 26 | labelList: string[]; 27 | } 28 | } 29 | 30 | interface UpdateObjectDetectorStatus { 31 | type: typeof Action.UPDATE_OBJECT_DETECTOR_STATUS; 32 | payload: { 33 | isObjectDetectorLoaded: boolean; 34 | } 35 | } 36 | 37 | interface UpdatePoseDetectorStatus { 38 | type: typeof Action.UPDATE_POSE_DETECTOR_STATUS; 39 | payload: { 40 | isPoseDetectorLoaded: boolean; 41 | } 42 | } 43 | 44 | interface UpdateDisabledAIFlag { 45 | type: typeof Action.UPDATE_DISABLED_AI_FLAG; 46 | payload: { 47 | isAIDisabled: boolean; 48 | } 49 | } 50 | 51 | export type AIActionTypes = UpdateSuggestedLabelList 52 | | UpdateRejectedSuggestedLabelList 53 | | UpdateObjectDetectorStatus 54 | | UpdatePoseDetectorStatus 55 | | UpdateDisabledAIFlag -------------------------------------------------------------------------------- /src/store/general/actionCreators.ts: -------------------------------------------------------------------------------- 1 | import {ISize} from "../../interfaces/ISize"; 2 | import {GeneralActionTypes, ProjectData} from "./types"; 3 | import {Action} from "../Actions"; 4 | import {PopupWindowType} from "../../data/enums/PopupWindowType"; 5 | import {CustomCursorStyle} from "../../data/enums/CustomCursorStyle"; 6 | import {ContextType} from "../../data/enums/ContextType"; 7 | 8 | export function updateWindowSize(windowSize: ISize): GeneralActionTypes { 9 | return { 10 | type: Action.UPDATE_WINDOW_SIZE, 11 | payload: { 12 | windowSize, 13 | }, 14 | }; 15 | } 16 | 17 | export function updateActivePopupType(activePopupType: PopupWindowType): GeneralActionTypes { 18 | return { 19 | type: Action.UPDATE_ACTIVE_POPUP_TYPE, 20 | payload: { 21 | activePopupType, 22 | } 23 | } 24 | } 25 | 26 | export function updateCustomCursorStyle(customCursorStyle: CustomCursorStyle): GeneralActionTypes { 27 | return { 28 | type: Action.UPDATE_CUSTOM_CURSOR_STYLE, 29 | payload: { 30 | customCursorStyle, 31 | } 32 | } 33 | } 34 | 35 | export function updateActiveContext(activeContext: ContextType): GeneralActionTypes { 36 | return { 37 | type: Action.UPDATE_CONTEXT, 38 | payload: { 39 | activeContext, 40 | }, 41 | }; 42 | } 43 | 44 | export function updatePreventCustomCursorStatus(preventCustomCursor: boolean): GeneralActionTypes { 45 | return { 46 | type: Action.UPDATE_PREVENT_CUSTOM_CURSOR_STATUS, 47 | payload: { 48 | preventCustomCursor, 49 | }, 50 | }; 51 | } 52 | 53 | export function updateImageDragModeStatus(imageDragMode: boolean): GeneralActionTypes { 54 | return { 55 | type: Action.UPDATE_IMAGE_DRAG_MODE_STATUS, 56 | payload: { 57 | imageDragMode, 58 | }, 59 | }; 60 | } 61 | 62 | export function updateCrossHairVisibleStatus(crossHairVisible: boolean): GeneralActionTypes { 63 | return { 64 | type: Action.UPDATE_CROSS_HAIR_VISIBLE_STATUS, 65 | payload: { 66 | crossHairVisible, 67 | }, 68 | }; 69 | } 70 | 71 | export function updateProjectData(projectData: ProjectData): GeneralActionTypes { 72 | return { 73 | type: Action.UPDATE_PROJECT_DATA, 74 | payload: { 75 | projectData, 76 | }, 77 | }; 78 | } 79 | 80 | export function updateZoom(zoom: number): GeneralActionTypes { 81 | return { 82 | type: Action.UPDATE_ZOOM, 83 | payload: { 84 | zoom, 85 | }, 86 | }; 87 | } -------------------------------------------------------------------------------- /src/store/general/reducer.ts: -------------------------------------------------------------------------------- 1 | import {GeneralActionTypes, GeneralState} from "./types"; 2 | import {Action} from "../Actions"; 3 | import {CustomCursorStyle} from "../../data/enums/CustomCursorStyle"; 4 | import {ViewPointSettings} from "../../settings/ViewPointSettings"; 5 | 6 | const initialState: GeneralState = { 7 | windowSize: null, 8 | activePopupType: null, 9 | customCursorStyle: CustomCursorStyle.DEFAULT, 10 | activeContext: null, 11 | preventCustomCursor: false, 12 | imageDragMode: false, 13 | crossHairVisible: true, 14 | projectData: { 15 | type: null, 16 | name: "my-project-name", 17 | }, 18 | zoom: ViewPointSettings.MIN_ZOOM 19 | }; 20 | 21 | export function generalReducer( 22 | state = initialState, 23 | action: GeneralActionTypes 24 | ): GeneralState { 25 | switch (action.type) { 26 | case Action.UPDATE_WINDOW_SIZE: { 27 | return { 28 | ...state, 29 | windowSize: action.payload.windowSize 30 | } 31 | } 32 | case Action.UPDATE_ACTIVE_POPUP_TYPE: { 33 | return { 34 | ...state, 35 | activePopupType: action.payload.activePopupType 36 | } 37 | } 38 | case Action.UPDATE_CUSTOM_CURSOR_STYLE: { 39 | return { 40 | ...state, 41 | customCursorStyle: action.payload.customCursorStyle 42 | } 43 | } 44 | case Action.UPDATE_CONTEXT: { 45 | return { 46 | ...state, 47 | activeContext: action.payload.activeContext 48 | } 49 | } 50 | case Action.UPDATE_PREVENT_CUSTOM_CURSOR_STATUS: { 51 | return { 52 | ...state, 53 | preventCustomCursor: action.payload.preventCustomCursor 54 | } 55 | } 56 | case Action.UPDATE_IMAGE_DRAG_MODE_STATUS: { 57 | return { 58 | ...state, 59 | imageDragMode: action.payload.imageDragMode 60 | } 61 | } 62 | case Action.UPDATE_CROSS_HAIR_VISIBLE_STATUS: { 63 | return { 64 | ...state, 65 | crossHairVisible: action.payload.crossHairVisible 66 | } 67 | } 68 | case Action.UPDATE_PROJECT_DATA: { 69 | return { 70 | ...state, 71 | projectData: action.payload.projectData 72 | } 73 | } 74 | case Action.UPDATE_ZOOM: { 75 | return { 76 | ...state, 77 | zoom: action.payload.zoom 78 | } 79 | } 80 | default: 81 | return state; 82 | } 83 | } -------------------------------------------------------------------------------- /src/store/general/types.ts: -------------------------------------------------------------------------------- 1 | import {ISize} from "../../interfaces/ISize"; 2 | import {Action} from "../Actions"; 3 | import {PopupWindowType} from "../../data/enums/PopupWindowType"; 4 | import {CustomCursorStyle} from "../../data/enums/CustomCursorStyle"; 5 | import {ContextType} from "../../data/enums/ContextType"; 6 | import {ProjectType} from "../../data/enums/ProjectType"; 7 | 8 | export type ProjectData = { 9 | type: ProjectType; 10 | name: string, 11 | } 12 | 13 | export type GeneralState = { 14 | windowSize: ISize; 15 | activePopupType: PopupWindowType; 16 | customCursorStyle: CustomCursorStyle; 17 | preventCustomCursor: boolean; 18 | imageDragMode: boolean; 19 | crossHairVisible: boolean; 20 | activeContext: ContextType; 21 | projectData: ProjectData; 22 | zoom: number; 23 | } 24 | 25 | interface UpdateProjectData { 26 | type: typeof Action.UPDATE_PROJECT_DATA; 27 | payload: { 28 | projectData: ProjectData; 29 | } 30 | } 31 | 32 | interface UpdateWindowSize { 33 | type: typeof Action.UPDATE_WINDOW_SIZE; 34 | payload: { 35 | windowSize: ISize; 36 | } 37 | } 38 | 39 | interface UpdateActivePopupType { 40 | type: typeof Action.UPDATE_ACTIVE_POPUP_TYPE; 41 | payload: { 42 | activePopupType: PopupWindowType; 43 | } 44 | } 45 | 46 | interface UpdateCustomCursorStyle { 47 | type: typeof Action.UPDATE_CUSTOM_CURSOR_STYLE; 48 | payload: { 49 | customCursorStyle: CustomCursorStyle; 50 | } 51 | } 52 | 53 | interface UpdateActiveContext { 54 | type: typeof Action.UPDATE_CONTEXT; 55 | payload: { 56 | activeContext: ContextType; 57 | } 58 | } 59 | 60 | interface UpdatePreventCustomCursorStatus { 61 | type: typeof Action.UPDATE_PREVENT_CUSTOM_CURSOR_STATUS; 62 | payload: { 63 | preventCustomCursor: boolean; 64 | } 65 | } 66 | 67 | interface UpdateImageDragModeStatus { 68 | type: typeof Action.UPDATE_IMAGE_DRAG_MODE_STATUS; 69 | payload: { 70 | imageDragMode: boolean; 71 | } 72 | } 73 | 74 | interface UpdateCrossHairVisibleStatus { 75 | type: typeof Action.UPDATE_CROSS_HAIR_VISIBLE_STATUS; 76 | payload: { 77 | crossHairVisible: boolean; 78 | } 79 | } 80 | 81 | interface UpdateZoom { 82 | type: typeof Action.UPDATE_ZOOM, 83 | payload: { 84 | zoom: number; 85 | } 86 | } 87 | 88 | export type GeneralActionTypes = UpdateProjectData 89 | | UpdateWindowSize 90 | | UpdateActivePopupType 91 | | UpdateCustomCursorStyle 92 | | UpdateActiveContext 93 | | UpdatePreventCustomCursorStatus 94 | | UpdateImageDragModeStatus 95 | | UpdateCrossHairVisibleStatus 96 | | UpdateZoom -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import {labelsReducer} from "./labels/reducer"; 3 | import {generalReducer} from "./general/reducer"; 4 | import {aiReducer} from "./ai/reducer"; 5 | 6 | export const rootReducer = combineReducers({ 7 | general: generalReducer, 8 | labels: labelsReducer, 9 | ai: aiReducer 10 | }); 11 | 12 | export type AppState = ReturnType; -------------------------------------------------------------------------------- /src/store/labels/actionCreators.ts: -------------------------------------------------------------------------------- 1 | import {LabelsActionTypes, ImageData, LabelName} from "./types"; 2 | import {Action} from "../Actions"; 3 | import {LabelType} from "../../data/enums/LabelType"; 4 | 5 | export function updateActiveImageIndex(activeImageIndex: number): LabelsActionTypes { 6 | return { 7 | type: Action.UPDATE_ACTIVE_IMAGE_INDEX, 8 | payload: { 9 | activeImageIndex, 10 | }, 11 | }; 12 | } 13 | 14 | export function updateActiveLabelNameId(activeLabelNameId: string): LabelsActionTypes { 15 | return { 16 | type: Action.UPDATE_ACTIVE_LABEL_NAME_ID, 17 | payload: { 18 | activeLabelNameId, 19 | }, 20 | }; 21 | } 22 | 23 | export function updateActiveLabelId(activeLabelId: string): LabelsActionTypes { 24 | return { 25 | type: Action.UPDATE_ACTIVE_LABEL_ID, 26 | payload: { 27 | activeLabelId, 28 | }, 29 | }; 30 | } 31 | 32 | export function updateHighlightedLabelId(highlightedLabelId: string): LabelsActionTypes { 33 | return { 34 | type: Action.UPDATE_HIGHLIGHTED_LABEL_ID, 35 | payload: { 36 | highlightedLabelId, 37 | }, 38 | }; 39 | } 40 | 41 | export function updateActiveLabelType(activeLabelType: LabelType): LabelsActionTypes { 42 | return { 43 | type: Action.UPDATE_ACTIVE_LABEL_TYPE, 44 | payload: { 45 | activeLabelType, 46 | }, 47 | }; 48 | } 49 | 50 | export function updateImageDataById(id: string, newImageData: ImageData): LabelsActionTypes { 51 | return { 52 | type: Action.UPDATE_IMAGE_DATA_BY_ID, 53 | payload: { 54 | id, 55 | newImageData 56 | }, 57 | }; 58 | } 59 | 60 | export function addImageData(imageData: ImageData[]): LabelsActionTypes { 61 | return { 62 | type: Action.ADD_IMAGES_DATA, 63 | payload: { 64 | imageData, 65 | }, 66 | }; 67 | } 68 | 69 | export function updateImageData(imageData: ImageData[]): LabelsActionTypes { 70 | return { 71 | type: Action.UPDATE_IMAGES_DATA, 72 | payload: { 73 | imageData, 74 | }, 75 | }; 76 | } 77 | 78 | export function updateLabelNames(labels: LabelName[]) { 79 | return { 80 | type: Action.UPDATE_LABEL_NAMES, 81 | payload: { 82 | labels 83 | } 84 | } 85 | } 86 | 87 | export function updateFirstLabelCreatedFlag(firstLabelCreatedFlag: boolean) { 88 | return { 89 | type: Action.UPDATE_FIRST_LABEL_CREATED_FLAG, 90 | payload: { 91 | firstLabelCreatedFlag 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /src/store/labels/reducer.ts: -------------------------------------------------------------------------------- 1 | import {LabelsActionTypes, LabelsState, ImageData} from "./types"; 2 | import {Action} from "../Actions"; 3 | 4 | const initialState: LabelsState = { 5 | activeImageIndex: null, 6 | activeLabelNameId: null, 7 | activeLabelType: null, 8 | activeLabelId: null, 9 | highlightedLabelId: null, 10 | imagesData: [], 11 | firstLabelCreatedFlag: false, 12 | labels: [] 13 | }; 14 | 15 | export function labelsReducer( 16 | state = initialState, 17 | action: LabelsActionTypes 18 | ): LabelsState { 19 | switch (action.type) { 20 | case Action.UPDATE_ACTIVE_IMAGE_INDEX: { 21 | return { 22 | ...state, 23 | activeImageIndex: action.payload.activeImageIndex 24 | } 25 | } 26 | case Action.UPDATE_ACTIVE_LABEL_NAME_ID: { 27 | return { 28 | ...state, 29 | activeLabelNameId: action.payload.activeLabelNameId 30 | } 31 | } 32 | case Action.UPDATE_ACTIVE_LABEL_ID: { 33 | return { 34 | ...state, 35 | activeLabelId: action.payload.activeLabelId 36 | } 37 | } 38 | case Action.UPDATE_HIGHLIGHTED_LABEL_ID: { 39 | return { 40 | ...state, 41 | highlightedLabelId: action.payload.highlightedLabelId 42 | } 43 | } 44 | case Action.UPDATE_ACTIVE_LABEL_TYPE: { 45 | return { 46 | ...state, 47 | activeLabelType: action.payload.activeLabelType 48 | } 49 | } 50 | case Action.UPDATE_IMAGE_DATA_BY_ID: { 51 | return { 52 | ...state, 53 | imagesData: state.imagesData.map((imageData: ImageData) => 54 | imageData.id === action.payload.id ? action.payload.newImageData : imageData 55 | ) 56 | } 57 | } 58 | case Action.ADD_IMAGES_DATA: { 59 | return { 60 | ...state, 61 | imagesData: state.imagesData.concat(action.payload.imageData) 62 | } 63 | } 64 | case Action.UPDATE_IMAGES_DATA: { 65 | return { 66 | ...state, 67 | imagesData: action.payload.imageData 68 | } 69 | } 70 | case Action.UPDATE_LABEL_NAMES: { 71 | return { 72 | ...state, 73 | labels: action.payload.labels 74 | } 75 | } 76 | case Action.UPDATE_FIRST_LABEL_CREATED_FLAG: { 77 | return { 78 | ...state, 79 | firstLabelCreatedFlag: action.payload.firstLabelCreatedFlag 80 | } 81 | } 82 | default: 83 | return state; 84 | } 85 | } -------------------------------------------------------------------------------- /src/store/selectors/AISelector.ts: -------------------------------------------------------------------------------- 1 | import {store} from "../.."; 2 | 3 | export class AISelector { 4 | public static getSuggestedLabelList(): string[] { 5 | return store.getState().ai.suggestedLabelList; 6 | } 7 | 8 | public static getRejectedSuggestedLabelList(): string[] { 9 | return store.getState().ai.rejectedSuggestedLabelList; 10 | } 11 | 12 | public static isAIObjectDetectorModelLoaded(): boolean { 13 | return store.getState().ai.isObjectDetectorLoaded; 14 | } 15 | 16 | public static isAIPoseDetectorModelLoaded(): boolean { 17 | return store.getState().ai.isPoseDetectorLoaded; 18 | } 19 | 20 | public static isAIDisabled(): boolean { 21 | return store.getState().ai.isAIDisabled; 22 | } 23 | } -------------------------------------------------------------------------------- /src/store/selectors/GeneralSelector.ts: -------------------------------------------------------------------------------- 1 | import {store} from "../.."; 2 | import {PopupWindowType} from "../../data/enums/PopupWindowType"; 3 | import {ContextType} from "../../data/enums/ContextType"; 4 | import {CustomCursorStyle} from "../../data/enums/CustomCursorStyle"; 5 | import {ProjectType} from "../../data/enums/ProjectType"; 6 | 7 | export class GeneralSelector { 8 | public static getActivePopupType(): PopupWindowType { 9 | return store.getState().general.activePopupType; 10 | } 11 | 12 | public static getActiveContext(): ContextType { 13 | return store.getState().general.activeContext; 14 | } 15 | 16 | public static getPreventCustomCursorStatus(): boolean { 17 | return store.getState().general.preventCustomCursor; 18 | } 19 | 20 | public static getImageDragModeStatus(): boolean { 21 | return store.getState().general.imageDragMode; 22 | } 23 | 24 | public static getCrossHairVisibleStatus(): boolean { 25 | return store.getState().general.crossHairVisible; 26 | } 27 | 28 | public static getCustomCursorStyle(): CustomCursorStyle { 29 | return store.getState().general.customCursorStyle; 30 | } 31 | 32 | public static getProjectName(): string { 33 | return store.getState().general.projectData.name; 34 | } 35 | 36 | public static getProjectType(): ProjectType { 37 | return store.getState().general.projectData.type; 38 | } 39 | 40 | public static getZoom(): number { 41 | return store.getState().general.zoom; 42 | } 43 | } -------------------------------------------------------------------------------- /src/utils/ArrayUtil.ts: -------------------------------------------------------------------------------- 1 | export type PartitionResult = { 2 | pass: T[] 3 | fail: T[] 4 | } 5 | 6 | export class ArrayUtilAmbiguousMatchError extends Error { 7 | constructor() { 8 | super("Given predicate results in more than one value being matched."); 9 | this.name = "ArrayUtilAmbiguousMatchError"; 10 | } 11 | } 12 | 13 | export class ArrayUtil { 14 | public static partition(array: T[], predicate: (T) => boolean): PartitionResult { 15 | return array.reduce((acc: PartitionResult, item: T) => { 16 | if (predicate(item)) 17 | acc.pass.push(item) 18 | else 19 | acc.fail.push(item) 20 | return acc 21 | }, {pass: [], fail: []}) 22 | } 23 | 24 | public static match(array1: T[], array2: P[], predicate: (key: T, value: P) => boolean): [T, P][] { 25 | return array1.reduce((acc: [T, P][], key: T) => { 26 | const match = array2.filter((value: P) => predicate(key, value)) 27 | if (match.length === 1) { 28 | acc.push([key, match[0]]) 29 | } else if (match.length > 1) { 30 | throw new ArrayUtilAmbiguousMatchError() 31 | } 32 | return acc 33 | }, []) 34 | } 35 | 36 | public static unzip(array: [T, P][]): [T[], P[]] { 37 | return array.reduce((acc: [T[], P[]], i: [T, P]) => { 38 | acc[0].push(i[0]); 39 | acc[1].push(i[1]); 40 | return acc; 41 | }, [[], []]) 42 | } 43 | } -------------------------------------------------------------------------------- /src/utils/CanvasUtil.ts: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {IPoint} from "../interfaces/IPoint"; 3 | import {IRect} from "../interfaces/IRect"; 4 | import {ISize} from "../interfaces/ISize"; 5 | 6 | export class CanvasUtil { 7 | public static getMousePositionOnCanvasFromEvent(event: React.MouseEvent | MouseEvent, canvas: HTMLCanvasElement): IPoint { 8 | if (!!canvas && !!event) { 9 | const canvasRect: ClientRect | DOMRect = canvas.getBoundingClientRect(); 10 | return { 11 | x: event.clientX - canvasRect.left, 12 | y: event.clientY - canvasRect.top 13 | } 14 | } 15 | return null; 16 | } 17 | 18 | public static getClientRect(canvas: HTMLCanvasElement): IRect { 19 | if (!!canvas) { 20 | const canvasRect: ClientRect | DOMRect = canvas.getBoundingClientRect(); 21 | return { 22 | x: canvasRect.left, 23 | y: canvasRect.top, 24 | width: canvasRect.width, 25 | height: canvasRect.height 26 | } 27 | } 28 | return null; 29 | } 30 | 31 | public static getSize(canvas: HTMLCanvasElement): ISize { 32 | if (!!canvas) { 33 | const canvasRect: ClientRect | DOMRect = canvas.getBoundingClientRect(); 34 | return { 35 | width: canvasRect.width, 36 | height: canvasRect.height 37 | } 38 | } 39 | return null; 40 | } 41 | } -------------------------------------------------------------------------------- /src/utils/DirectionUtil.ts: -------------------------------------------------------------------------------- 1 | import {Direction} from "../data/enums/Direction"; 2 | import {IPoint} from "../interfaces/IPoint"; 3 | 4 | export class DirectionUtil { 5 | public static convertDirectionToVector(direction: Direction): IPoint { 6 | switch (direction) { 7 | case Direction.RIGHT: 8 | return {x: 1, y: 0}; 9 | case Direction.LEFT: 10 | return {x: -1, y: 0}; 11 | case Direction.TOP: 12 | return {x: 0, y: 1}; 13 | case Direction.BOTTOM: 14 | return {x: 0, y: -1}; 15 | case Direction.TOP_RIGHT: 16 | return {x: 1, y: 1}; 17 | case Direction.TOP_LEFT: 18 | return {x: -1, y: 1}; 19 | case Direction.BOTTOM_RIGHT: 20 | return {x: 1, y: -1}; 21 | case Direction.BOTTOM_LEFT: 22 | return {x: -1, y: -1}; 23 | case Direction.CENTER: 24 | return {x: 0, y: 0}; 25 | default: 26 | return null; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/utils/EditorUtil.ts: -------------------------------------------------------------------------------- 1 | import {CustomCursorStyle} from "../data/enums/CustomCursorStyle"; 2 | import classNames from "classnames"; 3 | 4 | export class EditorUtil { 5 | public static getIndicator = (cursorStyle: CustomCursorStyle): string => { 6 | switch (cursorStyle) { 7 | case CustomCursorStyle.ADD: 8 | return "ico/plus.png"; 9 | case CustomCursorStyle.RESIZE: 10 | return "ico/resize.png"; 11 | case CustomCursorStyle.CLOSE: 12 | return "ico/close.png"; 13 | case CustomCursorStyle.MOVE: 14 | return "ico/move.png"; 15 | case CustomCursorStyle.CANCEL: 16 | return "ico/cancel.png"; 17 | case CustomCursorStyle.GRAB: 18 | return "ico/hand-fill.png"; 19 | case CustomCursorStyle.GRABBING: 20 | return "ico/hand-fill-grab.png"; 21 | default: 22 | return null; 23 | } 24 | }; 25 | 26 | public static getCursorStyle = (cursorStyle: CustomCursorStyle) => { 27 | return classNames( 28 | "Cursor", { 29 | "move": cursorStyle === CustomCursorStyle.MOVE, 30 | "add": cursorStyle === CustomCursorStyle.ADD, 31 | "resize": cursorStyle === CustomCursorStyle.RESIZE, 32 | "close": cursorStyle === CustomCursorStyle.CLOSE, 33 | "cancel": cursorStyle === CustomCursorStyle.CANCEL, 34 | "grab": cursorStyle === CustomCursorStyle.GRAB, 35 | "grabbing": cursorStyle === CustomCursorStyle.GRABBING 36 | } 37 | ); 38 | }; 39 | } -------------------------------------------------------------------------------- /src/utils/EnvironmentUtil.ts: -------------------------------------------------------------------------------- 1 | export class EnvironmentUtil { 2 | public static isDev(): boolean { 3 | return process.env.NODE_ENV === 'development'; 4 | } 5 | 6 | public static isProd(): boolean { 7 | return process.env.NODE_ENV === 'production'; 8 | } 9 | } -------------------------------------------------------------------------------- /src/utils/ExporterUtil.ts: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | import {GeneralSelector} from "../store/selectors/GeneralSelector"; 3 | import {saveAs} from "file-saver"; 4 | 5 | export class ExporterUtil { 6 | public static getExportFileName(): string { 7 | const projectName: string = GeneralSelector.getProjectName(); 8 | const date: string = moment().format('YYYY-MM-DD-hh-mm-ss'); 9 | return `labels_${projectName}_${date}` 10 | } 11 | 12 | public static saveAs(content: string, fileName: string): void { 13 | const blob = new Blob([content], {type: "text/plain;charset=utf-8"}); 14 | try { 15 | saveAs(blob, fileName); 16 | } catch (error) { 17 | //TODO: Implement file save error handling 18 | throw new Error(error); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/utils/FileUtil.ts: -------------------------------------------------------------------------------- 1 | export class FileUtil { 2 | public static loadImage(fileData: File): Promise { 3 | return new Promise((resolve, reject) => { 4 | const url = URL.createObjectURL(fileData); 5 | const image = new Image(); 6 | image.src = url; 7 | image.onload = () => resolve(image); 8 | image.onerror = reject; 9 | }) 10 | } 11 | 12 | public static loadImages(fileData: File[]): Promise { 13 | return new Promise((resolve, reject) => { 14 | const promises: Promise[] = fileData.map((fileData: File) => FileUtil.loadImage(fileData)) 15 | Promise 16 | .all(promises) 17 | .then((values: HTMLImageElement[]) => resolve(values)) 18 | .catch((error) => reject(error)); 19 | }); 20 | } 21 | 22 | public static readFile(fileData: File): Promise { 23 | return new Promise((resolve, reject) => { 24 | let reader = new FileReader(); 25 | reader.onloadend = (event: any) => { 26 | resolve(event.target.result); 27 | }; 28 | reader.onerror = reject; 29 | reader.readAsText(fileData); 30 | }) 31 | } 32 | 33 | public static readFiles(fileData: File[]): Promise { 34 | return new Promise((resolve, reject) => { 35 | const promises: Promise[] = fileData.map((fileData: File) => FileUtil.readFile(fileData)) 36 | Promise 37 | .all(promises) 38 | .then((values: string[]) => resolve(values)) 39 | .catch((error) => reject(error)); 40 | }); 41 | } 42 | 43 | public static extractFileExtension(name: string): string | null { 44 | const parts = name.split("."); 45 | return parts.length > 1 ? parts[1] : null; 46 | } 47 | 48 | public static extractFileName(name: string): string | null { 49 | return name.split(".")[0]; 50 | } 51 | } -------------------------------------------------------------------------------- /src/utils/ImageDataUtil.ts: -------------------------------------------------------------------------------- 1 | import {ImageData} from "../store/labels/types"; 2 | import uuidv4 from "uuid/v4"; 3 | import {FileUtil} from "./FileUtil"; 4 | import {ImageRepository} from "../logic/imageRepository/ImageRepository"; 5 | 6 | export class ImageDataUtil { 7 | public static createImageDataFromFileData(fileData: File): ImageData { 8 | return { 9 | id: uuidv4(), 10 | fileData: fileData, 11 | loadStatus: false, 12 | labelRects: [], 13 | labelPoints: [], 14 | labelLines: [], 15 | labelPolygons: [], 16 | labelNameIds: [], 17 | isVisitedByObjectDetector: false, 18 | isVisitedByPoseDetector: false 19 | } 20 | } 21 | 22 | public static cleanAnnotations(item: ImageData): ImageData { 23 | return { 24 | ...item, 25 | labelRects: [], 26 | labelPoints: [], 27 | labelLines: [], 28 | labelPolygons: [], 29 | labelNameIds: [] 30 | } 31 | } 32 | 33 | public static arrange(items: ImageData[], idArrangement: string[]): ImageData[] { 34 | return items.sort((a: ImageData, b: ImageData) => { 35 | return idArrangement.indexOf(a.id) - idArrangement.indexOf(b.id) 36 | }) 37 | } 38 | 39 | public static loadMissingImages(images: ImageData[]): Promise { 40 | return new Promise((resolve, reject) => { 41 | const missingImages = images.filter((i: ImageData) => !i.loadStatus); 42 | const missingImagesFiles = missingImages.map((i: ImageData) => i.fileData); 43 | FileUtil.loadImages(missingImagesFiles) 44 | .then((images:HTMLImageElement[]) => { 45 | ImageRepository.storeImages(missingImages.map((i: ImageData) => i.id), images); 46 | resolve() 47 | }) 48 | .catch((error: Error) => reject(error)); 49 | }); 50 | } 51 | } -------------------------------------------------------------------------------- /src/utils/ImageUtil.ts: -------------------------------------------------------------------------------- 1 | import {ISize} from "../interfaces/ISize"; 2 | 3 | export class ImageUtil { 4 | public static getSize(image: HTMLImageElement): ISize { 5 | if (!image) return null; 6 | return { 7 | width: image.width, 8 | height: image.height 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /src/utils/LabelUtil.ts: -------------------------------------------------------------------------------- 1 | import {LabelName, LabelPolygon, LabelRect} from "../store/labels/types"; 2 | import uuidv4 from 'uuid/v4'; 3 | import {find} from "lodash"; 4 | import {IRect} from "../interfaces/IRect"; 5 | import {LabelStatus} from "../data/enums/LabelStatus"; 6 | import {IPoint} from "../interfaces/IPoint"; 7 | 8 | export class LabelUtil { 9 | public static createLabelName(name: string): LabelName { 10 | return { 11 | id: uuidv4(), 12 | name: name 13 | } 14 | } 15 | 16 | public static createLabelRect(labelId: string, rect: IRect): LabelRect { 17 | return { 18 | id: uuidv4(), 19 | labelId: labelId, 20 | rect, 21 | isCreatedByAI: false, 22 | status: LabelStatus.ACCEPTED, 23 | suggestedLabel: null 24 | } 25 | } 26 | 27 | public static createLabelPolygon(labelId: string, vertices: IPoint[]): LabelPolygon { 28 | return { 29 | id: uuidv4(), 30 | labelId: labelId, 31 | vertices: vertices 32 | } 33 | } 34 | 35 | public static convertLabelNamesListToMap(labelNames: LabelName[]): any { 36 | return labelNames.reduce((map: any, labelNameRecord: LabelName) => { 37 | map[labelNameRecord.id] = labelNameRecord.name; 38 | return map; 39 | }, {}) 40 | } 41 | 42 | public static convertMapToLabelNamesList(object: any): LabelName[] { 43 | const labelNamesList: LabelName[] = []; 44 | Object.keys(object).forEach((key) => { 45 | if (!!object[key]) { 46 | labelNamesList.push({ 47 | id: key, 48 | name: object[key] 49 | }) 50 | } 51 | }); 52 | return labelNamesList; 53 | } 54 | 55 | public static labelNamesIdsDiff(oldLabelNames: LabelName[], newLabelNames: LabelName[]): string[] { 56 | return oldLabelNames.reduce((missingIds: string[], labelName: LabelName) => { 57 | if (!find(newLabelNames, { 'id': labelName.id })) { 58 | missingIds.push(labelName.id); 59 | } 60 | return missingIds 61 | }, []) 62 | } 63 | } -------------------------------------------------------------------------------- /src/utils/LineUtil.ts: -------------------------------------------------------------------------------- 1 | import {ILine} from "../interfaces/ILine"; 2 | import {IPoint} from "../interfaces/IPoint"; 3 | 4 | export class LineUtil { 5 | public static getDistanceFromLine(l: ILine, p: IPoint): number { 6 | if (l.start.x !== l.end.x || l.start.y !== l.end.y) { 7 | const nom: number = Math.abs((l.end.y - l.start.y) * p.x - (l.end.x - l.start.x) * p.y + l.end.x * l.start.y - l.end.y * l.start.x); 8 | const denom: number = Math.sqrt(Math.pow(l.end.y - l.start.y, 2) + Math.pow(l.end.x - l.start.x, 2)); 9 | return nom / denom; 10 | } 11 | return null; 12 | } 13 | 14 | public static getCenter(l: ILine): IPoint { 15 | return { 16 | x: (l.start.x + l.end.x) / 2, 17 | y: (l.start.y + l.end.y) / 2 18 | } 19 | } 20 | 21 | public static getPoints(l: ILine): IPoint[] { 22 | return [l.start, l.end] 23 | } 24 | } -------------------------------------------------------------------------------- /src/utils/MouseEventUtil.ts: -------------------------------------------------------------------------------- 1 | import {EventType} from "../data/enums/EventType"; 2 | 3 | export class MouseEventUtil { 4 | public static getEventType(event: Event): EventType | null { 5 | if (!event) return null; 6 | 7 | switch (event.type) { 8 | case EventType.MOUSE_DOWN: 9 | return EventType.MOUSE_DOWN; 10 | case EventType.MOUSE_UP: 11 | return EventType.MOUSE_UP; 12 | case EventType.MOUSE_MOVE: 13 | return EventType.MOUSE_MOVE; 14 | default: 15 | return null; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/utils/NumberUtil.ts: -------------------------------------------------------------------------------- 1 | export class NumberUtil { 2 | public static snapValueToRange(value: number, min: number, max: number): number { 3 | if (value < min) 4 | return min; 5 | if (value > max) 6 | return max; 7 | 8 | return value; 9 | } 10 | 11 | public static isValueInRange(value: number, min: number, max: number): boolean { 12 | return value >= min && value <= max; 13 | } 14 | } -------------------------------------------------------------------------------- /src/utils/PlatformUtil.ts: -------------------------------------------------------------------------------- 1 | import {MobileDeviceData} from "../data/MobileDeviceData"; 2 | import MobileDetect from 'mobile-detect' 3 | 4 | export class PlatformUtil { 5 | public static getMobileDeviceData(userAgent: string): MobileDeviceData { 6 | const mobileDetect = new MobileDetect(userAgent); 7 | return { 8 | manufacturer: mobileDetect.mobile(), 9 | browser: mobileDetect.userAgent(), 10 | os: mobileDetect.os() 11 | } 12 | } 13 | 14 | public static isMac(userAgent: string): boolean { 15 | return !!userAgent.toLowerCase().match("mac"); 16 | } 17 | 18 | public static isSafari(userAgent: string): boolean { 19 | return !!userAgent.toLowerCase().match("safari"); 20 | } 21 | 22 | public static isFirefox(userAgent: string): boolean { 23 | return !!userAgent.toLowerCase().match("firefox"); 24 | } 25 | } -------------------------------------------------------------------------------- /src/utils/PointUtil.ts: -------------------------------------------------------------------------------- 1 | import {IPoint} from "../interfaces/IPoint"; 2 | 3 | export class PointUtil { 4 | public static equals(p1: IPoint, p2: IPoint): boolean { 5 | return p1.x === p2.x && p1.y === p2.y; 6 | } 7 | 8 | public static add(p1: IPoint, p2: IPoint): IPoint { 9 | return { 10 | x: p1.x + p2.x, 11 | y: p1.y + p2.y 12 | } 13 | } 14 | 15 | public static subtract(p1: IPoint, p2: IPoint): IPoint { 16 | return { 17 | x: p1.x - p2.x, 18 | y: p1.y - p2.y 19 | } 20 | } 21 | 22 | public static multiply(p1: IPoint, factor: number) { 23 | return { 24 | x: p1.x * factor, 25 | y: p1.y * factor 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/utils/SizeUtil.ts: -------------------------------------------------------------------------------- 1 | import {ISize} from "../interfaces/ISize"; 2 | 3 | export class SizeUtil { 4 | public static scale(size: ISize, scale: number): ISize { 5 | return { 6 | width: size.width * scale, 7 | height: size.height * scale 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/utils/UnitUtil.ts: -------------------------------------------------------------------------------- 1 | export class UnitUtil { 2 | 3 | public static deg2rad(angleDeg:number) { 4 | return(angleDeg * Math.PI/180); 5 | } 6 | 7 | public static rad2deg(angleRad:number) { 8 | return(angleRad * 180/Math.PI); 9 | } 10 | } -------------------------------------------------------------------------------- /src/utils/VirtualListUtil.ts: -------------------------------------------------------------------------------- 1 | import {ISize} from "../interfaces/ISize"; 2 | import {IPoint} from "../interfaces/IPoint"; 3 | 4 | export class VirtualListUtil { 5 | public static calculateGridSize(listSize: ISize, childSize: ISize, childCount: number): ISize { 6 | const columnCount: number = Math.floor(listSize.width / childSize.width); 7 | const rowCount: number = Math.ceil(childCount / columnCount); 8 | return {width: columnCount, height: rowCount}; 9 | } 10 | 11 | public static calculateContentSize(listSize: ISize, childSize: ISize, gridSize: ISize): ISize { 12 | const sizeFromGrid:ISize = { 13 | width: childSize.width * gridSize.width, 14 | height: childSize.height * gridSize.height 15 | }; 16 | 17 | return { 18 | width: Math.max(listSize.width, sizeFromGrid.width), 19 | height: sizeFromGrid.height 20 | } 21 | } 22 | 23 | public static calculateAnchorPoints(listSize: ISize, childSize: ISize, childCount: number): IPoint[] { 24 | const gridSize: ISize = VirtualListUtil.calculateGridSize(listSize, childSize, childCount); 25 | const contentWrapperSize: ISize = VirtualListUtil.calculateContentSize(listSize, childSize, gridSize); 26 | const horizontalMargin = (contentWrapperSize.width - gridSize.width * childSize.width) / (gridSize.width + 1); 27 | 28 | let anchors = []; 29 | for (let i = 0; i < childCount; i++) { 30 | const rowCount: number = Math.floor(i / gridSize.width); 31 | const columnCount: number = i % gridSize.width; 32 | 33 | const anchor: IPoint = { 34 | x: rowCount * horizontalMargin + columnCount * childSize.width, 35 | y: rowCount * childSize.height 36 | }; 37 | anchors.push(anchor); 38 | } 39 | return anchors; 40 | } 41 | } -------------------------------------------------------------------------------- /src/utils/XMLSanitizerUtil.ts: -------------------------------------------------------------------------------- 1 | export class XMLSanitizerUtil { 2 | public static sanitize(input: string): string { 3 | return input 4 | .replace('<', '<') 5 | .replace('>', '>') 6 | .replace('&', '&') 7 | .replace("'", ''') 8 | .replace("/", '/') 9 | } 10 | } -------------------------------------------------------------------------------- /src/utils/__tests__/ArrayUtil.test.ts: -------------------------------------------------------------------------------- 1 | import {ArrayUtil, ArrayUtilAmbiguousMatchError} from "../ArrayUtil"; 2 | import {YOLOUtils} from "../../logic/import/yolo/YOLOUtils"; 3 | import {AnnotationsParsingError} from "../../logic/import/yolo/YOLOErrors"; 4 | 5 | describe('ArrayUtil partition method', () => { 6 | it('should return empty PartitionResult if array is empty', () => { 7 | // when 8 | const result = ArrayUtil.partition([], (item: any) => true) 9 | 10 | // then 11 | expect(result.pass.length).toEqual(0); 12 | expect(result.fail.length).toEqual(0); 13 | }); 14 | 15 | it('should pass even numbers and fail odd numbers', () => { 16 | // given 17 | const items = [1, 2, 3, 4, 5, 6] 18 | const predicate = (item: number) => item % 2 === 0 19 | 20 | // when 21 | const result = ArrayUtil.partition(items, predicate) 22 | 23 | // then 24 | expect(result.pass).toEqual([2, 4 ,6]); 25 | expect(result.fail).toEqual([1, 3, 5]); 26 | }); 27 | 28 | it('should pass items with key name from list', () => { 29 | // given 30 | type MockObject = { 31 | name: string 32 | } 33 | 34 | const nameList = ['aaa', 'bbb', 'ccc'] 35 | const items = [ 36 | {name: 'aaa'}, 37 | {name: 'aab'}, 38 | {name: 'abb'}, 39 | {name: 'bbb'} 40 | ] 41 | const predicate = (item: MockObject) => nameList.includes(item.name) 42 | 43 | // when 44 | const result = ArrayUtil.partition(items, predicate) 45 | 46 | // then 47 | expect(result.pass.length).toEqual(2); 48 | expect(result.fail.length).toEqual(2); 49 | expect(result.pass.map((item: MockObject) => item.name)).toEqual(['aaa', 'bbb']); 50 | expect(result.fail.map((item: MockObject) => item.name)).toEqual(['aab', 'abb']); 51 | }); 52 | }); 53 | 54 | describe('ArrayUtil match method', () => { 55 | it('should return empty array', () => { 56 | // when 57 | const result = ArrayUtil.match([], [], (k, v) => true) 58 | 59 | // then 60 | expect(result.length).toEqual(0); 61 | }); 62 | 63 | it('should return correct array when number of keys and values is even', () => { 64 | // when 65 | const result = ArrayUtil.match([4, 2, 1, 3], [1, 2, 4, 3], (k, v) => k === v) 66 | 67 | // then 68 | const expectedResult = [[4, 4], [2, 2], [1, 1], [3, 3]]; 69 | expect(result.length).toEqual(4); 70 | expect(JSON.stringify(result)).toEqual(JSON.stringify(expectedResult)); 71 | }); 72 | 73 | it('should return correct array when number of keys smaller than values', () => { 74 | // given 75 | const array1 = ["aa", "bb", "cc",]; 76 | const array2 = ["bb1", "aa2", "cc4", "cc3", "aa1", "bb2", "aa3"]; 77 | const predicate = (k, v) => v.startsWith(k); 78 | 79 | function wrapper() { 80 | return ArrayUtil.match(array1, array2, predicate) 81 | } 82 | expect(wrapper).toThrowError(new ArrayUtilAmbiguousMatchError()); 83 | }); 84 | }); -------------------------------------------------------------------------------- /src/utils/__tests__/FileUtil.test.ts: -------------------------------------------------------------------------------- 1 | import {FileUtil} from "../FileUtil"; 2 | 3 | describe('FileUtil extractFileExtension method', () => { 4 | it('should return file extension', () => { 5 | // given 6 | const name: string = "labels.txt"; 7 | 8 | // when 9 | const result = FileUtil.extractFileExtension(name); 10 | 11 | // then 12 | const expectedResult = "txt"; 13 | expect(result).toEqual(expectedResult); 14 | }); 15 | 16 | it('should return null', () => { 17 | // given 18 | const name: string = "labels"; 19 | 20 | // when 21 | const result = FileUtil.extractFileExtension(name); 22 | 23 | // then 24 | const expectedResult = null; 25 | expect(result).toEqual(expectedResult); 26 | }); 27 | }); 28 | 29 | describe('FileUtil extractFileName method', () => { 30 | it('should return file name', () => { 31 | // given 32 | const name: string = "labels.txt"; 33 | 34 | // when 35 | const result = FileUtil.extractFileName(name); 36 | 37 | // then 38 | const expectedResult = "labels"; 39 | expect(result).toEqual(expectedResult); 40 | }); 41 | }); -------------------------------------------------------------------------------- /src/utils/__tests__/ImageDataUtil.test.ts: -------------------------------------------------------------------------------- 1 | import {ImageData} from "../../store/labels/types"; 2 | import uuidv4 from 'uuid/v4'; 3 | import {LabelUtil} from "../LabelUtil"; 4 | import {ImageDataUtil} from "../ImageDataUtil"; 5 | import {AcceptedFileType} from "../../data/enums/AcceptedFileType"; 6 | 7 | 8 | const getDummyImageData = (id: string): ImageData => { 9 | return { 10 | id: id, 11 | fileData: new File([""], "filename.txt", { type: AcceptedFileType.TEXT }), 12 | loadStatus: true, 13 | labelRects: [], 14 | labelPoints: [], 15 | labelLines: [], 16 | labelPolygons: [], 17 | labelNameIds: [], 18 | isVisitedByObjectDetector: false, 19 | isVisitedByPoseDetector: false 20 | } 21 | } 22 | 23 | 24 | describe('ImageDataUtil cleanAnnotation method', () => { 25 | it('should return new ImageData object without annotations', () => { 26 | // given 27 | let item: ImageData = getDummyImageData(uuidv4()); 28 | item.labelRects = [ 29 | LabelUtil.createLabelRect('label-id', {x: 1, y: 1, width: 1, height: 1}), 30 | LabelUtil.createLabelRect('label-id', {x: 1, y: 1, width: 1, height: 1}) 31 | ] 32 | 33 | // when 34 | const result = ImageDataUtil.cleanAnnotations(item); 35 | 36 | // then 37 | expect(result.labelRects.length).toEqual(0); 38 | expect(item.labelRects.length).toEqual(2); 39 | }); 40 | }); 41 | 42 | describe('ImageDataUtil arrange method', () => { 43 | it('should return new array with correctly arranged ImageData objects', () => { 44 | // given 45 | const idA = uuidv4(), idB = uuidv4(), idC = uuidv4(), idD = uuidv4(); 46 | const givenIdArrangement = [idD, idA, idB, idC]; 47 | const expectedIdArrangement = [idA, idB, idC, idD]; 48 | const items = givenIdArrangement.map((id: string) => getDummyImageData(id)); 49 | 50 | // when 51 | const result = ImageDataUtil.arrange(items, expectedIdArrangement); 52 | const resultIdArrangement = result.map((item: ImageData) => item.id) 53 | 54 | // then 55 | expect(JSON.stringify(expectedIdArrangement)).toBe(JSON.stringify(resultIdArrangement)); 56 | }); 57 | }); -------------------------------------------------------------------------------- /src/utils/__tests__/RectUtil.test.ts: -------------------------------------------------------------------------------- 1 | import {RectUtil} from "../RectUtil"; 2 | import {IRect} from "../../interfaces/IRect"; 3 | 4 | describe('RectUtil getRatio method', () => { 5 | it('should return correct value of rect ratio', () => { 6 | const rect: IRect = {x: 0, y: 0, width: 10, height: 5}; 7 | expect(RectUtil.getRatio(rect)).toBe(2); 8 | }); 9 | 10 | it('should return null', () => { 11 | expect(RectUtil.getRatio(null)).toBe(null); 12 | }); 13 | }); -------------------------------------------------------------------------------- /src/views/Common/ImageButton/ImageButton.scss: -------------------------------------------------------------------------------- 1 | .ImageButton { 2 | display: flex; 3 | flex-direction: row; 4 | flex-wrap: nowrap; 5 | justify-content: center; 6 | align-content: center; 7 | align-items: center; 8 | border-radius: 50%; 9 | box-sizing: border-box; 10 | transition: background-color 0.7s ease; 11 | margin: 5px 2px; 12 | 13 | > img { 14 | user-select: none; 15 | } 16 | 17 | &:hover ~ .Cursor { 18 | width: 20px; 19 | height: 20px; 20 | border-color: transparent; 21 | background-color: rgba(0, 0, 0, 0.2); 22 | } 23 | } 24 | 25 | .ImageButton:not(.disabled):hover { 26 | cursor: pointer; 27 | } 28 | 29 | .ImageButton:hover { 30 | background-color: rgba(255, 255, 255, 0.3); 31 | } -------------------------------------------------------------------------------- /src/views/Common/ImageButton/ImageButton.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {ISize} from "../../../interfaces/ISize"; 3 | import './ImageButton.scss'; 4 | import classNames from "classnames"; 5 | import {LegacyRef} from "react"; 6 | 7 | export interface ImageButtonProps extends React.HTMLProps { 8 | buttonSize:ISize, 9 | padding?:number; 10 | image:string, 11 | imageAlt:string, 12 | href?:string 13 | onClick?:() => any; 14 | style?:React.CSSProperties 15 | isActive?:boolean; 16 | isDisabled?:boolean; 17 | externalClassName?:string; 18 | } 19 | 20 | export const ImageButton = React.forwardRef((props: ImageButtonProps, ref: LegacyRef) => { 21 | const {buttonSize, padding, image, imageAlt, href, onClick, style, isActive, isDisabled, externalClassName} = props; 22 | const imagePadding:number = !!padding ? padding : 10; 23 | 24 | const onClickHandler = (event: React.MouseEvent) => { 25 | event.stopPropagation(); 26 | !!onClick && onClick(); 27 | }; 28 | 29 | const buttonStyle:React.CSSProperties = { 30 | ...style, 31 | width: buttonSize.width, 32 | height: buttonSize.height 33 | }; 34 | 35 | const imageStyle:React.CSSProperties = { 36 | maxWidth: buttonSize.width - imagePadding, 37 | maxHeight: buttonSize.height - imagePadding 38 | }; 39 | 40 | const getClassName = () => { 41 | return classNames( 42 | "ImageButton", 43 | externalClassName, 44 | { 45 | "active": isActive, 46 | "disabled": isDisabled, 47 | } 48 | ); 49 | }; 50 | 51 | return( 52 |
58 | {!!href && 59 | {imageAlt} 65 | } 66 | {!href && {imageAlt}} 72 |
73 | ); 74 | }); -------------------------------------------------------------------------------- /src/views/Common/TextButton/TextButton.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .TextButton { 4 | display: block; 5 | cursor: pointer; 6 | user-select: none; 7 | line-height: 0; 8 | word-wrap: break-word; 9 | padding: 20px 30px; 10 | text-decoration: none; 11 | box-shadow: black 0 0 0 2px inset; 12 | color: black; 13 | margin: initial; 14 | border-radius: 2px; 15 | transition: 0.3s ease-in-out; 16 | background: transparent; 17 | font-weight: 700; 18 | 19 | > a { 20 | text-decoration: none; 21 | color: black; 22 | } 23 | 24 | &:hover { 25 | box-shadow: inherit; 26 | color: white; 27 | background-color: black; 28 | 29 | > a { 30 | text-decoration: none; 31 | color: white; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/views/Common/TextButton/TextButton.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import './TextButton.scss'; 3 | import classNames from "classnames"; 4 | 5 | interface IProps { 6 | key?:string; 7 | label:string; 8 | onClick?:() => any; 9 | style?:React.CSSProperties; 10 | isActive?:boolean; 11 | isDisabled?:boolean; 12 | externalClassName?:string; 13 | } 14 | 15 | export const TextButton = (props:IProps) => { 16 | const { key, label, onClick, style, isActive, isDisabled, externalClassName} = props; 17 | 18 | const getClassName = () => { 19 | return classNames( 20 | "TextButton", 21 | externalClassName, 22 | { 23 | "active": isActive, 24 | "disabled": isDisabled 25 | } 26 | ); 27 | }; 28 | 29 | const onClickHandler = (event: React.MouseEvent) => { 30 | event.stopPropagation(); 31 | !!onClick && onClick(); 32 | }; 33 | 34 | return( 35 |
41 | {label} 42 |
43 | ) 44 | }; -------------------------------------------------------------------------------- /src/views/Common/TextInput/TextInput.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | $secondary-color: white; 4 | $main-color: transparent; 5 | $width: 320px; 6 | 7 | * { 8 | box-sizing: border-box; 9 | } 10 | 11 | .TextInput { 12 | height: $width/6; 13 | overflow: hidden; 14 | position: relative; 15 | } 16 | 17 | label { 18 | position: absolute; 19 | top: $width/15; 20 | color: white; 21 | font: 400 $width/15; 22 | cursor: text; 23 | transition: .25s ease; 24 | } 25 | 26 | input { 27 | display: block; 28 | width: 100%; 29 | padding-top: $width/15; 30 | border: none; 31 | border-radius: 0; // For iOS 32 | color: white; 33 | background: $main-color; 34 | font-size: $width/20; 35 | transition: .3s ease; 36 | &:valid { 37 | ~label { 38 | top: 0; 39 | font: 700 $width/25; 40 | color: white; 41 | } 42 | } 43 | &:focus { 44 | outline: none; 45 | ~label { 46 | top: 0; 47 | font: 700 $width/25; 48 | color: $secondaryColor; // fallback if new css variables are not supported by browser 49 | color: var(--leading-color); 50 | } 51 | 52 | ~ .Bar { 53 | height: 2px; 54 | } 55 | } 56 | 57 | // Stop Chrome's hideous pale yellow background on auto-fill 58 | 59 | &:-webkit-autofill { 60 | -webkit-box-shadow: 0 0 0px 1000px $main-color inset; 61 | -webkit-text-fill-color: white !important; 62 | } 63 | } 64 | 65 | .Bar { 66 | background: white; 67 | content: ''; 68 | width: $width; 69 | height: 1px; 70 | transition: .3s ease; 71 | position: relative; 72 | } 73 | 74 | ::selection { 75 | background: rgba($secondaryColor, .3); 76 | background: rgba(var(--leading-color), .3); 77 | } -------------------------------------------------------------------------------- /src/views/Common/TextInput/TextInput.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './TextInput.scss'; 3 | 4 | interface IProps { 5 | key: string; 6 | label?: string; 7 | isPassword: boolean; 8 | onChange?: (event: React.ChangeEvent) => any; 9 | onFocus?: (event: React.FocusEvent) => any; 10 | inputStyle?: React.CSSProperties; 11 | labelStyle?: React.CSSProperties; 12 | barStyle?: React.CSSProperties; 13 | value?: string; 14 | onKeyUp?: (event: React.KeyboardEvent) => void; 15 | } 16 | 17 | const TextInput = (props: IProps) => { 18 | 19 | const { 20 | key, 21 | label, 22 | isPassword, 23 | onChange, 24 | onFocus, 25 | inputStyle, 26 | labelStyle, 27 | barStyle, 28 | value, 29 | onKeyUp 30 | } = props; 31 | 32 | const getInputType = () => { 33 | return isPassword ? "password" : "text"; 34 | }; 35 | 36 | return ( 37 |
38 | 48 | {!!label && } 54 |
58 |
59 | ); 60 | }; 61 | 62 | export default TextInput; -------------------------------------------------------------------------------- /src/views/Common/UnderlineTextButton/UnderlineTextButton.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .UnderlineTextButton { 4 | position: relative; 5 | font-size: 14px; 6 | font-weight: 700; 7 | cursor: pointer; 8 | user-select: none; 9 | width: fit-content; 10 | line-height: 0; 11 | word-wrap: break-word; 12 | height: 3px; // <- adjust 13 | padding: 15px 0; // <- adjust 14 | margin: 0 10px; 15 | text-decoration: none; 16 | color: white; 17 | 18 | &:hover, 19 | &.active { 20 | color: $secondaryColor; // fallback if new css variables are not supported by browser 21 | color: var(--leading-color); 22 | } 23 | 24 | &.over::before { 25 | background-color: $secondaryColor; // fallback if new css variables are not supported by browser 26 | background-color: var(--leading-color); 27 | width: 100%; 28 | position: absolute; 29 | top: 0; 30 | height: 2px; // <- adjust 31 | left: 0; 32 | z-index: 100; 33 | display: block; 34 | } 35 | 36 | &.under::after { 37 | background-color: $secondaryColor; // fallback if new css variables are not supported by browser 38 | background-color: var(--leading-color); 39 | width: 100%; 40 | position: absolute; 41 | bottom: 0; 42 | height: 2px; // <- adjust 43 | left: 0; 44 | z-index: 100; 45 | display: block; 46 | } 47 | 48 | &.over:hover::before, 49 | &.over.active::before { 50 | content: ''; 51 | } 52 | 53 | &.under:hover::after, 54 | &.under.active::after { 55 | content: ''; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/views/Common/UnderlineTextButton/UnderlineTextButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import classNames from 'classnames' 3 | import './UnderlineTextButton.scss' 4 | 5 | interface IProps { 6 | under?: boolean 7 | over?: boolean 8 | active?: boolean 9 | key?: string 10 | label: string 11 | onClick?: () => any 12 | style?: React.CSSProperties 13 | } 14 | 15 | export const UnderlineTextButton = (props: IProps) => { 16 | const { under, over, active, key, label, onClick, style } = props; 17 | 18 | const getClassName = () => { 19 | return classNames('UnderlineTextButton', { 20 | under: under, 21 | over: over, 22 | active: active, 23 | }) 24 | }; 25 | 26 | return ( 27 |
33 | {label} 34 |
35 | ) 36 | }; 37 | -------------------------------------------------------------------------------- /src/views/EditorView/Editor/Editor.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .Editor { 4 | align-self: stretch; 5 | flex: 1; 6 | position: relative; 7 | 8 | .ViewPortContent { 9 | position: relative; 10 | 11 | .track-horizontal { 12 | cursor: none; 13 | } 14 | 15 | .track-vertical { 16 | cursor: none; 17 | } 18 | 19 | .ImageCanvas { 20 | position: absolute; 21 | top: 0; 22 | left: 0; 23 | cursor: none; 24 | 25 | &:hover { 26 | cursor: none; 27 | } 28 | } 29 | } 30 | 31 | .MousePositionIndicator { 32 | position: absolute; 33 | color: white; 34 | font-size: 12px; 35 | background-color: $darkThemeThirdColor; 36 | opacity: 0.6; 37 | padding: 5px; 38 | user-select: none; 39 | pointer-events: none; 40 | z-index: 100; 41 | width: fit-content; 42 | } 43 | 44 | .Cursor { 45 | position: absolute; 46 | width: 6px; 47 | height: 6px; 48 | transition: width 0.05s ease-out, height 0.05s ease-out, background-color 0.05s ease-in; 49 | transform: translate(-50%, -50%); 50 | border-radius: 50%; 51 | pointer-events: none; 52 | border: 2px solid white; 53 | background-color: white; 54 | z-index: 100000; 55 | 56 | > img { 57 | position: absolute; 58 | max-width: 20px; 59 | max-height: 20px; 60 | filter: brightness(0) invert(1); 61 | bottom: calc(50% + 10px); 62 | left: calc(50% + 10px); 63 | display: none; 64 | user-select: none; 65 | } 66 | 67 | &.move, &.add, &.resize, &.close { 68 | width: 24px; 69 | height: 24px; 70 | background-color: transparent; 71 | } 72 | 73 | &.grabbing { 74 | width: 18px; 75 | height: 18px; 76 | background-color: rgba(255, 255, 255, 0.5); 77 | border: 2px solid transparent; 78 | } 79 | 80 | &.move, &.add, &.resize, &.close, &.cancel, &.grab, &.grabbing { 81 | > img { 82 | display: block; 83 | } 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /src/views/EditorView/EditorBottomNavigationBar/EditorBottomNavigationBar.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .EditorBottomNavigationBar { 4 | align-self: stretch; 5 | height: $editorBottomNavigationBarHeight; 6 | border-top: solid 1px $darkThemeFirstColor; 7 | background-color: $darkThemeSecondColor; 8 | user-select: none; 9 | 10 | display: flex; 11 | flex-direction: row; 12 | flex-wrap: nowrap; 13 | justify-content: center; 14 | align-items: center; 15 | align-content: center; 16 | 17 | &.with-context { 18 | background-color: $darkThemeForthColor; 19 | } 20 | 21 | .ImageButton { 22 | transition: transform 0.3s; 23 | 24 | img { 25 | filter: brightness(0) invert(1); 26 | user-select: none; 27 | } 28 | 29 | &:hover { 30 | background-color: transparent; 31 | } 32 | 33 | &:not(.disabled):hover { 34 | filter: brightness(35%) sepia(100%) hue-rotate(172deg) saturate(2000%); // fallback if new css variables are not supported by browser 35 | filter: brightness(35%) sepia(100%) hue-rotate(var(--hue-value)) saturate(2000%); 36 | &.right { 37 | transform: translate(2px); 38 | } 39 | &.left { 40 | transform: translate(-2px); 41 | } 42 | } 43 | 44 | &.disabled { 45 | img { 46 | filter: invert(1) opacity(25%); 47 | user-select: none; 48 | } 49 | } 50 | } 51 | 52 | .CurrentImageName { 53 | min-width: 200px; 54 | padding: 0 20px; 55 | color: white; 56 | font-size: 14px; 57 | 58 | display: flex; 59 | flex-direction: row; 60 | flex-wrap: nowrap; 61 | justify-content: center; 62 | align-items: center; 63 | align-content: center; 64 | } 65 | 66 | .CurrentImageCount { 67 | min-width: 80px; 68 | padding: 0 20px; 69 | color: white; 70 | 71 | display: flex; 72 | flex-direction: row; 73 | flex-wrap: nowrap; 74 | justify-content: center; 75 | align-items: center; 76 | align-content: center; 77 | } 78 | } -------------------------------------------------------------------------------- /src/views/EditorView/EditorBottomNavigationBar/EditorBottomNavigationBar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './EditorBottomNavigationBar.scss'; 3 | import {ImageData} from "../../../store/labels/types"; 4 | import {AppState} from "../../../store"; 5 | import {connect} from "react-redux"; 6 | import {ImageButton} from "../../Common/ImageButton/ImageButton"; 7 | import {ISize} from "../../../interfaces/ISize"; 8 | import {ContextType} from "../../../data/enums/ContextType"; 9 | import classNames from "classnames"; 10 | import {ImageActions} from "../../../logic/actions/ImageActions"; 11 | 12 | interface IProps { 13 | size: ISize; 14 | imageData: ImageData; 15 | totalImageCount: number; 16 | activeImageIndex: number; 17 | activeContext: ContextType; 18 | } 19 | 20 | const EditorBottomNavigationBar: React.FC = ({size, imageData, totalImageCount, activeImageIndex, activeContext}) => { 21 | const minWidth:number = 400; 22 | 23 | const getImageCounter = () => { 24 | return (activeImageIndex + 1) + " / " + totalImageCount; 25 | }; 26 | 27 | const getClassName = () => { 28 | return classNames( 29 | "EditorBottomNavigationBar", 30 | { 31 | "with-context": activeContext === ContextType.EDITOR 32 | } 33 | ); 34 | }; 35 | 36 | return ( 37 |
38 | ImageActions.getPreviousImage()} 43 | isDisabled={activeImageIndex === 0} 44 | externalClassName={"left"} 45 | /> 46 | {size.width > minWidth ? 47 |
{imageData.fileData.name}
: 48 |
{getImageCounter()}
49 | } 50 | ImageActions.getNextImage()} 55 | isDisabled={activeImageIndex === totalImageCount - 1} 56 | externalClassName={"right"} 57 | /> 58 |
59 | ); 60 | }; 61 | 62 | const mapDispatchToProps = {}; 63 | 64 | const mapStateToProps = (state: AppState) => ({ 65 | activeImageIndex: state.labels.activeImageIndex, 66 | activeContext: state.general.activeContext 67 | }); 68 | 69 | export default connect( 70 | mapStateToProps, 71 | mapDispatchToProps 72 | )(EditorBottomNavigationBar); 73 | -------------------------------------------------------------------------------- /src/views/EditorView/EditorContainer/EditorContainer.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .EditorContainer { 4 | align-self: stretch; 5 | flex: 0 0 calc(100% - #{$topNavigationBarHeight}); 6 | height: calc(100% - #{$topNavigationBarHeight}); 7 | 8 | display: flex; 9 | flex-direction: row; 10 | flex-wrap: nowrap; 11 | justify-content: flex-start; 12 | align-items: center; 13 | align-content: flex-start; 14 | 15 | .EditorWrapper { 16 | align-self: stretch; 17 | flex: 1; 18 | min-width: 0; 19 | display: flex; 20 | flex-direction: column; 21 | flex-wrap: nowrap; 22 | justify-content: center; 23 | align-items: center; 24 | align-content: center; 25 | } 26 | } -------------------------------------------------------------------------------- /src/views/EditorView/EditorTopNavigationBar/EditorTopNavigationBar.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .EditorTopNavigationBar { 4 | align-self: stretch; 5 | height: $editorTopNavigationBarHeight; 6 | border-bottom: solid 1px $darkThemeFirstColor; 7 | background-color: $darkThemeSecondColor; 8 | user-select: none; 9 | 10 | display: flex; 11 | flex-direction: row; 12 | flex-wrap: nowrap; 13 | justify-content: flex-start; 14 | align-items: center; 15 | align-content: flex-start; 16 | 17 | padding: 0 5px; 18 | 19 | &.with-context { 20 | background-color: $darkThemeForthColor; 21 | } 22 | 23 | .ButtonWrapper { 24 | display: flex; 25 | flex-direction: row; 26 | flex-wrap: nowrap; 27 | justify-content: flex-start; 28 | align-items: center; 29 | align-content: flex-start; 30 | 31 | &:not(:last-child) { 32 | padding-right: 5px; 33 | margin-right: 5px; 34 | border-right: solid 1px $darkThemeFirstColor; 35 | } 36 | } 37 | 38 | .ImageButton { 39 | transition: transform 0.3s; 40 | margin: 0 2px; 41 | 42 | img { 43 | filter: brightness(0) invert(1); 44 | } 45 | 46 | &:hover { 47 | border-radius: 3px; 48 | background-color: rgba(0, 0, 0, 0.2); 49 | } 50 | 51 | &.active { 52 | border-radius: 3px; 53 | background-color: rgba(0, 0, 0, 0.4); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /src/views/EditorView/EditorView.scss: -------------------------------------------------------------------------------- 1 | @import '../../settings/_Settings'; 2 | 3 | .EditorView { 4 | position: absolute; 5 | height: 100vh; 6 | width: 100vw; 7 | margin: 0; 8 | padding: 0; 9 | 10 | background-color: $darkThemeThirdColor; 11 | 12 | display: flex; 13 | flex-direction: column; 14 | flex-wrap: nowrap; 15 | justify-content: flex-start; 16 | align-items: center; 17 | align-content: flex-start; 18 | 19 | &.withPopup { 20 | filter: blur(2px) brightness(0.3); 21 | pointer-events: none; 22 | } 23 | } -------------------------------------------------------------------------------- /src/views/EditorView/EditorView.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './EditorView.scss'; 3 | import EditorContainer from "./EditorContainer/EditorContainer"; 4 | import {PopupWindowType} from "../../data/enums/PopupWindowType"; 5 | import {AppState} from "../../store"; 6 | import {connect} from "react-redux"; 7 | import classNames from "classnames"; 8 | import TopNavigationBar from "./TopNavigationBar/TopNavigationBar"; 9 | 10 | interface IProps { 11 | activePopupType: PopupWindowType; 12 | } 13 | 14 | const EditorView: React.FC = ({activePopupType}) => { 15 | 16 | const getClassName = () => { 17 | return classNames( 18 | "EditorView", 19 | { 20 | "withPopup": !!activePopupType 21 | } 22 | ); 23 | }; 24 | 25 | return ( 26 |
30 | 31 | 32 |
33 | ); 34 | }; 35 | 36 | const mapStateToProps = (state: AppState) => ({ 37 | activePopupType: state.general.activePopupType 38 | }); 39 | 40 | export default connect( 41 | mapStateToProps 42 | )(EditorView); -------------------------------------------------------------------------------- /src/views/EditorView/FeatureInProgress/FeatureInProgress.scss: -------------------------------------------------------------------------------- 1 | .FeatureInProgress { 2 | display: flex; 3 | flex-direction: column; 4 | flex-wrap: nowrap; 5 | justify-content: center; 6 | align-items: center; 7 | align-content: center; 8 | text-align: center; 9 | width: 150px; 10 | color: white; 11 | font-size: 16px; 12 | user-select: none; 13 | 14 | > img { 15 | filter: brightness(0) invert(1); 16 | max-width: 100px; 17 | max-height: 100px; 18 | user-select: none; 19 | margin: 20px; 20 | } 21 | 22 | > p { 23 | &.extraBold { 24 | font-size: 18px; 25 | font-weight: 600; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/views/EditorView/FeatureInProgress/FeatureInProgress.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './FeatureInProgress.scss'; 3 | 4 | export const FeatureInProgress: React.FC = () => { 5 | return( 6 |
9 | {"take_off"} 14 |

new feature
coming soon...

15 |
16 | ) 17 | }; -------------------------------------------------------------------------------- /src/views/EditorView/LabelControlPanel/LabelControlPanel.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .LabelControlPanel { 4 | position: absolute; 5 | cursor: pointer; 6 | 7 | display: flex; 8 | flex-direction: row; 9 | flex-wrap: nowrap; 10 | justify-content: center; 11 | align-items: center; 12 | align-content: center; 13 | 14 | .ImageButton { 15 | transition: transform 0.3s; 16 | 17 | img { 18 | filter: brightness(0) invert(1); 19 | user-select: none; 20 | } 21 | 22 | &:hover { 23 | background-color: transparent; 24 | } 25 | 26 | &:not(.disabled):hover { 27 | filter: brightness(35%) sepia(100%) hue-rotate(172deg) saturate(2000%); // fallback if new css variables are not supported by browser 28 | filter: brightness(35%) sepia(100%) hue-rotate(var(--hue-value)) saturate(2000%); 29 | 30 | &.right { 31 | transform: translate(2px); 32 | } 33 | 34 | &.left { 35 | transform: translate(-2px); 36 | } 37 | } 38 | 39 | &.disabled { 40 | img { 41 | filter: invert(1) opacity(25%); 42 | user-select: none; 43 | } 44 | } 45 | } 46 | 47 | .SuggestedLabel { 48 | align-self: stretch; 49 | 50 | justify-content: center; 51 | align-items: center; 52 | align-content: center; 53 | 54 | padding: 0 10px 0 15px; 55 | color: white; 56 | } 57 | 58 | $randomNumber: random(5); 59 | 60 | &.is-active { 61 | background-color: $darkThemeSecondColor; 62 | border: solid 1px $primaryColor; 63 | animation: brColor 5s infinite linear; 64 | animation-delay: $randomNumber + s; 65 | transform: translate(-15px, -15px); 66 | border-radius: 3px; 67 | min-width: 30px; 68 | height: 30px; 69 | z-index: 1000; 70 | } 71 | 72 | &:not(.is-active) { 73 | background-color: $primaryColor; 74 | animation: bgColor 5s infinite linear; 75 | animation-delay: $randomNumber + s; 76 | transform: translate(-6px, -6px); 77 | border-radius: 6px; 78 | width: 12px; 79 | height: 12px; 80 | z-index: 1; 81 | } 82 | } 83 | 84 | @keyframes bgColor { 85 | 50% { 86 | background-color: $secondaryColor; 87 | } 88 | 100% { 89 | background-color: $primaryColor; 90 | } 91 | } 92 | 93 | @keyframes brColor { 94 | 50% { 95 | border-color: $secondaryColor; 96 | } 97 | 100% { 98 | border-color: $primaryColor; 99 | } 100 | } -------------------------------------------------------------------------------- /src/views/EditorView/SideNavigationBar/EmptyLabelList/EmptyLabelList.scss: -------------------------------------------------------------------------------- 1 | .EmptyLabelList { 2 | display: flex; 3 | flex-direction: column; 4 | flex-wrap: nowrap; 5 | justify-content: center; 6 | align-items: center; 7 | align-content: center; 8 | text-align: center; 9 | width: 150px; 10 | color: white; 11 | font-size: 16px; 12 | user-select: none; 13 | 14 | > img { 15 | filter: brightness(0) invert(1); 16 | max-width: 80px; 17 | max-height: 80px; 18 | margin-bottom: 20px; 19 | user-select: none; 20 | } 21 | 22 | > p { 23 | &.extraBold { 24 | font-size: 16px; 25 | font-weight: 600; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/views/EditorView/SideNavigationBar/EmptyLabelList/EmptyLabelList.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './EmptyLabelList.scss'; 3 | import {AppState} from "../../../../store"; 4 | import {connect} from "react-redux"; 5 | 6 | interface IProps { 7 | firstLabelCreatedFlag: boolean; 8 | labelBefore: string; 9 | labelAfter: string; 10 | } 11 | 12 | const EmptyLabelList: React.FC = ({firstLabelCreatedFlag, labelBefore, labelAfter}) => { 13 | const before = <> 14 | {"lets_start"} 19 |

{labelBefore}

20 | ; 21 | 22 | const after = <> 23 | {"no_labels"} 28 |

{labelAfter}

29 | ; 30 | 31 | return(
32 | {!firstLabelCreatedFlag ? before : after} 33 |
) 34 | }; 35 | 36 | const mapDispatchToProps = {}; 37 | 38 | const mapStateToProps = (state: AppState) => ({ 39 | firstLabelCreatedFlag: state.labels.firstLabelCreatedFlag 40 | }); 41 | 42 | export default connect( 43 | mapStateToProps, 44 | mapDispatchToProps 45 | )(EmptyLabelList); -------------------------------------------------------------------------------- /src/views/EditorView/SideNavigationBar/ImagePreview/ImagePreview.scss: -------------------------------------------------------------------------------- 1 | @import '../../../../settings/Settings'; 2 | 3 | .ImagePreview { 4 | position: absolute; 5 | cursor: pointer; 6 | 7 | display: flex; 8 | flex-direction: row; 9 | flex-wrap: nowrap; 10 | justify-content: center; 11 | align-items: center; 12 | align-content: center; 13 | 14 | border-bottom: solid 1px $darkThemeFirstColor; 15 | &:nth-child(odd) { 16 | border-right: solid 1px $darkThemeFirstColor; 17 | } 18 | 19 | .Foreground { 20 | position: absolute; 21 | z-index: 100; 22 | transition: transform 0.3s; 23 | 24 | .Image { 25 | position: absolute; 26 | left: 0; 27 | top: 0; 28 | border: solid 1px $darkThemeSecondColor; 29 | user-select: none; 30 | } 31 | 32 | .CheckBox { 33 | position: absolute; 34 | z-index: 1000; 35 | max-width: 20px; 36 | max-height: 20px; 37 | bottom: -10px; 38 | left: -10px; 39 | filter: invert(1) brightness(35%) sepia(100%) hue-rotate(172deg) saturate(2000%); // fallback if new css variables are not supported by browser 40 | filter: invert(1) brightness(35%) sepia(100%) hue-rotate(var(--hue-value)) saturate(2000%); 41 | } 42 | } 43 | 44 | .Background { 45 | position: absolute; 46 | z-index: 0; 47 | background-color: $darkThemeThirdColor; 48 | transition: background-color 0.3s ease-in-out, transform 0.3s; 49 | } 50 | 51 | &.selected { 52 | .Foreground { 53 | transform: translate(-2px, 2px); 54 | } 55 | .Background { 56 | transform: translate(2px, -2px); 57 | background-color: $secondaryColor; // fallback if new css variables are not supported by browser 58 | background-color: var(--leading-color); 59 | } 60 | } 61 | 62 | &:hover { 63 | .Foreground { 64 | transform: translate(-2px, 2px); 65 | } 66 | .Background { 67 | transform: translate(2px, -2px); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /src/views/EditorView/SideNavigationBar/ImagesList/ImagesList.scss: -------------------------------------------------------------------------------- 1 | .ImagesList { 2 | align-self: stretch; 3 | flex: 1; 4 | overflow: hidden; 5 | } -------------------------------------------------------------------------------- /src/views/EditorView/SideNavigationBar/LabelsToolkit/LabelsToolkit.scss: -------------------------------------------------------------------------------- 1 | @import '../../../../settings/Settings'; 2 | 3 | .LabelsToolkit { 4 | align-self: stretch; 5 | flex: 1; 6 | overflow: hidden; 7 | 8 | display: flex; 9 | flex-direction: column; 10 | flex-wrap: nowrap; 11 | justify-content: flex-start; 12 | align-items: center; 13 | align-content: flex-start; 14 | 15 | .Header { 16 | position: relative; 17 | align-self: stretch; 18 | box-sizing: border-box; 19 | padding: 0 25px; 20 | color: white; 21 | font-size: 14px; 22 | user-select: none; 23 | cursor: pointer; 24 | 25 | display: flex; 26 | flex-direction: row; 27 | flex-wrap: nowrap; 28 | justify-content: space-between; 29 | align-items: center; 30 | align-content: space-between; 31 | 32 | .HeaderGroupWrapper { 33 | align-self: stretch; 34 | 35 | display: flex; 36 | flex-direction: row; 37 | flex-wrap: nowrap; 38 | justify-content: center; 39 | align-items: center; 40 | align-content: center; 41 | } 42 | 43 | .Marker { 44 | position: absolute; 45 | height: calc(100% - 4px); 46 | width: 5px; 47 | top: 2px; 48 | left: -5px; 49 | background-color: $darkThemeThirdColor; 50 | transition: background-color 0.3s ease-in-out, transform 0.3s; 51 | } 52 | 53 | .Ico { 54 | max-width: 20px; 55 | max-height: 20px; 56 | margin-right: 20px; 57 | filter: brightness(0) invert(1); 58 | } 59 | 60 | .Arrow { 61 | max-width: 12px; 62 | max-height: 12px; 63 | filter: brightness(0) invert(1); 64 | } 65 | 66 | &:hover { 67 | .Marker { 68 | transform: translate(5px); 69 | } 70 | } 71 | 72 | &.active { 73 | background-color: rgba(0, 0, 0, 0.1); 74 | .Marker { 75 | transform: translate(5px); 76 | background-color: $secondaryColor; // fallback if new css variables are not supported by browser 77 | background-color: var(--leading-color); 78 | } 79 | 80 | .Arrow { 81 | transform: rotate(180deg); 82 | } 83 | } 84 | } 85 | 86 | .Content { 87 | align-self: stretch; 88 | box-sizing: border-box; 89 | height: 0; 90 | transition: height 0.3s ease-in-out; 91 | overflow: hidden; 92 | 93 | display: flex; 94 | flex-direction: column; 95 | flex-wrap: nowrap; 96 | justify-content: center; 97 | align-items: center; 98 | align-content: center; 99 | } 100 | } 101 | 102 | -------------------------------------------------------------------------------- /src/views/EditorView/SideNavigationBar/LineLabelsList/LineLabelsList.scss: -------------------------------------------------------------------------------- 1 | .LineLabelsList { 2 | display: flex; 3 | flex-direction: column; 4 | flex-wrap: nowrap; 5 | justify-content: center; 6 | align-items: center; 7 | align-content: center; 8 | 9 | .LineLabelsListContent { 10 | display: flex; 11 | flex-direction: column; 12 | flex-wrap: nowrap; 13 | justify-content: flex-start; 14 | align-items: center; 15 | align-content: flex-start; 16 | } 17 | } -------------------------------------------------------------------------------- /src/views/EditorView/SideNavigationBar/PointLabelsList/PointLabelsList.scss: -------------------------------------------------------------------------------- 1 | .PointLabelsList { 2 | display: flex; 3 | flex-direction: column; 4 | flex-wrap: nowrap; 5 | justify-content: center; 6 | align-items: center; 7 | align-content: center; 8 | 9 | .PointLabelsListContent { 10 | display: flex; 11 | flex-direction: column; 12 | flex-wrap: nowrap; 13 | justify-content: flex-start; 14 | align-items: center; 15 | align-content: flex-start; 16 | } 17 | } -------------------------------------------------------------------------------- /src/views/EditorView/SideNavigationBar/PolygonLabelsList/PolygonLabelsList.scss: -------------------------------------------------------------------------------- 1 | .PolygonLabelsList { 2 | display: flex; 3 | flex-direction: column; 4 | flex-wrap: nowrap; 5 | justify-content: center; 6 | align-items: center; 7 | align-content: center; 8 | 9 | .PolygonLabelsListContent { 10 | display: flex; 11 | flex-direction: column; 12 | flex-wrap: nowrap; 13 | justify-content: flex-start; 14 | align-items: center; 15 | align-content: flex-start; 16 | } 17 | } -------------------------------------------------------------------------------- /src/views/EditorView/SideNavigationBar/RectLabelsList/RectLabelsList.scss: -------------------------------------------------------------------------------- 1 | .RectLabelsList { 2 | display: flex; 3 | flex-direction: column; 4 | flex-wrap: nowrap; 5 | justify-content: center; 6 | align-items: center; 7 | align-content: center; 8 | 9 | .RectLabelsListContent { 10 | display: flex; 11 | flex-direction: column; 12 | flex-wrap: nowrap; 13 | justify-content: flex-start; 14 | align-items: center; 15 | align-content: flex-start; 16 | } 17 | } -------------------------------------------------------------------------------- /src/views/EditorView/SideNavigationBar/SideNavigationBar.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .SideNavigationBar { 4 | align-self: stretch; 5 | background-color: $darkThemeSecondColor; 6 | 7 | display: flex; 8 | flex-wrap: nowrap; 9 | justify-content: flex-start; 10 | align-items: center; 11 | align-content: flex-start; 12 | 13 | &.with-context { 14 | .NavigationBarContentWrapper { 15 | background-color: $darkThemeForthColor; 16 | } 17 | } 18 | 19 | .CompanionBar { 20 | align-self: stretch; 21 | min-width: $sideNavigationBarCompanionWidth; 22 | width: $sideNavigationBarCompanionWidth; 23 | display: flex; 24 | flex-direction: column; 25 | flex-wrap: nowrap; 26 | justify-content: flex-start; 27 | align-items: center; 28 | align-content: flex-start; 29 | } 30 | 31 | .NavigationBarContentWrapper { 32 | align-self: stretch; 33 | min-width: $sideNavigationBarContentWidth; 34 | width: $sideNavigationBarContentWidth; 35 | flex-direction: column; 36 | display: flex; 37 | flex-wrap: nowrap; 38 | justify-content: flex-end; 39 | align-items: center; 40 | align-content: flex-end; 41 | } 42 | 43 | &.left { 44 | flex-direction: row; 45 | border-right: solid 1px $darkThemeFirstColor; 46 | 47 | .CompanionBar { 48 | border-right: solid 1px $darkThemeFirstColor; 49 | 50 | .VerticalEditorButton { 51 | transform: rotate(-90deg) translateY(-2px); 52 | } 53 | } 54 | } 55 | 56 | &.right { 57 | flex-direction: row-reverse; 58 | border-left: solid 1px $darkThemeFirstColor; 59 | 60 | .CompanionBar { 61 | border-left: solid 1px $darkThemeFirstColor; 62 | 63 | .VerticalEditorButton { 64 | transform: rotate(-90deg) translateY(-1px); 65 | } 66 | } 67 | } 68 | 69 | &.closed { 70 | border: none; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/views/EditorView/SideNavigationBar/SideNavigationBar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | import './SideNavigationBar.scss'; 4 | import {Direction} from "../../../data/enums/Direction"; 5 | 6 | interface IProps { 7 | direction: Direction 8 | isOpen: boolean; 9 | isWithContext?: boolean; 10 | renderCompanion?: () => any; 11 | renderContent?: () => any; 12 | } 13 | 14 | export const SideNavigationBar: React.FC = (props) => { 15 | const {direction, isOpen, isWithContext, renderContent, renderCompanion} = props; 16 | 17 | const getClassName = () => { 18 | return classNames( 19 | "SideNavigationBar", 20 | { 21 | "left": direction === Direction.LEFT, 22 | "right": direction === Direction.RIGHT, 23 | "with-context": isWithContext, 24 | "closed": !isOpen 25 | } 26 | ); 27 | }; 28 | 29 | return ( 30 |
31 |
32 | {renderCompanion && renderCompanion()} 33 |
34 | {isOpen &&
35 | {renderContent && renderContent()} 36 |
} 37 |
38 | ); 39 | }; -------------------------------------------------------------------------------- /src/views/EditorView/SideNavigationBar/TagLabelsList/TagLabelsList.scss: -------------------------------------------------------------------------------- 1 | @import '../../../../settings/Settings'; 2 | 3 | .TagLabelsList { 4 | display: flex; 5 | flex-direction: column; 6 | flex-wrap: nowrap; 7 | justify-content: center; 8 | align-items: center; 9 | align-content: center; 10 | 11 | .EmptyLabelList { 12 | display: flex; 13 | flex-direction: column; 14 | flex-wrap: nowrap; 15 | justify-content: center; 16 | align-items: center; 17 | align-content: center; 18 | text-align: center; 19 | width: 150px; 20 | color: white; 21 | font-size: 16px; 22 | user-select: none; 23 | 24 | > img { 25 | filter: brightness(0) invert(1); 26 | max-width: 80px; 27 | max-height: 80px; 28 | margin-bottom: 20px; 29 | user-select: none; 30 | } 31 | 32 | > p { 33 | &.extraBold { 34 | font-size: 16px; 35 | font-weight: 600; 36 | } 37 | } 38 | } 39 | 40 | .TagLabelsListContent { 41 | display: flex; 42 | flex-direction: row; 43 | flex-wrap: wrap; 44 | justify-content: flex-start; 45 | align-items: center; 46 | align-content: flex-start; 47 | flex: 1 1 auto; 48 | } 49 | 50 | .TagItem { 51 | margin-right: 10px; 52 | margin-bottom: 10px; 53 | padding: 5px 20px; 54 | border-radius: 3px; 55 | background-color: rgba(0, 0, 0, 0.2); 56 | font-size: 14px; 57 | font-weight: 700; 58 | cursor: pointer; 59 | user-select: none; 60 | text-decoration: none; 61 | color: white; 62 | 63 | &:hover { 64 | background-color: rgba(0, 0, 0, 0.4); 65 | } 66 | 67 | &.active { 68 | background-color: $secondaryColor; // fallback if new css variables are not supported by browser 69 | background-color: var(--leading-color); 70 | } 71 | } 72 | 73 | .ImageButton { 74 | margin: 0 0 10px 0; 75 | border-radius: 3px; 76 | background-color: rgba(0, 0, 0, 0.2); 77 | 78 | img { 79 | filter: brightness(0) invert(1); 80 | user-select: none; 81 | width: 16px; 82 | height: 16px; 83 | } 84 | 85 | &:hover { 86 | background-color: rgba(0, 0, 0, 0.4); 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /src/views/EditorView/StateBar/StateBar.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .StateBar { 4 | align-self: stretch; 5 | height: $stateBarHeight; 6 | background-color: $darkThemeFirstColor; 7 | 8 | display: flex; 9 | flex-direction: row; 10 | flex-wrap: nowrap; 11 | justify-content: flex-start; 12 | align-items: center; 13 | align-content: flex-start; 14 | 15 | .done { 16 | align-self: stretch; 17 | background-color: $secondaryColor; // fallback if new css variables are not supported by browser 18 | background-color: var(--leading-color); 19 | transition: width 0.4s ease-out; 20 | } 21 | } -------------------------------------------------------------------------------- /src/views/EditorView/StateBar/StateBar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './StateBar.scss'; 3 | import {ImageData} from "../../../store/labels/types"; 4 | import {AppState} from "../../../store"; 5 | import {connect} from "react-redux"; 6 | import {LabelType} from "../../../data/enums/LabelType"; 7 | 8 | interface IProps { 9 | imagesData: ImageData[]; 10 | activeLabelType: LabelType; 11 | } 12 | 13 | const StateBar: React.FC = ({imagesData, activeLabelType}) => { 14 | 15 | const pointLabeledImages = imagesData.reduce((currentCount: number, currentImage: ImageData) => { 16 | return currentCount + (currentImage.labelPoints.length > 0 ? 1 : 0); 17 | }, 0); 18 | 19 | const rectLabeledImages = imagesData.reduce((currentCount: number, currentImage: ImageData) => { 20 | return currentCount + (currentImage.labelRects.length > 0 ? 1 : 0); 21 | }, 0); 22 | 23 | const polygonLabeledImages = imagesData.reduce((currentCount: number, currentImage: ImageData) => { 24 | return currentCount + (currentImage.labelPolygons.length > 0 ? 1 : 0); 25 | }, 0); 26 | 27 | const lineLabeledImages = imagesData.reduce((currentCount: number, currentImage: ImageData) => { 28 | return currentCount + (currentImage.labelLines.length > 0 ? 1 : 0); 29 | }, 0); 30 | 31 | const tagLabeledImages = imagesData.reduce((currentCount: number, currentImage: ImageData) => { 32 | return currentCount + (currentImage.labelNameIds.length !== 0 ? 1 : 0); 33 | }, 0); 34 | 35 | const getProgress = () => { 36 | switch (activeLabelType) { 37 | case LabelType.POINT: 38 | return (100 * pointLabeledImages) / imagesData.length; 39 | case LabelType.RECT: 40 | return (100 * rectLabeledImages) / imagesData.length; 41 | case LabelType.POLYGON: 42 | return (100 * polygonLabeledImages) / imagesData.length; 43 | case LabelType.LINE: 44 | return (100 * lineLabeledImages) / imagesData.length; 45 | case LabelType.IMAGE_RECOGNITION: 46 | return (100 * tagLabeledImages) / imagesData.length; 47 | default: 48 | return 0; 49 | } 50 | }; 51 | 52 | return ( 53 |
54 |
58 |
59 | ); 60 | }; 61 | 62 | const mapDispatchToProps = {}; 63 | 64 | const mapStateToProps = (state: AppState) => ({ 65 | imagesData: state.labels.imagesData, 66 | activeLabelType: state.labels.activeLabelType 67 | }); 68 | 69 | export default connect( 70 | mapStateToProps, 71 | mapDispatchToProps 72 | )(StateBar); -------------------------------------------------------------------------------- /src/views/EditorView/TopNavigationBar/DropDownMenu/DropDownMenu.scss: -------------------------------------------------------------------------------- 1 | @import '../../../../settings/Settings'; 2 | 3 | .DropDownMenuWrapper { 4 | align-self: stretch; 5 | display: flex; 6 | flex-wrap: nowrap; 7 | justify-content: center; 8 | align-items: center; 9 | align-content: center; 10 | 11 | .DropDownMenuContent { 12 | z-index: 10000; 13 | position: absolute; 14 | background-color: $darkThemeSecondColor; 15 | width: 250px; 16 | border-bottom-left-radius: 3px; 17 | border-bottom-right-radius: 3px; 18 | border-bottom: solid 1px $darkThemeFirstColor; 19 | border-left: solid 1px $darkThemeFirstColor; 20 | border-right: solid 1px $darkThemeFirstColor; 21 | box-shadow: 0px 4px 8px 0px rgba(0,0,0,0.75); 22 | 23 | display: flex; 24 | flex-direction: column; 25 | flex-wrap: nowrap; 26 | justify-content: flex-start; 27 | align-items: flex-start; 28 | align-content: flex-start; 29 | 30 | .DropDownMenuContentOption { 31 | display: flex; 32 | flex-direction: row; 33 | flex-wrap: nowrap; 34 | justify-content: flex-start; 35 | align-items: center; 36 | align-content: center; 37 | cursor: pointer; 38 | align-self: stretch; 39 | height: 40px; 40 | 41 | .Marker { 42 | height: 36px; 43 | width: 5px; 44 | background-color: transparent; 45 | transition: background-color 0.3s ease-in-out, transform 0.3s; 46 | } 47 | 48 | img { 49 | max-width: 18px; 50 | max-height: 18px; 51 | margin: 0 20px; 52 | filter: brightness(0) invert(1); 53 | } 54 | 55 | &:hover { 56 | background-color: rgba(0, 0, 0, 0.1); 57 | 58 | .Marker { 59 | background-color: $darkThemeThirdColor; 60 | } 61 | 62 | &.active { 63 | .Marker { 64 | background-color: $secondaryColor; 65 | } 66 | } 67 | } 68 | } 69 | } 70 | 71 | .DropDownMenuTab { 72 | display: flex; 73 | flex-wrap: nowrap; 74 | justify-content: center; 75 | align-items: center; 76 | align-content: center; 77 | padding: 0 10px; 78 | border-radius: 3px; 79 | align-self: stretch; 80 | cursor: pointer; 81 | font-weight: 700; 82 | transition: 0.3s ease-in-out; 83 | 84 | &:not(:last-child) { 85 | margin-right: 4px; 86 | } 87 | 88 | img { 89 | width: 15px; 90 | height: 15px; 91 | margin-right: 10px; 92 | user-select: none; 93 | filter: brightness(0) invert(1); 94 | } 95 | 96 | &:hover { 97 | border-radius: 3px; 98 | background-color: rgba(0, 0, 0, 0.5); 99 | } 100 | 101 | &.active { 102 | border-radius: 3px; 103 | background-color: rgba(0, 0, 0, 1.0); 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /src/views/EditorView/VerticalEditorButton/VerticalEditorButton.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .VerticalEditorButton { 4 | height: $sideNavigationBarCompanionWidth; 5 | width: 80px; 6 | background-color: transparent; 7 | transition: 0.3s ease-in-out; 8 | transform: rotate(-90deg); 9 | transform-origin: 55px 25px; 10 | margin-bottom: 60px; 11 | user-select: none; 12 | cursor: pointer; 13 | 14 | display: flex; 15 | flex-direction: row; 16 | flex-wrap: nowrap; 17 | justify-content: center; 18 | align-items: center; 19 | align-content: center; 20 | 21 | color: white; 22 | font-size: 13px; 23 | 24 | img { 25 | width: 15px; 26 | height: 15px; 27 | filter: brightness(0) invert(1); 28 | margin-right: 5px; 29 | user-select: none; 30 | } 31 | 32 | &:hover { 33 | background-color: $secondaryColor; // fallback if new css variables are not supported by browser 34 | background-color: var(--leading-color); 35 | } 36 | 37 | &.active { 38 | background-color: $secondaryColor; // fallback if new css variables are not supported by browser 39 | background-color: var(--leading-color); 40 | } 41 | } -------------------------------------------------------------------------------- /src/views/EditorView/VerticalEditorButton/VerticalEditorButton.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import classNames from "classnames"; 3 | import './VerticalEditorButton.scss'; 4 | 5 | interface IProps { 6 | key?:string; 7 | label:string; 8 | onClick?:() => any; 9 | style?:React.CSSProperties; 10 | isActive?:boolean; 11 | isDisabled?:boolean; 12 | image?:string, 13 | imageAlt?:string, 14 | } 15 | 16 | export const VerticalEditorButton = (props:IProps) => { 17 | 18 | const { key, label, onClick, style, isActive, isDisabled, image, imageAlt} = props; 19 | 20 | const getClassName = () => { 21 | return classNames( 22 | "VerticalEditorButton", 23 | { 24 | "active": isActive, 25 | "disabled": isDisabled 26 | } 27 | ); 28 | }; 29 | 30 | return( 31 |
37 | {image && {imageAlt}} 42 | {label} 43 |
44 | ) 45 | }; -------------------------------------------------------------------------------- /src/views/MainView/ImagesDropZone/ImagesDropZone.scss: -------------------------------------------------------------------------------- 1 | .ImagesDropZone { 2 | opacity: 0; 3 | display: flex; 4 | flex-direction: column; 5 | flex-wrap: nowrap; 6 | justify-content: center; 7 | align-items: center; 8 | 9 | .DropZone { 10 | user-select: none; 11 | width: 400px; 12 | height: 250px; 13 | box-shadow: black 0 0 0 2px inset; 14 | border-radius: 4px; 15 | margin-bottom: 6px; 16 | 17 | display: flex; 18 | flex-direction: column; 19 | justify-items: center; 20 | justify-content: center; 21 | align-items: center; 22 | cursor: pointer; 23 | outline: none; 24 | 25 | > img { 26 | max-width: 60px; 27 | max-height: 60px; 28 | margin-bottom: 10px; 29 | user-select: none; 30 | } 31 | 32 | > input { 33 | outline: none; 34 | } 35 | 36 | > p { 37 | margin-top: 2px; 38 | margin-bottom: 0; 39 | 40 | &.extraBold { 41 | font-size: 18px; 42 | font-weight: 600; 43 | } 44 | } 45 | } 46 | 47 | .DropZoneButtons { 48 | align-self: stretch; 49 | display: flex; 50 | flex-direction: row; 51 | flex-wrap: nowrap; 52 | justify-content: space-between; 53 | align-items: center; 54 | 55 | .TextButton { 56 | width: calc(50% - 3px); 57 | display: flex; 58 | flex-direction: row; 59 | flex-wrap: nowrap; 60 | justify-content: center; 61 | align-items: center; 62 | 63 | &.disabled { 64 | box-shadow: rgba(0, 0, 0, 0.2) 0 0 0 2px inset; 65 | color: rgba(0, 0, 0, 0.2); 66 | cursor: default; 67 | 68 | &:hover { 69 | box-shadow: rgba(0, 0, 0, 0.2) 0 0 0 2px inset; 70 | color: rgba(0, 0, 0, 0.2); 71 | background-color: transparent; 72 | } 73 | } 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /src/views/PopupView/ExitProjectPopup/ExitProjectPopup.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .ExitProjectPopupContent { 4 | align-self: stretch; 5 | flex: 1; 6 | display: flex; 7 | flex-direction: column; 8 | flex-wrap: nowrap; 9 | justify-content: flex-start; 10 | align-items: center; 11 | align-content: flex-start; 12 | padding-top: 30px; 13 | 14 | .Message { 15 | align-self: stretch; 16 | color: white; 17 | font-size: 15px; 18 | padding: 0 40px 30px 40px; 19 | } 20 | } -------------------------------------------------------------------------------- /src/views/PopupView/ExitProjectPopup/ExitProjectPopup.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import './ExitProjectPopup.scss' 3 | import {GenericYesNoPopup} from "../GenericYesNoPopup/GenericYesNoPopup"; 4 | import { 5 | updateActiveImageIndex, 6 | updateActiveLabelNameId, 7 | updateFirstLabelCreatedFlag, 8 | updateImageData, 9 | updateLabelNames 10 | } from "../../../store/labels/actionCreators"; 11 | import {AppState} from "../../../store"; 12 | import {connect} from "react-redux"; 13 | import {ImageData, LabelName} from "../../../store/labels/types"; 14 | import {PopupActions} from "../../../logic/actions/PopupActions"; 15 | import {ProjectData} from "../../../store/general/types"; 16 | import {updateProjectData} from "../../../store/general/actionCreators"; 17 | 18 | interface IProps { 19 | updateActiveImageIndex: (activeImageIndex: number) => any; 20 | updateActiveLabelNameId: (activeLabelId: string) => any; 21 | updateLabelNames: (labelNames: LabelName[]) => any; 22 | updateImageData: (imageData: ImageData[]) => any; 23 | updateFirstLabelCreatedFlag: (firstLabelCreatedFlag: boolean) => any; 24 | updateProjectData: (projectData: ProjectData) => any; 25 | } 26 | 27 | const ExitProjectPopup: React.FC = (props) => { 28 | const { 29 | updateActiveLabelNameId, 30 | updateLabelNames, 31 | updateActiveImageIndex, 32 | updateImageData, 33 | updateFirstLabelCreatedFlag, 34 | updateProjectData 35 | } = props; 36 | 37 | const renderContent = () => { 38 | return( 39 |
40 |
41 | Are you sure you want to leave the editor? You will permanently lose all your progress. 42 |
43 |
44 | ) 45 | }; 46 | 47 | const onAccept = () => { 48 | updateActiveLabelNameId(null); 49 | updateLabelNames([]); 50 | updateProjectData({type: null, name: "my-project-name"}); 51 | updateActiveImageIndex(null); 52 | updateImageData([]); 53 | updateFirstLabelCreatedFlag(false); 54 | PopupActions.close(); 55 | }; 56 | 57 | const onReject = () => { 58 | PopupActions.close(); 59 | }; 60 | 61 | return( 62 | ) 70 | }; 71 | 72 | const mapDispatchToProps = { 73 | updateActiveLabelNameId, 74 | updateLabelNames, 75 | updateProjectData, 76 | updateActiveImageIndex, 77 | updateImageData, 78 | updateFirstLabelCreatedFlag 79 | }; 80 | 81 | const mapStateToProps = (state: AppState) => ({}); 82 | 83 | export default connect( 84 | mapStateToProps, 85 | mapDispatchToProps 86 | )(ExitProjectPopup); -------------------------------------------------------------------------------- /src/views/PopupView/ExportLabelsPopup/ExportLabelPopup.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .GenericLabelTypePopupContent { 4 | 5 | .RightContainer { 6 | display: flex; 7 | flex-direction: column; 8 | flex-wrap: nowrap; 9 | justify-content: flex-start; 10 | align-items: center; 11 | align-content: flex-start; 12 | 13 | .Message { 14 | align-self: stretch; 15 | color: white; 16 | font-size: 15px; 17 | padding: 30px 40px; 18 | border-bottom: solid 1px $darkThemeFirstColor; 19 | } 20 | 21 | .Options { 22 | align-self: stretch; 23 | padding: 30px 40px; 24 | 25 | display: flex; 26 | flex-direction: column; 27 | flex-wrap: nowrap; 28 | justify-content: flex-start; 29 | align-items: flex-start; 30 | align-content: flex-start; 31 | 32 | .OptionsItem { 33 | display: flex; 34 | flex-direction: row; 35 | flex-wrap: nowrap; 36 | justify-content: center; 37 | align-items: center; 38 | align-content: center; 39 | color: white; 40 | 41 | cursor: pointer; 42 | user-select: none; 43 | font-size: 15px; 44 | padding: 5px 0; 45 | 46 | > img { 47 | max-width: 20px; 48 | max-height: 20px; 49 | margin-right: 10px; 50 | filter: invert(1) brightness(35%) sepia(100%) hue-rotate(172deg) saturate(2000%); // fallback if new css variables are not supported by browser 51 | filter: invert(1) brightness(35%) sepia(100%) hue-rotate(var(--hue-value)) saturate(2000%); 52 | user-select: none; 53 | } 54 | } 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /src/views/PopupView/GenericLabelTypePopup/GenericLabelTypePopup.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .GenericLabelTypePopupContent { 4 | align-self: stretch; 5 | flex: 1; 6 | display: flex; 7 | flex-direction: row; 8 | flex-wrap: nowrap; 9 | justify-content: flex-start; 10 | align-items: center; 11 | align-content: flex-start; 12 | min-height: 300px; 13 | 14 | .LeftContainer { 15 | width: 50px; 16 | align-self: stretch; 17 | border-right: solid 1px $darkThemeFirstColor; 18 | 19 | display: flex; 20 | flex-direction: column; 21 | flex-wrap: nowrap; 22 | justify-content: flex-start; 23 | align-items: center; 24 | align-content: flex-start; 25 | padding: 3px 0; 26 | 27 | .ImageButton { 28 | transition: transform 0.3s; 29 | 30 | img { 31 | filter: brightness(0) invert(1); 32 | } 33 | 34 | &:hover { 35 | border-radius: 3px; 36 | background-color: rgba(0, 0, 0, 0.2); 37 | } 38 | 39 | &.active { 40 | border-radius: 3px; 41 | background-color: rgba(0, 0, 0, 0.4); 42 | } 43 | } 44 | } 45 | 46 | .RightContainer { 47 | align-self: stretch; 48 | flex: 1; 49 | } 50 | } -------------------------------------------------------------------------------- /src/views/PopupView/GenericYesNoPopup/GenericYesNoPopup.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .GenericYesNoPopup { 4 | max-height: 600px; 5 | max-width: 500px; 6 | width: 50%; 7 | border-radius: 5px; 8 | user-select: none; 9 | box-shadow: 0px 0px 10px 3px rgba(255, 255, 255, 0.02); 10 | 11 | display: flex; 12 | flex-direction: column; 13 | flex-wrap: nowrap; 14 | overflow: hidden; 15 | 16 | .Header { 17 | align-self: stretch; 18 | height: 40px; 19 | box-shadow: 0px 2px 15px 0px rgba(0,0,0,0.4); 20 | color: white; 21 | font-weight: 900; 22 | font-size: 18px; 23 | background-color: $darkThemeFirstColor; 24 | 25 | display: flex; 26 | flex-direction: column; 27 | flex-wrap: nowrap; 28 | justify-content: center; 29 | align-items: center; 30 | align-content: center; 31 | } 32 | 33 | .Content { 34 | align-self: stretch; 35 | font-size: 14px; 36 | background-color: $darkThemeSecondColor; 37 | flex: 1; 38 | 39 | display: flex; 40 | flex-direction: column; 41 | flex-wrap: nowrap; 42 | justify-content: center; 43 | align-items: center; 44 | align-content: center; 45 | } 46 | 47 | .Footer { 48 | padding: 0 40px; 49 | height: 80px; 50 | align-self: stretch; 51 | background-color: $darkThemeSecondColor; 52 | border-top: solid 1px $darkThemeFirstColor; 53 | 54 | display: flex; 55 | flex-direction: row; 56 | flex-wrap: nowrap; 57 | justify-content: flex-end; 58 | align-items: center; 59 | align-content: flex-end; 60 | 61 | .TextButton { 62 | margin-left: 20px; 63 | box-shadow: $secondaryColor 0 0 0 2px inset; // fallback if new css variables are not supported by browser 64 | box-shadow: var(--leading-color) 0 0 0 2px inset; 65 | background-color: $secondaryColor; // fallback if new css variables are not supported by browser 66 | background-color: var(--leading-color); 67 | color: white; // fallback if new css variables are not supported by browser 68 | color: var(--button-text-color); 69 | 70 | &:hover { 71 | background-color: transparent; 72 | color: $secondaryColor; // fallback if new css variables are not supported by browser 73 | color: var(--leading-color); 74 | } 75 | 76 | &.disabled { 77 | box-shadow: $darkThemeThirdColor 0 0 0 2px inset; 78 | background-color: $darkThemeThirdColor; 79 | cursor: default; 80 | opacity: 0.6; 81 | 82 | &:hover { 83 | box-shadow: $darkThemeThirdColor 0 0 0 2px inset; 84 | background-color: $darkThemeThirdColor; 85 | color: white; 86 | } 87 | } 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /src/views/PopupView/GenericYesNoPopup/GenericYesNoPopup.tsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect, useState} from 'react' 2 | import './GenericYesNoPopup.scss' 3 | import {TextButton} from "../../Common/TextButton/TextButton"; 4 | import {ContextManager} from "../../../logic/context/ContextManager"; 5 | import {ContextType} from "../../../data/enums/ContextType"; 6 | 7 | interface IProps { 8 | title: string; 9 | renderContent: () => any; 10 | acceptLabel?: string; 11 | onAccept?: () => any; 12 | skipAcceptButton?: boolean; 13 | disableAcceptButton?: boolean; 14 | rejectLabel?: string; 15 | onReject?: () => any; 16 | skipRejectButton?: boolean; 17 | disableRejectButton?: boolean; 18 | } 19 | 20 | export const GenericYesNoPopup: React.FC = ( 21 | { 22 | title, 23 | renderContent, 24 | acceptLabel, 25 | onAccept, 26 | skipAcceptButton, 27 | disableAcceptButton, 28 | rejectLabel, 29 | onReject, 30 | skipRejectButton, 31 | disableRejectButton 32 | }) => { 33 | 34 | const [status, setMountStatus] = useState(false); 35 | useEffect(() => { 36 | if (!status) { 37 | ContextManager.switchCtx(ContextType.POPUP); 38 | setMountStatus(true); 39 | } 40 | }, [status]); 41 | 42 | return ( 43 |
44 |
45 | {title} 46 |
47 |
48 | {renderContent()} 49 |
50 |
51 | {!skipAcceptButton && } 57 | {!skipRejectButton && } 63 |
64 |
65 | ) 66 | }; -------------------------------------------------------------------------------- /src/views/PopupView/ImportLabelPopup/ImportLabelPopup.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .GenericLabelTypePopupContent { 4 | 5 | .RightContainer { 6 | display: flex; 7 | flex-direction: column; 8 | flex-wrap: nowrap; 9 | justify-content: flex-start; 10 | align-items: center; 11 | align-content: flex-start; 12 | 13 | .FeatureInProgress { 14 | flex: 1; 15 | } 16 | 17 | .DropZone { 18 | user-select: none; 19 | align-self: stretch; 20 | border-radius: 4px; 21 | flex: 1; 22 | color: white; 23 | 24 | display: flex; 25 | flex-direction: column; 26 | justify-items: center; 27 | justify-content: center; 28 | align-items: center; 29 | cursor: pointer; 30 | outline: none; 31 | 32 | &:hover { 33 | background-color: rgba(0, 0, 0, 0.03); 34 | } 35 | 36 | > img { 37 | max-width: 60px; 38 | max-height: 60px; 39 | margin-bottom: 30px; 40 | filter: brightness(0) invert(1); 41 | user-select: none; 42 | } 43 | 44 | > input { 45 | outline: none; 46 | } 47 | 48 | > p { 49 | margin-top: 2px; 50 | margin-bottom: 0; 51 | 52 | &.extraBold { 53 | font-size: 18px; 54 | font-weight: 600; 55 | } 56 | } 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /src/views/PopupView/LoadLabelNamesPopup/LoadLabelNamesPopup.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .LoadLabelsPopupContent { 4 | display: flex; 5 | flex-direction: column; 6 | flex-wrap: nowrap; 7 | justify-content: center; 8 | align-items: center; 9 | align-content: center; 10 | padding-top: 30px; 11 | flex: 1; 12 | min-height: 450px; 13 | 14 | .Message { 15 | align-self: stretch; 16 | color: white; 17 | font-size: 15px; 18 | padding: 0 40px 30px 40px; 19 | border-bottom: solid 1px $darkThemeFirstColor; 20 | } 21 | 22 | .DropZone { 23 | user-select: none; 24 | align-self: stretch; 25 | border-radius: 4px; 26 | flex: 1; 27 | color: white; 28 | 29 | display: flex; 30 | flex-direction: column; 31 | justify-items: center; 32 | justify-content: center; 33 | align-items: center; 34 | cursor: pointer; 35 | outline: none; 36 | 37 | &:hover { 38 | background-color: rgba(0, 0, 0, 0.03); 39 | } 40 | 41 | > img { 42 | max-width: 60px; 43 | max-height: 60px; 44 | margin-bottom: 30px; 45 | filter: brightness(0) invert(1); 46 | user-select: none; 47 | } 48 | 49 | > input { 50 | outline: none; 51 | } 52 | 53 | > p { 54 | margin-top: 2px; 55 | margin-bottom: 0; 56 | 57 | &.extraBold { 58 | font-size: 18px; 59 | font-weight: 600; 60 | } 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /src/views/PopupView/LoadModelPopup/LoadModelPopup.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .LoadModelPopupContent { 4 | display: flex; 5 | flex-direction: column; 6 | flex-wrap: nowrap; 7 | justify-content: center; 8 | align-items: center; 9 | align-content: center; 10 | padding-top: 30px; 11 | flex: 1; 12 | 13 | .Message { 14 | align-self: stretch; 15 | color: white; 16 | font-size: 15px; 17 | padding: 0 40px 30px 40px; 18 | } 19 | 20 | .Companion { 21 | align-self: stretch; 22 | padding-bottom: 30px; 23 | display: flex; 24 | flex-direction: column; 25 | flex-wrap: nowrap; 26 | justify-content: center; 27 | align-items: center; 28 | align-content: center; 29 | height: 150px; 30 | 31 | .Options { 32 | align-self: stretch; 33 | flex: 1; 34 | padding: 0 40px; 35 | 36 | .OptionsItem { 37 | display: flex; 38 | flex-direction: row; 39 | flex-wrap: nowrap; 40 | justify-content: flex-start; 41 | align-items: center; 42 | align-content: flex-start; 43 | color: white; 44 | 45 | cursor: pointer; 46 | user-select: none; 47 | font-size: 15px; 48 | padding: 5px 0; 49 | 50 | > img { 51 | max-width: 20px; 52 | max-height: 20px; 53 | margin-right: 10px; 54 | filter: invert(1) brightness(35%) sepia(100%) hue-rotate(172deg) saturate(2000%); // fallback if new css variables are not supported by browser 55 | filter: invert(1) brightness(35%) sepia(100%) hue-rotate(var(--hue-value)) saturate(2000%); 56 | user-select: none; 57 | } 58 | } 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /src/views/PopupView/LoadMoreImagesPopup/LoadMoreImagesPopup.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .LoadMoreImagesPopupContent { 4 | display: flex; 5 | flex-direction: column; 6 | flex-wrap: nowrap; 7 | justify-content: center; 8 | align-items: center; 9 | align-content: center; 10 | flex: 1; 11 | min-height: 300px; 12 | align-self: stretch; 13 | 14 | .DropZone { 15 | user-select: none; 16 | align-self: stretch; 17 | border-radius: 4px; 18 | flex: 1; 19 | color: white; 20 | 21 | display: flex; 22 | flex-direction: column; 23 | justify-items: center; 24 | justify-content: center; 25 | align-items: center; 26 | cursor: pointer; 27 | outline: none; 28 | 29 | &:hover { 30 | background-color: rgba(0, 0, 0, 0.03); 31 | } 32 | 33 | > img { 34 | max-width: 60px; 35 | max-height: 60px; 36 | margin-bottom: 30px; 37 | filter: brightness(0) invert(1); 38 | user-select: none; 39 | } 40 | 41 | > input { 42 | outline: none; 43 | } 44 | 45 | > p { 46 | margin-top: 2px; 47 | margin-bottom: 0; 48 | 49 | &.extraBold { 50 | font-size: 18px; 51 | font-weight: 600; 52 | } 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /src/views/PopupView/PopupView.scss: -------------------------------------------------------------------------------- 1 | @import '../../settings/_Settings'; 2 | 3 | .PopupView { 4 | position: absolute; 5 | height: 100vh; 6 | width: 100vw; 7 | margin: 0; 8 | padding: 0; 9 | z-index: 1000; 10 | 11 | display: flex; 12 | flex-direction: column; 13 | flex-wrap: nowrap; 14 | justify-content: center; 15 | align-items: center; 16 | align-content: center; 17 | } -------------------------------------------------------------------------------- /src/views/PopupView/PopupView.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './PopupView.scss'; 3 | import { PopupWindowType } from "../../data/enums/PopupWindowType"; 4 | import { AppState } from "../../store"; 5 | import { connect } from "react-redux"; 6 | import LoadLabelsPopup from "./LoadLabelNamesPopup/LoadLabelNamesPopup"; 7 | import InsertLabelNamesPopup from "./InsertLabelNamesPopup/InsertLabelNamesPopup"; 8 | import ExitProjectPopup from "./ExitProjectPopup/ExitProjectPopup"; 9 | import LoadMoreImagesPopup from "./LoadMoreImagesPopup/LoadMoreImagesPopup"; 10 | import { LoadModelPopup } from "./LoadModelPopup/LoadModelPopup"; 11 | import SuggestLabelNamesPopup from "./SuggestLabelNamesPopup/SuggestLabelNamesPopup"; 12 | import { CSSHelper } from "../../logic/helpers/CSSHelper"; 13 | import { ClipLoader } from "react-spinners"; 14 | import ImportLabelPopup from "./ImportLabelPopup/ImportLabelPopup"; 15 | import ExportLabelPopup from "./ExportLabelsPopup/ExportLabelPopup"; 16 | 17 | interface IProps { 18 | activePopupType: PopupWindowType; 19 | } 20 | 21 | const PopupView: React.FC = ({ activePopupType }) => { 22 | 23 | const selectPopup = () => { 24 | switch (activePopupType) { 25 | case PopupWindowType.LOAD_LABEL_NAMES: 26 | return ; 27 | case PopupWindowType.EXPORT_ANNOTATIONS: 28 | return ; 29 | case PopupWindowType.IMPORT_ANNOTATIONS: 30 | return ; 31 | case PopupWindowType.INSERT_LABEL_NAMES: 32 | return ; 35 | case PopupWindowType.UPDATE_LABEL: 36 | return ; 39 | case PopupWindowType.EXIT_PROJECT: 40 | return ; 41 | case PopupWindowType.IMPORT_IMAGES: 42 | return ; 43 | case PopupWindowType.LOAD_AI_MODEL: 44 | return ; 45 | case PopupWindowType.SUGGEST_LABEL_NAMES: 46 | return ; 47 | case PopupWindowType.LOADER: 48 | return ; 53 | default: 54 | return null; 55 | } 56 | }; 57 | 58 | return ( 59 | activePopupType &&
60 | {selectPopup()} 61 |
62 | ); 63 | }; 64 | 65 | const mapStateToProps = (state: AppState) => ({ 66 | activePopupType: state.general.activePopupType 67 | }); 68 | 69 | export default connect( 70 | mapStateToProps 71 | )(PopupView); -------------------------------------------------------------------------------- /src/views/PopupView/SuggestLabelNamesPopup/SuggestLabelNamesPopup.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .SuggestLabelNamesPopupContent { 4 | display: flex; 5 | flex-direction: column; 6 | flex-wrap: nowrap; 7 | justify-content: flex-start; 8 | align-items: center; 9 | align-content: flex-start; 10 | padding-top: 30px; 11 | flex: 1; 12 | min-height: 350px; 13 | 14 | .Message { 15 | align-self: stretch; 16 | color: white; 17 | font-size: 15px; 18 | padding: 0 40px 0 40px; 19 | } 20 | 21 | .OptionsItem { 22 | display: flex; 23 | flex-direction: row; 24 | flex-wrap: nowrap; 25 | justify-content: flex-start; 26 | align-items: center; 27 | align-content: flex-start; 28 | color: white; 29 | 30 | cursor: pointer; 31 | user-select: none; 32 | font-size: 15px; 33 | padding: 5px 0; 34 | 35 | > img { 36 | max-width: 20px; 37 | max-height: 20px; 38 | margin-right: 10px; 39 | filter: invert(1) brightness(35%) sepia(100%) hue-rotate(172deg) saturate(2000%); // fallback if new css variables are not supported by browser 40 | filter: invert(1) brightness(35%) sepia(100%) hue-rotate(var(--hue-value)) saturate(2000%); 41 | user-select: none; 42 | } 43 | } 44 | 45 | .AllToggle { 46 | align-self: stretch; 47 | color: white; 48 | font-size: 15px; 49 | padding: 10px 40px 20px 40px; 50 | border-bottom: solid 1px $darkThemeFirstColor; 51 | } 52 | 53 | .LabelNamesContainer { 54 | align-self: stretch; 55 | flex: 1; 56 | 57 | display: flex; 58 | flex-direction: column; 59 | flex-wrap: nowrap; 60 | justify-content: flex-start; 61 | align-items: center; 62 | align-content: flex-start; 63 | 64 | color: white; 65 | 66 | .LabelNamesContent { 67 | margin-left: 40px; 68 | margin-right: 10px; 69 | margin-top: 20px; 70 | 71 | display: flex; 72 | flex-direction: column; 73 | flex-wrap: nowrap; 74 | justify-content: flex-start; 75 | align-items: flex-start; 76 | align-content: flex-start; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/views/SizeItUpView/SizeItUpView.scss: -------------------------------------------------------------------------------- 1 | @import '../../settings/_Settings'; 2 | 3 | .SizeItUpView { 4 | position: absolute; 5 | height: 100vh; 6 | width: 100vw; 7 | margin: 0; 8 | padding: 0; 9 | 10 | background-color: $darkThemeSecondColor; 11 | 12 | display: flex; 13 | flex-direction: column; 14 | flex-wrap: nowrap; 15 | justify-content: center; 16 | align-items: center; 17 | align-content: center; 18 | 19 | text-align: center; 20 | color: white; 21 | font-size: 18px; 22 | user-select: none; 23 | 24 | > img { 25 | filter: brightness(0) invert(1); 26 | max-width: 100px; 27 | max-height: 100px; 28 | user-select: none; 29 | margin: 25px; 30 | } 31 | 32 | > p { 33 | &.extraBold { 34 | font-size: 18px; 35 | font-weight: 600; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/views/SizeItUpView/SizeItUpView.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './SizeItUpView.scss'; 3 | import {Settings} from "../../settings/Settings"; 4 | 5 | export const SizeItUpView: React.FC = () => { 6 | return(
7 |

Ops... This window is to tight for me!

8 | {"small_window"} 13 |

Please... make it at least {Settings.EDITOR_MIN_WIDTH} x {Settings.EDITOR_MIN_HEIGHT} px.

14 |
) 15 | }; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "downlevelIteration": true, 5 | "lib": [ 6 | "dom", 7 | "dom.iterable", 8 | "esnext" 9 | ], 10 | "allowJs": true, 11 | "skipLibCheck": true, 12 | "esModuleInterop": true, 13 | "allowSyntheticDefaultImports": true, 14 | "strict": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "preserve", 22 | "strictNullChecks": false, 23 | "noImplicitReturns": true, 24 | "noImplicitThis": true, 25 | "suppressImplicitAnyIndexErrors": true, 26 | "noImplicitAny": false, 27 | "strictPropertyInitialization": false 28 | }, 29 | "include": [ 30 | "src" 31 | ] 32 | } 33 | --------------------------------------------------------------------------------