├── .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 |
65 | }
66 | {!href &&

}
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 |

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 |
19 | {labelBefore}
20 | >;
21 |
22 | const after = <>
23 |
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 |
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 &&

}
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 |

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 |
--------------------------------------------------------------------------------