update research

This commit is contained in:
Fam Zheng 2025-02-23 18:47:21 +00:00
parent 28dac7da3f
commit 004a0174dd
26 changed files with 15264 additions and 2 deletions

View File

@ -1,3 +1,3 @@
#!/bin/bash #!/bin/bash
rsync -arP zy:/data/emblem-research/ data/raw rsync -azrP zy:/data/emblem-research/ data/raw

28
research/index.css Normal file
View File

@ -0,0 +1,28 @@
div.frames div.frame-container {
border: 4px solid green;
border-radius: 10px;
padding: 5px;
margin: 10px;
}
img.frame {
max-width: 400px;
max-height: 400px;
display: inline-block;
}
img.qr {
max-width: 200px;
max-height: 200px;
display: inline-block;
}
img.roi {
max-width: 100px;
max-height: 100px;
display: inline-block;
}
h3.warning {
color: #ff0000
}

2263
research/index.html Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,4 @@
> 1%
last 2 versions
not dead
not ie 11

View File

@ -0,0 +1,17 @@
module.exports = {
root: true,
env: {
node: true
},
'extends': [
'plugin:vue/vue3-essential',
'eslint:recommended'
],
parserOptions: {
parser: '@babel/eslint-parser'
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
}
}

23
research/research-tools/.gitignore vendored Normal file
View File

@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@ -0,0 +1,24 @@
# research-tools
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

View File

@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}

12279
research/research-tools/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,25 @@
{
"name": "research-tools",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"core-js": "^3.8.3",
"vue": "^3.2.13",
"vue-router": "^4.0.3"
},
"devDependencies": {
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

View File

@ -0,0 +1,10 @@
<template>
<nav>
<router-link to="/">Home</router-link> |
<router-link to="/folders">Folders</router-link>
</nav>
<router-view/>
</template>
<style>
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -0,0 +1,59 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br>
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router" target="_blank" rel="noopener">router</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
</ul>
<h3>Essential Links</h3>
<ul>
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>

View File

@ -0,0 +1,5 @@
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
createApp(App).use(router).mount('#app')

View File

@ -0,0 +1,42 @@
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const routes = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
},
{
path: '/folders',
name: 'folders',
component: () => import(/* webpackChunkName: "folders" */ '../views/FoldersView.vue')
},
{
path: '/folder/:folder',
name: 'folder',
props: true,
component: () => import(/* webpackChunkName: "folder" */ '../views/FolderView.vue')
},
{
path: '/frame/:folder/:name',
name: 'frame',
props: true,
component: () => import(/* webpackChunkName: "frame" */ '../views/FrameView.vue')
},
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
export default router

View File

@ -0,0 +1,5 @@
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>

View File

