Compare commits
No commits in common. "dcdccb57e284d29690ffe4a1c86a232917ae3d8d" and "40d8250ec559577b08f80040e0d388d1567b14e9" have entirely different histories.
dcdccb57e2
...
40d8250ec5
@ -1,3 +0,0 @@
|
|||||||
DB_HOST="127.0.0.1"
|
|
||||||
DB_PORT="6379"
|
|
||||||
DEBUG=1
|
|
1
.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
.env
|
|
29
Dockerfile
@ -1,29 +0,0 @@
|
|||||||
FROM python:3.8-slim
|
|
||||||
LABEL name=laser-app version=0.1
|
|
||||||
|
|
||||||
WORKDIR /opt
|
|
||||||
RUN apt update
|
|
||||||
RUN apt install -y --no-install-recommends build-essential\
|
|
||||||
gcc\
|
|
||||||
libagg2-dev\
|
|
||||||
libpotrace-dev\
|
|
||||||
nginx-light\
|
|
||||||
pkg-config\
|
|
||||||
python-dev\
|
|
||||||
redis-server
|
|
||||||
RUN rm -f /etc/nginx/sites-enabled/*
|
|
||||||
RUN pip3 install flask numpy pillow redis
|
|
||||||
RUN pip3 install pypotrace
|
|
||||||
|
|
||||||
COPY ./files/nginx/sites-enabled/site.conf /etc/nginx/sites-enabled
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
COPY entrypoint.sh /usr/bin/
|
|
||||||
RUN chmod +x /usr/bin/entrypoint.sh
|
|
||||||
|
|
||||||
EXPOSE 80
|
|
||||||
EXPOSE 5000
|
|
||||||
EXPOSE 9001
|
|
||||||
|
|
||||||
# Start the main process.
|
|
||||||
CMD ["python", "./server.py", "-i", "db"]
|
|
@ -1,8 +1,10 @@
|
|||||||
|
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
|
|
||||||
|
console.log("ok")
|
||||||
|
|
||||||
var binary_level = 100
|
var binary_level = 100
|
||||||
var url = document.URL
|
|
||||||
|
|
||||||
// Grab elements, create settings, etc.
|
// Grab elements, create settings, etc.
|
||||||
var canvas = document.getElementById('canvas');
|
var canvas = document.getElementById('canvas');
|
||||||
@ -17,7 +19,6 @@ $(document).ready(function(){
|
|||||||
context.drawImage(video, 0, 0, 640, 480);
|
context.drawImage(video, 0, 0, 640, 480);
|
||||||
binary(context)
|
binary(context)
|
||||||
$("#result").show()
|
$("#result").show()
|
||||||
$('html, body').animate({scrollTop: $("#result").offset().top}, 2000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -61,7 +62,7 @@ $(document).ready(function(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Trigger photo take
|
// Trigger photo take
|
||||||
$('.snap').on('click', function() {
|
document.getElementById('snap').addEventListener('click', function() {
|
||||||
snap()
|
snap()
|
||||||
});
|
});
|
||||||
function binary(context){
|
function binary(context){
|
||||||
@ -76,6 +77,7 @@ $(document).ready(function(){
|
|||||||
var thresh_red = binary_level;
|
var thresh_red = binary_level;
|
||||||
var thresh_green = binary_level;
|
var thresh_green = binary_level;
|
||||||
var thresh_blue = binary_level;
|
var thresh_blue = binary_level;
|
||||||
|
console.log( binary_level )
|
||||||
|
|
||||||
var channels = image.data.length/4;
|
var channels = image.data.length/4;
|
||||||
for(var i=0;i<channels;i++){
|
for(var i=0;i<channels;i++){
|
||||||
@ -108,33 +110,6 @@ $(document).ready(function(){
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
$("#send").on("click",function(){
|
$("#send").on("click",function(){
|
||||||
var canvas = document.getElementById('canvas');
|
alert("Sorry, this is still a work in progress ¯\_(ツ)_/¯")
|
||||||
image_data = canvas.toDataURL();
|
|
||||||
$("#messages").html('<div class="alert alert-primary" role="alert">Sending...</div>')
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: url + "/image",
|
|
||||||
method:"POST",
|
|
||||||
dataType: "json",
|
|
||||||
data:{
|
|
||||||
image_data : image_data,
|
|
||||||
text : $("#text").val()
|
|
||||||
},
|
|
||||||
|
|
||||||
}).done(function(d,m,j){
|
|
||||||
if( d.errors ){
|
|
||||||
msg = d.errors.join(" / ")
|
|
||||||
$("#messages").html(`<div class="alert alert-danger" role="alert">Something went wrong: ${msg}</div>`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
$("#messages").html(`<div class="alert alert-success" role="alert">OK, your image will be on ${d.hash_name}</div>`)
|
|
||||||
imageList = localStorage.getItem("imageList") ? JSON.parse(localStorage.getItem("imageList")) : []
|
|
||||||
imageList.push(d.hash_name)
|
|
||||||
localStorage.setItem("imageList",JSON.stringify(imageList))
|
|
||||||
})
|
|
||||||
.fail(function(d,m,jq){
|
|
||||||
$("#messages").html(`<div class="alert alert-danger" role="alert">Ooops... Something went wrong: ${jq}</div>`)
|
|
||||||
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
@ -1,43 +0,0 @@
|
|||||||
version: '2'
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
laserrdb:
|
|
||||||
external: true
|
|
||||||
|
|
||||||
services:
|
|
||||||
|
|
||||||
db:
|
|
||||||
env_file: .env
|
|
||||||
image: redis:6-alpine
|
|
||||||
ports:
|
|
||||||
- "6379:6379"
|
|
||||||
volumes:
|
|
||||||
- laserrdb:/var/lib/redis
|
|
||||||
|
|
||||||
app:
|
|
||||||
env_file: .env
|
|
||||||
build: .
|
|
||||||
image: teamlaser/laser-app:latest
|
|
||||||
ports:
|
|
||||||
- "5000:5000"
|
|
||||||
depends_on:
|
|
||||||
- db
|
|
||||||
command: python ./server.py
|
|
||||||
|
|
||||||
assets:
|
|
||||||
env_file: .env
|
|
||||||
image: teamlaser/laser-app:latest
|
|
||||||
ports:
|
|
||||||
- "8080:80"
|
|
||||||
depends_on:
|
|
||||||
- app
|
|
||||||
command: nginx -g 'daemon off;error_log /dev/stdout info;'
|
|
||||||
|
|
||||||
worker:
|
|
||||||
env_file: .env
|
|
||||||
build: .
|
|
||||||
image: teamlaser/laser-app:latest
|
|
||||||
depends_on:
|
|
||||||
- db
|
|
||||||
command: bash -c "while true; do python ./worker.py; sleep 30; done"
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
set -e
|
|
||||||
|
|
||||||
|
|
||||||
exec "$@"
|
|
@ -1,13 +0,0 @@
|
|||||||
server {
|
|
||||||
server_name _;
|
|
||||||
listen 80 default_server;
|
|
||||||
listen [::]:80 default_server;
|
|
||||||
root /opt/;
|
|
||||||
location /image {
|
|
||||||
proxy_pass http://app:5000;
|
|
||||||
}
|
|
||||||
# Docker specific conf
|
|
||||||
resolver 127.0.0.11 ipv6=off;
|
|
||||||
access_log /dev/stdout;
|
|
||||||
|
|
||||||
}
|
|
19
index.html
@ -12,12 +12,12 @@
|
|||||||
#canvas { margin-top: 20px; border: 1px solid #ccc; display: block; }
|
#canvas { margin-top: 20px; border: 1px solid #ccc; display: block; }
|
||||||
span.ui-slider-handle.ui-corner-all.ui-state-default { background: dodgerblue;}
|
span.ui-slider-handle.ui-corner-all.ui-state-default { background: dodgerblue;}
|
||||||
</style>
|
</style>
|
||||||
<link rel="stylesheet" type="text/css" href="css/bootstrap/css/bootstrap.min.css" />
|
<link rel="stylesheet" type="text/css" href="bootstrap/css/bootstrap.min.css" />
|
||||||
<link rel="stylesheet" type="text/css" href="js/jquery-ui/jquery-ui.css" />
|
<link rel="stylesheet" type="text/css" href="jquery-ui/jquery-ui.css" />
|
||||||
|
|
||||||
<script src="js/jquery-3.5.1.min.js"></script>
|
<script src="jquery-3.5.1.min.js"></script>
|
||||||
<script src="js/jquery-ui/jquery-ui.js"></script>
|
<script src="jquery-ui/jquery-ui.js"></script>
|
||||||
<script src="js/canvas.js"></script>
|
<script src="canvas.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script>
|
<script>
|
||||||
@ -36,7 +36,7 @@ window.ORIGINAL_JSON=window.JSON;
|
|||||||
<video id="video" autoplay></video>
|
<video id="video" autoplay></video>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm">
|
<div class="col-sm">
|
||||||
<button class="snap btn btn-lg btn-primary">Take a picture!</button>
|
<button id="snap" class="btn btn-lg btn-primary">Take a picture!</button>
|
||||||
<p class="lead">By allowing the use of your camera, you will be able to take snapshots, convert them to black and white images before sending them to the laser server. How cool is that?!</p>
|
<p class="lead">By allowing the use of your camera, you will be able to take snapshots, convert them to black and white images before sending them to the laser server. How cool is that?!</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -45,7 +45,7 @@ window.ORIGINAL_JSON=window.JSON;
|
|||||||
<canvas id="canvas" width="640" height="480"></canvas>
|
<canvas id="canvas" width="640" height="480"></canvas>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm">
|
<div class="col-sm">
|
||||||
<h3 class="snap btn btn-lg btn-primary">Not satisfied of the contrast? </h3>
|
<h3 class="btn btn-lg btn-primary">Not satisfied of the contrast? </h3>
|
||||||
<br/>
|
<br/>
|
||||||
<div id="slider"></div>
|
<div id="slider"></div>
|
||||||
<br/>
|
<br/>
|
||||||
@ -56,12 +56,9 @@ window.ORIGINAL_JSON=window.JSON;
|
|||||||
<h3>Satisfied? </h3>
|
<h3>Satisfied? </h3>
|
||||||
<p class="lead">Type the text visible on your image (if any) and push Send.</p>
|
<p class="lead">Type the text visible on your image (if any) and push Send.</p>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input id="text" type="text" class="form-control form-control-lg" placeholder="Type text here"/>
|
<input type="text" class="form-control form-control-lg" placeholder="Type text here"/>
|
||||||
<button id="send" class="form-control form-control-lg btn btn-lg btn-warning btn-block">Send</button>
|
<button id="send" class="form-control form-control-lg btn btn-lg btn-warning btn-block">Send</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="messages">
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 418 B After Width: | Height: | Size: 418 B |
Before Width: | Height: | Size: 312 B After Width: | Height: | Size: 312 B |
Before Width: | Height: | Size: 262 B After Width: | Height: | Size: 262 B |
Before Width: | Height: | Size: 348 B After Width: | Height: | Size: 348 B |
Before Width: | Height: | Size: 207 B After Width: | Height: | Size: 207 B |
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 278 B After Width: | Height: | Size: 278 B |
Before Width: | Height: | Size: 328 B After Width: | Height: | Size: 328 B |
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
72
manage.py
@ -1,72 +0,0 @@
|
|||||||
#! /usr/bin/python3
|
|
||||||
from datetime import datetime
|
|
||||||
from redis import Redis
|
|
||||||
import argparse
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
modeList = {
|
|
||||||
"list" : ["l","list"],
|
|
||||||
"delete" : ["d","delete"],
|
|
||||||
"exit" : ["x","q","exit"],
|
|
||||||
"preview" : ["p","preview"],
|
|
||||||
}
|
|
||||||
|
|
||||||
argsparser = argparse.ArgumentParser(description="manager")
|
|
||||||
argsparser.add_argument("-m","--mode",help="The action to perform. One of : {}".format(", ".join(modeList.keys())),default="list",type=str)
|
|
||||||
argsparser.add_argument("-P","--path",help="clitools path",default="~/code/lj-nano/clitools",type=str)
|
|
||||||
argsparser.add_argument("-i","--ip",help="IP address of the Redis server ",default="127.0.0.1",type=str)
|
|
||||||
argsparser.add_argument("-p","--port",help="Port of the Redis server ",default="6379",type=str)
|
|
||||||
args = argsparser.parse_args()
|
|
||||||
mode = args.mode
|
|
||||||
clipath = args.path
|
|
||||||
port = args.port
|
|
||||||
ip = args.ip
|
|
||||||
|
|
||||||
r = Redis(host=ip, port=port)
|
|
||||||
|
|
||||||
hashList = []
|
|
||||||
|
|
||||||
def refreshList():
|
|
||||||
global hashList
|
|
||||||
hashList = {}
|
|
||||||
theList = r.hgetall("images")
|
|
||||||
for i in theList:
|
|
||||||
item = json.loads(theList[i])
|
|
||||||
theList[i] = item
|
|
||||||
|
|
||||||
# Sort by reverse Date
|
|
||||||
for i in sorted(theList.items(),key=lambda x: x[1]['created_at'],reverse=True):
|
|
||||||
hashList[i[0].decode("ascii")] = i[1]
|
|
||||||
|
|
||||||
|
|
||||||
while True:
|
|
||||||
refreshList()
|
|
||||||
if mode in modeList["list"] :
|
|
||||||
print("hash\t\t\tdate\t\t\tpoints info")
|
|
||||||
for i in hashList:
|
|
||||||
item = hashList[i]
|
|
||||||
pl = item["points_list"]
|
|
||||||
created_at = item["created_at"]
|
|
||||||
print("{}\t{}\t{} points".format(i,datetime.ctime(datetime.fromtimestamp(created_at)),len(pl)))
|
|
||||||
|
|
||||||
elif mode in modeList["delete"]:
|
|
||||||
print("Select a hash to delete")
|
|
||||||
hash_name = input()
|
|
||||||
result = r.hdel("images",hash_name)
|
|
||||||
print("Result: {}".format(result))
|
|
||||||
|
|
||||||
elif mode in modeList["preview"]:
|
|
||||||
print("Select a hash to preview")
|
|
||||||
hash_name = input()
|
|
||||||
item = json.loads(hashList[bytes(hash_name,'ascii')])
|
|
||||||
points_list = json.dumps(item["points_list"])
|
|
||||||
bashCommand = "while true; do echo '"+points_list+"'; sleep 0.5; done | /usr/bin/python3 "+clipath+"/exports/tosimu.py"
|
|
||||||
os.system(bashCommand)
|
|
||||||
|
|
||||||
elif mode in modeList["exit"]:
|
|
||||||
break
|
|
||||||
|
|
||||||
print("Next action? (l)ist (d)elete e(x)it (p)review")
|
|
||||||
mode = input()
|
|
66
server.py
@ -1,66 +0,0 @@
|
|||||||
#! /usr/bin/python3
|
|
||||||
from redis import Redis
|
|
||||||
from flask import Flask, redirect, url_for, request
|
|
||||||
import flask
|
|
||||||
import hashlib
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import time
|
|
||||||
|
|
||||||
# Redis init
|
|
||||||
environ = os.environ
|
|
||||||
host = environ['DB_HOST'] if 'DB_HOST' in os.environ else "localhost"
|
|
||||||
port = environ['DB_PORT'] if 'DB_PORT' in os.environ else 6379
|
|
||||||
debug = environ['DEBUG'] if 'DEBUG' in os.environ else False
|
|
||||||
sep='**************************************'
|
|
||||||
print("\n{}\nConnecting to Redis server on {}:{}\n{}\n".format(sep,host,port,sep))
|
|
||||||
r = Redis(host="db", port=port)
|
|
||||||
|
|
||||||
# hashlib init
|
|
||||||
m = hashlib.sha256()
|
|
||||||
|
|
||||||
# Flask app init
|
|
||||||
app = Flask(__name__)
|
|
||||||
|
|
||||||
@app.before_first_request
|
|
||||||
def setup_logging():
|
|
||||||
if not app.debug:
|
|
||||||
# In production mode, add log handler to sys.stdout.
|
|
||||||
app.logger.addHandler(logging.StreamHandler(stream=sys.stdout))
|
|
||||||
app.logger.setLevel(logging.INFO)
|
|
||||||
|
|
||||||
# Routing requests
|
|
||||||
@app.route('/image',methods = ['POST', 'GET'])
|
|
||||||
def image():
|
|
||||||
try:
|
|
||||||
data = dict()
|
|
||||||
|
|
||||||
# Clean the request
|
|
||||||
if "text" in request.form:
|
|
||||||
data["text"] = request.form["text"][:256]
|
|
||||||
else :
|
|
||||||
data["text"] = ""
|
|
||||||
|
|
||||||
if "image_data" in request.form:
|
|
||||||
data["image_data"] = request.form["image_data"]
|
|
||||||
else:
|
|
||||||
raise Exception("image_data is mandatory")
|
|
||||||
|
|
||||||
m.update( bytes(time.asctime(), 'utf-8'))
|
|
||||||
hash_name = m.hexdigest()[:16]
|
|
||||||
data["hash_name"] = hash_name
|
|
||||||
|
|
||||||
# Save to queue
|
|
||||||
r.rpush("image-convert",json.dumps(data))
|
|
||||||
return( json.dumps({
|
|
||||||
"message":"ok",
|
|
||||||
"hash_name":hash_name
|
|
||||||
}) )
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print("woah",e)
|
|
||||||
return( json.dumps( {"errors":[ str(e)]}))
|
|
||||||
|
|
||||||
# Run Flask
|
|
||||||
if __name__ == '__main__':
|
|
||||||
app.run(host= '0.0.0.0',debug = True)
|
|
70
worker.py
@ -1,70 +0,0 @@
|
|||||||
#! /usr/bin/python3
|
|
||||||
from io import BytesIO
|
|
||||||
from numpy import asarray
|
|
||||||
from PIL import Image
|
|
||||||
from redis import Redis
|
|
||||||
import base64
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import potrace
|
|
||||||
import re
|
|
||||||
import time
|
|
||||||
|
|
||||||
color = 65280
|
|
||||||
environ = os.environ
|
|
||||||
host = environ['DB_HOST'] if 'DB_HOST' in os.environ else "localhost"
|
|
||||||
port = environ['DB_PORT'] if 'DB_PORT' in os.environ else 6379
|
|
||||||
r = Redis(host="db", port=port)
|
|
||||||
|
|
||||||
|
|
||||||
def convertImg(src, image_path="/tmp/"):
|
|
||||||
base64_data = re.sub('^data:image/.+;base64,', '', src)
|
|
||||||
byte_data = base64.b64decode(base64_data)
|
|
||||||
image_data = BytesIO(byte_data)
|
|
||||||
img = Image.open(image_data).convert("1")
|
|
||||||
return img
|
|
||||||
|
|
||||||
# Read results from redis
|
|
||||||
data = r.lpop('image-convert')
|
|
||||||
while data :
|
|
||||||
try:
|
|
||||||
item = json.loads(data)
|
|
||||||
image_data = item["image_data"]
|
|
||||||
text = item["text"]
|
|
||||||
hash_name = item["hash_name"]
|
|
||||||
|
|
||||||
# Vectorize the image
|
|
||||||
image = convertImg( image_data )
|
|
||||||
bmp = potrace.Bitmap( asarray( image ) )
|
|
||||||
|
|
||||||
# Trace the bitmap to a path
|
|
||||||
path = bmp.trace(turdsize=16,alphamax=0.0, opticurve=0, opttolerance=1.0)
|
|
||||||
pl = []
|
|
||||||
|
|
||||||
for curve in path:
|
|
||||||
start = curve.start_point
|
|
||||||
pl.append([int(start[0]),int(start[1]),0])
|
|
||||||
pl.append([int(start[0]),int(start[1]),color])
|
|
||||||
for segment in curve:
|
|
||||||
end_point_x, end_point_y = segment.end_point
|
|
||||||
if segment.is_corner:
|
|
||||||
c_x, c_y = segment.c
|
|
||||||
pl.append([int(c_x),int(c_y),color])
|
|
||||||
pass
|
|
||||||
#
|
|
||||||
else:
|
|
||||||
c1_x, c1_y = segment.c1
|
|
||||||
x, y = segment.c2
|
|
||||||
pl.append([int(x),int(y),color])
|
|
||||||
pl.append([int(c1_x),int(c1_y),color])
|
|
||||||
#
|
|
||||||
pl.append([start[0],start[1],0])
|
|
||||||
item["points_list"] = pl
|
|
||||||
item["created_at"] = time.time()
|
|
||||||
r.hset("images",hash_name, json.dumps(item))
|
|
||||||
print("Handled image {}".format(hash_name))
|
|
||||||
data = r.lpop('image-convert')
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print("woops",e)
|
|
||||||
break
|
|