This commit is contained in:
Abubakar Abid 2019-03-25 13:17:46 -07:00
parent 45a35514f4
commit 1d5b6fc2d0
45 changed files with 4600 additions and 9333 deletions

View File

@ -1,2 +1,2 @@
recursive-include gradio/static/*
recursive-include gradio/templates/*
recursive-include gradio/static *
recursive-include gradio/templates *

View File

@ -0,0 +1,305 @@
/*!
* Cropper v4.0.0
* https://github.com/fengyuanchen/cropper
*
* Copyright (c) 2014-2018 Chen Fengyuan
* Released under the MIT license
*
* Date: 2018-04-01T06:26:32.417Z
*/
.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 {/*Avoid margin top issue (Occur only when margin-top <= -height)
*/
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-wrap-box,
.cropper-canvas,
.cropper-drag-box,
.cropper-crop-box,
.cropper-modal {
bottom: 0;
left: 0;
position: absolute;
right: 0;
top: 0;
}
.cropper-wrap-box,
.cropper-canvas {
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-color: rgba(51, 153, 255, 0.75);
outline: 1px solid #39f;
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:before,
.cropper-center:after {
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

@ -0,0 +1,59 @@
var MAX_PREVIEW_ROWS = 100
$('body').on('click', ".input_csv.drop_mode", function (e) {
$(this).parent().find(".hidden_upload").click();
})
$('body').on('drag dragstart dragend dragover dragenter dragleave drop', ".input_csv.drop_mode", function(e) {
e.preventDefault();
e.stopPropagation();
})
function loadTableFromFiles(files) {
Papa.parse(files[0], {
complete: function(results) {
$(".input_csv").hide()
$(".input_csv").removeClass("drop_mode")
var data_array = results.data
var table_html = ""
for (var i = 0; i < data_array.length && i <= MAX_PREVIEW_ROWS; i++) {
row = data_array[i]
if (i == 0) {
table_html += "<tr class='header'>"
} else {
table_html += "<tr>"
}
for (var c = 0; c < row.length; c++) {
table_html += "<td>" + row[c] + "</td>"
}
table_html += "</tr>"
}
table_html += ""
$(".csv_preview").html(table_html)
$(".table_holder").show()
}
})
}
$(".input_csv").on('drop', function(e) {
files = e.originalEvent.dataTransfer.files;
loadTableFromFiles(files)
});
$(".hidden_upload").on("change", function() {
var files = !!this.files ? this.files : []
if (!files.length || !window.FileReader) {
return
}
loadTableFromFiles(files)
})
$('body').on('click', '.clear', function(e) {
$(".hidden_upload").prop("value", "")
$(".input_csv").show()
$(".input_csv").addClass("drop_mode")
$(".table_holder").hide()
})
$('body').on('click', '.submit', function(e) {
loadStart();
})

View File

@ -0,0 +1,70 @@
var cropper;
var aspectRatio = "{{aspect_ratio}}"
$('body').on('click', ".input_image.drop_mode", function (e) {
$(this).parent().find(".hidden_upload").click();
})
$('body').on('drag dragstart dragend dragover dragenter dragleave drop', ".input_image.drop_mode", function(e) {
e.preventDefault();
e.stopPropagation();
})
function loadPreviewFromFiles(files) {
var ReaderObj = new FileReader()
ReaderObj.readAsDataURL(files[0])
ReaderObj.onloadend = function() {
$(".input_caption").hide()
$(".input_image").removeClass("drop_mode")
var image = $(".input_image img")
image.attr("src", this.result)
image.cropper({
aspectRatio : aspectRatio,
background: false
});
if (!cropper) {
cropper = image.data('cropper');
}
}
}
$(".input_image").on('drop', function(e) {
files = e.originalEvent.dataTransfer.files;
loadPreviewFromFiles(files)
});
$(".hidden_upload").on("change", function() {
var files = !!this.files ? this.files : []
if (!files.length || !window.FileReader) {
return
}
if (/^image/.test(files[0].type)) {
loadPreviewFromFiles(files)
} else {
alert("Invalid input")
}
})
$('body').on('click', '.clear', function(e) {
if (cropper) {
cropper.destroy();
cropper = null
$(".input_image img").remove()
$(".input_image").append("<img>")
}
$(".hidden_upload").prop("value", "")
$(".input_caption").show()
$(".input_image img").removeAttr("src");
$(".input_image").addClass("drop_mode")
})
$('body').on('click', '.submit', function(e) {
loadStart();
src = cropper.getCroppedCanvas({
maxWidth: 360,
maxHeight: 360,
fillColor: "white"
}).toDataURL();
ws.send(src, function(e) {
notifyError(e)
})
})

View File

@ -0,0 +1,26 @@
var dimension = Math.min($(".canvas_holder").width(),
$(".canvas_holder").height()) - 2 // minimum dimension - border
var sketchpad = new Sketchpad({
element: '.canvas_holder canvas',
width: dimension,
height: dimension
});
sketchpad.penSize = $(".brush.selected").attr("size");
canvas = $('.canvas_holder canvas')[0]
context = canvas.getContext("2d");
$(".brush").click(function (e) {
$(".brush").removeClass("selected")
$(this).addClass("selected")
sketchpad.penSize = $(this).attr("size")
})
$('body').on('click', '.clear', function(e) {
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
})
$('body').on('click', '.submit', function(e) {
loadStart();
var dataURL = canvas.toDataURL("image/png");
ws.send(dataURL, function(e){
notifyError(e)
});
})

View File

@ -0,0 +1,10 @@
$('body').on('click', '.clear', function(e) {
$(".input_text").val("");
})
$('body').on('click', '.submit', function(e) {
loadStart();
text = $(".input_text").val();
ws.send(text, function(e){
notifyError(e)
});
})

View File

@ -0,0 +1,10 @@
ws.onmessage = function (event) {
loadEnd();
$(".output_image img").attr("src", event.data);
$(".output_image img").show();
}
$('body').on('click', '.clear', function(e) {
$(".output_image img").removeAttr("src");
$(".output_image img").hide();
})

View File

@ -0,0 +1,32 @@
ws.onmessage = function (event) {
loadEnd();
var data = JSON.parse(event.data)
// data = {
// label: "happy",
// confidences : [
// {
// label : "happy",
// confidence: 0.7
// },
// {
// label : "sad",
// confidence: 0.3
// },
// ]
// }
$(".output_class").text(data["label"])
$(".confidence_intervals").empty()
if ("confidences" in data) {
data["confidences"].forEach(function (c) {
var confidence = c["confidence"]
$(".confidence_intervals").append(`<div class="confidence"><div class=
"label">${c["label"]}</div><div class="level" style="flex-grow:
${confidence}">${Math.round(confidence * 100)}%</div></div>`)
})
}
}
$('body').on('click', '.clear', function(e) {
$(".output_class").text("")
$(".confidence_intervals").empty()
})

View File

@ -0,0 +1,8 @@
ws.onmessage = function (event) {
loadEnd();
$(".output_text").val(event.data);
}
$('body').on('click', '.clear', function(e) {
$(".output_text").val("");
})

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,297 @@
// The MIT License (MIT)
//
// Copyright (c) 2014 YIOM
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
function Sketchpad(config) {
// Enforces the context for all functions
for (var key in this.constructor.prototype) {
this[key] = this[key].bind(this);
}
// Warn the user if no DOM element was selected
if (!config.hasOwnProperty('element')) {
console.error('SKETCHPAD ERROR: No element selected');
return;
}
this.element = config.element;
// Width can be defined on the HTML or programatically
this._width = config.width || $(this.element).attr('data-width') || 0;
this._height = config.height || $(this.element).attr('data-height') || 0;
// Pen attributes
this.color = config.color || $(this.element).attr('data-color') || '#000000';
this.penSize = config.penSize || $(this.element).attr('data-penSize') || 5;
// ReadOnly sketchpads may not be modified
this.readOnly = config.readOnly ||
$(this.element).attr('data-readOnly') ||
false;
if (!this.readOnly) {
$(this.element).css({cursor: 'crosshair'});
}
// Stroke control variables
this.strokes = config.strokes || [];
this._currentStroke = {
color: null,
size: null,
lines: [],
};
// Undo History
this.undoHistory = config.undoHistory || [];
// Animation function calls
this.animateIds = [];
// Set sketching state
this._sketching = false;
// Setup canvas sketching listeners
this.reset();
}
//
// Private API
//
Sketchpad.prototype._cursorPosition = function(event) {
return {
x: event.pageX - $(this.canvas).offset().left,
y: event.pageY - $(this.canvas).offset().top,
};
};
Sketchpad.prototype._draw = function(start, end, color, size) {
this._stroke(start, end, color, size, 'source-over');
};
Sketchpad.prototype._erase = function(start, end, color, size) {
this._stroke(start, end, color, size, 'destination-out');
};
Sketchpad.prototype._stroke = function(start, end, color, size, compositeOperation) {
this.context.save();
this.context.lineJoin = 'round';
this.context.lineCap = 'round';
this.context.strokeStyle = color;
this.context.lineWidth = size;
this.context.globalCompositeOperation = compositeOperation;
this.context.beginPath();
this.context.moveTo(start.x, start.y);
this.context.lineTo(end.x, end.y);
this.context.closePath();
this.context.stroke();
this.context.restore();
};
//
// Callback Handlers
//
Sketchpad.prototype._mouseDown = function(event) {
this._lastPosition = this._cursorPosition(event);
this._currentStroke.color = this.color;
this._currentStroke.size = this.penSize;
this._currentStroke.lines = [];
this._sketching = true;
this.canvas.addEventListener('mousemove', this._mouseMove);
};
Sketchpad.prototype._mouseUp = function(event) {
if (this._sketching) {
this.strokes.push($.extend(true, {}, this._currentStroke));
this._sketching = false;
}
this.canvas.removeEventListener('mousemove', this._mouseMove);
};
Sketchpad.prototype._mouseMove = function(event) {
var currentPosition = this._cursorPosition(event);
this._draw(this._lastPosition, currentPosition, this.color, this.penSize);
this._currentStroke.lines.push({
start: $.extend(true, {}, this._lastPosition),
end: $.extend(true, {}, currentPosition),
});
this._lastPosition = currentPosition;
};
Sketchpad.prototype._touchStart = function(event) {
event.preventDefault();
if (this._sketching) {
return;
}
this._lastPosition = this._cursorPosition(event.changedTouches[0]);
this._currentStroke.color = this.color;
this._currentStroke.size = this.penSize;
this._currentStroke.lines = [];
this._sketching = true;
this.canvas.addEventListener('touchmove', this._touchMove, false);
};
Sketchpad.prototype._touchEnd = function(event) {
event.preventDefault();
if (this._sketching) {
this.strokes.push($.extend(true, {}, this._currentStroke));
this._sketching = false;
}
this.canvas.removeEventListener('touchmove', this._touchMove);
};
Sketchpad.prototype._touchCancel = function(event) {
event.preventDefault();
if (this._sketching) {
this.strokes.push($.extend(true, {}, this._currentStroke));
this._sketching = false;
}
this.canvas.removeEventListener('touchmove', this._touchMove);
};
Sketchpad.prototype._touchLeave = function(event) {
event.preventDefault();
if (this._sketching) {
this.strokes.push($.extend(true, {}, this._currentStroke));
this._sketching = false;
}
this.canvas.removeEventListener('touchmove', this._touchMove);
};
Sketchpad.prototype._touchMove = function(event) {
event.preventDefault();
var currentPosition = this._cursorPosition(event.changedTouches[0]);
this._draw(this._lastPosition, currentPosition, this.color, this.penSize);
this._currentStroke.lines.push({
start: $.extend(true, {}, this._lastPosition),
end: $.extend(true, {}, currentPosition),
});
this._lastPosition = currentPosition;
};
//
// Public API
//
Sketchpad.prototype.reset = function() {
// Set attributes
this.canvas = $(this.element)[0];
this.canvas.width = this._width;
this.canvas.height = this._height;
this.context = this.canvas.getContext('2d');
// Setup event listeners
this.redraw(this.strokes);
if (this.readOnly) {
return;
}
// Mouse
this.canvas.addEventListener('mousedown', this._mouseDown);
this.canvas.addEventListener('mouseout', this._mouseUp);
this.canvas.addEventListener('mouseup', this._mouseUp);
// Touch
this.canvas.addEventListener('touchstart', this._touchStart);
this.canvas.addEventListener('touchend', this._touchEnd);
this.canvas.addEventListener('touchcancel', this._touchCancel);
this.canvas.addEventListener('touchleave', this._touchLeave);
};
Sketchpad.prototype.drawStroke = function(stroke) {
for (var j = 0; j < stroke.lines.length; j++) {
var line = stroke.lines[j];
this._draw(line.start, line.end, stroke.color, stroke.size);
}
};
Sketchpad.prototype.redraw = function(strokes) {
for (var i = 0; i < strokes.length; i++) {
this.drawStroke(strokes[i]);
}
};
Sketchpad.prototype.toObject = function() {
return {
width: this.canvas.width,
height: this.canvas.height,
strokes: this.strokes,
undoHistory: this.undoHistory,
};
};
Sketchpad.prototype.toJSON = function() {
return JSON.stringify(this.toObject());
};
Sketchpad.prototype.animate = function(ms, loop, loopDelay) {
this.clear();
var delay = ms;
var callback = null;
for (var i = 0; i < this.strokes.length; i++) {
var stroke = this.strokes[i];
for (var j = 0; j < stroke.lines.length; j++) {
var line = stroke.lines[j];
callback = this._draw.bind(this, line.start, line.end,
stroke.color, stroke.size);
this.animateIds.push(setTimeout(callback, delay));
delay += ms;
}
}
if (loop) {
loopDelay = loopDelay || 0;
callback = this.animate.bind(this, ms, loop, loopDelay);
this.animateIds.push(setTimeout(callback, delay + loopDelay));
}
};
Sketchpad.prototype.cancelAnimation = function() {
for (var i = 0; i < this.animateIds.length; i++) {
clearTimeout(this.animateIds[i]);
}
};
Sketchpad.prototype.clear = function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
};
Sketchpad.prototype.undo = function() {
this.clear();
var stroke = this.strokes.pop();
if (stroke) {
this.undoHistory.push(stroke);
this.redraw(this.strokes);
}
};
Sketchpad.prototype.redo = function() {
var stroke = this.undoHistory.pop();
if (stroke) {
this.strokes.push(stroke);
this.drawStroke(stroke);
}
};

View File

@ -1,7 +0,0 @@
include gradio/static/*
include gradio/static/css/*
include gradio/static/js/*
include gradio/static/img/*
include gradio/templates/*
include gradio/templates/input/*
include gradio/templates/output/*

View File

@ -1,73 +0,0 @@
# Gradiome / Gradio
`Gradio` is a python library that allows you to place input and output interfaces over trained models to make it easy for you to "play around" with your model. Gradio runs entirely locally using your browser.
To get a sense of `gradio`, take a look at the python notebooks in the `examples` folder, or read on below!
## Installation
```
pip install gradio
```
(you may need to replace `pip` with `pip3` if you're running `python3`).
## Usage
Gradio is very easy to use with your existing code. The general way it's used is something like this:
```python
import tensorflow as tf
import gradio
mdl = tf.keras.models.Sequential()
# ... define and train the model as you would normally
iface = gradio.Interface(input=“sketchpad”, output=“class”, model_type=“keras”, model=mdl)
iface.launch()
```
Changing the `input` and `output` parameters in the `Interface` face object allow you to create different interfaces, depending on the needs of your model. Take a look at the python notebooks for more examples. The currently supported interfaces are as follows:
**Input interfaces**:
* Sketchpad
* ImageUplaod
* Webcam
* Textbox
**Output interfaces**:
* Class
* Textbox
## Screenshots
Here are a few screenshots that show examples of gradio interfaces
#### MNIST Digit Recognition (Input: Sketchpad, Output: Class)
```python
iface = gradio.Interface(input='sketchpad', output='class', model=model, model_type='keras')
iface.launch()
```
![alt text](https://raw.githubusercontent.com/abidlabs/gradio/master/screenshots/mnist4.png)
#### Facial Emotion Detector (Input: Webcam, Output: Class)
```python
iface = gradio.Interface(input='webcam', output='class', model=model, model_type='keras')
iface.launch()
```
![alt text](https://raw.githubusercontent.com/abidlabs/gradio/master/screenshots/webcam_happy.png)
#### Sentiment Analysis (Input: Textbox, Output: Class)
```python
iface = gradio.Interface(input='textbox', output='class', model=model, model_type='keras')
iface.launch()
```
![alt text](https://raw.githubusercontent.com/abidlabs/gradio/master/screenshots/sentiment_positive.png)

View File

@ -1,11 +0,0 @@
Metadata-Version: 1.0
Name: gradio
Version: 0.3.5
Summary: Python library for easily interacting with trained machine learning models
Home-page: https://github.com/abidlabs/gradio
Author: Abubakar Abid
Author-email: a12d@stanford.edu
License: UNKNOWN
Description: UNKNOWN
Keywords: machine learning,visualization,reproducibility
Platform: UNKNOWN

View File

@ -1,43 +0,0 @@
MANIFEST.in
README.md
setup.py
gradio/__init__.py
gradio/imagenet_class_labels.py
gradio/inputs.py
gradio/interface.py
gradio/networking.py
gradio/outputs.py
gradio/preprocessing_utils.py
gradio/validation_data.py
gradio.egg-info/PKG-INFO
gradio.egg-info/SOURCES.txt
gradio.egg-info/dependency_links.txt
gradio.egg-info/requires.txt
gradio.egg-info/top_level.txt
gradio/static/config.json
gradio/static/css/.DS_Store
gradio/static/css/font-awesome.min.css
gradio/static/css/gradio.css
gradio/static/css/loading.css
gradio/static/css/style.css
gradio/static/img/logo.png
gradio/static/img/logo_inline.png
gradio/static/img/logo_mini.png
gradio/static/img/mic.png
gradio/static/img/table.png
gradio/static/img/webcam.png
gradio/static/js/all-io.js
gradio/static/js/jquery-3.2.1.slim.min.js
gradio/static/js/utils.js
gradio/templates/base_template.html
gradio/templates/input/csv.html
gradio/templates/input/image_upload.html
gradio/templates/input/sketchpad.html
gradio/templates/input/textbox.html
gradio/templates/output/image.html
gradio/templates/output/label.html
gradio/templates/output/textbox.html
test/test_inputs.py
test/test_interface.py
test/test_networking.py
test/test_outputs.py

View File

@ -1,7 +0,0 @@
numpy
websockets
nest_asyncio
beautifulsoup4
Pillow
requests
psutil

View File

@ -1 +0,0 @@
gradio

View File

@ -1 +0,0 @@
from gradio.interface import Interface # This makes it possible to import `Interface` as `gradio.Interface`.

File diff suppressed because it is too large Load Diff

View File

@ -1,185 +0,0 @@
"""
This module defines various classes that can serve as the `input` to an interface. Each class must inherit from
`AbstractInput`, and each class must define a path to its template. All of the subclasses of `AbstractInput` are
automatically added to a registry, which allows them to be easily referenced in other parts of the code.
"""
from abc import ABC, abstractmethod
import base64
from gradio import preprocessing_utils, validation_data
from io import BytesIO
import numpy as np
from PIL import Image, ImageOps
# Where to find the static resources associated with each template.
BASE_INPUT_INTERFACE_TEMPLATE_PATH = 'templates/input/{}.html'
BASE_INPUT_INTERFACE_JS_PATH = 'static/js/interfaces/input/{}.js'
class AbstractInput(ABC):
"""
An abstract class for defining the methods that all gradio inputs should have.
When this is subclassed, it is automatically added to the registry
"""
def __init__(self, preprocessing_fn=None):
"""
:param preprocessing_fn: an optional preprocessing function that overrides the default
"""
if preprocessing_fn is not None:
if not callable(preprocessing_fn):
raise ValueError('`preprocessing_fn` must be a callable function')
self.preprocess = preprocessing_fn
super().__init__()
def get_validation_inputs(self):
"""
An interface can optionally implement a method that returns a list of examples inputs that it should be able to
accept and preprocess for validation purposes.
"""
return []
def get_js_context(self):
"""
:return: a dictionary with context variables for the javascript file associated with the context
"""
return {}
def get_template_context(self):
"""
:return: a dictionary with context variables for the javascript file associated with the context
"""
return {}
@abstractmethod
def get_name(self):
"""
All interfaces should define a method that returns a name used for identifying the related static resources.
"""
pass
@abstractmethod
def preprocess(self, inp):
"""
All interfaces should define a default preprocessing method
"""
pass
class Sketchpad(AbstractInput):
def __init__(self, preprocessing_fn=None, image_width=28, image_height=28,
invert_colors=True):
self.image_width = image_width
self.image_height = image_height
self.invert_colors = invert_colors
super().__init__(preprocessing_fn=preprocessing_fn)
def get_name(self):
return 'sketchpad'
def preprocess(self, inp):
"""
Default preprocessing method for the SketchPad is to convert the sketch to black and white and resize 28x28
"""
content = inp.split(';')[1]
image_encoded = content.split(',')[1]
im = Image.open(BytesIO(base64.b64decode(image_encoded))).convert('L')
if self.invert_colors:
im = ImageOps.invert(im)
im = preprocessing_utils.resize_and_crop(im, (self.image_width, self.image_height))
array = np.array(im).flatten().reshape(1, self.image_width, self.image_height)
return array
class Webcam(AbstractInput):
def __init__(self, preprocessing_fn=None, image_width=224, image_height=224, num_channels=3):
self.image_width = image_width
self.image_height = image_height
self.num_channels = num_channels
super().__init__(preprocessing_fn=preprocessing_fn)
def get_validation_inputs(self):
return validation_data.BASE64_COLOR_IMAGES
def get_name(self):
return 'webcam'
def preprocess(self, inp):
"""
Default preprocessing method for is to convert the picture to black and white and resize to be 48x48
"""
content = inp.split(';')[1]
image_encoded = content.split(',')[1]
im = Image.open(BytesIO(base64.b64decode(image_encoded))).convert('RGB')
im = preprocessing_utils.resize_and_crop(im, (self.image_width, self.image_height))
array = np.array(im).flatten().reshape(1, self.image_width, self.image_height, self.num_channels)
return array
class Textbox(AbstractInput):
def get_validation_inputs(self):
return validation_data.ENGLISH_TEXTS
def get_name(self):
return 'textbox'
def preprocess(self, inp):
"""
By default, no pre-processing is applied to text.
"""
return inp
class ImageUpload(AbstractInput):
def __init__(self, preprocessing_fn=None, image_width=224, image_height=224, num_channels=3, image_mode='RGB',
scale=1/127.5, shift=-1, aspect_ratio="false"):
self.image_width = image_width
self.image_height = image_height
self.num_channels = num_channels
self.image_mode = image_mode
self.scale = scale
self.shift = shift
self.aspect_ratio = aspect_ratio
super().__init__(preprocessing_fn=preprocessing_fn)
def get_validation_inputs(self):
return validation_data.BASE64_COLOR_IMAGES
def get_name(self):
return 'image_upload'
def get_js_context(self):
return {'aspect_ratio': self.aspect_ratio}
def preprocess(self, inp):
"""
Default preprocessing method for is to convert the picture to black and white and resize to be 48x48
"""
content = inp.split(';')[1]
image_encoded = content.split(',')[1]
im = Image.open(BytesIO(base64.b64decode(image_encoded))).convert(self.image_mode)
im = preprocessing_utils.resize_and_crop(im, (self.image_width, self.image_height))
im = np.array(im).flatten()
im = im * self.scale + self.shift
if self.num_channels is None:
array = im.reshape(1, self.image_width, self.image_height)
else:
array = im.reshape(1, self.image_width, self.image_height, self.num_channels)
return array
class CSV(AbstractInput):
def get_name(self):
# return 'templates/input/csv.html'
return 'csv'
def preprocess(self, inp):
"""
By default, no pre-processing is applied to a CSV file (TODO:aliabid94 fix this)
"""
return inp
# Automatically adds all subclasses of AbstractInput into a dictionary (keyed by class name) for easy referencing.
registry = {cls.__name__.lower(): cls for cls in AbstractInput.__subclasses__()}

View File

@ -1,282 +0,0 @@
'''
This is the core file in the `gradio` package, and defines the Interface class, including methods for constructing the
interface using the input and output types.
'''
import asyncio
import websockets
import nest_asyncio
import webbrowser
import gradio.inputs
import gradio.outputs
from gradio import networking
import tempfile
import threading
import traceback
nest_asyncio.apply()
LOCALHOST_IP = '127.0.0.1'
INITIAL_WEBSOCKET_PORT = 9200
TRY_NUM_PORTS = 100
class Interface:
"""
The Interface class represents a general input/output interface for a machine learning model. During construction,
the appropriate inputs and outputs
"""
# Dictionary in which each key is a valid `model_type` argument to constructor, and the value being the description.
VALID_MODEL_TYPES = {'sklearn': 'sklearn model', 'keras': 'Keras model', 'function': 'python function',
'pytorch': 'PyTorch model'}
STATUS_TYPES = {'OFF': 'off', 'RUNNING': 'running'}
def __init__(self, inputs, outputs, model, model_type=None, preprocessing_fns=None, postprocessing_fns=None,
verbose=True):
"""
:param inputs: a string or `AbstractInput` representing the input interface.
:param outputs: a string or `AbstractOutput` representing the output interface.
:param model: the model object, such as a sklearn classifier or keras model.
:param model_type: what kind of trained model, can be 'keras' or 'sklearn' or 'function'. Inferred if not
provided.
:param preprocessing_fns: an optional function that overrides the preprocessing function of the input interface.
:param postprocessing_fns: an optional function that overrides the postprocessing fn of the output interface.
"""
if isinstance(inputs, str):
self.input_interface = gradio.inputs.registry[inputs.lower()](preprocessing_fns)
elif isinstance(inputs, gradio.inputs.AbstractInput):
self.input_interface = inputs
else:
raise ValueError('Input interface must be of type `str` or `AbstractInput`')
if isinstance(outputs, str):
self.output_interface = gradio.outputs.registry[outputs.lower()](postprocessing_fns)
elif isinstance(outputs, gradio.outputs.AbstractOutput):
self.output_interface = outputs
else:
raise ValueError('Output interface must be of type `str` or `AbstractOutput`')
self.model_obj = model
if model_type is None:
model_type = self._infer_model_type(model)
if verbose:
print("Model type not explicitly identified, inferred to be: {}".format(
self.VALID_MODEL_TYPES[model_type]))
elif not(model_type.lower() in self.VALID_MODEL_TYPES):
ValueError('model_type must be one of: {}'.format(self.VALID_MODEL_TYPES))
self.model_type = model_type
self.verbose = verbose
self.status = self.STATUS_TYPES['OFF']
self.validate_flag = False
self.simple_server = None
@staticmethod
def _infer_model_type(model):
""" Helper method that attempts to identify the type of trained ML model."""
try:
import sklearn
if isinstance(model, sklearn.base.BaseEstimator):
return 'sklearn'
except ImportError:
pass
try:
import tensorflow as tf
if isinstance(model, tf.keras.Model):
return 'keras'
except ImportError:
pass
try:
import keras
if isinstance(model, keras.Model):
return 'keras'
except ImportError:
pass
if callable(model):
return 'function'
raise ValueError("model_type could not be inferred, please specify parameter `model_type`")
async def communicate(self, websocket, path):
"""
Method that defines how this interface should communicates with the websocket. (1) When an input is received by
the websocket, it is passed into the input interface and preprocssed. (2) Then the model is called to make a
prediction. (3) Finally, the prediction is postprocessed to get something to be displayed by the output.
:param websocket: a Websocket server used to communicate with the interface frontend
:param path: not used, but required for compliance with websocket library
"""
while True:
try:
msg = await websocket.recv()
processed_input = self.input_interface.preprocess(msg)
prediction = self.predict(processed_input)
processed_output = self.output_interface.postprocess(prediction)
await websocket.send(str(processed_output))
except websockets.exceptions.ConnectionClosed:
pass
# except Exception as e:
# print(e)
def predict(self, preprocessed_input):
"""
Method that calls the relevant method of the model object to make a prediction.
:param preprocessed_input: the preprocessed input returned by the input interface
"""
if self.model_type=='sklearn':
return self.model_obj.predict(preprocessed_input)
elif self.model_type=='keras':
return self.model_obj.predict(preprocessed_input)
elif self.model_type=='function':
return self.model_obj(preprocessed_input)
elif self.model_type=='pytorch':
import torch
value = torch.from_numpy(preprocessed_input)
value = torch.autograd.Variable(value)
prediction = self.model_obj(value)
return prediction.data.numpy()
else:
ValueError('model_type must be one of: {}'.format(self.VALID_MODEL_TYPES))
def validate(self):
if self.validate_flag:
if self.verbose:
print("Interface already validated")
return
validation_inputs = self.input_interface.get_validation_inputs()
n = len(validation_inputs)
if n == 0:
self.validate_flag = True
if self.verbose:
print("No validation samples for this interface... skipping validation.")
return
for m, msg in enumerate(validation_inputs):
if self.verbose:
print(f"Validating samples: {m+1}/{n} [" + "="*(m+1) + "."*(n-m-1) + "]", end='\r')
try:
processed_input = self.input_interface.preprocess(msg)
prediction = self.predict(processed_input)
except Exception as e:
if self.verbose:
print("\n----------")
print("Validation failed, likely due to incompatible pre-processing and model input. See below:\n")
print(traceback.format_exc())
break
try:
_ = self.output_interface.postprocess(prediction)
except Exception as e:
if self.verbose:
print("\n----------")
print("Validation failed, likely due to incompatible model output and post-processing."
"See below:\n")
print(traceback.format_exc())
break
else: # This means if a break was not explicitly called
self.validate_flag = True
if self.verbose:
print("\n\nValidation passed successfully!")
return
raise RuntimeError("Validation did not pass")
def launch(self, inline=None, inbrowser=None, share=False, validate=True):
"""
Standard method shared by interfaces that creates the interface and sets up a websocket to communicate with it.
:param inline: boolean. If True, then a gradio interface is created inline (e.g. in jupyter or colab notebook)
:param inbrowser: boolean. If True, then a new browser window opens with the gradio interface.
:param share: boolean. If True, then a share link is generated using ngrok is displayed to the user.
:param validate: boolean. If True, then the validation is run if the interface has not already been validated.
"""
if validate and not self.validate_flag:
self.validate()
# If an existing interface is running with this instance, close it.
if self.status == self.STATUS_TYPES['RUNNING']:
if self.verbose:
print("Closing existing server...")
if self.simple_server is not None:
try:
networking.close_server(self.simple_server)
except OSError:
pass
output_directory = tempfile.mkdtemp()
# Set up a port to serve the directory containing the static files with interface.
server_port, httpd = networking.start_simple_server(output_directory)
path_to_local_server = 'http://localhost:{}/'.format(server_port)
path_to_local_interface_page = path_to_local_server + networking.TEMPLATE_TEMP
networking.build_template(output_directory, self.input_interface, self.output_interface)
# Set up a port to serve a websocket that sets up the communication between the front-end and model.
websocket_port = networking.get_first_available_port(
INITIAL_WEBSOCKET_PORT, INITIAL_WEBSOCKET_PORT + TRY_NUM_PORTS)
start_server = websockets.serve(self.communicate, LOCALHOST_IP, websocket_port)
networking.set_socket_port_in_js(output_directory, websocket_port) # sets the websocket port in the JS file.
networking.set_interface_types_in_config_file(output_directory,
self.input_interface.__class__.__name__.lower(),
self.output_interface.__class__.__name__.lower())
self.status = self.STATUS_TYPES['RUNNING']
self.simple_server = httpd
is_colab = False
try: # Check if running interactively using ipython.
from_ipynb = get_ipython()
if 'google.colab' in str(from_ipynb):
is_colab = True
except NameError:
pass
if self.verbose:
print("NOTE: Gradio is in beta stage, please report all bugs to: a12d@stanford.edu")
if not is_colab:
print(f"Model is running locally at: {path_to_local_interface_page}")
if share:
try:
path_to_ngrok_server = networking.setup_ngrok(server_port, websocket_port, output_directory)
path_to_ngrok_interface_page = path_to_ngrok_server + '/' + networking.TEMPLATE_TEMP
if self.verbose:
print(f"Model available publicly for 8 hours at: {path_to_ngrok_interface_page}")
except RuntimeError:
path_to_ngrok_server = None
if self.verbose:
print("Unable to create public link for interface, please check internet connection.")
else:
if self.verbose:
print("To create a public link, set `share=True` in the argument to `launch()`")
path_to_ngrok_server = None
if is_colab: # for a colab notebook, create a public link even if share is False.
path_to_ngrok_server = networking.setup_ngrok(server_port, websocket_port, output_directory)
path_to_ngrok_interface_page = path_to_ngrok_server + '/' + networking.TEMPLATE_TEMP
print(f"Cannot display local interface on google colab, public link created at:"
f"{path_to_ngrok_interface_page} and displayed below.")
# Keep the server running in the background.
asyncio.get_event_loop().run_until_complete(start_server)
try:
_ = get_ipython()
except NameError: # Runtime errors are thrown in jupyter notebooks because of async.
t = threading.Thread(target=asyncio.get_event_loop().run_forever, daemon=True)
t.start()
if inline is None:
try: # Check if running interactively using ipython.
get_ipython()
inline = True
if inbrowser is None:
inbrowser = False
except NameError:
inline = False
if inbrowser is None:
inbrowser = True
else:
if inbrowser is None:
inbrowser = False
if inbrowser and not is_colab:
webbrowser.open(path_to_local_interface_page) # Open a browser tab with the interface.
if inline:
from IPython.display import IFrame
if is_colab: # Embed the remote interface page if on google colab; otherwise, embed the local page.
display(IFrame(path_to_ngrok_interface_page, width=1000, height=500))
else:
display(IFrame(path_to_local_interface_page, width=1000, height=500))
return httpd, path_to_local_server, path_to_ngrok_server

View File

@ -1,262 +0,0 @@
'''
Defines helper methods useful for setting up ports, launching servers, and handling `ngrok`
'''
import subprocess
import requests
import zipfile
import io
import sys
import os
import socket
from psutil import process_iter, AccessDenied, NoSuchProcess
from signal import SIGTERM # or SIGKILL
import threading
from http.server import HTTPServer as BaseHTTPServer, SimpleHTTPRequestHandler
import stat
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
import pkg_resources
from bs4 import BeautifulSoup
from distutils import dir_util
from gradio import inputs, outputs
INITIAL_PORT_VALUE = 7860 # The http server will try to open on port 7860. If not available, 7861, 7862, etc.
TRY_NUM_PORTS = 100 # Number of ports to try before giving up and throwing an exception.
LOCALHOST_NAME = 'localhost'
NGROK_TUNNELS_API_URL = "http://localhost:4040/api/tunnels" # TODO(this should be captured from output)
NGROK_TUNNELS_API_URL2 = "http://localhost:4041/api/tunnels" # TODO(this should be captured from output)
BASE_TEMPLATE = pkg_resources.resource_filename('gradio', 'templates/base_template.html')
STATIC_PATH_LIB = pkg_resources.resource_filename('gradio', 'static/')
STATIC_PATH_TEMP = 'static/'
TEMPLATE_TEMP = 'interface.html'
BASE_JS_FILE = 'static/js/all-io.js'
CONFIG_FILE = 'static/config.json'
NGROK_ZIP_URLS = {
"linux": "https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip",
"darwin": "https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-darwin-amd64.zip",
"win32": "https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-windows-amd64.zip",
}
def build_template(temp_dir, input_interface, output_interface):
"""
Builds a complete HTML template with supporting JS and CSS files in a given directory.
:param temp_dir: string with path to temp directory in which the template should be built
:param input_interface: an AbstractInput object which includes is used to get the input template
:param output_interface: an AbstractInput object which includes is used to get the input template
"""
input_template_path = pkg_resources.resource_filename(
'gradio', inputs.BASE_INPUT_INTERFACE_TEMPLATE_PATH.format(input_interface.get_name()))
output_template_path = pkg_resources.resource_filename(
'gradio', outputs.BASE_OUTPUT_INTERFACE_TEMPLATE_PATH.format(output_interface.get_name()))
input_page = open(input_template_path)
output_page = open(output_template_path)
input_soup = BeautifulSoup(render_string_or_list_with_tags(
input_page.read(), input_interface.get_template_context()), features="html.parser")
output_soup = BeautifulSoup(
render_string_or_list_with_tags(
output_page.read(), output_interface.get_template_context()), features="html.parser")
all_io_page = open(BASE_TEMPLATE)
all_io_soup = BeautifulSoup(all_io_page.read(), features="html.parser")
input_tag = all_io_soup.find("div", {"id": "input"})
output_tag = all_io_soup.find("div", {"id": "output"})
input_tag.replace_with(input_soup)
output_tag.replace_with(output_soup)
f = open(os.path.join(temp_dir, TEMPLATE_TEMP), "w")
f.write(str(all_io_soup))
copy_files(STATIC_PATH_LIB, os.path.join(temp_dir, STATIC_PATH_TEMP))
render_template_with_tags(os.path.join(
temp_dir, inputs.BASE_INPUT_INTERFACE_JS_PATH.format(input_interface.get_name())),
input_interface.get_js_context())
render_template_with_tags(os.path.join(
temp_dir, outputs.BASE_OUTPUT_INTERFACE_JS_PATH.format(output_interface.get_name())),
output_interface.get_js_context())
def copy_files(src_dir, dest_dir):
"""
Copies all the files from one directory to another
:param src_dir: string path to source directory
:param dest_dir: string path to destination directory
"""
dir_util.copy_tree(src_dir, dest_dir)
def render_template_with_tags(template_path, context):
"""
Combines the given template with a given context dictionary by replacing all of the occurrences of tags (enclosed
in double curly braces) with corresponding values.
:param template_path: a string with the path to the template file
:param context: a dictionary whose string keys are the tags to replace and whose string values are the replacements.
"""
with open(template_path) as fin:
old_lines = fin.readlines()
new_lines = render_string_or_list_with_tags(old_lines, context)
with open(template_path, 'w') as fout:
for line in new_lines:
fout.write(line)
def render_string_or_list_with_tags(old_lines, context):
# Handle string case
if isinstance(old_lines, str):
for key, value in context.items():
old_lines = old_lines.replace(r'{{' + key + r'}}', str(value))
return old_lines
# Handle list case
new_lines = []
for line in old_lines:
for key, value in context.items():
line = line.replace(r'{{' + key + r'}}', str(value))
new_lines.append(line)
return new_lines
#TODO(abidlabs): Handle the http vs. https issue that sometimes happens (a ws cannot be loaded from an https page)
def set_ngrok_url_in_js(temp_dir, ngrok_socket_url):
ngrok_socket_url = ngrok_socket_url.replace('http', 'ws')
js_file = os.path.join(temp_dir, BASE_JS_FILE)
render_template_with_tags(js_file, {'ngrok_socket_url': ngrok_socket_url})
config_file = os.path.join(temp_dir, CONFIG_FILE)
render_template_with_tags(config_file, {'ngrok_socket_url': ngrok_socket_url})
def set_socket_port_in_js(temp_dir, socket_port):
js_file = os.path.join(temp_dir, BASE_JS_FILE)
render_template_with_tags(js_file, {'socket_port': str(socket_port)})
def set_interface_types_in_config_file(temp_dir, input_interface, output_interface):
config_file = os.path.join(temp_dir, CONFIG_FILE)
render_template_with_tags(config_file, {'input_interface_type': input_interface,
'output_interface_type': output_interface})
def get_first_available_port(initial, final):
"""
Gets the first open port in a specified range of port numbers
:param initial: the initial value in the range of port numbers
:param final: final (exclusive) value in the range of port numbers, should be greater than `initial`
:return:
"""
for port in range(initial, final):
try:
s = socket.socket() # create a socket object
s.bind((LOCALHOST_NAME, port)) # Bind to the port
s.close()
return port
except OSError:
pass
raise OSError("All ports from {} to {} are in use. Please close a port.".format(initial, final))
def serve_files_in_background(port, directory_to_serve=None):
class HTTPHandler(SimpleHTTPRequestHandler):
"""This handler uses server.base_path instead of always using os.getcwd()"""
def translate_path(self, path):
path = SimpleHTTPRequestHandler.translate_path(self, path)
relpath = os.path.relpath(path, os.getcwd())
fullpath = os.path.join(self.server.base_path, relpath)
return fullpath
def log_message(self, format, *args):
return
class HTTPServer(BaseHTTPServer):
"""The main server, you pass in base_path which is the path you want to serve requests from"""
def __init__(self, base_path, server_address, RequestHandlerClass=HTTPHandler):
self.base_path = base_path
BaseHTTPServer.__init__(self, server_address, RequestHandlerClass)
httpd = HTTPServer(directory_to_serve, (LOCALHOST_NAME, port))
# Now loop forever
def serve_forever():
try:
while True:
# sys.stdout.flush()
httpd.serve_forever()
except (KeyboardInterrupt, OSError):
httpd.server_close()
thread = threading.Thread(target=serve_forever, daemon=True)
thread.start()
return httpd
def start_simple_server(directory_to_serve=None):
port = get_first_available_port(INITIAL_PORT_VALUE, INITIAL_PORT_VALUE + TRY_NUM_PORTS)
httpd = serve_files_in_background(port, directory_to_serve)
return port, httpd
def close_server(server):
server.shutdown()
server.server_close()
def download_ngrok():
try:
zip_file_url = NGROK_ZIP_URLS[sys.platform]
except KeyError:
print("Sorry, we don't currently support your operating system, please leave us a note on GitHub, and "
"we'll look into it!")
return
r = requests.get(zip_file_url)
z = zipfile.ZipFile(io.BytesIO(r.content))
z.extractall()
if sys.platform == 'darwin' or sys.platform == 'linux':
st = os.stat('ngrok')
os.chmod('ngrok', st.st_mode | stat.S_IEXEC)
def create_ngrok_tunnel(local_port, api_url):
if not(os.path.isfile('ngrok.exe') or os.path.isfile('ngrok')):
download_ngrok()
if sys.platform == 'win32':
subprocess.Popen(['ngrok', 'http', str(local_port)])
else:
subprocess.Popen(['./ngrok', 'http', str(local_port)])
session = requests.Session()
retry = Retry(connect=3, backoff_factor=0.5)
adapter = HTTPAdapter(max_retries=retry)
session.mount('http://', adapter)
session.mount('https://', adapter)
r = session.get(api_url)
for tunnel in r.json()['tunnels']:
if '{}:'.format(LOCALHOST_NAME) + str(local_port) in tunnel['config']['addr']:
return tunnel['public_url']
raise RuntimeError("Not able to retrieve ngrok public URL")
def setup_ngrok(server_port, websocket_port, output_directory):
kill_processes([4040, 4041]) #TODO(abidlabs): better way to do this
site_ngrok_url = create_ngrok_tunnel(server_port, NGROK_TUNNELS_API_URL)
socket_ngrok_url = create_ngrok_tunnel(websocket_port, NGROK_TUNNELS_API_URL2)
set_ngrok_url_in_js(output_directory, socket_ngrok_url)
return site_ngrok_url
def kill_processes(process_ids): #TODO(abidlabs): remove this, we shouldn't need to kill
for proc in process_iter():
try:
for conns in proc.connections(kind='inet'):
if conns.laddr.port in process_ids:
proc.send_signal(SIGTERM) # or SIGKILL
except (AccessDenied, NoSuchProcess):
pass

View File

@ -1,133 +0,0 @@
"""
This module defines various classes that can serve as the `output` to an interface. Each class must inherit from
`AbstractOutput`, and each class must define a path to its template. All of the subclasses of `AbstractOutput` are
automatically added to a registry, which allows them to be easily referenced in other parts of the code.
"""
from abc import ABC, abstractmethod
import numpy as np
import json
from gradio import imagenet_class_labels
# Where to find the static resources associated with each template.
BASE_OUTPUT_INTERFACE_TEMPLATE_PATH = 'templates/output/{}.html'
BASE_OUTPUT_INTERFACE_JS_PATH = 'static/js/interfaces/output/{}.js'
class AbstractOutput(ABC):
"""
An abstract class for defining the methods that all gradio inputs should have.
When this is subclassed, it is automatically added to the registry
"""
def __init__(self, postprocessing_fn=None):
"""
:param postprocessing_fn: an optional postprocessing function that overrides the default
"""
if postprocessing_fn is not None:
self.postprocess = postprocessing_fn
super().__init__()
def get_js_context(self):
"""
:return: a dictionary with context variables for the javascript file associated with the context
"""
return {}
def get_template_context(self):
"""
:return: a dictionary with context variables for the javascript file associated with the context
"""
return {}
@abstractmethod
def get_name(self):
"""
All outputs should define a method that returns a name used for identifying the related static resources.
"""
pass
@abstractmethod
def postprocess(self, prediction):
"""
All interfaces should define a default postprocessing method
"""
pass
class Label(AbstractOutput):
LABEL_KEY = 'label'
CONFIDENCES_KEY = 'confidences'
CONFIDENCE_KEY = 'confidence'
def __init__(self, postprocessing_fn=None, num_top_classes=3, show_confidences=True, label_names=None,
max_label_length=None):
self.num_top_classes = num_top_classes
self.show_confidences = show_confidences
self.label_names = label_names
self.max_label_length = max_label_length
super().__init__(postprocessing_fn=postprocessing_fn)
def get_name(self):
return 'label'
def get_label_name(self, label):
if self.label_names is None:
name = label
elif self.label_names == 'imagenet1000': # TODO:(abidlabs) better way to handle this
name = imagenet_class_labels.NAMES1000[label]
else: # if list or dictionary
name = self.label_names[label]
if self.max_label_length is not None:
name = name[:self.max_label_length]
return name
def postprocess(self, prediction):
"""
"""
response = dict()
# TODO(abidlabs): check if list, if so convert to numpy array
if isinstance(prediction, np.ndarray):
prediction = prediction.squeeze()
if prediction.size == 1: # if it's single value
response[Label.LABEL_KEY] = self.get_label_name(np.asscalar(prediction))
elif len(prediction.shape) == 1: # if a 1D
response[Label.LABEL_KEY] = self.get_label_name(int(prediction.argmax()))
if self.show_confidences:
response[Label.CONFIDENCES_KEY] = []
for i in range(self.num_top_classes):
response[Label.CONFIDENCES_KEY].append({
Label.LABEL_KEY: self.get_label_name(int(prediction.argmax())),
Label.CONFIDENCE_KEY: float(prediction.max()),
})
prediction[prediction.argmax()] = 0
elif isinstance(prediction, str):
response[Label.LABEL_KEY] = prediction
else:
raise ValueError("Unable to post-process model prediction.")
return json.dumps(response)
class Textbox(AbstractOutput):
def get_name(self):
return 'textbox'
def postprocess(self, prediction):
"""
"""
return prediction
class Image(AbstractOutput):
def get_name(self):
return 'image'
def postprocess(self, prediction):
"""
"""
return prediction
registry = {cls.__name__.lower(): cls for cls in AbstractOutput.__subclasses__()}

View File

@ -1,53 +0,0 @@
from PIL import Image
def resize_and_crop(img, size, crop_type='top'):
"""
Resize and crop an image to fit the specified size.
args:
img_path: path for the image to resize.
modified_path: path to store the modified image.
size: `(width, height)` tuple.
crop_type: can be 'top', 'middle' or 'bottom', depending on this
value, the image will cropped getting the 'top/left', 'midle' or
'bottom/rigth' of the image to fit the size.
raises:
Exception: if can not open the file in img_path of there is problems
to save the image.
ValueError: if an invalid `crop_type` is provided.
"""
# Get current and desired ratio for the images
img_ratio = img.size[0] // float(img.size[1])
ratio = size[0] // float(size[1])
# The image is scaled//cropped vertically or horizontally depending on the ratio
if ratio > img_ratio:
img = img.resize((size[0], size[0] * img.size[1] // img.size[0]),
Image.ANTIALIAS)
# Crop in the top, middle or bottom
if crop_type == 'top':
box = (0, 0, img.size[0], size[1])
elif crop_type == 'middle':
box = (0, (img.size[1] - size[1]) // 2, img.size[0], (img.size[1] + size[1]) // 2)
elif crop_type == 'bottom':
box = (0, img.size[1] - size[1], img.size[0], img.size[1])
else:
raise ValueError('ERROR: invalid value for crop_type')
img = img.crop(box)
elif ratio < img_ratio:
img = img.resize((size[1] * img.size[0] // img.size[1], size[1]),
Image.ANTIALIAS)
# Crop in the top, middle or bottom
if crop_type == 'top':
box = (0, 0, size[0], img.size[1])
elif crop_type == 'middle':
box = ((img.size[0] - size[0]) // 2, 0, (img.size[0] + size[0]) // 2, img.size[1])
elif crop_type == 'bottom':
box = (img.size[0] - size[0], 0, img.size[0], img.size[1])
else:
raise ValueError('ERROR: invalid value for crop_type')
img = img.crop(box)
else:
img = img.resize((size[0], size[1]),
Image.ANTIALIAS)
# If the scale is the same, we do not need to crop
return img

View File

@ -1,5 +0,0 @@
{
"input_interface_type": "{{input_interface_type}}",
"output_interface_type": "{{output_interface_type}}",
"ngrok_socket_url": "{{ngrok_socket_url}}"
}

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -1,232 +0,0 @@
.panel {
display: inline-block;
max-width: 45%;
min-width: 300px;
box-sizing: border-box;
vertical-align: top;
}
.panel {
margin: 0 14px 14px;
}
.instructions {
margin-bottom: 10px;
}
.input, .output {
width: 100%;
height: 360px;
background-color: #F6F6F6;
margin-bottom: 16px;
box-sizing: border-box;
padding: 6px;
display: flex;
flex-flow: column;
}
.loading {
margin-left: auto;
}
.loading img {
display: none;
height: 22px;
}
.panel_head {
display: flex
}
.role {
text-transform: uppercase;
font-family: Arial;
color: #BBB;
margin-bottom: 6px;
font-size: 14px;
font-weight: bold;
}
.input.text .role, .output.text .role {
margin-left: 1px;
}
.submit, .clear, .flag, .message, .send-message {
background-color: #F6F6F6 !important;
padding: 8px !important;
box-sizing: border-box;
width: calc(50% - 8px);
text-transform: uppercase;
font-weight: bold;
border: 0 none;
}
.clear {
background-color: #F6F6F6 !important;
}
.submit {
background-color: #EEA45D !important;
color: white !important;
}
.submit {
margin-right: 8px;
}
.clear {
margin-left: 8px;
}
/*.flag:focus {
background-color: pink !important;
}
*/
.input_text, .output_text {
background: transparent;
resize: none
border: 0 none;
width: 100%;
font-size: 18px;
outline: none;
height: 100%;
padding: 0;
}
.input_image, .input_audio, .input_snapshot, .input_mic, .input_csv, .output_class,
.output_image, .output_csv {
flex: 1 1 auto;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
.input_image, .input_audio, .input_snapshot, .input_mic, .input_csv {
font-weight: bold;
font-size: 24px;
color: #BBB;
cursor: pointer;
}
.input_image img {
max-height: 100%;
max-width: 100%;
}
.hidden_upload {
display: none;
}
.output_class {
font-weight: bold;
font-size: 36px;
}
.drop_mode {
border: dashed 8px #DDD;
}
.input_image, .input_audio {
line-height: 1.5em;
}
.input_snapshot, .input_mic {
flex-direction: column;
}
.input_snapshot .webcam, .input_mic .mic {
height: 80px;
}
.output_image img {
width: 100%; /* or any custom size */
height: 100%;
object-fit: contain;
}
.confidence_intervals {
font-size: 16px;
}
.confidence {
padding: 3px;
display: flex;
}
.level, .label {
display: inline-block;
}
.label {
width: 60px;
}
.confidence_intervals .level {
font-size: 14px;
margin-left: 8px;
margin-right: 8px;
background-color: #AAA;
padding: 2px 4px;
text-align: right;
font-family: monospace;
color: white;
font-weight: bold;
}
.confidence_intervals > * {
vertical-align: bottom;
}
.flag.flagged {
background-color: pink !important;
}
.sketchpad canvas {
background-color: white;
}
.sketch_tools {
flex: 0 1 auto;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 16px;
}
.brush {
border-radius: 50%;
background-color: #AAA;
margin: 0px 20px;
cursor: pointer;
}
.brush.selected, .brush:hover {
background-color: black;
}
#brush_1 {
height: 8px;
width: 8px;
}
#brush_2 {
height: 16px;
width: 16px;
}
#brush_3 {
height: 24px;
width: 24px;
}
.canvas_holder {
flex: 1 1 auto;
text-align: center;
}
canvas {
border: solid 1px black;
}
.text textarea {
resize: none;
background-color: white;
border: none;
box-sizing: border-box;
padding: 4px;
}
.output_image img {
display: none
}
.table_holder {
max-width: 100%;
max-height: 100%;
overflow: scroll;
display: none;
}
.csv_preview {
background-color: white;
max-width: 100%;
max-height: 100%;
font-size: 12px;
font-family: monospace;
}
.csv_preview tr {
border-bottom: solid 1px black;
}
.csv_preview tr.header td {
background-color: #EEA45D;
font-weight: bold;
}
.csv_preview td {
padding: 2px 4px;
}
.csv_preview td:nth-child(even) {
background-color: #EEEEEE;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,45 +0,0 @@
body {
font-family: 'Open Sans', sans-serif;
font-size: 12px;
margin: 0;
}
button, input[type="submit"], input[type="reset"], input[type="text"], input[type="button"], select[type="submit"] {
background: none;
color: inherit;
border: none;
padding: 0;
font: inherit;
cursor: pointer;
outline: inherit;
-webkit-appearance: none;
border-radius: 0;
}
input[type="text"] {
cursor: text;
text-transform: none;
}
body > div, body > nav {
margin-left: 60px;
margin-right: 60px;
}
nav {
text-align: center;
padding: 16px 0 8px;
}
nav img {
margin-right: auto;
height: 32px;
}
#panels {
display: flex;
flex-flow: row;
flex-wrap: wrap;
justify-content: center;
}
.panel {
min-width: 300px;
margin: 40px 0 0;
flex-grow: 1;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -1,68 +0,0 @@
var NGROK_URL = "{{ngrok_socket_url}}"
var SOCKET_PORT = "{{socket_port}}"
function notifyError(error) {
$.notify({
// options
message: 'Not able to communicate with model (is python code still running?)'
},{
// settings
type: 'danger',
animate: {
enter: 'animated fadeInDown',
exit: 'animated fadeOutUp'
},
placement: {
from: "bottom",
align: "right"
},
delay: 5000
});
}
try {
var origin = window.location.origin;
if (origin.includes("ngrok")){
var ws = new WebSocket(NGROK_URL)
} else {
var ws = new WebSocket("ws://127.0.0.1:" + SOCKET_PORT + "/")
}
ws.onerror = function(evt) {
notifyError(evt)
};
ws.onclose = function(event) {
console.log("WebSocket is closed now.");
var model_status = $('#model-status')
model_status.html('Model: closed');
model_status.css('color', '#e23e44');
$('#overlay').css('visibility','visible')
};
} catch (e) {
console.log(e)
}
const sleep = (milliseconds) => {
return new Promise(resolve => setTimeout(resolve, milliseconds))
}
$('body').on('click', '.flag', function(e) {
if ($(".flag").hasClass("flagged")) {
$(".flag").removeClass("flagged").attr("value", "flag");
} else {
$(".flag").addClass("flagged").attr("value", "flagged");
}
})
var start_time;
function loadStart() {
$(".loading img").show();
$(".loading_time").text("");
start_time = new Date().getTime()
}
function loadEnd() {
$(".loading img").hide();
end_time = new Date().getTime()
$(".loading_time").text(((end_time - start_time) / 1000).toFixed(2) + "s");
}

File diff suppressed because one or more lines are too long

View File

@ -1,25 +0,0 @@
try:
from setuptools import setup
except ImportError:
from distutils.core import setup
setup(
name='gradio',
version='0.3.5',
include_package_data=True,
description='Python library for easily interacting with trained machine learning models',
author='Abubakar Abid',
author_email='a12d@stanford.edu',
url='https://github.com/abidlabs/gradio',
packages=['gradio'],
keywords=['machine learning', 'visualization', 'reproducibility'],
install_requires=[
'numpy',
'websockets',
'nest_asyncio',
'beautifulsoup4',
'Pillow',
'requests',
'psutil',
],
)

View File

@ -1,6 +1,6 @@
Metadata-Version: 1.0
Name: gradio
Version: 0.4.2
Version: 0.4.4
Summary: Python library for easily interacting with trained machine learning models
Home-page: https://github.com/abidlabs/gradio
Author: Abubakar Abid

View File

@ -20,6 +20,7 @@ gradio/static/css/font-awesome.min.css
gradio/static/css/gradio.css
gradio/static/css/loading.css
gradio/static/css/style.css
gradio/static/css/vendor/cropper.css
gradio/static/img/logo.png
gradio/static/img/logo_inline.png
gradio/static/img/logo_mini.png
@ -29,6 +30,16 @@ gradio/static/img/webcam.png
gradio/static/js/all-io.js
gradio/static/js/jquery-3.2.1.slim.min.js
gradio/static/js/utils.js
gradio/static/js/interfaces/input/csv.js
gradio/static/js/interfaces/input/image_upload.js
gradio/static/js/interfaces/input/sketchpad.js
gradio/static/js/interfaces/input/textbox.js
gradio/static/js/interfaces/output/image.js
gradio/static/js/interfaces/output/label.js
gradio/static/js/interfaces/output/textbox.js
gradio/static/js/vendor/cropper.js
gradio/static/js/vendor/papaparse.min.js
gradio/static/js/vendor/sketchpad.js
gradio/templates/base_template.html
gradio/templates/input/csv.html
gradio/templates/input/image_upload.html

View File

@ -5,7 +5,7 @@ except ImportError:
setup(
name='gradio',
version='0.4.2',
version='0.4.4',
include_package_data=True,
description='Python library for easily interacting with trained machine learning models',
author='Abubakar Abid',