@ -0,0 +1,229 @@
<template>
<div>
<h1>Folder: {{ folder }}</h1>
<div class="selection-controls">
<button @click="selectAll">Select All</button>
<button @click="selectNone">Select None</button>
<button @click="selectInverse">Inverse Selection</button>
<button @click="deleteSelected" class="delete-button">Delete Selected</button>
<input v-model="targetFolder" placeholder="Target folder name">
<button @click="copyToFolder">Copy to Folder</button>
<div class="size-control">
<label for="imageSize">Image Size: {{ imageSize }}px</label>
<input
type="range"
id="imageSize"
v-model="imageSize"
min="100"
max="1000"
step="10">
</div>
</div>
<div class="datapoints">
<div class="datapoint" v-for="datapoint in datapoints" :key="datapoint.path">
<div class="datapoint-container">
<input type="checkbox" v-model="selectedDatapoints[datapoint.path]">
<router-link target="_blank" :to="`/datapoint/${folder}/${datapoint.basename}`" @click.prevent="showModal(datapoint)">
<img :title="datapoint_title(datapoint)" class="datapoint-image" :src="datapoint.image_data_url">
</router-link>
</div>
</div>
</div>
<!-- Add modal component -->
<div v-if="selectedImage" class="modal" @click="closeModal">
<img :src="selectedImage.image_data_url" class="modal-image">
</div>
</div>
</template>
<script>
export default {
name: 'FolderView',
props: ['folder'],
data() {
return {
datapoints: [],
selectedDatapoints: {},
selectedDatapointPaths: [],
targetFolder: '',
imageSize: parseInt(localStorage.getItem('imageSize')) || 200,
selectedImage: null,
}
},
watch: {
imageSize(newSize) {
localStorage.setItem('imageSize', newSize);
}
},
mounted() {
this.reload();
},
methods: {
async reload() {
const res = await fetch(`/api/datapoints?folder=${this.folder}`);
const data = await res.json();
console.log(data);
this.datapoints = data.datapoints.sort((a, b) => a.session_id - b.session_id);
// Initialize selection state for new datapoints
for (const datapoint of this.datapoints) {
this.selectedDatapoints[datapoint.path] = false;
}
},
selectAll() {
for (const datapoint of this.datapoints) {
this.selectedDatapoints[datapoint.path] = true;
}
},
selectNone() {
for (const datapoint of this.datapoints) {
this.selectedDatapoints[datapoint.path] = false;
}
},
selectInverse() {
for (const datapoint of this.datapoints) {
this.selectedDatapoints[datapoint.path] = !this.selectedDatapoints[datapoint.path];
}
},
datapoint_title(datapoint) {
return `${datapoint.basename} ${datapoint.session_id} ${datapoint.seq_num}`;
},
async deleteSelected() {
// Filter out selected frames
this.datapoints = this.datapoints.filter(datapoint => !this.selectedDatapoints[datapoint.path]);
// Reset selection state for deleted frames
this.selectedDatapoints = Object.fromEntries(
this.datapoints.map(datapoint => [datapoint.path, false])
);
},
async copyToFolder() {
if (!this.targetFolder) {
alert('Please enter a target folder name');
return;
}
const selectedPaths = this.datapoints
.filter(datapoint => this.selectedDatapoints[datapoint.path])
.map(datapoint => datapoint.path);
if (selectedPaths.length === 0) {
alert('Please select datapoints to copy');
return;
}
try {
const response = await fetch('/api/copy-to-folder', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
paths: selectedPaths,
target_folder: this.targetFolder
}),
});
if (!response.ok) {
throw new Error('Failed to copy datapoints');
}
// Navigate to the new folder
this.$router.push(`/folder/${this.targetFolder}`);
} catch (error) {
alert('Error copying datapoints: ' + error.message);
}
},
showModal(datapoint) {
this.selectedImage = datapoint;
},
closeModal() {
this.selectedImage = null;
},
},
}
</script>
<style>
.datapoint {
position: relative;
margin: 5px;
}
.datapoint-container {
position: relative;
}
.datapoint input[type="checkbox"] {
position: absolute;
top: 10px;
left: 10px;
width: 30px;
height: 30px;
opacity: 0;
z-index: 1;
cursor: pointer;
transition: opacity 0.2s;
}
.datapoint:hover input[type="checkbox"],
.datapoint input[type="checkbox"]:checked {
opacity: 1;
}
.datapoint-image {
height: v-bind(imageSize + 'px');
display: block;
}
div.datapoints {
display: flex;
flex-wrap: wrap;
}
div.datapoints img {
margin: 5px;
}
.selection-controls {
margin: 10px 0;
}
.selection-controls button {
margin-right: 10px;
}
.delete-button {
background-color: #ff4444;
color: white;
}
.selection-controls input {
margin-right: 10px;
padding: 5px;
}
.size-control {
display: inline-flex;
align-items: center;
gap: 10px;
margin-left: 10px;
}
.size-control input[type="range"] {
width: 150px;
}
/* Add modal styles */
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.9);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
cursor: pointer;
}
.modal-image {
max-width: 90%;
max-height: 90vh;
object-fit: contain;
}
</style>

View File

@ -0,0 +1,34 @@
<template>
<div>
<h1>Folders</h1>
<ul>
<li v-for="folder in folders" :key="folder.folder">
<router-link :to="`/folder/${folder.folder}`">{{ folder.folder }}</router-link>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'FoldersView',
data() {
return {
folders: [],
}
},
methods: {
async reload() {
const res = await fetch('/api/folders');
const data = await res.json();
console.log(data);
this.folders = data.folders.sort((a, b) => new Date(b.folder) - new Date(a.folder));
},
},
mounted() {
this.reload();
},
}
</script>
<style scoped></style>

View File

@ -0,0 +1,35 @@
<template>
<div>
<h1>Frame</h1>
<div v-if="frame">
session_id={{ frame.session_id }}
seq_num={{ frame.seq_num }}
<a :href="`/api/frame-image?folder=${folder}&name=${name}`" target="_blank">
<img :src="frame.image_data_url" />
</a>
</div>
</div>
</template>
<script>
export default {
name: 'FrameView',
props: ['folder', 'name'],
data() {
return {
frame: null,
}
},
mounted() {
this.loadFrame();
},
methods: {
async loadFrame() {
const res = await fetch(`/api/frame?folder=${this.folder}&name=${this.name}`);
const data = await res.json();
console.log(data);
this.frame = data.frame;
}
}
}
</script>

