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