Showing
22 changed files
with
1030 additions
and
0 deletions
ImageLabeller/.editorconfig
0 → 100644
ImageLabeller/.gitignore
0 → 100644
1 | +.DS_Store | ||
2 | +node_modules | ||
3 | +/dist | ||
4 | + | ||
5 | +# local env files | ||
6 | +.env.local | ||
7 | +.env.*.local | ||
8 | + | ||
9 | +# Log files | ||
10 | +npm-debug.log* | ||
11 | +yarn-debug.log* | ||
12 | +yarn-error.log* | ||
13 | + | ||
14 | +# Editor directories and files | ||
15 | +.idea | ||
16 | +.vscode | ||
17 | +*.suo | ||
18 | +*.ntvs* | ||
19 | +*.njsproj | ||
20 | +*.sln | ||
21 | +*.sw? | ||
22 | + | ||
23 | +#Electron-builder output | ||
24 | +/dist_electron | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
ImageLabeller/README.md
0 → 100644
1 | +# image-labeller | ||
2 | +Crop된 명패 이미지 분류 작업을 도와주는 툴. | ||
3 | + | ||
4 | +### Build | ||
5 | +``` | ||
6 | +yarn electron:build | ||
7 | +``` | ||
8 | +dist_electron/win-unpacked 폴더에서 image-labeller.exe를 실행. | ||
9 | + | ||
10 | +### 사용 설명 | ||
11 | +1. 좌측 사이드바에서 Settings 클릭. | ||
12 | +2. 결과물 CSV 파일의 이름을 지정. (저장 경로는 데이터셋이 위치한 Workspace 폴더) | ||
13 | +3. 데이터셋의 시작 파일 이름을 지정. (비어있으면 첫 파일부터 읽음) | ||
14 | +4. 데이터셋이 위치한 폴더를 선택. | ||
15 | +5. 좌측 사이드바에서 Main 클릭. | ||
16 | +6. 이미지 분류 작업 시작. | ||
17 | + | ||
18 | +* 단축키 | ||
19 | + * 방향키 (좌/우) : 이전/다음 이미지 로딩 | ||
20 | + * 숫자키 (0~9) : 호수 입력. | ||
21 | + * backspace / delete : 입력한 마지막 자리부터 제거. | ||
22 | + * s : CSV 파일로 저장. | ||
23 | + | ||
24 | +* 참고 사항 | ||
25 | + * 실제로 라벨링은 숫자 6자리로 변환됨. (hasNum, digitLen, digit1, digit2, digit3, digit4) | ||
26 | + * 아무것도 입력하지 않고 다음 이미지 로딩 시 [0, 0, 10, 10, 10, 10]으로 자동 라벨링됨. | ||
27 | +![img1](./src/img1.png) |
ImageLabeller/babel.config.js
0 → 100644
ImageLabeller/package.json
0 → 100644
1 | +{ | ||
2 | + "name": "image-labeller", | ||
3 | + "version": "0.1.0", | ||
4 | + "private": true, | ||
5 | + "scripts": { | ||
6 | + "serve": "vue-cli-service serve", | ||
7 | + "build": "vue-cli-service build", | ||
8 | + "lint": "vue-cli-service lint", | ||
9 | + "electron:build": "vue-cli-service electron:build", | ||
10 | + "electron:serve": "vue-cli-service electron:serve", | ||
11 | + "postinstall": "electron-builder install-app-deps", | ||
12 | + "postuninstall": "electron-builder install-app-deps" | ||
13 | + }, | ||
14 | + "main": "background.js", | ||
15 | + "dependencies": { | ||
16 | + "core-js": "^3.6.4", | ||
17 | + "csv-writer": "^1.6.0", | ||
18 | + "vue": "^2.6.11", | ||
19 | + "vue-router": "^3.1.6", | ||
20 | + "vuetify": "^2.2.11", | ||
21 | + "vuex": "^3.1.3" | ||
22 | + }, | ||
23 | + "devDependencies": { | ||
24 | + "@vue/cli-plugin-babel": "~4.3.0", | ||
25 | + "@vue/cli-plugin-eslint": "~4.3.0", | ||
26 | + "@vue/cli-plugin-router": "~4.3.0", | ||
27 | + "@vue/cli-plugin-vuex": "~4.3.0", | ||
28 | + "@vue/cli-service": "~4.3.0", | ||
29 | + "@vue/eslint-config-standard": "^5.1.2", | ||
30 | + "babel-eslint": "^10.1.0", | ||
31 | + "electron": "^6.0.0", | ||
32 | + "eslint": "^6.7.2", | ||
33 | + "eslint-plugin-import": "^2.20.2", | ||
34 | + "eslint-plugin-node": "^11.1.0", | ||
35 | + "eslint-plugin-promise": "^4.2.1", | ||
36 | + "eslint-plugin-standard": "^4.0.0", | ||
37 | + "eslint-plugin-vue": "^6.2.2", | ||
38 | + "node-sass": "^4.12.0", | ||
39 | + "sass": "^1.19.0", | ||
40 | + "sass-loader": "^8.0.2", | ||
41 | + "vue-cli-plugin-electron-builder": "~1.4.6", | ||
42 | + "vue-cli-plugin-vuetify": "~2.0.5", | ||
43 | + "vue-template-compiler": "^2.6.11", | ||
44 | + "vuetify-loader": "^1.3.0" | ||
45 | + }, | ||
46 | + "eslintConfig": { | ||
47 | + "root": true, | ||
48 | + "env": { | ||
49 | + "node": true | ||
50 | + }, | ||
51 | + "extends": [ | ||
52 | + "plugin:vue/essential", | ||
53 | + "@vue/standard" | ||
54 | + ], | ||
55 | + "parserOptions": { | ||
56 | + "parser": "babel-eslint" | ||
57 | + }, | ||
58 | + "rules": {} | ||
59 | + }, | ||
60 | + "browserslist": [ | ||
61 | + "> 1%", | ||
62 | + "last 2 versions", | ||
63 | + "not dead" | ||
64 | + ] | ||
65 | +} |
ImageLabeller/public/favicon.ico
0 → 100644
No preview for this file type
ImageLabeller/public/index.html
0 → 100644
1 | +<!DOCTYPE html> | ||
2 | +<html lang="en"> | ||
3 | + <head> | ||
4 | + <meta charset="utf-8"> | ||
5 | + <meta http-equiv="X-UA-Compatible" content="IE=edge"> | ||
6 | + <meta name="viewport" content="width=device-width,initial-scale=1.0"> | ||
7 | + <link rel="icon" href="<%= BASE_URL %>favicon.ico"> | ||
8 | + <title><%= htmlWebpackPlugin.options.title %></title> | ||
9 | + <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900"> | ||
10 | + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css"> | ||
11 | + </head> | ||
12 | + <body> | ||
13 | + <noscript> | ||
14 | + <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> | ||
15 | + </noscript> | ||
16 | + <div id="app"></div> | ||
17 | + <!-- built files will be auto injected --> | ||
18 | + </body> | ||
19 | +</html> |
ImageLabeller/src/App.vue
0 → 100644
1 | +<template> | ||
2 | + <v-app> | ||
3 | + | ||
4 | + <Sidebar/> | ||
5 | + | ||
6 | + <v-content> | ||
7 | + <router-view></router-view> | ||
8 | + </v-content> | ||
9 | + </v-app> | ||
10 | +</template> | ||
11 | + | ||
12 | +<script> | ||
13 | +import Sidebar from './components/Sidebar' | ||
14 | + | ||
15 | +export default { | ||
16 | + name: 'App', | ||
17 | + components: { | ||
18 | + Sidebar | ||
19 | + }, | ||
20 | + data: () => ({ | ||
21 | + // | ||
22 | + }) | ||
23 | +} | ||
24 | +</script> |
ImageLabeller/src/assets/logo.png
0 → 100644
6.69 KB
ImageLabeller/src/assets/logo.svg
0 → 100644
1 | +<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.5 100"><defs><style>.cls-1{fill:#1697f6;}.cls-2{fill:#7bc6ff;}.cls-3{fill:#1867c0;}.cls-4{fill:#aeddff;}</style></defs><title>Artboard 46</title><polyline class="cls-1" points="43.75 0 23.31 0 43.75 48.32"/><polygon class="cls-2" points="43.75 62.5 43.75 100 0 14.58 22.92 14.58 43.75 62.5"/><polyline class="cls-3" points="43.75 0 64.19 0 43.75 48.32"/><polygon class="cls-4" points="64.58 14.58 87.5 14.58 43.75 100 43.75 62.5 64.58 14.58"/></svg> |
ImageLabeller/src/assets/scss/common.scss
0 → 100644
ImageLabeller/src/background.js
0 → 100644
1 | +'use strict' | ||
2 | + | ||
3 | +import { app, protocol, BrowserWindow } from 'electron' | ||
4 | +import { | ||
5 | + createProtocol | ||
6 | + /* installVueDevtools */ | ||
7 | +} from 'vue-cli-plugin-electron-builder/lib' | ||
8 | +const isDevelopment = process.env.NODE_ENV !== 'production' | ||
9 | + | ||
10 | +// Keep a global reference of the window object, if you don't, the window will | ||
11 | +// be closed automatically when the JavaScript object is garbage collected. | ||
12 | +let win | ||
13 | + | ||
14 | +// Scheme must be registered before the app is ready | ||
15 | +protocol.registerSchemesAsPrivileged([{ scheme: 'app', privileges: { secure: true, standard: true } }]) | ||
16 | + | ||
17 | +function createWindow () { | ||
18 | + // Create the browser window. | ||
19 | + win = new BrowserWindow({ | ||
20 | + width: 1000, | ||
21 | + height: 600, | ||
22 | + titleBarStyle: 'customButtonsOnHover', | ||
23 | + webPreferences: { | ||
24 | + nodeIntegration: true, | ||
25 | + webSecurity: false | ||
26 | + } | ||
27 | + }) | ||
28 | + win.removeMenu() // Remove top toolbar | ||
29 | + | ||
30 | + if (process.env.WEBPACK_DEV_SERVER_URL) { | ||
31 | + // Load the url of the dev server if in development mode | ||
32 | + win.loadURL(process.env.WEBPACK_DEV_SERVER_URL) | ||
33 | + if (!process.env.IS_TEST) win.webContents.openDevTools() | ||
34 | + } else { | ||
35 | + createProtocol('app') | ||
36 | + // Load the index.html when not in development | ||
37 | + win.loadURL('app://./index.html') | ||
38 | + } | ||
39 | + | ||
40 | + win.on('closed', () => { | ||
41 | + win = null | ||
42 | + }) | ||
43 | +} | ||
44 | + | ||
45 | +// Quit when all windows are closed. | ||
46 | +app.on('window-all-closed', () => { | ||
47 | + // On macOS it is common for applications and their menu bar | ||
48 | + // to stay active until the user quits explicitly with Cmd + Q | ||
49 | + if (process.platform !== 'darwin') { | ||
50 | + app.quit() | ||
51 | + } | ||
52 | +}) | ||
53 | + | ||
54 | +app.on('activate', () => { | ||
55 | + // On macOS it's common to re-create a window in the app when the | ||
56 | + // dock icon is clicked and there are no other windows open. | ||
57 | + if (win === null) { | ||
58 | + createWindow() | ||
59 | + } | ||
60 | +}) | ||
61 | + | ||
62 | +// This method will be called when Electron has finished | ||
63 | +// initialization and is ready to create browser windows. | ||
64 | +// Some APIs can only be used after this event occurs. | ||
65 | +app.on('ready', async () => { | ||
66 | + if (isDevelopment && !process.env.IS_TEST) { | ||
67 | + // Install Vue Devtools | ||
68 | + // Devtools extensions are broken in Electron 6.0.0 and greater | ||
69 | + // See https://github.com/nklayman/vue-cli-plugin-electron-builder/issues/378 for more info | ||
70 | + // Electron will not launch with Devtools extensions installed on Windows 10 with dark mode | ||
71 | + // If you are not using Windows 10 dark mode, you may uncomment these lines | ||
72 | + // In addition, if the linked issue is closed, you can upgrade electron and uncomment these lines | ||
73 | + // try { | ||
74 | + // await installVueDevtools() | ||
75 | + // } catch (e) { | ||
76 | + // console.error('Vue Devtools failed to install:', e.toString()) | ||
77 | + // } | ||
78 | + | ||
79 | + } | ||
80 | + createWindow() | ||
81 | +}) | ||
82 | + | ||
83 | +// Exit cleanly on request from parent process in development mode. | ||
84 | +if (isDevelopment) { | ||
85 | + if (process.platform === 'win32') { | ||
86 | + process.on('message', data => { | ||
87 | + if (data === 'graceful-exit') { | ||
88 | + app.quit() | ||
89 | + } | ||
90 | + }) | ||
91 | + } else { | ||
92 | + process.on('SIGTERM', () => { | ||
93 | + app.quit() | ||
94 | + }) | ||
95 | + } | ||
96 | +} | ||
97 | + | ||
98 | +/************************************/ | ||
99 | +/* Custom */ | ||
100 | +/************************************/ | ||
101 | +var fs = require('fs') | ||
102 | +const ipc = require('electron').ipcMain | ||
103 | + | ||
104 | +// global variables | ||
105 | +global.workspacePath = '' | ||
106 | +global.resultFilename = '' | ||
107 | +global.startFilename = '' | ||
108 | +global.workspaceLoaded = false | ||
109 | +global.setResultFilename = function (val) { | ||
110 | + global.resultFilename = val | ||
111 | +} | ||
112 | +global.setStartFilename = function (val) { | ||
113 | + global.startFilename = val | ||
114 | +} | ||
115 | + | ||
116 | +// Variables | ||
117 | +var wsControl = { | ||
118 | + filenames: [], | ||
119 | + fileLength: 0, | ||
120 | + current: { | ||
121 | + filename: '', | ||
122 | + filepath: '', | ||
123 | + idx: -1, | ||
124 | + digits: [] | ||
125 | + } | ||
126 | +} | ||
127 | +var resultDigits = [] | ||
128 | + | ||
129 | +// functions | ||
130 | +function loadWorkspace () { | ||
131 | + var files = fs.readdirSync(global.workspacePath) | ||
132 | + files.sort((a, b) => { return Number(a.substr(0, a.length - 3)) - Number(b.substr(0, b.length - 3)) }) | ||
133 | + console.log(files) | ||
134 | + | ||
135 | + var nowStart = (global.startFilename === '') | ||
136 | + for (var i = 0; i < files.length; i++) { | ||
137 | + var _file = files[i] | ||
138 | + if (!nowStart) { | ||
139 | + if (_file === global.startFilename) nowStart = true | ||
140 | + else continue | ||
141 | + } | ||
142 | + | ||
143 | + var _suffix = _file.substr(_file.length - 4, _file.length) | ||
144 | + if (_suffix === '.png' || _suffix === '.PNG') { | ||
145 | + wsControl.filenames.push(_file) | ||
146 | + wsControl.fileLength = wsControl.filenames.length | ||
147 | + } | ||
148 | + } | ||
149 | + | ||
150 | + if (wsControl.fileLength === 0) { | ||
151 | + return false | ||
152 | + } | ||
153 | + return true | ||
154 | +} | ||
155 | + | ||
156 | +function setCurrentIdx (idx) { | ||
157 | + if (idx >= wsControl.fileLength || idx < 0) { | ||
158 | + return false | ||
159 | + } | ||
160 | + wsControl.current.idx = idx | ||
161 | + wsControl.current.filename = wsControl.filenames[idx] | ||
162 | + wsControl.current.filepath = global.workspacePath + '\\' + wsControl.filenames[idx] | ||
163 | + return true | ||
164 | +} | ||
165 | + | ||
166 | +function setCurrDigits (digits) { | ||
167 | + wsControl.current.digits = digits | ||
168 | + if (wsControl.current.idx < resultDigits.length) { | ||
169 | + resultDigits[wsControl.current.idx] = digits | ||
170 | + } else if (wsControl.current.idx >= resultDigits.length) { | ||
171 | + resultDigits.push(digits) | ||
172 | + } | ||
173 | +} | ||
174 | + | ||
175 | +function loadCurrDigits (idx) { | ||
176 | + if (idx < 0 || idx >= resultDigits.length) wsControl.current.digits = [{ id: 0, value: '' }, { id: 1, value: '' }, { id: 2, value: '' }, { id: 3, value: '' }] | ||
177 | + else wsControl.current.digits = resultDigits[idx] | ||
178 | +} | ||
179 | + | ||
180 | +function _digitsToLabels (digits) { | ||
181 | + var result = [] | ||
182 | + var len = 0 | ||
183 | + if (digits[0].value === '') return [0, 0, 10, 10, 10, 10] | ||
184 | + else { | ||
185 | + result.push(1) // hasNum | ||
186 | + result.push(0) // digitLen | ||
187 | + for (var i = 0; i < 4; i++) { | ||
188 | + if (digits[i].value !== '') { | ||
189 | + result.push(digits[i].value) | ||
190 | + len++ | ||
191 | + } else { | ||
192 | + result.push(10) | ||
193 | + } | ||
194 | + } | ||
195 | + result[1] = len | ||
196 | + } | ||
197 | + return result | ||
198 | +} | ||
199 | + | ||
200 | +// Linked with openWorkspace() in "@/views/Settings" | ||
201 | +// listen to an open-file-dialog command and sending back selected information | ||
202 | +const dialog = require('electron').dialog | ||
203 | + | ||
204 | +ipc.on('open-file-dialog', function (event) { | ||
205 | + dialog.showOpenDialog({ | ||
206 | + properties: ['openDirectory'] | ||
207 | + }, function (files) { | ||
208 | + if (files) { | ||
209 | + global.workspacePath = files[0] | ||
210 | + global.workspaceLoaded = loadWorkspace() | ||
211 | + event.sender.send('selected-file', files[0]) | ||
212 | + event.sender.send('workspace-load-event', global.workspaceLoaded) | ||
213 | + } | ||
214 | + }) | ||
215 | +}) | ||
216 | + | ||
217 | +// Image control | ||
218 | +ipc.on('set-current', function (event, data) { | ||
219 | + var ok = false | ||
220 | + | ||
221 | + if (data.key === 'prev') { | ||
222 | + setCurrDigits(data.digits) | ||
223 | + ok = setCurrentIdx(wsControl.current.idx - 1) | ||
224 | + loadCurrDigits(wsControl.current.idx) | ||
225 | + } else if (data.key === 'next') { | ||
226 | + setCurrDigits(data.digits) | ||
227 | + ok = setCurrentIdx(wsControl.current.idx + 1) | ||
228 | + loadCurrDigits(wsControl.current.idx) | ||
229 | + } | ||
230 | + | ||
231 | + if (ok) { | ||
232 | + event.sender.send('current-image-changed', wsControl.current) | ||
233 | + } | ||
234 | +}) | ||
235 | + | ||
236 | +// Export as CSV | ||
237 | +const createCsvWriter = require('csv-writer').createObjectCsvWriter | ||
238 | +ipc.on('save-to-csv', function (event) { | ||
239 | + const csvWriter = createCsvWriter({ | ||
240 | + path: global.workspacePath + '\\' + global.resultFilename, | ||
241 | + header: [ | ||
242 | + { id: 'filename', title: 'FILENAME' }, | ||
243 | + { id: 'label1', title: 'hasNum' }, | ||
244 | + { id: 'label2', title: 'digitLen' }, | ||
245 | + { id: 'label3', title: 'DIGIT1' }, | ||
246 | + { id: 'label4', title: 'DIGIT2' }, | ||
247 | + { id: 'label5', title: 'DIGIT3' }, | ||
248 | + { id: 'label6', title: 'DIGIT4' } | ||
249 | + ] | ||
250 | + }) | ||
251 | + | ||
252 | + var records = [] | ||
253 | + for (var i = 0; i < resultDigits.length; i++) { | ||
254 | + var record = { filename: '', label1: 0, label2: 0, label3: 0, label4: 0, label5: 0, label6: 0 } | ||
255 | + var labels = _digitsToLabels(resultDigits[i]) | ||
256 | + console.log(labels) | ||
257 | + record.filename = wsControl.filenames[i] | ||
258 | + record.label1 = labels[0] | ||
259 | + record.label2 = labels[1] | ||
260 | + record.label3 = labels[2] | ||
261 | + record.label4 = labels[3] | ||
262 | + record.label5 = labels[4] | ||
263 | + record.label6 = labels[5] | ||
264 | + records.push(record) | ||
265 | + } | ||
266 | + | ||
267 | + csvWriter.writeRecords(records) | ||
268 | + .then(() => { | ||
269 | + event.returnValue = 'Saved successfully.' | ||
270 | + }) | ||
271 | + .catch(function (err) { | ||
272 | + event.returnValue = String(err) | ||
273 | + }) | ||
274 | +}) |
ImageLabeller/src/components/Sidebar.vue
0 → 100644
1 | +<template> | ||
2 | + <div class="sidebar"> | ||
3 | + <!-- Side bar --> | ||
4 | + <v-navigation-drawer | ||
5 | + app | ||
6 | + dark | ||
7 | + width="220" | ||
8 | + permanent | ||
9 | + > | ||
10 | + | ||
11 | + <!-- Header --> | ||
12 | + <v-list-item> | ||
13 | + <v-list-item-content> | ||
14 | + <v-list-item-title class="title nonselectable-text"> | ||
15 | + {{ title }} | ||
16 | + </v-list-item-title> | ||
17 | + <v-list-item-subtitle class="nonselectable-text"> | ||
18 | + {{ subtitle }} | ||
19 | + </v-list-item-subtitle> | ||
20 | + </v-list-item-content> | ||
21 | + </v-list-item> | ||
22 | + | ||
23 | + <v-divider></v-divider> | ||
24 | + | ||
25 | + <!-- Sidebar Items --> | ||
26 | + <v-list dense nav> | ||
27 | + <v-list-item | ||
28 | + v-for="(item, idx) in items" | ||
29 | + :to="item.route" | ||
30 | + :key="idx" | ||
31 | + class="sidebar-link" | ||
32 | + link | ||
33 | + > | ||
34 | + <v-list-item-icon> | ||
35 | + <v-icon>{{ item.icon }}</v-icon> | ||
36 | + </v-list-item-icon> | ||
37 | + | ||
38 | + <v-list-item-content> | ||
39 | + <v-list-item-title>{{ item.title }}</v-list-item-title> | ||
40 | + </v-list-item-content> | ||
41 | + </v-list-item> | ||
42 | + </v-list> | ||
43 | + | ||
44 | + <!-- Version --> | ||
45 | + <div class="version-wrapper"> | ||
46 | + <div class="body-2">{{ version.main }}</div> | ||
47 | + <div class="caption">{{ version.build }}</div> | ||
48 | + </div> | ||
49 | + | ||
50 | + </v-navigation-drawer> | ||
51 | + </div> | ||
52 | +</template> | ||
53 | + | ||
54 | +<script> | ||
55 | +export default { | ||
56 | + name: 'Sidebar', | ||
57 | + data: () => ({ | ||
58 | + title: 'Image Labeller', | ||
59 | + subtitle: 'for KHU Capstone Design 1', | ||
60 | + items: [ | ||
61 | + { title: 'Main', icon: 'mdi-view-dashboard', route: '/main' }, | ||
62 | + { title: 'Settings', icon: 'mdi-cog-outline', route: '/settings' } | ||
63 | + ], | ||
64 | + version: { | ||
65 | + main: 'v0.7.3-alpha', | ||
66 | + build: 'build-2004080227' | ||
67 | + } | ||
68 | + }) | ||
69 | +} | ||
70 | +</script> | ||
71 | + | ||
72 | +<style lang="scss" scoped> | ||
73 | +.sidebar { | ||
74 | + .nonselectable-text { | ||
75 | + user-select: none; | ||
76 | + } | ||
77 | + .sidebar-link { | ||
78 | + -webkit-app-region: no-drag; | ||
79 | + } | ||
80 | + .version-wrapper { | ||
81 | + position: absolute; | ||
82 | + bottom: 10px; | ||
83 | + right: 10px; | ||
84 | + color: #aaa; | ||
85 | + text-align: right; | ||
86 | + } | ||
87 | +} | ||
88 | +</style> |
ImageLabeller/src/img1.png
0 → 100644
204 KB
ImageLabeller/src/main.js
0 → 100644
1 | +import Vue from 'vue' | ||
2 | +import App from './App.vue' | ||
3 | +import router from './router' | ||
4 | +import store from './store' | ||
5 | +import vuetify from './plugins/vuetify' | ||
6 | + | ||
7 | +Vue.config.productionTip = false | ||
8 | + | ||
9 | +new Vue({ | ||
10 | + router, | ||
11 | + store, | ||
12 | + vuetify, | ||
13 | + render: h => h(App) | ||
14 | +}).$mount('#app') |
ImageLabeller/src/plugins/vuetify.js
0 → 100644
ImageLabeller/src/router/index.js
0 → 100644
1 | +import Vue from 'vue' | ||
2 | +import VueRouter from 'vue-router' | ||
3 | + | ||
4 | +import Main from '../views/Main.vue' | ||
5 | +import Settings from '../views/Settings.vue' | ||
6 | + | ||
7 | +Vue.use(VueRouter) | ||
8 | + | ||
9 | +const routes = [ | ||
10 | + { | ||
11 | + path: '/', | ||
12 | + redirect: '/settings' | ||
13 | + }, | ||
14 | + { | ||
15 | + path: '/main', | ||
16 | + name: 'Main', | ||
17 | + component: Main | ||
18 | + }, | ||
19 | + { | ||
20 | + path: '/settings', | ||
21 | + name: 'Settings', | ||
22 | + component: Settings | ||
23 | + } | ||
24 | +] | ||
25 | + | ||
26 | +const router = new VueRouter({ | ||
27 | + mode: 'history', | ||
28 | + base: process.env.BASE_URL, | ||
29 | + routes, | ||
30 | + linkActiveClass: 'nav-item active' | ||
31 | +}) | ||
32 | + | ||
33 | +export default router |
ImageLabeller/src/store/index.js
0 → 100644
ImageLabeller/src/views/Main.vue
0 → 100644
1 | +<template> | ||
2 | + <div class="content"> | ||
3 | + <div class="content-wrapper" :key="$route.fullPath"> | ||
4 | + | ||
5 | + <div v-if="!workspaceLoaded" style="color: red">Workspace not loaded!</div> | ||
6 | + | ||
7 | + <div v-else :key="image.now"> | ||
8 | + <!-- Image Panel --> | ||
9 | + <div class="image-panel"> | ||
10 | + <div class="image-wrapper" :key="image.filename"> | ||
11 | + <span class="body-2">{{ image.filename }}</span> | ||
12 | + <v-img id="target-image" :src="image.filepath"></v-img> | ||
13 | + </div> | ||
14 | + </div> | ||
15 | + | ||
16 | + <v-divider></v-divider> | ||
17 | + | ||
18 | + <!-- Input Panel --> | ||
19 | + <div class="input-panel"> | ||
20 | + <v-container> | ||
21 | + <v-row justify="space-around"> | ||
22 | + <v-col | ||
23 | + v-for="digit in digits" | ||
24 | + :key="digit.id" | ||
25 | + :id="`input-digit-`+digit.id" | ||
26 | + cols="12" | ||
27 | + md="3" | ||
28 | + > | ||
29 | + <v-sheet | ||
30 | + class="pa-12" | ||
31 | + color="grey lighten-3" | ||
32 | + > | ||
33 | + <v-sheet | ||
34 | + :elevation="6" | ||
35 | + class="mx-auto digit-wrapper" | ||
36 | + height="80" | ||
37 | + width="60" | ||
38 | + > | ||
39 | + <span class="display-3">{{ digit.value }}</span> | ||
40 | + </v-sheet> | ||
41 | + </v-sheet> | ||
42 | + </v-col> | ||
43 | + </v-row> | ||
44 | + </v-container> | ||
45 | + </div> | ||
46 | + | ||
47 | + <!-- Control Panel --> | ||
48 | + <v-bottom-navigation class="control-panel"> | ||
49 | + <v-btn value="prev" @click="prevImage"> | ||
50 | + <span>Prev</span> | ||
51 | + <v-icon>mdi-skip-previous-outline</v-icon> | ||
52 | + </v-btn> | ||
53 | + | ||
54 | + <v-btn class="non-clickable"> | ||
55 | + <span>{{ image.filename }}</span> | ||
56 | + </v-btn> | ||
57 | + | ||
58 | + <v-btn value="next" @click="nextImage"> | ||
59 | + <span>Next</span> | ||
60 | + <v-icon>mdi-skip-next-outline</v-icon> | ||
61 | + </v-btn> | ||
62 | + </v-bottom-navigation> | ||
63 | + | ||
64 | + <v-snackbar | ||
65 | + v-model="snackbar.isOpened" | ||
66 | + :timeout="3000" | ||
67 | + > | ||
68 | + {{ snackbar.text }} | ||
69 | + <v-btn | ||
70 | + color="blue" | ||
71 | + text | ||
72 | + @click="snackbar.isOpened = false" | ||
73 | + > | ||
74 | + Close | ||
75 | + </v-btn> | ||
76 | + </v-snackbar> | ||
77 | + | ||
78 | + </div> | ||
79 | + </div> | ||
80 | + </div> | ||
81 | +</template> | ||
82 | + | ||
83 | +<script> | ||
84 | +export default { | ||
85 | + name: 'Main', | ||
86 | + data: () => ({ | ||
87 | + workspaceLoaded: false, | ||
88 | + currDigitID: 0, | ||
89 | + digits: [ | ||
90 | + { id: 0, value: '' }, | ||
91 | + { id: 1, value: '' }, | ||
92 | + { id: 2, value: '' }, | ||
93 | + { id: 3, value: '' } | ||
94 | + ], | ||
95 | + image: { | ||
96 | + filepath: '', | ||
97 | + filename: '', | ||
98 | + now: null | ||
99 | + }, | ||
100 | + snackbar: { | ||
101 | + isOpened: false, | ||
102 | + text: '' | ||
103 | + } | ||
104 | + }), | ||
105 | + mounted () { | ||
106 | + // Check if workspace loaded | ||
107 | + this.checkWorkspaceLoaded() | ||
108 | + | ||
109 | + // Detect current image data | ||
110 | + var vm = this | ||
111 | + const ipc = require('electron').ipcRenderer | ||
112 | + ipc.on('current-image-changed', function (event, curr) { | ||
113 | + console.log(curr) | ||
114 | + vm.image.now = curr.idx | ||
115 | + vm.image.filepath = curr.filepath | ||
116 | + vm.image.filename = curr.filename | ||
117 | + // vm.currDigitID = 0 | ||
118 | + // vm.digits = curr.digits | ||
119 | + vm.loadDigits(curr.digits) | ||
120 | + }) | ||
121 | + | ||
122 | + // Capture keydown events | ||
123 | + this._keyCapture() | ||
124 | + }, | ||
125 | + methods: { | ||
126 | + checkWorkspaceLoaded () { | ||
127 | + const remote = require('electron').remote | ||
128 | + this.workspaceLoaded = remote.getGlobal('workspaceLoaded') | ||
129 | + }, | ||
130 | + saveToCSV () { | ||
131 | + const ipc = require('electron').ipcRenderer | ||
132 | + // ipc.send('save-to-csv') | ||
133 | + var result = ipc.sendSync('save-to-csv') | ||
134 | + console.log(result) | ||
135 | + | ||
136 | + this.snackbar.text = result | ||
137 | + this.snackbar.isOpened = true | ||
138 | + }, | ||
139 | + prevImage () { | ||
140 | + this._setCurrentImage('prev') | ||
141 | + }, | ||
142 | + nextImage () { | ||
143 | + this._setCurrentImage('next') | ||
144 | + }, | ||
145 | + inputDigit (keyCode) { | ||
146 | + if (this.currDigitID > 3) return false | ||
147 | + else { | ||
148 | + this.digits[this.currDigitID].value = keyCode - 48 | ||
149 | + this.currDigitID++ | ||
150 | + return true | ||
151 | + } | ||
152 | + }, | ||
153 | + removeDigit () { | ||
154 | + if (this.currDigitID <= 0) return false | ||
155 | + else { | ||
156 | + this.digits[this.currDigitID - 1].value = '' | ||
157 | + this.currDigitID-- | ||
158 | + return true | ||
159 | + } | ||
160 | + }, | ||
161 | + loadDigits (newDigits) { | ||
162 | + var len = 0 | ||
163 | + for (var i = 0; i < 4; i++) { | ||
164 | + if (newDigits[i].value !== '') len++ | ||
165 | + else break | ||
166 | + } | ||
167 | + this.currDigitID = len | ||
168 | + this.digits = newDigits | ||
169 | + console.log(len) | ||
170 | + }, | ||
171 | + _setCurrentImage (key) { | ||
172 | + const ipc = require('electron').ipcRenderer | ||
173 | + ipc.send('set-current', { key: key, digits: this.digits }) | ||
174 | + }, | ||
175 | + _clearDigits () { | ||
176 | + for (var i = 0; i < 4; i++) this.digits[i].value = '' | ||
177 | + this.currDigitID = 0 | ||
178 | + }, | ||
179 | + _keyCapture () { | ||
180 | + var vm = this | ||
181 | + window.addEventListener('keyup', function (e) { | ||
182 | + if (e.keyCode === 37) { // Left arrow | ||
183 | + vm.prevImage() | ||
184 | + } else if (e.keyCode === 39) { // Right arrow | ||
185 | + vm.nextImage() | ||
186 | + } else if (e.keyCode === 83) { // 's' Save | ||
187 | + vm.saveToCSV() | ||
188 | + } else if (e.keyCode >= 48 && e.keyCode <= 57) { // Numbers | ||
189 | + vm.inputDigit(e.keyCode) | ||
190 | + } else if (e.keyCode === 8 || e.keyCode === 46) { // Remove | ||
191 | + vm.removeDigit() | ||
192 | + } | ||
193 | + }) | ||
194 | + } | ||
195 | + } | ||
196 | +} | ||
197 | +</script> | ||
198 | + | ||
199 | +<style lang="scss" scoped> | ||
200 | +@import "@/assets/scss/common.scss"; | ||
201 | + | ||
202 | +.image-panel { | ||
203 | + .image-wrapper { | ||
204 | + width: 240px; | ||
205 | + margin: 10px auto; | ||
206 | + | ||
207 | + #target-image { | ||
208 | + width: 240px; | ||
209 | + height: 240px; | ||
210 | + background-color: #000000; | ||
211 | + } | ||
212 | + } | ||
213 | +} | ||
214 | + | ||
215 | +.input-panel { | ||
216 | + .digit-wrapper { | ||
217 | + text-align: center; | ||
218 | + vertical-align: middle; | ||
219 | + display: table-cell; | ||
220 | + } | ||
221 | +} | ||
222 | + | ||
223 | +.control-panel { | ||
224 | + position: absolute; | ||
225 | + bottom: 0; | ||
226 | + padding-top: 5px; | ||
227 | + | ||
228 | + .non-clickable { | ||
229 | + cursor: default; | ||
230 | + } | ||
231 | +} | ||
232 | +</style> |
ImageLabeller/src/views/Settings.vue
0 → 100644
1 | +<template> | ||
2 | + <div class="content"> | ||
3 | + <div class="content-wrapper" :key="$route.fullPath"> | ||
4 | + | ||
5 | + <!-- Output Control --> | ||
6 | + <v-expansion-panels | ||
7 | + class="control-panel" | ||
8 | + v-model="panels[0]" | ||
9 | + > | ||
10 | + <v-expansion-panel> | ||
11 | + <v-expansion-panel-header>Output File Control</v-expansion-panel-header> | ||
12 | + <v-expansion-panel-content> | ||
13 | + <v-text-field | ||
14 | + v-model="output" | ||
15 | + label="Output Filename" | ||
16 | + prepend-icon="mdi-file-delimited-outline" | ||
17 | + > | ||
18 | + {{ output }} | ||
19 | + </v-text-field> | ||
20 | + <div class="control-button-wrapper"> | ||
21 | + <v-btn class="control-button" | ||
22 | + small | ||
23 | + color="primary" | ||
24 | + @click="saveOutputFilename" | ||
25 | + > | ||
26 | + Save Filename | ||
27 | + </v-btn> | ||
28 | + </div> | ||
29 | + </v-expansion-panel-content> | ||
30 | + </v-expansion-panel> | ||
31 | + </v-expansion-panels> | ||
32 | + | ||
33 | + <v-divider></v-divider> | ||
34 | + | ||
35 | + <!-- Start Filename Control --> | ||
36 | + <v-expansion-panels | ||
37 | + class="start-filename-panel" | ||
38 | + v-model="panels[1]" | ||
39 | + > | ||
40 | + <v-expansion-panel> | ||
41 | + <v-expansion-panel-header>Start Filename Control</v-expansion-panel-header> | ||
42 | + <v-expansion-panel-content> | ||
43 | + <v-text-field | ||
44 | + v-model="startFilename" | ||
45 | + label="Start Filename" | ||
46 | + prepend-icon="mdi-file-delimited-outline" | ||
47 | + > | ||
48 | + {{ startFilename }} | ||
49 | + </v-text-field> | ||
50 | + <div class="control-button-wrapper"> | ||
51 | + <v-btn class="control-button" | ||
52 | + small | ||
53 | + color="primary" | ||
54 | + @click="saveStartFilename" | ||
55 | + > | ||
56 | + Save Filename | ||
57 | + </v-btn> | ||
58 | + </div> | ||
59 | + </v-expansion-panel-content> | ||
60 | + </v-expansion-panel> | ||
61 | + </v-expansion-panels> | ||
62 | + | ||
63 | + <v-divider></v-divider> | ||
64 | + | ||
65 | + <!-- Workspace Control --> | ||
66 | + <v-expansion-panels | ||
67 | + class="control-panel" | ||
68 | + v-model="panels[2]" | ||
69 | + > | ||
70 | + <v-expansion-panel> | ||
71 | + <v-expansion-panel-header>Workspace Control</v-expansion-panel-header> | ||
72 | + <v-expansion-panel-content> | ||
73 | + <span v-if='workspace !== null' class="font-italic font-weight-light">{{ workspace }}</span> | ||
74 | + <span v-else class="font-italic font-weight-light">Please open your workspace</span> | ||
75 | + <div class="control-button-wrapper"> | ||
76 | + <v-btn | ||
77 | + class="control-button" | ||
78 | + id="btn-workspace" | ||
79 | + small | ||
80 | + color="primary" | ||
81 | + @click="openWorkspace" | ||
82 | + > | ||
83 | + Open Workspace | ||
84 | + </v-btn> | ||
85 | + </div> | ||
86 | + </v-expansion-panel-content> | ||
87 | + </v-expansion-panel> | ||
88 | + </v-expansion-panels> | ||
89 | + | ||
90 | + <v-snackbar | ||
91 | + v-model="snackbar.isOpened" | ||
92 | + :timeout="4000" | ||
93 | + > | ||
94 | + {{ snackbar.text }} | ||
95 | + <v-btn | ||
96 | + color="blue" | ||
97 | + text | ||
98 | + @click="snackbar.isOpened = false" | ||
99 | + > | ||
100 | + Close | ||
101 | + </v-btn> | ||
102 | + </v-snackbar> | ||
103 | + | ||
104 | + </div> | ||
105 | + </div> | ||
106 | +</template> | ||
107 | + | ||
108 | +<script> | ||
109 | +export default { | ||
110 | + name: 'settings', | ||
111 | + data: () => ({ | ||
112 | + panels: [0, 0, 0], | ||
113 | + workspace: null, | ||
114 | + output: 'result.csv', | ||
115 | + startFilename: '0.png', | ||
116 | + snackbar: { | ||
117 | + isOpened: false, | ||
118 | + text: '' | ||
119 | + } | ||
120 | + }), | ||
121 | + mounted () { | ||
122 | + this.getSetting() | ||
123 | + }, | ||
124 | + methods: { | ||
125 | + getSetting () { | ||
126 | + const remote = require('electron').remote | ||
127 | + | ||
128 | + var _workspace = remote.getGlobal('workspacePath') | ||
129 | + if (_workspace !== '') this.workspace = _workspace | ||
130 | + else this.workspace = null | ||
131 | + | ||
132 | + var _result = remote.getGlobal('resultFilename') | ||
133 | + if (_result !== '') this.output = _result | ||
134 | + else { | ||
135 | + this.output = 'result.csv' | ||
136 | + remote.getGlobal('setResultFilename')(this.output) | ||
137 | + } | ||
138 | + }, | ||
139 | + openWorkspace () { | ||
140 | + var vm = this | ||
141 | + | ||
142 | + const ipc = require('electron').ipcRenderer | ||
143 | + | ||
144 | + ipc.send('open-file-dialog') | ||
145 | + ipc.on('selected-file', function (event, path) { | ||
146 | + vm.workspace = `${path}` | ||
147 | + }) | ||
148 | + ipc.on('workspace-load-event', function (event, ok) { | ||
149 | + if (ok === true) { | ||
150 | + vm.snackbar.text = 'Workspace loaded successfully!' | ||
151 | + vm.snackbar.isOpened = true | ||
152 | + } else { | ||
153 | + vm.snackbar.text = 'Workspace loaded failed!' | ||
154 | + vm.snackbar.isOpened = true | ||
155 | + } | ||
156 | + }) | ||
157 | + }, | ||
158 | + saveOutputFilename () { | ||
159 | + const remote = require('electron').remote | ||
160 | + remote.getGlobal('setResultFilename')(this.output) | ||
161 | + }, | ||
162 | + saveStartFilename () { | ||
163 | + const remote = require('electron').remote | ||
164 | + remote.getGlobal('setStartFilename')(this.startFilename) | ||
165 | + } | ||
166 | + } | ||
167 | +} | ||
168 | +</script> | ||
169 | + | ||
170 | +<style lang="scss" scoped> | ||
171 | +@import "@/assets/scss/common.scss"; | ||
172 | + | ||
173 | +.control-panel { | ||
174 | + margin: 10px 0; | ||
175 | +} | ||
176 | + | ||
177 | +.control-button-wrapper { | ||
178 | + float: right; | ||
179 | + .control-button { | ||
180 | + margin-right: 10px; | ||
181 | + } | ||
182 | +} | ||
183 | +</style> |
ImageLabeller/vue.config.js
0 → 100644
ImageLabeller/yarn.lock
0 → 100644
This diff could not be displayed because it is too large.
-
Please register or login to post a comment