View File

@ -0,0 +1,18 @@
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>
<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'
export default {
name: 'HomeView',
components: {
HelloWorld
}
}
</script>

View File

@ -0,0 +1,11 @@
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
devServer: {
proxy: {
'/api': {
target: 'http://localhost:26966',
},
},
},
})

View File

@ -1,15 +1,26 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from flask import Flask, request, jsonify from flask import Flask, request, jsonify, send_file
from datetime import datetime from datetime import datetime
import os import os
import json import json
import uuid import uuid
import shutil
import base64
from io import BytesIO
app = Flask(__name__) app = Flask(__name__)
DATA_DIR = os.environ.get('DATA_DIR', '/var/tmp/themblem-research') DATA_DIR = os.environ.get('DATA_DIR', '/var/tmp/themblem-research')
os.makedirs(DATA_DIR, exist_ok=True) os.makedirs(DATA_DIR, exist_ok=True)
def image_data_url_to_binary(image_data_url):
if image_data_url.startswith('data:image/jpeg;base64,'):
return base64.b64decode(image_data_url.split(',')[1]), 'image/jpeg'
elif image_data_url.startswith('data:image/png;base64,'):
return base64.b64decode(image_data_url.split(',')[1]), 'png'
else:
raise ValueError(f"Unsupported image data URL: {image_data_url}")
@app.route('/') @app.route('/')
def index(): def index():
return "Emblem research API\n" return "Emblem research API\n"
@ -27,5 +38,87 @@ def event(category):
"message": "Event saved", "message": "Event saved",
} }
def get_folders():
return [
x for x in os.listdir(DATA_DIR + "/json")
if os.path.isdir(os.path.join(DATA_DIR, "json", x))
]
@app.route('/api/folders')
def folders():
x = get_folders()
ret = []
for d in x:
ret.append({
"folder": d,
"count": len(os.listdir(os.path.join(DATA_DIR, "json", d))),
})
return {
"ok": True,
"folders": ret,
}
@app.route('/api/datapoints')
def datapoints():
folder = request.args.get('folder')
folder_dir = os.path.join(DATA_DIR, "json", folder)
x = os.listdir(folder_dir)
datapoints = []
for f in x:
try:
datapoints.append(load_datapoint(os.path.join(folder_dir, f)))
except Exception as e:
print(e)
return {
"ok": True,
"datapoints": datapoints,
}
def load_datapoint(path):
with open(path, 'rb') as f:
data = json.load(f)
data["path"] = path
return data
@app.route('/api/image')
def image():
folder = request.args.get('folder')
name = request.args.get('name')
path = os.path.join(DATA_DIR, "json", folder, name)
with open(path, 'rb') as f:
data = json.load(f)
binary, mimetype = image_data_url_to_binary(data["image_data_url"])
buf = BytesIO(binary)
return send_file(buf, mimetype=mimetype)
@app.route('/api/datapoint')
def datapoint():
folder = request.args.get('folder')
name = request.args.get('name')
path = os.path.join(DATA_DIR, "json", folder, name)
return {
"ok": True,
"datapoint": load_datapoint(path),
}
def do_copy_to_folder(paths, target_folder):
for path in paths:
src = os.path.join(DATA_DIR, "json", path)
dst = os.path.join(DATA_DIR, "json", target_folder)
os.makedirs(dst, exist_ok=True)
print(f"Copying {src} to {dst}")
shutil.copy(src, dst)
@app.route('/api/copy-to-folder', methods=['POST'])
def copy_to_folder():
data = request.get_json()
paths = data.get('paths')
target_folder = data.get('target_folder')
do_copy_to_folder(paths, target_folder)
return {
"ok": True,
"message": "Datapoints copied to folder",
}
if __name__ == '__main__': if __name__ == '__main__':
app.run(host='0.0.0.0', port=26966, debug=True) app.run(host='0.0.0.0', port=26966, debug=True)

17
research/sharpness.py Executable file
View File

@ -0,0 +1,17 @@
#!/usr/bin/env python3
import json
import os
def all_datapoints():
for r, d, fs in os.walk("data/raw/camera-frame"):
for f in fs:
fp = os.path.join(r, f)
if os.path.isfile(fp):
yield fp
def main():
all = list(all_datapoints())
print(f"loaded {len(all)} datapoints")
if __name__ == "__main__":
main()