scrolling image

This commit is contained in:
Ali Abid 2020-08-20 06:06:06 -07:00
parent 0504c5750a
commit 03c0bfed96
16 changed files with 121 additions and 16 deletions

View File

@ -241,19 +241,21 @@ class Image(InputComponent):
Input type: Union[numpy.array, PIL.Image, str]
"""
def __init__(self, shape=None, image_mode='RGB', invert_colors=False, source="upload", type="numpy", label=None):
def __init__(self, shape=None, image_mode='RGB', invert_colors=False, source="upload", tool="editor", type="numpy", label=None):
'''
Parameters:
shape (Tuple[int, int]): shape to crop and resize image to; if None, matches input image size.
image_mode (str): "RGB" if color, or "L" if black and white.
invert_colors (bool): whether to invert the image as a preprocessing step.
source (str): Source of image. "upload" creates a box where user can drop an image file, "webcam" allows user to take snapshot from their webcam, "canvas" defaults to a white image that can be edited and drawn upon with tools.
tool (str): Tools used for editing. "editor" allows a full screen editor, "select" provides a cropping and zoom tool.
type (str): Type of value to be returned by component. "numpy" returns a numpy array with shape (width, height, 3), "pil" returns a PIL image object, "file" returns a temporary file object whose path can be retrieved by file_obj.name.
label (str): component name in interface.
'''
self.shape = shape
self.image_mode = image_mode
self.source = source
self.tool = tool
self.type = type
self.invert_colors = invert_colors
super().__init__(label)
@ -270,6 +272,7 @@ class Image(InputComponent):
return {
"image_mode": self.image_mode,
"source": self.source,
"tool": self.tool,
**super().get_template_context()
}

View File

@ -151,13 +151,11 @@ def serve_files_in_background(interface, port, directory_to_serve=None, server_n
if self.path == "/api/predict/":
# Make the prediction.
self._set_headers()
print("in")
data_string = self.rfile.read(
int(self.headers["Content-Length"]))
msg = json.loads(data_string)
raw_input = msg["data"]
prediction, durations = interface.process(raw_input)
print("prediction")
output = {"data": prediction, "durations": durations}
self.wfile.write(json.dumps(output).encode())

View File

@ -0,0 +1,9 @@
/*!
* Cropper.js v1.5.7
* https://fengyuanchen.github.io/cropperjs
*
* Copyright 2015-present Chen Fengyuan
* Released under the MIT license
*
* Date: 2020-05-23T05:22:57.283Z
*/.cropper-container{direction:ltr;font-size:0;line-height:0;position:relative;-ms-touch-action:none;touch-action:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.cropper-container img{display:block;height:100%;image-orientation:0deg;max-height:none!important;max-width:none!important;min-height:0!important;min-width:0!important;width:100%}.cropper-canvas,.cropper-crop-box,.cropper-drag-box,.cropper-modal,.cropper-wrap-box{bottom:0;left:0;position:absolute;right:0;top:0}.cropper-canvas,.cropper-wrap-box{overflow:hidden}.cropper-drag-box{background-color:#fff;opacity:0}.cropper-modal{background-color:#000;opacity:.5}.cropper-view-box{display:block;height:100%;outline:1px solid #39f;outline-color:rgba(51,153,255,.75);overflow:hidden;width:100%}.cropper-dashed{border:0 dashed #eee;display:block;opacity:.5;position:absolute}.cropper-dashed.dashed-h{border-bottom-width:1px;border-top-width:1px;height:33.33333%;left:0;top:33.33333%;width:100%}.cropper-dashed.dashed-v{border-left-width:1px;border-right-width:1px;height:100%;left:33.33333%;top:0;width:33.33333%}.cropper-center{display:block;height:0;left:50%;opacity:.75;position:absolute;top:50%;width:0}.cropper-center:after,.cropper-center:before{background-color:#eee;content:" ";display:block;position:absolute}.cropper-center:before{height:1px;left:-3px;top:0;width:7px}.cropper-center:after{height:7px;left:0;top:-3px;width:1px}.cropper-face,.cropper-line,.cropper-point{display:block;height:100%;opacity:.1;position:absolute;width:100%}.cropper-face{background-color:#fff;left:0;top:0}.cropper-line{background-color:#39f}.cropper-line.line-e{cursor:ew-resize;right:-3px;top:0;width:5px}.cropper-line.line-n{cursor:ns-resize;height:5px;left:0;top:-3px}.cropper-line.line-w{cursor:ew-resize;left:-3px;top:0;width:5px}.cropper-line.line-s{bottom:-3px;cursor:ns-resize;height:5px;left:0}.cropper-point{background-color:#39f;height:5px;opacity:.75;width:5px}.cropper-point.point-e{cursor:ew-resize;margin-top:-3px;right:-3px;top:50%}.cropper-point.point-n{cursor:ns-resize;left:50%;margin-left:-3px;top:-3px}.cropper-point.point-w{cursor:ew-resize;left:-3px;margin-top:-3px;top:50%}.cropper-point.point-s{bottom:-3px;cursor:s-resize;left:50%;margin-left:-3px}.cropper-point.point-ne{cursor:nesw-resize;right:-3px;top:-3px}.cropper-point.point-nw{cursor:nwse-resize;left:-3px;top:-3px}.cropper-point.point-sw{bottom:-3px;cursor:nesw-resize;left:-3px}.cropper-point.point-se{bottom:-3px;cursor:nwse-resize;height:20px;opacity:1;right:-3px;width:20px}@media (min-width:768px){.cropper-point.point-se{height:15px;width:15px}}@media (min-width:992px){.cropper-point.point-se{height:10px;width:10px}}@media (min-width:1200px){.cropper-point.point-se{height:5px;opacity:.75;width:5px}}.cropper-point.point-se:before{background-color:#39f;bottom:-50%;content:" ";display:block;height:200%;opacity:0;position:absolute;right:-50%;width:200%}.cropper-invisible{opacity:0}.cropper-bg{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC")}.cropper-hide{display:block;height:0;position:absolute;width:0}.cropper-hidden{display:none!important}.cropper-move{cursor:move}.cropper-crop{cursor:crosshair}.cropper-disabled .cropper-drag-box,.cropper-disabled .cropper-face,.cropper-disabled .cropper-line,.cropper-disabled .cropper-point{cursor:not-allowed}

View File

@ -1,6 +1,7 @@
var io_master_template = {
gather: function() {
this.clear();
this.gathering = true;
for (let iface of this.input_interfaces) {
iface.submit();
}
@ -8,12 +9,16 @@ var io_master_template = {
clear: function() {
this.last_input = new Array(this.input_interfaces.length);
this.input_count = 0;
if (this.gather_timeout) {
window.clearTimeout(this.gather_timeout);
}
},
input: function(interface_id, data) {
this.last_input[interface_id] = data;
this.input_count += 1;
if (this.input_count == this.input_interfaces.length) {
this.submit();
this.gathering = false;
}
},
submit: function() {
@ -48,7 +53,7 @@ var io_master_template = {
if (this.config.live) {
var io = this;
var refresh_lag = this.config.refresh_lag || 0;
window.setTimeout(function() {
this.gather_timeout = window.setTimeout(function() {
io.gather();
}, refresh_lag);
} else {
@ -56,6 +61,15 @@ var io_master_template = {
this.target.find(".output_interfaces").css("opacity", 1);
}
},
no_input: function() {
if (this.gathering && this.config.live) {
var io = this;
this.gather_timeout = window.setTimeout(function() {
io.gather();
}, 200);
}
this.gathering = false;
},
flag: function() {
var post_data = {
'data': {

View File

@ -45,6 +45,7 @@ const image_input = {
init: function(opts) {
var io = this;
this.source = opts.source;
this.tool = opts.tool;
$('body').append(this.overlay_html.format(this.id));
this.overlay_target = $(`.overlay[interface_id=${this.id}]`);
if (this.source == "upload") {
@ -139,9 +140,15 @@ const image_input = {
var io = this;
if (this.source == "canvas") {
var dataURL = this.canvas.toDataURL("image/png");
this.io_master.input(this.id, dataURL);
this.io_master.input(this.id, dataURL);
} else if (this.state == "IMAGE_LOADED") {
io.io_master.input(io.id, this.image_data);
if (io.tool == "select") {
let canvas = io.cropper.getCroppedCanvas();
var dataURL = canvas.toDataURL("image/png");
this.io_master.input(this.id, dataURL);
} else {
io.io_master.input(io.id, this.image_data);
}
} else if (this.source == "webcam") {
Webcam.snap(function(image_data) {
io.target.find(".webcam").hide();
@ -150,6 +157,8 @@ const image_input = {
io.state = "IMAGE_LOADED";
io.io_master.input(io.id, image_data);
});
} else {
io.io_master.no_input();
}
},
clear: function() {
@ -164,6 +173,9 @@ const image_input = {
this.target.find(".hidden_upload").prop("value", "")
this.state = "NO_IMAGE";
this.image_data = null;
if (this.cropper) {
this.cropper.destroy();
}
}
},
state: "NO_IMAGE",
@ -179,7 +191,10 @@ const image_input = {
io.tui_editor.ui.resizeEditor({ imageSize: sizeValue });
});
}
},
if (io.tool == "select") {
io.cropper = new Cropper(io.target.find(".image_preview")[0]);
}
},
load_preview_from_files: function(files) {
if (!files.length || !window.FileReader || !/^image/.test(files[0].type)) {
return

File diff suppressed because one or more lines are too long

View File

@ -37,6 +37,7 @@
<link type="text/css" href="/static/css/vendor/jexcel.min.css" rel="stylesheet">
<link type="text/css" href="/static/css/vendor/jsuites.min.css" rel="stylesheet">
<link type="text/css" href="/static/css/vendor/jsonTree.css" rel="stylesheet">
<link type="text/css" href="/static/css/vendor/cropper.min.css" rel="stylesheet">
<link rel="stylesheet" href="/static/css/style.css">
<link rel="stylesheet" href="/static/css/gradio.css">
@ -86,6 +87,7 @@
<script src="/static/js/vendor/p5.dom.min.js"></script>
<script src="/static/js/vendor/jexcel.min.js"></script>
<script src="/static/js/vendor/jsuites.min.js"></script>
<script src="/static/js/vendor/cropper.min.js"></script>
<script src="/static/js/utils.js"></script>
<script src="/static/js/all_io.js"></script>

View File

@ -8,7 +8,7 @@ def image_mod(image):
gr.Interface(image_mod,
gr.inputs.Image(type="pil"),
gr.inputs.Image(type="pil", tool="select"),
[
gr.outputs.Image(type="pil"),
gr.outputs.Image(type="pil"),
@ -17,5 +17,6 @@ gr.Interface(image_mod,
["images/cheetah1.jpg"],
["images/cheetah2.jpg"],
["images/lion.jpg"],
]
],
live=True,
).launch(share=True)

View File

@ -39,6 +39,7 @@ gradio/static/css/interfaces/output/json.css
gradio/static/css/interfaces/output/key_values.css
gradio/static/css/interfaces/output/label.css
gradio/static/css/interfaces/output/textbox.css
gradio/static/css/vendor/cropper.min.css
gradio/static/css/vendor/icons.svg
gradio/static/css/vendor/jexcel.min.css
gradio/static/css/vendor/jquery-ui.css
@ -95,6 +96,7 @@ gradio/static/js/interfaces/output/label.js
gradio/static/js/interfaces/output/textbox.js
gradio/static/js/vendor/FileSaver.min.js
gradio/static/js/vendor/black-theme.js
gradio/static/js/vendor/cropper.min.js
gradio/static/js/vendor/fabric.js
gradio/static/js/vendor/html2canvas.min.js
gradio/static/js/vendor/jexcel.min.js

View File

@ -241,19 +241,21 @@ class Image(InputComponent):
Input type: Union[numpy.array, PIL.Image, str]
"""
def __init__(self, shape=None, image_mode='RGB', invert_colors=False, source="upload", type="numpy", label=None):
def __init__(self, shape=None, image_mode='RGB', invert_colors=False, source="upload", tool="editor", type="numpy", label=None):
'''
Parameters:
shape (Tuple[int, int]): shape to crop and resize image to; if None, matches input image size.
image_mode (str): "RGB" if color, or "L" if black and white.
invert_colors (bool): whether to invert the image as a preprocessing step.
source (str): Source of image. "upload" creates a box where user can drop an image file, "webcam" allows user to take snapshot from their webcam, "canvas" defaults to a white image that can be edited and drawn upon with tools.
tool (str): Tools used for editing. "editor" allows a full screen editor, "select" provides a cropping and zoom tool.
type (str): Type of value to be returned by component. "numpy" returns a numpy array with shape (width, height, 3), "pil" returns a PIL image object, "file" returns a temporary file object whose path can be retrieved by file_obj.name.
label (str): component name in interface.
'''
self.shape = shape
self.image_mode = image_mode
self.source = source
self.tool = tool
self.type = type
self.invert_colors = invert_colors
super().__init__(label)
@ -270,6 +272,7 @@ class Image(InputComponent):
return {
"image_mode": self.image_mode,
"source": self.source,
"tool": self.tool,
**super().get_template_context()
}

