New initial message, gui and config modules
Still WIP, message handler does something at least if started. GUI is somewhat broken though.
This commit is contained in:
parent
46d5e9fde2
commit
f7ba17850c
4 changed files with 460 additions and 0 deletions
39
config.py
Normal file
39
config.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
import yaml
|
||||
import errno
|
||||
|
||||
class Config(object):
|
||||
def __init__(self, engine, gui, message):
|
||||
try:
|
||||
self.config_file = open("config", "r")
|
||||
self.data = yaml.load(self.config_file)
|
||||
except IOError, e:
|
||||
print "Dropping into developer mode due to missing or invalid config file"
|
||||
gui.enable()
|
||||
engine.set_debug(True)
|
||||
message.set_debug(True)
|
||||
self.data['timeout'] = 10.0
|
||||
self.data['status_target'] = 'http://10.8.0.171:8000/status'
|
||||
|
||||
if not hasattr(self.data, 'gui') or self.data['gui']:
|
||||
gui.enable()
|
||||
|
||||
debug = hasattr(self.data, 'debug') and self.data['debug']
|
||||
engine.set_debug(debug)
|
||||
message.set_debug(debug)
|
||||
|
||||
def save(self):
|
||||
self.config_file = open(self.config_file.name, "w")
|
||||
json.dump(self.config, self.config_file)
|
||||
self.config_file.close()
|
||||
|
||||
def set_active_rect(self, rect):
|
||||
self.config['active_rect'] = rect
|
||||
|
||||
def set_perspective_points(self, points):
|
||||
self.config['perspective_points'] = points
|
||||
|
||||
def set_status_target(self, status_url):
|
||||
self.config['status_target'] = status_url
|
||||
|
||||
def set_image_target(self, image_url):
|
||||
self.config['image_url'] = image_url
|
40
engine.py
Normal file
40
engine.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
import cv2
|
||||
import numpy as np
|
||||
|
||||
from threading import Thread, Timer
|
||||
|
||||
# TODO: GROT
|
||||
import time
|
||||
import random
|
||||
|
||||
class Engine(object):
|
||||
def __init__(self):
|
||||
self.thread = Thread(target=self.run, name = "JunkVision Engine")
|
||||
self.perspective = np.array(
|
||||
[[ 1.33036171e+00, 3.18020707e-01, -1.38751879e+02],
|
||||
[ -1.71116647e-01, 1.55350072e+00, -8.06609130e+00],
|
||||
[ -3.52454848e-04, 1.09892154e-03, 1.00000000e+00]]
|
||||
)
|
||||
self.areas = [(107, 68, 282, 209),
|
||||
(313, 67, 493, 308),
|
||||
(316, 323, 489, 475),
|
||||
(105, 209, 288, 476)]
|
||||
|
||||
def start(self):
|
||||
self.thread.start()
|
||||
self.thread.join()
|
||||
|
||||
def run(self):
|
||||
pass
|
||||
|
||||
def get_image(self, area_id):
|
||||
return open("D:/obrazek.jpg", "rb").read()
|
||||
|
||||
def get_areas(self):
|
||||
return self.areas
|
||||
|
||||
def get_movement_time(self, area_id):
|
||||
return time.time()
|
||||
|
||||
def get_percent_mess(self, area_id):
|
||||
return random.random() * 100
|
200
gui.py
Normal file
200
gui.py
Normal file
|
@ -0,0 +1,200 @@
|
|||
import cv2
|
||||
import cv2.cv
|
||||
|
||||
from threading import Thread
|
||||
|
||||
import numpy as np
|
||||
from functools import partial
|
||||
|
||||
class AreaSelector(object):
|
||||
def __init__(self, window_name, orig_img, max_areas = 4):
|
||||
self.window_name = window_name
|
||||
self.orig_img = orig_img
|
||||
self.selection = False
|
||||
self.area = 0
|
||||
self.max_areas = max_areas
|
||||
self.rects = []
|
||||
for i in xrange(max_areas):
|
||||
self.rects.append([])
|
||||
self.colors = [
|
||||
(0,255,0),
|
||||
(255,0,127),
|
||||
(0,127,255),
|
||||
(255,255,0)
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def handler(event, x, y, flags, self):
|
||||
if event == cv2.EVENT_FLAG_RBUTTON:
|
||||
self.area += 1
|
||||
self.area %= self.max_areas
|
||||
if self.selection:
|
||||
self.selection = False
|
||||
self.redrawRects()
|
||||
elif event == cv2.EVENT_LBUTTONDOWN and not self.selection:
|
||||
self.selection = True
|
||||
self.first_x = x
|
||||
self.first_y = y
|
||||
elif event == cv2.EVENT_LBUTTONDOWN and self.selection:
|
||||
self.selection = False
|
||||
self.redrawRects()
|
||||
elif event == cv2.EVENT_MOUSEMOVE and self.selection:
|
||||
self.rects[self.area] = (min(self.first_x, x), min(self.first_y, y), max(self.first_x, x), max(self.first_y, y))
|
||||
self.redrawRects()
|
||||
|
||||
def redrawRects(self):
|
||||
cv2.imshow(self.window_name, self.orig_img)
|
||||
new_img = self.orig_img.copy()
|
||||
for i, r in enumerate(self.rects):
|
||||
if not r:
|
||||
continue
|
||||
cv2.rectangle(new_img, (r[0], r[1]), (r[2], r[3]), self.colors[i], 4)
|
||||
cv2.imshow(self.window_name, new_img)
|
||||
|
||||
class CorrectionSelector(object):
|
||||
def __init__(self, window_name, orig_img):
|
||||
self.selecting_rect = True
|
||||
self.points = []
|
||||
self.cur_point = 0
|
||||
self.orig_img = orig_img
|
||||
self.window_name = window_name
|
||||
self.colors = [
|
||||
(0,255,127),
|
||||
(255,0,127),
|
||||
(0,127,255),
|
||||
(255,255,0)
|
||||
]
|
||||
self.transform = []
|
||||
self.rect_x = 0
|
||||
self.rect_y = 0
|
||||
|
||||
@staticmethod
|
||||
def handler(event, x, y, flags, self):
|
||||
if self.selecting_rect:
|
||||
if event == cv2.EVENT_FLAG_RBUTTON:
|
||||
self.selecting_rect = False
|
||||
self.redraw()
|
||||
elif event == cv2.EVENT_FLAG_LBUTTON:
|
||||
self.rect_x = x
|
||||
self.rect_y = y
|
||||
elif event == cv2.EVENT_MOUSEMOVE:
|
||||
self.rect = min(self.rect_x, x), min(self.rect_y, y), max(self.rect_x, x), max(self.rect_y, y)
|
||||
self.redraw()
|
||||
else:
|
||||
if event == cv2.EVENT_FLAG_RBUTTON:
|
||||
self.redraw()
|
||||
self.calc_transform()
|
||||
self.selecting_rect = True
|
||||
elif event == cv2.EVENT_FLAG_LBUTTON:
|
||||
if len(self.points) < 4:
|
||||
self.points.append((x,y))
|
||||
else:
|
||||
self.points[self.cur_point] = (x,y)
|
||||
self.cur_point += 1
|
||||
self.cur_point %= 4
|
||||
self.redraw()
|
||||
|
||||
def redraw(self):
|
||||
cv2.imshow(self.window_name, self.orig_img)
|
||||
new_img = self.orig_img.copy()
|
||||
if self.rect:
|
||||
cv2.rectangle(new_img, (self.rect[0], self.rect[1]), (self.rect[2], self.rect[3]), (0, 255, 0), 4)
|
||||
self.points.sort()
|
||||
for i, p in enumerate(self.points):
|
||||
cv2.circle(new_img, p, 4, self.colors[i], -1)
|
||||
cv2.imshow(self.window_name, new_img)
|
||||
|
||||
def calc_transform(self):
|
||||
self.rect_points = [
|
||||
(self.rect[0], self.rect[1]),
|
||||
(self.rect[0], self.rect[3]),
|
||||
(self.rect[2], self.rect[1]),
|
||||
(self.rect[2], self.rect[3])
|
||||
]
|
||||
sorted_points = []
|
||||
best_match = 0
|
||||
for i in xrange(4):
|
||||
min_delta = cap_width * cap_height
|
||||
for j in xrange(4):
|
||||
delta = (abs(self.rect_points[i][0] - self.points[j][0]), abs(self.rect_points[i][1] - self.points[j][1]))
|
||||
print i, j, delta
|
||||
if delta[0] + delta[1] < min_delta:
|
||||
best_match = j
|
||||
min_delta = delta[0] + delta[1]
|
||||
print best_match
|
||||
sorted_points.append(self.points[best_match])
|
||||
print np.array(self.points), np.array(self.rect_points), np.array(sorted_points)
|
||||
|
||||
self.transform = cv2.getPerspectiveTransform(np.array(sorted_points, dtype=np.float32), np.array(self.rect_points, dtype=np.float32))
|
||||
print self.transform
|
||||
|
||||
def get_transform(self):
|
||||
return self.transform
|
||||
|
||||
class ModeSelector(object):
|
||||
def __init__(self, gui, min_mode, max_mode):
|
||||
self.gui = gui
|
||||
self.min_mode = min_mode
|
||||
self.max_mode = max_mode
|
||||
self.mode = self.gui.mode
|
||||
|
||||
@staticmethod
|
||||
def handler(event, x, y, flags, self):
|
||||
if event == cv2.EVENT_MBUTTONDOWN:
|
||||
mode = self.gui.mode
|
||||
mode += 1
|
||||
mode %= self.max_mode
|
||||
if mode < self.min_mode:
|
||||
mode = self.min_mode
|
||||
self.gui.mode = mode
|
||||
|
||||
class Gui(object):
|
||||
def __init__(self, engine):
|
||||
self.engine = engine
|
||||
self.enabled = False
|
||||
self.mode = -2
|
||||
|
||||
def enable(self):
|
||||
if self.enabled:
|
||||
raise RuntimeError("Gui has already been started!")
|
||||
self.thread = Thread(target=self.run, name="JunkVision GUI")
|
||||
self.enabled = True
|
||||
self.thread.start()
|
||||
|
||||
def set_mode(self, mode):
|
||||
self.mode = mode
|
||||
|
||||
def disable(self):
|
||||
self.enabled = False
|
||||
if self.thread.is_alive:
|
||||
self.thread.join()
|
||||
|
||||
def is_enabled(self):
|
||||
return self.enabled
|
||||
|
||||
def run(self):
|
||||
cv2.namedWindow("JunkVision GUI")
|
||||
while self.enabled:
|
||||
if self.mode == -2:
|
||||
self.selector = AreaSelector("JunkVision GUI")
|
||||
cv2.setMouseCallback("JunkVision GUI", CorrectionSelector.handler, self.selector)
|
||||
cv2.waitKey()
|
||||
elif self.mode == -1:
|
||||
self.selector = CorrectionSelector("JunkVision GUI")
|
||||
cv2.setMouseCallback("JunkVision GUI", AreaSelector.handler, self.selector)
|
||||
cv2.waitKey()
|
||||
elif self.mode >= 0 and self.mode <= 2:
|
||||
if not isinstance(self.selector, ModeSelector):
|
||||
self.selector = ModeSelector(self, 0, 2)
|
||||
cv2.setMouseCallback("JunkVision GUI", ModeSelector.handler, self.selector)
|
||||
if self.mode == 0:
|
||||
img = self.engine.get_plain_image()
|
||||
elif self.mode == 1:
|
||||
img = self.engine.get_movement_image()
|
||||
elif self.mode == 2:
|
||||
img = self.engine.get_difference_image()
|
||||
|
||||
cv2.imshow("JunkVision GUI", img)
|
||||
if cv2.waitKey(10) == 27:
|
||||
self.enabled = False
|
||||
cv2.destroyWindow("JunkVision GUI")
|
181
message.py
Normal file
181
message.py
Normal file
|
@ -0,0 +1,181 @@
|
|||
import requests
|
||||
import json
|
||||
|
||||
import BaseHTTPServer
|
||||
|
||||
from threading import Thread
|
||||
|
||||
class MessageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
access_denied_text = """
|
||||
<html>
|
||||
<head><title>JunkVision - 403 Forbidden</title></head>
|
||||
<base>
|
||||
<h1>403 Forbidden</h1>
|
||||
<h2>Only LAN is allowed.</h2>
|
||||
<p>Contact <a href="mailto:bofh@hackerspace.pl">BOFHs</a> for access.</p>
|
||||
</base>
|
||||
</html>
|
||||
"""
|
||||
|
||||
not_found_text = """
|
||||
<html>
|
||||
<head><title>JunkVision - 404 Not Found</title></head>
|
||||
<base>
|
||||
<h1>404 Not found</h1>
|
||||
</base>
|
||||
</html>
|
||||
"""
|
||||
|
||||
bad_request_text = """
|
||||
<html>
|
||||
<head><title>JunkVision - 400 Bad Request</title></head>
|
||||
<base>
|
||||
<h1>400 Bad Request</h1>
|
||||
</base>
|
||||
</html>
|
||||
"""
|
||||
|
||||
def do_GET(self):
|
||||
self.server_version = "JunkVision/0.5"
|
||||
self.sys_version = ""
|
||||
self.protocol_version = "HTTP/1.1"
|
||||
if not self.client_address[0].startswith("10.8.") and self.client_address[0] != "127.0.0.1":
|
||||
self.send_response(403)
|
||||
self.send_default_headers()
|
||||
self.send_header("Content-Type", "text/html; charset=utf-8")
|
||||
self.send_header("Content-Length", "%d" % len(self.access_denied_text))
|
||||
self.end_headers()
|
||||
self.wfile.write(self.access_denied_text)
|
||||
return
|
||||
|
||||
data = {}
|
||||
data['json_response'] = self.server.get_default_data()
|
||||
|
||||
if self.path.startswith("/image/"):
|
||||
try:
|
||||
area = int(self.path.split("/")[2])
|
||||
except (ValueError, IndexError):
|
||||
self.send_bad_request()
|
||||
# TODO: logging
|
||||
print "Bad request", self.path
|
||||
return
|
||||
|
||||
img_jpg = self.server.engine.get_image(int(area))
|
||||
self.send_image(img_jpg)
|
||||
return
|
||||
|
||||
elif self.path.startswith("/status/"):
|
||||
data['json_response'].append({'type': 'status'})
|
||||
if self.path == "/status/":
|
||||
for area_id in xrange(len(self.server.engine.get_areas())):
|
||||
data['json_response'].append(self.server.get_status_data(area_id))
|
||||
else:
|
||||
try:
|
||||
area = int(self.path.split("/")[2])
|
||||
if area < 0 or area > len(self.server.engine.get_areas()):
|
||||
raise IndexError("Area out of range")
|
||||
data['json_response'].append(self.server.get_status_data(area))
|
||||
except (ValueError, IndexError):
|
||||
self.send_bad_request()
|
||||
# TODO: logging
|
||||
print "Bad request", self.path
|
||||
return
|
||||
|
||||
else:
|
||||
self.send_response(404)
|
||||
self.send_default_headers()
|
||||
self.send_header("Content-Type", "text/html; charset=utf-8")
|
||||
self.send_header("Content-Length", "%d" % len(self.not_found_text))
|
||||
self.end_headers()
|
||||
self.wfile.write(self.not_found_text)
|
||||
return
|
||||
|
||||
self.send_response(200)
|
||||
self.send_default_headers()
|
||||
self.send_header("Content-Type", "application/json; charset=utf-8")
|
||||
json_text = json.dumps(data)
|
||||
self.send_header("Content-Length", "%d" % len(json_text))
|
||||
self.send_header("Cache-Control", "max-age=60")
|
||||
self.end_headers()
|
||||
self.wfile.write(json_text)
|
||||
|
||||
def send_default_headers(self):
|
||||
self.send_header("Connection", "close")
|
||||
self.send_header("Transfer-Encoding", "identity")
|
||||
self.send_header("Allow", "GET")
|
||||
|
||||
def send_bad_request(self):
|
||||
self.send_response(400)
|
||||
self.send_default_headers()
|
||||
self.send_header("Content-Type", "text/html; charset=utf-8")
|
||||
self.send_header("Content-Length", "%d" % len(self.bad_request_text))
|
||||
self.end_headers()
|
||||
self.wfile.write(self.bad_request_text)
|
||||
|
||||
def send_image(self, img):
|
||||
self.send_response(200)
|
||||
self.send_default_headers()
|
||||
# XXX: parametrize?
|
||||
self.send_header("Content-Type", "image/jpeg")
|
||||
self.send_header("Content-Length", "%d" % len(img))
|
||||
self.send_header("Cache-Control", "no-store")
|
||||
self.end_headers()
|
||||
self.wfile.write(img)
|
||||
|
||||
|
||||
|
||||
class MessageServer(BaseHTTPServer.HTTPServer):
|
||||
def __init__(self, engine):
|
||||
BaseHTTPServer.HTTPServer.__init__(self, ("0.0.0.0", 4580), MessageHandler)
|
||||
self.engine = engine
|
||||
self.camera_id = "Unknown"
|
||||
self.enabled = False
|
||||
|
||||
def set_camera_id(self, camera_id):
|
||||
self.camera_id = camera_id
|
||||
|
||||
def set_status_target(self, status_url):
|
||||
self.status_target = status_url
|
||||
|
||||
def get_default_data(self):
|
||||
return [{"camera_id": self.camera_id}]
|
||||
|
||||
def get_status_data(self, area_id):
|
||||
return {
|
||||
"area_id": area_id,
|
||||
"area_rect": self.engine.get_areas()[area_id],
|
||||
"last_movement": self.engine.get_movement_time(area_id),
|
||||
"last_percent_mess": self.engine.get_percent_mess(area_id)
|
||||
}
|
||||
|
||||
def send_status(self, area_id=-1):
|
||||
data = {}
|
||||
data['status'] = self.get_default_data()
|
||||
if area_id < 0:
|
||||
for area_id in engine.get_areas():
|
||||
data['status'].append(self.get_status_data(area_id))
|
||||
else:
|
||||
data['status'].append(self.get_status_data(area_id))
|
||||
|
||||
json_text = json.dumps(data)
|
||||
headers = [("Content-Type", "application/json; charset=utf-8"),
|
||||
("Content-Length", "%d" % len(json_text)),
|
||||
("Server", "JunkVision/0.5")]
|
||||
resp = requests.post(self.status_target, data=json_text, headers=headers)
|
||||
# TODO: logging
|
||||
print resp
|
||||
|
||||
def start(self):
|
||||
if self.enabled:
|
||||
raise RuntimeError("Message handler is already started!")
|
||||
self.thread = Thread(target=self.run, name="JunkVision MessageHandler")
|
||||
self.enabled = True
|
||||
self.thread.start()
|
||||
|
||||
def stop(self):
|
||||
self.enabled = False
|
||||
self.thread.join()
|
||||
|
||||
def run(self):
|
||||
while self.enabled:
|
||||
self.handle_request()
|
Loading…
Add table
Reference in a new issue