info field. ability to go wwithout defaults
This commit is contained in:
34
README.md
34
README.md
@@ -18,41 +18,69 @@ Setup:
|
|||||||
{
|
{
|
||||||
"type": "checkbox",
|
"type": "checkbox",
|
||||||
"name": "my_check",
|
"name": "my_check",
|
||||||
"default": "off"
|
"title": "Click to enable",
|
||||||
|
"default": "on"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "info",
|
||||||
|
"title": "Piece of text shown here."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"name": "my_number",
|
"name": "my_number",
|
||||||
"default": "1.0"
|
"default": "1.0",
|
||||||
|
"title": "Only numbers allowed."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"name": "my_text",
|
"name": "my_text",
|
||||||
|
"title": "Any text here",
|
||||||
"default": "some text"
|
"default": "some text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "range",
|
"type": "range",
|
||||||
"name": "my_slider",
|
"name": "my_slider",
|
||||||
|
"title": "Slide away!",
|
||||||
"default": "75",
|
"default": "75",
|
||||||
"min": "0",
|
"min": "0",
|
||||||
"max": "100"
|
"max": "100"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "select",
|
"type": "select",
|
||||||
|
"title": "Select any from these",
|
||||||
"name": "my_selection",
|
"name": "my_selection",
|
||||||
"default": "select2",
|
"default": "select2",
|
||||||
"options": [
|
"options": [
|
||||||
"option1",
|
"option1",
|
||||||
"select2"
|
"select2",
|
||||||
|
"select4",
|
||||||
|
"select5",
|
||||||
|
"select9"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
- Copy some images under data/images/
|
- Copy some images under data/images/
|
||||||
- Start the docker instance
|
- Start the docker instance
|
||||||
- Open the URL in http://localhost:$EXPOSE
|
- Open the URL in http://localhost:$EXPOSE
|
||||||
|
|
||||||
|
## Label types
|
||||||
|
|
||||||
|
Label entries require "type" and "name". All labels can include
|
||||||
|
a "title" field, which is added as a hover-on text, and "default" for
|
||||||
|
the default value.
|
||||||
|
|
||||||
|
In some cases more fields required.
|
||||||
|
|
||||||
|
- checkbox: If "default": "on", checkbox is selected. Otherwise it is unselected.
|
||||||
|
- text: Any string, if no default: ""
|
||||||
|
- number: Any number entry, if no default: ""
|
||||||
|
- range: Requires "min" and "max" values. if no default, default = min.
|
||||||
|
- select: Requires a list of "options".
|
||||||
|
- info: Not a selection. Add "title" field to show text instead.
|
||||||
|
|
||||||
## With nginx:
|
## With nginx:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from flask import (
|
|||||||
from revprox import ReverseProxied
|
from revprox import ReverseProxied
|
||||||
|
|
||||||
# configuration
|
# configuration
|
||||||
|
VERSION = "2022.05.27"
|
||||||
IMAGEDIR = "/data/images/"
|
IMAGEDIR = "/data/images/"
|
||||||
LABELDIR = "/data/labels/"
|
LABELDIR = "/data/labels/"
|
||||||
CONFIG_FILE = "/data/config.json"
|
CONFIG_FILE = "/data/config.json"
|
||||||
@@ -33,6 +34,7 @@ app.wsgi_app = ReverseProxied(app.wsgi_app)
|
|||||||
|
|
||||||
@app.before_request
|
@app.before_request
|
||||||
def before_request_func():
|
def before_request_func():
|
||||||
|
g.version = app.config["VERSION"]
|
||||||
try:
|
try:
|
||||||
with open(app.config["CONFIG_FILE"], "rt") as fp:
|
with open(app.config["CONFIG_FILE"], "rt") as fp:
|
||||||
g.config = json.load(fp)
|
g.config = json.load(fp)
|
||||||
@@ -43,7 +45,7 @@ def before_request_func():
|
|||||||
logging.warning("config.json could not be read. using defaults.")
|
logging.warning("config.json could not be read. using defaults.")
|
||||||
g.labels = [
|
g.labels = [
|
||||||
{"type": "checkbox", "name": "my_check", "default": "off"},
|
{"type": "checkbox", "name": "my_check", "default": "off"},
|
||||||
{"type": "text", "name": "my_text", "default": "1.0"},
|
{"type": "text", "name": "my_text", "default": "Some text"},
|
||||||
{
|
{
|
||||||
"type": "range",
|
"type": "range",
|
||||||
"name": "my_slider",
|
"name": "my_slider",
|
||||||
@@ -55,8 +57,14 @@ def before_request_func():
|
|||||||
g.config = {"title": "Labeler", "labels": g.labels}
|
g.config = {"title": "Labeler", "labels": g.labels}
|
||||||
g.users = ["user"]
|
g.users = ["user"]
|
||||||
|
|
||||||
|
if not "title" in g.config:
|
||||||
|
g.config["title"] = "Labeler"
|
||||||
|
|
||||||
for label in g.labels:
|
for label in g.labels:
|
||||||
label["value"] = label["default"]
|
if label["type"] == "range":
|
||||||
|
label["value"] = label.get("default", label.get("min", ""))
|
||||||
|
else:
|
||||||
|
label["value"] = label.get("default", "")
|
||||||
|
|
||||||
|
|
||||||
def natural_key(string_):
|
def natural_key(string_):
|
||||||
@@ -152,12 +160,7 @@ def main(user=None):
|
|||||||
|
|
||||||
user_name = get_user()
|
user_name = get_user()
|
||||||
|
|
||||||
return render_template(
|
return render_template("main.html", current_user=user_name)
|
||||||
"main.html",
|
|
||||||
current_user=user_name,
|
|
||||||
users=g.users,
|
|
||||||
title=g.config.get("title", "Labeler"),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/image", methods=["GET", "POST"])
|
@app.route("/image", methods=["GET", "POST"])
|
||||||
@@ -194,5 +197,4 @@ def show_image(id=None):
|
|||||||
labels=labels,
|
labels=labels,
|
||||||
id_minus=id_minus,
|
id_minus=id_minus,
|
||||||
id_plus=id_plus,
|
id_plus=id_plus,
|
||||||
title=g.config.get("title", "Labeler"),
|
|
||||||
)
|
)
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
class ReverseProxied(object):
|
class ReverseProxied(object):
|
||||||
'''Wrap the application in this middleware and configure the
|
"""Wrap the application in this middleware and configure the
|
||||||
front-end server to add these headers, to let you quietly bind
|
front-end server to add these headers, to let you quietly bind
|
||||||
this to a URL other than / and to an HTTP scheme that is
|
this to a URL other than / and to an HTTP scheme that is
|
||||||
different than what is used locally.
|
different than what is used locally.
|
||||||
@@ -14,19 +14,20 @@ class ReverseProxied(object):
|
|||||||
}
|
}
|
||||||
|
|
||||||
:param app: the WSGI application
|
:param app: the WSGI application
|
||||||
'''
|
"""
|
||||||
|
|
||||||
def __init__(self, app):
|
def __init__(self, app):
|
||||||
self.app = app
|
self.app = app
|
||||||
|
|
||||||
def __call__(self, environ, start_response):
|
def __call__(self, environ, start_response):
|
||||||
script_name = environ.get('HTTP_X_SCRIPT_NAME', '')
|
script_name = environ.get("HTTP_X_SCRIPT_NAME", "")
|
||||||
if script_name:
|
if script_name:
|
||||||
environ['SCRIPT_NAME'] = script_name
|
environ["SCRIPT_NAME"] = script_name
|
||||||
path_info = environ['PATH_INFO']
|
path_info = environ["PATH_INFO"]
|
||||||
if path_info.startswith(script_name):
|
if path_info.startswith(script_name):
|
||||||
environ['PATH_INFO'] = path_info[len(script_name):]
|
environ["PATH_INFO"] = path_info[len(script_name) :]
|
||||||
|
|
||||||
scheme = environ.get('HTTP_X_SCHEME', '')
|
scheme = environ.get("HTTP_X_SCHEME", "")
|
||||||
if scheme:
|
if scheme:
|
||||||
environ['wsgi.url_scheme'] = scheme
|
environ["wsgi.url_scheme"] = scheme
|
||||||
return self.app(environ, start_response)
|
return self.app(environ, start_response)
|
||||||
|
|||||||
@@ -1,65 +1,125 @@
|
|||||||
body { font-family: sans-serif; background: #888; margin: 0px;
|
body {
|
||||||
min-height: 100vh; width: 100vw; overflow-x: hidden; }
|
font-family: sans-serif;
|
||||||
a, h1, h2 { color: #377ba8; }
|
background: #888;
|
||||||
h1, h2 { font-family: 'Georgia', serif; margin: 0; }
|
margin: 0px;
|
||||||
h1 { border-bottom: 2px solid #eee; }
|
min-height: 100vh;
|
||||||
h2 { font-size: 1.2em; }
|
width: 100vw;
|
||||||
|
overflow-x: hidden;
|
||||||
tr,td,tbody { margin: 0px; }
|
}
|
||||||
|
|
||||||
.page { margin: 0em;
|
.page {
|
||||||
padding: 0em; background: #888; min-height: 100vh; width: 100vw;}
|
margin: 0em;
|
||||||
.entries { list-style: none; margin: 0; padding: 0; width:100%; min-height: 95vh; }
|
padding: 0em;
|
||||||
|
background: #888;
|
||||||
.large { font-size: 3em; }
|
min-height: 100vh;
|
||||||
.right { text-align: right; }
|
width: 100vw;
|
||||||
.center { text-align: center; }
|
}
|
||||||
|
|
||||||
#image { position: absolute;
|
.entries {
|
||||||
left:0px; top:0px;
|
list-style: none;
|
||||||
width: calc(100vw - 220px); /*height:95vh;*/ }
|
margin: 0;
|
||||||
#img { max-width: calc(100vw - 220px); max-height:95vh;
|
padding: 0;
|
||||||
width: auto; height: auto;
|
width: 100%;
|
||||||
display: block; margin-left: auto;
|
min-height: 95vh;
|
||||||
margin-right: auto; }
|
}
|
||||||
|
|
||||||
#img_title { overflow-x: hidden;
|
.large {
|
||||||
overflow-wrap: anywhere;
|
font-size: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#home {
|
||||||
|
position: absolute;
|
||||||
|
left: 0px;
|
||||||
|
bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#home a {
|
||||||
|
color: black;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: large;
|
||||||
|
background-color: #aaa;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#image {
|
||||||
|
position: absolute;
|
||||||
|
left: 0px;
|
||||||
|
top: 0px;
|
||||||
|
width: calc(100vw - 220px);
|
||||||
|
/*height:95vh;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
#img {
|
||||||
|
max-width: calc(100vw - 220px);
|
||||||
|
max-height: 95vh;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
display: block;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#img_title {
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
#topright {
|
||||||
|
position: absolute;
|
||||||
|
right: 0px;
|
||||||
|
top: 0px;
|
||||||
|
/*width: 20vw;*/
|
||||||
|
height: 95vh;
|
||||||
|
font-size: large;
|
||||||
}
|
}
|
||||||
|
|
||||||
#topright { position: absolute;
|
|
||||||
right:0px; top:0px;
|
|
||||||
/*width: 20vw;*/
|
|
||||||
height:95vh;
|
|
||||||
font-size: large; }
|
|
||||||
.inputcontainer {
|
.inputcontainer {
|
||||||
z-index:1;
|
z-index: 1;
|
||||||
background-color: #aaa;
|
background-color: #aaa;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
margin-bottom: 3px;
|
margin-bottom: 3px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
width: 210px;
|
width: 210px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
input, select {
|
input,
|
||||||
width: 180px;
|
select {
|
||||||
font-size: large;
|
width: 180px;
|
||||||
}
|
font-size: large;
|
||||||
|
|
||||||
|
|
||||||
input[type="text"] {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
input[type="submit"] {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
input[type="text"] {}
|
||||||
|
|
||||||
|
input[type="submit"] {}
|
||||||
|
|
||||||
input[type="checkbox"] {
|
input[type="checkbox"] {
|
||||||
width: 3em;
|
width: 3em;
|
||||||
height: 1.5em;
|
height: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
output {
|
||||||
|
display: block;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
font-size: initial;
|
||||||
}
|
}
|
||||||
label { display: block; }
|
|
||||||
output { display: block; font-weight: bold; }
|
|
||||||
|
|
||||||
.button_next {
|
.button_next {
|
||||||
width: 2em;
|
width: 2em;
|
||||||
@@ -67,12 +127,15 @@ output { display: block; font-weight: bold; }
|
|||||||
font-size: large;
|
font-size: large;
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.float_left {
|
.float_left {
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.float_right {
|
.float_right {
|
||||||
float:right;
|
float: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button_save {
|
.button_save {
|
||||||
height: 3em;
|
height: 3em;
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
@@ -80,6 +143,7 @@ output { display: block; font-weight: bold; }
|
|||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
clear: both;
|
clear: both;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button_continue {
|
.button_continue {
|
||||||
height: 4em;
|
height: 4em;
|
||||||
margin-top: 3em;
|
margin-top: 3em;
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>{{ title }}</title>
|
<title>{{ g.config.title }}</title>
|
||||||
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}">
|
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
|
||||||
<meta name="viewport" content="width=440" />
|
<meta name="viewport" content="width=440">
|
||||||
<script language="javascript" src="{{ url_for('static', filename='script.js') }}" ></script>
|
<meta name="VERSION" content="{{ g.version }}">
|
||||||
|
<script language="javascript" src="{{ url_for('static', filename='script.js') }}"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class=page>
|
<div class="page">
|
||||||
|
{% block body %}{% endblock %}
|
||||||
{% block body %}{% endblock %}
|
</div>
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
{% extends "layout.html" %}
|
{% extends "layout.html" %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="entries">
|
<div class="entries">
|
||||||
|
|
||||||
<div class="center inputcontainer">
|
<div class="center inputcontainer">
|
||||||
<form action="{{ url_for('main') }}" method=post class=add-entry>
|
<form action="{{ url_for('main') }}" method=post class=add-entry>
|
||||||
<label>Choose user:</label>
|
<label>Choose user:</label>
|
||||||
<select name="user_name">
|
<select name="user_name">
|
||||||
{% for user in users %}
|
{% for user in g.users %}
|
||||||
<option value="{{user}}" {% if user == current_user %}SELECTED{% endif %}>{{user}}</option>
|
<option value="{{user}}" {% if user == current_user %}SELECTED{% endif %} >{{user}}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
<br>
|
<br>
|
||||||
|
|||||||
@@ -11,29 +11,33 @@
|
|||||||
<input type=hidden value="{{ image_name }}" name=image_name>
|
<input type=hidden value="{{ image_name }}" name=image_name>
|
||||||
{% for label in labels %}
|
{% for label in labels %}
|
||||||
<div class=inputcontainer>
|
<div class=inputcontainer>
|
||||||
|
{% if label.type == "info" %}
|
||||||
|
<label class=info>{{ label.title }}</label>
|
||||||
|
{% else %}
|
||||||
<label>{{ label.name }}:</label>
|
<label>{{ label.name }}:</label>
|
||||||
<div class=center>
|
<div class=center>
|
||||||
{% if label.type == "checkbox" %}
|
{% if label.type == "checkbox" %}
|
||||||
<input class=center type="checkbox" name="label_{{ label.name }}" {% if label.value == "on" %}checked{% endif %}>
|
<input class=center type="checkbox" name="label_{{ label.name }}" {% if label.value == "on" %}checked{% endif %} title="{{label.title}}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if label.type == "text" %}
|
{% if label.type == "text" %}
|
||||||
<input type="text" name="label_{{ label.name }}" value="{{ label.value }}">
|
<input type="text" name="label_{{ label.name }}" value="{{ label.value }}" title="{{label.title}}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if label.type == "number" %}
|
{% if label.type == "number" %}
|
||||||
<input type="number" step="any" name="label_{{ label.name }}" value="{{ label.value }}">
|
<input type="number" step="any" name="label_{{ label.name }}" value="{{ label.value }}" title="{{label.title}}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if label.type == "range" %}
|
{% if label.type == "range" %}
|
||||||
<input type="range" name="label_{{ label.name }}" value="{{ label.value }}" min="{{ label.min }}" max="{{ label.max }}" oninput="this.nextElementSibling.value = this.value">
|
<input type="range" name="label_{{ label.name }}" value="{{ label.value }}" min="{{ label.min }}" max="{{ label.max }}" oninput="this.nextElementSibling.value = this.value" title="{{label.title}}">
|
||||||
<output>{{label.value}}</output>
|
<output>{{label.value}}</output>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if label.type == "select" %}
|
{% if label.type == "select" %}
|
||||||
<select name="label_{{ label.name }}">
|
<select name="label_{{ label.name }}" title="{{label.title}}">
|
||||||
{% for opt in label.options %}
|
{% for opt in label.options %}
|
||||||
<option value="{{opt}}" {% if opt == label.value %}SELECTED{% endif %}>{{opt}}</option>
|
<option value="{{opt}}" {% if opt == label.value %}SELECTED{% endif %}>{{opt}}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<div class=center>
|
<div class=center>
|
||||||
@@ -45,8 +49,8 @@
|
|||||||
<button class="button_next float_right" onclick="location.href='{{ url_for('show_image', id = id_plus) }}';">→</button>
|
<button class="button_next float_right" onclick="location.href='{{ url_for('show_image', id = id_plus) }}';">→</button>
|
||||||
<button class="button_continue" onclick="location.href='{{ url_for('show_image') }}';">continue</button>
|
<button class="button_continue" onclick="location.href='{{ url_for('show_image') }}';">continue</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<div id="home"><a href="{{ url_for('main') }}" title="Back to user selection">🏠</a></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<img class=preload src="{{ url_for('static', filename=image_plus) }}" >
|
<img class=preload src="{{ url_for('static', filename=image_plus) }}" >
|
||||||
|
|||||||
Reference in New Issue
Block a user