View File

@ -151,13 +151,11 @@ def serve_files_in_background(interface, port, directory_to_serve=None, server_n
if self.path == "/api/predict/":
# Make the prediction.
self._set_headers()
print("in")
data_string = self.rfile.read(
int(self.headers["Content-Length"]))
msg = json.loads(data_string)
raw_input = msg["data"]
prediction, durations = interface.process(raw_input)
print("prediction")
output = {"data": prediction, "durations": durations}
self.wfile.write(json.dumps(output).encode())

View File

@ -0,0 +1,9 @@
/*!
* Cropper.js v1.5.7
* https://fengyuanchen.github.io/cropperjs
*
* Copyright 2015-present Chen Fengyuan
* Released under the MIT license
*
* Date: 2020-05-23T05:22:57.283Z
*/.cropper-container{direction:ltr;font-size:0;line-height:0;position:relative;-ms-touch-action:none;touch-action:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.cropper-container img{display:block;height:100%;image-orientation:0deg;max-height:none!important;max-width:none!important;min-height:0!important;min-width:0!important;width:100%}.cropper-canvas,.cropper-crop-box,.cropper-drag-box,.cropper-modal,.cropper-wrap-box{bottom:0;left:0;position:absolute;right:0;top:0}.cropper-canvas,.cropper-wrap-box{overflow:hidden}.cropper-drag-box{background-color:#fff;opacity:0}.cropper-modal{background-color:#000;opacity:.5}.cropper-view-box{display:block;height:100%;outline:1px solid #39f;outline-color:rgba(51,153,255,.75);overflow:hidden;width:100%}.cropper-dashed{border:0 dashed #eee;display:block;opacity:.5;position:absolute}.cropper-dashed.dashed-h{border-bottom-width:1px;border-top-width:1px;height:33.33333%;left:0;top:33.33333%;width:100%}.cropper-dashed.dashed-v{border-left-width:1px;border-right-width:1px;height:100%;left:33.33333%;top:0;width:33.33333%}.cropper-center{display:block;height:0;left:50%;opacity:.75;position:absolute;top:50%;width:0}.cropper-center:after,.cropper-center:before{background-color:#eee;content:" ";display:block;position:absolute}.cropper-center:before{height:1px;left:-3px;top:0;width:7px}.cropper-center:after{height:7px;left:0;top:-3px;width:1px}.cropper-face,.cropper-line,.cropper-point{display:block;height:100%;opacity:.1;position:absolute;width:100%}.cropper-face{background-color:#fff;left:0;top:0}.cropper-line{background-color:#39f}.cropper-line.line-e{cursor:ew-resize;right:-3px;top:0;width:5px}.cropper-line.line-n{cursor:ns-resize;height:5px;left:0;top:-3px}.cropper-line.line-w{cursor:ew-resize;left:-3px;top:0;width:5px}.cropper-line.line-s{bottom:-3px;cursor:ns-resize;height:5px;left:0}.cropper-point{background-color:#39f;height:5px;opacity:.75;width:5px}.cropper-point.point-e{cursor:ew-resize;margin-top:-3px;right:-3px;top:50%}.cropper-point.point-n{cursor:ns-resize;left:50%;margin-left:-3px;top:-3px}.cropper-point.point-w{cursor:ew-resize;left:-3px;margin-top:-3px;top:50%}.cropper-point.point-s{bottom:-3px;cursor:s-resize;left:50%;margin-left:-3px}.cropper-point.point-ne{cursor:nesw-resize;right:-3px;top:-3px}.cropper-point.point-nw{cursor:nwse-resize;left:-3px;top:-3px}.cropper-point.point-sw{bottom:-3px;cursor:nesw-resize;left:-3px}.cropper-point.point-se{bottom:-3px;cursor:nwse-resize;height:20px;opacity:1;right:-3px;width:20px}@media (min-width:768px){.cropper-point.point-se{height:15px;width:15px}}@media (min-width:992px){.cropper-point.point-se{height:10px;width:10px}}@media (min-width:1200px){.cropper-point.point-se{height:5px;opacity:.75;width:5px}}.cropper-point.point-se:before{background-color:#39f;bottom:-50%;content:" ";display:block;height:200%;opacity:0;position:absolute;right:-50%;width:200%}.cropper-invisible{opacity:0}.cropper-bg{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC")}.cropper-hide{display:block;height:0;position:absolute;width:0}.cropper-hidden{display:none!important}.cropper-move{cursor:move}.cropper-crop{cursor:crosshair}.cropper-disabled .cropper-drag-box,.cropper-disabled .cropper-face,.cropper-disabled .cropper-line,.cropper-disabled .cropper-point{cursor:not-allowed}

View File

@ -1,6 +1,7 @@
var io_master_template = {
gather: function() {
this.clear();
this.gathering = true;
for (let iface of this.input_interfaces) {
iface.submit();
}
@ -8,12 +9,16 @@ var io_master_template = {
clear: function() {
this.last_input = new Array(this.input_interfaces.length);
this.input_count = 0;
if (this.gather_timeout) {
window.clearTimeout(this.gather_timeout);
}
},
input: function(interface_id, data) {
this.last_input[interface_id] = data;
this.input_count += 1;
if (this.input_count == this.input_interfaces.length) {
this.submit();
this.gathering = false;
}
},
submit: function() {
@ -48,7 +53,7 @@ var io_master_template = {
if (this.config.live) {
var io = this;
var refresh_lag = this.config.refresh_lag || 0;
window.setTimeout(function() {
this.gather_timeout = window.setTimeout(function() {
io.gather();
}, refresh_lag);
} else {
@ -56,6 +61,15 @@ var io_master_template = {
this.target.find(".output_interfaces").css("opacity", 1);
}
},
no_input: function() {
if (this.gathering && this.config.live) {
var io = this;
this.gather_timeout = window.setTimeout(function() {
io.gather();
}, 200);
}
this.gathering = false;
},
flag: function() {
var post_data = {
'data': {

View File

@ -45,6 +45,7 @@ const image_input = {
init: function(opts) {
var io = this;
this.source = opts.source;
this.tool = opts.tool;
$('body').append(this.overlay_html.format(this.id));
this.overlay_target = $(`.overlay[interface_id=${this.id}]`);
if (this.source == "upload") {
@ -139,9 +140,15 @@ const image_input = {
var io = this;
if (this.source == "canvas") {
var dataURL = this.canvas.toDataURL("image/png");
this.io_master.input(this.id, dataURL);
this.io_master.input(this.id, dataURL);
} else if (this.state == "IMAGE_LOADED") {
io.io_master.input(io.id, this.image_data);
if (io.tool == "select") {
let canvas = io.cropper.getCroppedCanvas();
var dataURL = canvas.toDataURL("image/png");
this.io_master.input(this.id, dataURL);
} else {
io.io_master.input(io.id, this.image_data);
}
} else if (this.source == "webcam") {
Webcam.snap(function(image_data) {
io.target.find(".webcam").hide();
@ -150,6 +157,8 @@ const image_input = {
io.state = "IMAGE_LOADED";
io.io_master.input(io.id, image_data);
});
} else {
io.io_master.no_input();
}
},
clear: function() {
@ -164,6 +173,9 @@ const image_input = {
this.target.find(".hidden_upload").prop("value", "")
this.state = "NO_IMAGE";
this.image_data = null;
if (this.cropper) {
this.cropper.destroy();
}
}
},
state: "NO_IMAGE",
@ -179,7 +191,10 @@ const image_input = {
io.tui_editor.ui.resizeEditor({ imageSize: sizeValue });
});
}
},
if (io.tool == "select") {
io.cropper = new Cropper(io.target.find(".image_preview")[0]);
}
},
load_preview_from_files: function(files) {
if (!files.length || !window.FileReader || !/^image/.test(files[0].type)) {
return

10
gradio/static/js/vendor/cropper.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -37,6 +37,7 @@
<link type="text/css" href="/static/css/vendor/jexcel.min.css" rel="stylesheet">
<link type="text/css" href="/static/css/vendor/jsuites.min.css" rel="stylesheet">
<link type="text/css" href="/static/css/vendor/jsonTree.css" rel="stylesheet">
<link type="text/css" href="/static/css/vendor/cropper.min.css" rel="stylesheet">
<link rel="stylesheet" href="/static/css/style.css">
<link rel="stylesheet" href="/static/css/gradio.css">
@ -86,6 +87,7 @@
<script src="/static/js/vendor/p5.dom.min.js"></script>
<script src="/static/js/vendor/jexcel.min.js"></script>
<script src="/static/js/vendor/jsuites.min.js"></script>
<script src="/static/js/vendor/cropper.min.js"></script>
<script src="/static/js/utils.js"></script>
<script src="/static/js/all_io.js"></script>