diff --git a/build/lib/gradio/networking.py b/build/lib/gradio/networking.py
index 7004fc2ff0..bafe9fbda6 100644
--- a/build/lib/gradio/networking.py
+++ b/build/lib/gradio/networking.py
@@ -14,6 +14,8 @@ from gradio.tunneling import create_tunnel
import urllib.request
from shutil import copyfile
import requests
+import os
+
INITIAL_PORT_VALUE = (
7860
@@ -21,7 +23,7 @@ INITIAL_PORT_VALUE = (
TRY_NUM_PORTS = (
100
) # Number of ports to try before giving up and throwing an exception.
-LOCALHOST_NAME = "127.0.0.1"
+LOCALHOST_NAME = os.getenv('GRADIO_SERVER_NAME', "127.0.0.1")
GRADIO_API_SERVER = "https://api.gradio.app/v1/tunnel-request"
STATIC_TEMPLATE_LIB = pkg_resources.resource_filename("gradio", "templates/")
diff --git a/build/lib/gradio/outputs.py b/build/lib/gradio/outputs.py
index 872c8aa32c..8d49ed38dd 100644
--- a/build/lib/gradio/outputs.py
+++ b/build/lib/gradio/outputs.py
@@ -42,6 +42,7 @@ class AbstractOutput(ABC):
"""
return {}
+
class Label(AbstractOutput):
def __init__(self, num_top_classes=None, label=None):
self.num_top_classes = num_top_classes
diff --git a/dist/gradio-0.9.6-py3.7.egg b/dist/gradio-0.9.6-py3.7.egg
deleted file mode 100644
index 538c46fc99..0000000000
Binary files a/dist/gradio-0.9.6-py3.7.egg and /dev/null differ
diff --git a/dist/gradio-0.9.7-py3-none-any.whl b/dist/gradio-0.9.7-py3-none-any.whl
deleted file mode 100644
index b57c57b3a7..0000000000
Binary files a/dist/gradio-0.9.7-py3-none-any.whl and /dev/null differ
diff --git a/dist/gradio-0.9.7.tar.gz b/dist/gradio-0.9.7.tar.gz
deleted file mode 100644
index cdc296c1b9..0000000000
Binary files a/dist/gradio-0.9.7.tar.gz and /dev/null differ
diff --git a/gradio.egg-info/PKG-INFO b/gradio.egg-info/PKG-INFO
index a9992a24f6..4ca224d33b 100644
--- a/gradio.egg-info/PKG-INFO
+++ b/gradio.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.0
Name: gradio
-Version: 0.9.7
+Version: 0.9.8
Summary: Python library for easily interacting with trained machine learning models
Home-page: https://github.com/gradio-app/gradio-UI
Author: Abubakar Abid
diff --git a/gradio/__init__.py b/gradio/__init__.py
index 4b898437e3..bc36d88ec7 100644
--- a/gradio/__init__.py
+++ b/gradio/__init__.py
@@ -1 +1 @@
-from gradio.interface import Interface # This makes it possible to import `Interface` as `gradio.Interface`.
+from gradio.interface import * # This makes it possible to import `Interface` as `gradio.Interface`.
diff --git a/gradio/inputs.py b/gradio/inputs.py
index 8d6379bbcd..075317ff5f 100644
--- a/gradio/inputs.py
+++ b/gradio/inputs.py
@@ -67,7 +67,7 @@ class AbstractInput(ABC):
class Sketchpad(AbstractInput):
- def __init__(self, cast_to="numpy", shape=(28, 28), invert_colors=True,
+ def __init__(self, shape=(28, 28), invert_colors=True,
flatten=False, scale=1/255, shift=0,
dtype='float64', sample_inputs=None, label=None):
self.image_width = shape[0]
diff --git a/gradio/interface.py b/gradio/interface.py
index 3c59a26fe0..5d470c07a2 100644
--- a/gradio/interface.py
+++ b/gradio/interface.py
@@ -17,6 +17,8 @@ import random
import time
import inspect
from IPython import get_ipython
+import sys
+import weakref
PKG_VERSION_URL = "https://gradio.app/api/pkg-version"
@@ -27,6 +29,7 @@ class Interface:
The Interface class represents a general input/output interface for a machine learning model. During construction,
the appropriate inputs and outputs
"""
+ instances = weakref.WeakSet()
def __init__(self, fn, inputs, outputs, saliency=None, verbose=False, examples=None,
live=False, show_input=True, show_output=True,
@@ -82,6 +85,9 @@ class Interface:
self.description = description
self.thumbnail = thumbnail
self.examples = examples
+ self.server_port = None
+ self.simple_server = None
+ Interface.instances.add(self)
def get_config_file(self):
config = {
@@ -204,7 +210,12 @@ class Interface:
return
raise RuntimeError("Validation did not pass")
- def launch(self, inline=None, inbrowser=None, share=False, validate=True):
+ def close(self):
+ if self.simple_server and not(self.simple_server.fileno() == -1): # checks to see if server is running
+ print("Closing Gradio server on port {}...".format(self.server_port))
+ networking.close_server(self.simple_server)
+
+ def launch(self, inline=None, inbrowser=None, share=False, validate=True, debug=False):
"""
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)
@@ -223,22 +234,13 @@ class Interface:
except (ImportError, AttributeError): # If they are using TF >= 2.0 or don't have TF, just ignore this.
pass
- # If an existing interface is running with this instance, close it.
- if self.status == "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(self, output_directory, self.server_name)
path_to_local_server = "http://{}:{}/".format(self.server_name, server_port)
networking.build_template(output_directory)
+ self.server_port = server_port
self.status = "RUNNING"
self.simple_server = httpd
@@ -247,6 +249,7 @@ class Interface:
from_ipynb = get_ipython()
if "google.colab" in str(from_ipynb):
is_colab = True
+ print("Google colab notebook detected.")
except NameError:
pass
@@ -264,11 +267,17 @@ class Interface:
if not is_colab:
print(strings.en["RUNNING_LOCALLY"].format(path_to_local_server))
+ else:
+ if debug:
+ print("This cell will run indefinitely so that you can see errors and logs. To turn off, "
+ "set debug=False in launch().")
+ else:
+ print("To show errors in colab notebook, set debug=True in launch()")
if share:
try:
share_url = networking.setup_tunnel(server_port)
- print("External URL:", share_url)
+ print("Running on External URL:", share_url)
except RuntimeError:
share_url = None
if self.verbose:
@@ -330,4 +339,18 @@ class Interface:
networking.set_config(config, output_directory)
+ if debug:
+ while True:
+ sys.stdout.flush()
+ time.sleep(0.1)
+
return httpd, path_to_local_server, share_url
+
+ @classmethod
+ def get_instances(cls):
+ return list(Interface.instances) #Returns list of all current instances
+
+
+def reset_all():
+ for io in Interface.get_instances():
+ io.close()
diff --git a/gradio/networking.py b/gradio/networking.py
index 7004fc2ff0..5d0853e4c8 100644
--- a/gradio/networking.py
+++ b/gradio/networking.py
@@ -14,6 +14,8 @@ from gradio.tunneling import create_tunnel
import urllib.request
from shutil import copyfile
import requests
+import sys
+
INITIAL_PORT_VALUE = (
7860
@@ -21,7 +23,7 @@ INITIAL_PORT_VALUE = (
TRY_NUM_PORTS = (
100
) # Number of ports to try before giving up and throwing an exception.
-LOCALHOST_NAME = "127.0.0.1"
+LOCALHOST_NAME = os.getenv('GRADIO_SERVER_NAME', "127.0.0.1")
GRADIO_API_SERVER = "https://api.gradio.app/v1/tunnel-request"
STATIC_TEMPLATE_LIB = pkg_resources.resource_filename("gradio", "templates/")
@@ -115,7 +117,6 @@ def get_first_available_port(initial, final):
def serve_files_in_background(interface, port, directory_to_serve=None, server_name=LOCALHOST_NAME):
class HTTPHandler(SimpleHTTPRequestHandler):
"""This handler uses server.base_path instead of always using os.getcwd()"""
-
def _set_headers(self):
self.send_response(200)
self.send_header("Content-type", "application/json")
@@ -132,7 +133,6 @@ def serve_files_in_background(interface, port, directory_to_serve=None, server_n
def do_POST(self):
# Read body of the request.
-
if self.path == "/api/predict/":
# Make the prediction.
self._set_headers()
@@ -196,12 +196,13 @@ def serve_files_in_background(interface, port, directory_to_serve=None, server_n
# Now loop forever
def serve_forever():
- # try:
- while True:
- # sys.stdout.flush()
- httpd.serve_forever()
- # except (KeyboardInterrupt, OSError):
- # httpd.server_close()
+ try:
+ while True:
+ sys.stdout.flush()
+ httpd.serve_forever()
+ except (KeyboardInterrupt, OSError):
+ httpd.shutdown()
+ httpd.server_close()
thread = threading.Thread(target=serve_forever, daemon=False)
thread.start()
@@ -213,13 +214,11 @@ def start_simple_server(interface, directory_to_serve=None, server_name=None):
port = get_first_available_port(
INITIAL_PORT_VALUE, INITIAL_PORT_VALUE + TRY_NUM_PORTS
)
- httpd = serve_files_in_background(
- interface, port, directory_to_serve, server_name)
+ httpd = serve_files_in_background(interface, port, directory_to_serve, server_name)
return port, httpd
def close_server(server):
- server.shutdown()
server.server_close()
diff --git a/gradio/outputs.py b/gradio/outputs.py
index 872c8aa32c..f34c7d2981 100644
--- a/gradio/outputs.py
+++ b/gradio/outputs.py
@@ -42,7 +42,12 @@ class AbstractOutput(ABC):
"""
return {}
+
class Label(AbstractOutput):
+ LABEL_KEY = "label"
+ CONFIDENCE_KEY = "confidence"
+ CONFIDENCES_KEY = "confidences"
+
def __init__(self, num_top_classes=None, label=None):
self.num_top_classes = num_top_classes
super().__init__(label)
@@ -59,16 +64,19 @@ class Label(AbstractOutput):
if self.num_top_classes is not None:
sorted_pred = sorted_pred[:self.num_top_classes]
return {
- "label": sorted_pred[0][0],
- "confidences": [
+ self.LABEL_KEY: sorted_pred[0][0],
+ self.CONFIDENCES_KEY: [
{
- "label": pred[0],
- "confidence" : pred[1]
+ self.LABEL_KEY: pred[0],
+ self.CONFIDENCE_KEY: pred[1]
} for pred in sorted_pred
]
}
+ elif isinstance(prediction, int) or isinstance(prediction, float):
+ return {self.LABEL_KEY: str(prediction)}
else:
- raise ValueError("Function output should be string or dict")
+ raise ValueError("The `Label` output interface expects one of: a string label, or an int label, a "
+ "float label, or a dictionary whose keys are labels and values are confidences.")
@classmethod
def get_shortcut_implementations(cls):
@@ -81,6 +89,13 @@ class KeyValues(AbstractOutput):
def __init__(self, label=None):
super().__init__(label)
+ def postprocess(self, prediction):
+ if isinstance(prediction, dict):
+ return prediction
+ else:
+ raise ValueError("The `KeyValues` output interface expects an output that is a dictionary whose keys are "
+ "labels and values are corresponding values.")
+
@classmethod
def get_shortcut_implementations(cls):
return {
@@ -110,9 +125,11 @@ class Textbox(AbstractOutput):
}
def postprocess(self, prediction):
- """
- """
- return prediction
+ if isinstance(prediction, str) or isinstance(prediction, int) or isinstance(prediction, float):
+ return str(prediction)
+ else:
+ raise ValueError("The `Textbox` output interface expects an output that is one of: a string, or"
+ "an int/float that can be converted to a string.")
class Image(AbstractOutput):
@@ -131,9 +148,16 @@ class Image(AbstractOutput):
"""
"""
if self.plot:
- return preprocessing_utils.encode_plot_to_base64(prediction)
+ try:
+ return preprocessing_utils.encode_plot_to_base64(prediction)
+ except:
+ raise ValueError("The `Image` output interface expects a `matplotlib.pyplot` object"
+ "if plt=True.")
else:
- return preprocessing_utils.encode_array_to_base64(prediction)
+ try:
+ return preprocessing_utils.encode_array_to_base64(prediction)
+ except:
+ raise ValueError("The `Image` output interface (with plt=False) expects a numpy array.")
def rebuild_flagged(self, dir, msg):
"""
diff --git a/gradio/static/js/all_io.js b/gradio/static/js/all_io.js
index 9da8b92949..411ee33e44 100644
--- a/gradio/static/js/all_io.js
+++ b/gradio/static/js/all_io.js
@@ -38,8 +38,22 @@ var io_master_template = {
for (let i = 0; i < this.output_interfaces.length; i++) {
this.output_interfaces[i].output(data["data"][i]);
-// this.output_interfaces[i].target.parent().find(`.loading_time[interface="${i}"]`).text("Latency: " + ((data["durations"][i])).toFixed(2) + "s");
}
+
+ let ratio;
+ if (data["durations"].length === 1) {
+ this.output_interfaces[0].target.parent().find(`.loading_time[interface="${this.output_interfaces.length - 1}"]`).text("Latency: " + ((data["durations"][0])).toFixed(2) + "s");
+ } else if (this.output_interfaces.length === data["durations"].length) {
+ for (let i = 0; i < this.output_interfaces.length; i++) {
+ this.output_interfaces[i].target.parent().find(`.loading_time[interface="${i}"]`).text("Latency: " + ((data["durations"][i])).toFixed(2) + "s");
+ }
+ } else {
+ ratio = this.output_interfaces.length / data["durations"].length;
+ for (let i = 0; i < this.output_interfaces.length; i = i + ratio) {
+ this.output_interfaces[i].target.parent().find(`.loading_time[interface="${i + ratio - 1}"]`).text("Latency: " + ((data["durations"][i / ratio])).toFixed(2) + "s");
+ }
+ }
+
if (this.config.live) {
this.gather();
} else {
diff --git a/gradio/strings.py b/gradio/strings.py
index 1fc88f28ef..2818b1e763 100644
--- a/gradio/strings.py
+++ b/gradio/strings.py
@@ -1,6 +1,6 @@
en = {
"BETA_MESSAGE": "NOTE: Gradio is in beta stage, please report all bugs to: gradio.app@gmail.com",
- "RUNNING_LOCALLY": "Model is running locally at: {}",
+ "RUNNING_LOCALLY": "Running locally at: {}",
"NGROK_NO_INTERNET": "Unable to create public link for interface, please check internet connection or try "
"restarting python interpreter.",
"COLAB_NO_LOCAL": "Cannot display local interface on google colab, public link created.",
diff --git a/index.html b/index.html
deleted file mode 100644
index dfe69e1ff7..0000000000
--- a/index.html
+++ /dev/null
@@ -1,331 +0,0 @@
-
-
-
-
-
-
-
-
-
-
- Gradio: An Interactive Demo for Your Trained ML Model
-
-
-
-
-
-
-
-
-
-
-
-
- Gradio
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Getting started
-
- Gradio (gradient input-output) is a python library that allows you to easily transform your trained model into a webpage that easily allows you to interface with it. This allows you to:
-
- Show off the model to others
- Identify response to unusual behavior
- Encourage fairness and reproducibility of your model
-
- Gradio is designed to be very simple to use. For example, in just 2 lines of Python code, you can create a sketchpad that allows you write digits and see whether your MNIST model can correctly identify them.
-
-
-
Installation
-
Gradio is a standard python library, so install it from your terminal like this:
-
-
- pip install gradio
-
-
-
-
Usage
-
To use gradio in your code, simply import it, and then launch the appropriate interface
-
-
- import gradio
- import tensorflow as tf
-
- model = tf.keras.Sequential()
- # Define your model, as you ordinarily do
- # Train your model, as you ordinarily do
-
- gradio.interfaces.Sketchpad(model=model, model_type='keras').launch()
-
-
-
-
- This will open a new browser window which will allow you to draw into a "sketchpad". Behind the scenes, gradio transforms the user drawing into a numeric array and passes it into your model for your prediction.
- Then, gradio will fetch the result and show it to the user. Like this:
-
- Currently, the following
model_type
are supported:
-
- Keras models
- Sklearn models
- Arbitrary python functions
-
-
- See the gallery of interfaces below to see what kind of interfaces can be supported. Contribute your own interfaces by visiting our GitHub page.
-
-
-
Gallery of Interfaces
-
-
-
-
-
-
-
Draw a Digit : allow users to draw their own digits to test MNIST-type models.
-
-
-
-
-
-
-
-
-
Emotion Detector : allow users to make emotions through their webcam and detect their emotion with your model.
-
-
-
-
-
-
-
-
-
Automated Author : use an RNN to generate a sequence of words beginning the words that the user has supplied
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/setup.py b/setup.py
index c646380767..4a9fb8aee2 100644
--- a/setup.py
+++ b/setup.py
@@ -5,7 +5,7 @@ except ImportError:
setup(
name='gradio',
- version='0.9.7',
+ version='0.9.8',
include_package_data=True,
description='Python library for easily interacting with trained machine learning models',
author='Abubakar Abid',
diff --git a/test/test_interface.py b/test/test_interface.py
index 84a19ddd43..fc9afbe4f1 100644
--- a/test/test_interface.py
+++ b/test/test_interface.py
@@ -7,8 +7,7 @@ import gradio.outputs
class TestInterface(unittest.TestCase):
def test_input_output_mapping(self):
- io = gr.Interface(inputs='SketCHPad', outputs='textBOX', fn=lambda
- x: x)
+ io = gr.Interface(inputs='SketCHPad', outputs='textBOX', fn=lambda x: x)
self.assertIsInstance(io.input_interfaces[0], gradio.inputs.Sketchpad)
self.assertIsInstance(io.output_interfaces[0], gradio.outputs.Textbox)
@@ -18,52 +17,15 @@ class TestInterface(unittest.TestCase):
self.assertEqual(io.input_interfaces[0], inp)
def test_output_interface_is_instance(self):
- # out = gradio.outputs.Label(show_confidences=False)
out = gradio.outputs.Label()
io = gr.Interface(inputs='SketCHPad', outputs=out, fn=lambda x: x)
self.assertEqual(io.output_interfaces[0], out)
- def test_keras_model(self):
- try:
- import tensorflow as tf
- except:
- raise unittest.SkipTest("Need tensorflow installed to do keras-based tests")
- inputs = tf.keras.Input(shape=(3,))
- x = tf.keras.layers.Dense(4, activation=tf.nn.relu)(inputs)
- outputs = tf.keras.layers.Dense(5, activation=tf.nn.softmax)(x)
- model = tf.keras.Model(inputs=inputs, outputs=outputs)
- io = gr.Interface(inputs='SketCHPad', outputs='textBOX', fn=model)
- # pred = io.predict(np.ones(shape=(1, 3), ))
- # self.assertEqual(pred.shape, (1, 5))
-
- def test_func_model(self):
+ def test_prediction(self):
def model(x):
return 2*x
- io = gr.Interface(inputs='SketCHPad', outputs='textBOX', fn=model)
- # pred = io.predict(np.ones(shape=(1, 3)))
- # self.assertEqual(pred.shape, (1, 3))
-
- def test_pytorch_model(self):
- try:
- import torch
- except:
- raise unittest.SkipTest("Need torch installed to do pytorch-based tests")
-
- class TwoLayerNet(torch.nn.Module):
- def __init__(self):
- super(TwoLayerNet, self).__init__()
- self.linear1 = torch.nn.Linear(3, 4)
- self.linear2 = torch.nn.Linear(4, 5)
-
- def forward(self, x):
- h_relu = torch.nn.functional.relu(self.linear1(x))
- y_pred = self.linear2(h_relu)
- return y_pred
-
- model = TwoLayerNet()
- io = gr.Interface(inputs='SketCHPad', outputs='textBOX', fn=model)
- # pred = io.predict(np.ones(shape=(1, 3), dtype=np.float32))
- # self.assertEqual(pred.shape, (1, 5))
+ io = gr.Interface(inputs='textbox', outputs='textBOX', fn=model)
+ self.assertEqual(io.predict[0](11), 22)
if __name__ == '__main__':
diff --git a/test/test_outputs.py b/test/test_outputs.py
index a78bcc96d0..f4d854a14b 100644
--- a/test/test_outputs.py
+++ b/test/test_outputs.py
@@ -14,37 +14,39 @@ class TestLabel(unittest.TestCase):
path = outputs.BASE_OUTPUT_INTERFACE_JS_PATH.format(out.__class__.__name__.lower())
self.assertTrue(os.path.exists(os.path.join(PACKAGE_NAME, path)))
- # def test_postprocessing_string(self):
- # string = 'happy'
- # out = outputs.Label()
- # label = json.loads(out.postprocess(string))
- # self.assertDictEqual(label, {outputs.Label.LABEL_KEY: string})
- #
- # def test_postprocessing_1D_array(self):
- # array = np.array([0.1, 0.2, 0, 0.7, 0])
- # true_label = {outputs.Label.LABEL_KEY: 3,
- # outputs.Label.CONFIDENCES_KEY: [
- # {outputs.Label.LABEL_KEY: 3, outputs.Label.CONFIDENCE_KEY: 0.7},
- # {outputs.Label.LABEL_KEY: 1, outputs.Label.CONFIDENCE_KEY: 0.2},
- # {outputs.Label.LABEL_KEY: 0, outputs.Label.CONFIDENCE_KEY: 0.1},
- # ]}
- # out = outputs.Label()
- # label = json.loads(out.postprocess(array))
- # self.assertDictEqual(label, true_label)
+ def test_postprocessing_string(self):
+ string = 'happy'
+ out = outputs.Label()
+ label = out.postprocess(string)
+ self.assertDictEqual(label, {outputs.Label.LABEL_KEY: string})
- # def test_postprocessing_1D_array_no_confidences(self):
- # array = np.array([0.1, 0.2, 0, 0.7, 0])
- # true_label = {outputs.Label.LABEL_KEY: 3}
- # out = outputs.Label(show_confidences=False)
- # label = json.loads(out.postprocess(array))
- # self.assertDictEqual(label, true_label)
- #
- # def test_postprocessing_int(self):
- # true_label_array = np.array([[[3]]])
- # true_label = {outputs.Label.LABEL_KEY: 3}
- # out = outputs.Label()
- # label = json.loads(out.postprocess(true_label_array))
- # self.assertDictEqual(label, true_label)
+ def test_postprocessing_dict(self):
+ orig_label = {
+ 3: 0.7,
+ 1: 0.2,
+ 0: 0.1
+ }
+ true_label = {outputs.Label.LABEL_KEY: 3,
+ outputs.Label.CONFIDENCES_KEY: [
+ {outputs.Label.LABEL_KEY: 3, outputs.Label.CONFIDENCE_KEY: 0.7},
+ {outputs.Label.LABEL_KEY: 1, outputs.Label.CONFIDENCE_KEY: 0.2},
+ {outputs.Label.LABEL_KEY: 0, outputs.Label.CONFIDENCE_KEY: 0.1},
+ ]}
+ out = outputs.Label()
+ label = out.postprocess(orig_label)
+ self.assertDictEqual(label, true_label)
+
+ def test_postprocessing_array(self):
+ array = np.array([0.1, 0.2, 0, 0.7, 0])
+ out = outputs.Label()
+ self.assertRaises(ValueError, out.postprocess, array)
+
+ def test_postprocessing_int(self):
+ label = 3
+ true_label = {outputs.Label.LABEL_KEY: '3'}
+ out = outputs.Label()
+ label = out.postprocess(label)
+ self.assertDictEqual(label, true_label)
class TestTextbox(unittest.TestCase):
diff --git a/upload_to_pypi.sh b/upload_to_pypi.sh
new file mode 100644
index 0000000000..b61a6272c5
--- /dev/null
+++ b/upload_to_pypi.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+# Before running this, go to setup.py and update the version # of the library
+
+echo "Make sure to update the version number in setup.py!!!"
+rm dist/*
+python setup.py sdist bdist_wheel
+python -m twine upload dist/*
+git add -A
+git commit -m "updated PyPi version"
+git push origin master