--- /dev/null
+import os
+import time
+import cPickle
+import datetime
+import logging
+import flask
+import werkzeug
+import optparse
+import tornado.wsgi
+import tornado.httpserver
+import numpy as np
+import pandas as pd
+from PIL import Image as PILImage
+import cStringIO as StringIO
+import urllib
+import caffe
+import exifutil
+
+REPO_DIRNAME = os.path.abspath(os.path.dirname(__file__) + '/../..')
+UPLOAD_FOLDER = '/tmp/caffe_demos_uploads'
+ALLOWED_IMAGE_EXTENSIONS = set(['png', 'bmp', 'jpg', 'jpe', 'jpeg', 'gif'])
+
+# Obtain the flask app object
+app = flask.Flask(__name__)
+
+
+@app.route('/')
+def index():
+ return flask.render_template('index.html', has_result=False)
+
+
+@app.route('/classify_url', methods=['GET'])
+def classify_url():
+ imageurl = flask.request.args.get('imageurl', '')
+ try:
+ string_buffer = StringIO.StringIO(
+ urllib.urlopen(imageurl).read())
+ image = caffe.io.load_image(string_buffer)
+
+ except Exception as err:
+ # For any exception we encounter in reading the image, we will just
+ # not continue.
+ logging.info('URL Image open error: %s', err)
+ return flask.render_template(
+ 'index.html', has_result=True,
+ result=(False, 'Cannot open image from URL.')
+ )
+
+ logging.info('Image: %s', imageurl)
+ result = app.clf.classify_image(image)
+ return flask.render_template(
+ 'index.html', has_result=True, result=result, imagesrc=imageurl)
+
+
+@app.route('/classify_upload', methods=['POST'])
+def classify_upload():
+ try:
+ # We will save the file to disk for possible data collection.
+ imagefile = flask.request.files['imagefile']
+ filename_ = str(datetime.datetime.now()).replace(' ', '_') + \
+ werkzeug.secure_filename(imagefile.filename)
+ filename = os.path.join(UPLOAD_FOLDER, filename_)
+ imagefile.save(filename)
+ logging.info('Saving to %s.', filename)
+ image = exifutil.open_oriented_im(filename)
+
+ except Exception as err:
+ logging.info('Uploaded image open error: %s', err)
+ return flask.render_template(
+ 'index.html', has_result=True,
+ result=(False, 'Cannot open uploaded image.')
+ )
+
+ result = app.clf.classify_image(image)
+ return flask.render_template(
+ 'index.html', has_result=True, result=result,
+ imagesrc=embed_image_html(image)
+ )
+
+
+def embed_image_html(image):
+ """Creates an image embedded in HTML base64 format."""
+ image_pil = PILImage.fromarray((255 * image).astype('uint8'))
+ image_pil = image_pil.resize((256, 256))
+ string_buf = StringIO.StringIO()
+ image_pil.save(string_buf, format='png')
+ data = string_buf.getvalue().encode('base64').replace('\n', '')
+ return 'data:image/png;base64,' + data
+
+
+def allowed_file(filename):
+ return (
+ '.' in filename and
+ filename.rsplit('.', 1)[1] in ALLOWED_IMAGE_EXTENSIONS
+ )
+
+
+class ImagenetClassifier(object):
+ default_args = {
+ 'model_def_file': (
+ '{}/examples/imagenet/imagenet_deploy.prototxt'.format(REPO_DIRNAME)),
+ 'pretrained_model_file': (
+ '{}/examples/imagenet/caffe_reference_imagenet_model'.format(REPO_DIRNAME)),
+ 'mean_file': (
+ '{}/python/caffe/imagenet/ilsvrc_2012_mean.npy'.format(REPO_DIRNAME)),
+ 'class_labels_file': (
+ '{}/data/ilsvrc12/synset_words.txt'.format(REPO_DIRNAME)),
+ 'bet_file': (
+ '{}/data/ilsvrc12/imagenet.bet.pickle'.format(REPO_DIRNAME)),
+ }
+ for key, val in default_args.iteritems():
+ if not os.path.exists(val):
+ raise Exception(
+ "File for {} is missing. Should be at: {}".format(key, val))
+ default_args['image_dim'] = 227
+ default_args['gpu_mode'] = True
+
+ def __init__(self, model_def_file, pretrained_model_file, mean_file,
+ class_labels_file, bet_file, image_dim, gpu_mode=False):
+ logging.info('Loading net and associated files...')
+ self.net = caffe.Classifier(
+ model_def_file, pretrained_model_file, input_scale=255,
+ image_dims=(image_dim, image_dim), gpu=gpu_mode,
+ mean_file=mean_file, channel_swap=(2, 1, 0)
+ )
+
+ with open(class_labels_file) as f:
+ labels_df = pd.DataFrame([
+ {
+ 'synset_id': l.strip().split(' ')[0],
+ 'name': ' '.join(l.strip().split(' ')[1:]).split(',')[0]
+ }
+ for l in f.readlines()
+ ])
+ self.labels = labels_df.sort('synset_id')['name'].values
+
+ self.bet = cPickle.load(open(bet_file))
+ # A bias to prefer children nodes in single-chain paths
+ # I am setting the value to 0.1 as a quick, simple model.
+ # We could use better psychological models here...
+ self.bet['infogain'] -= np.array(self.bet['preferences']) * 0.1
+
+ def classify_image(self, image):
+ try:
+ starttime = time.time()
+ scores = self.net.predict([image], oversample=True).flatten()
+ endtime = time.time()
+
+ indices = (-scores).argsort()[:5]
+ predictions = self.labels[indices]
+
+ # In addition to the prediction text, we will also produce
+ # the length for the progress bar visualization.
+ meta = [
+ (p, '%.5f' % scores[i])
+ for i, p in zip(indices, predictions)
+ ]
+ logging.info('result: %s', str(meta))
+
+ # Compute expected information gain
+ expected_infogain = np.dot(
+ self.bet['probmat'], scores[self.bet['idmapping']])
+ expected_infogain *= self.bet['infogain']
+
+ # sort the scores
+ infogain_sort = expected_infogain.argsort()[::-1]
+ bet_result = [(self.bet['words'][v], '%.5f' % expected_infogain[v])
+ for v in infogain_sort[:5]]
+ logging.info('bet result: %s', str(bet_result))
+
+ return (True, meta, bet_result, '%.3f' % (endtime - starttime))
+
+ except Exception as err:
+ logging.info('Classification error: %s', err)
+ return (False, 'Something went wrong when classifying the '
+ 'image. Maybe try another one?')
+
+
+def start_tornado(app, port=5000):
+ http_server = tornado.httpserver.HTTPServer(
+ tornado.wsgi.WSGIContainer(app))
+ http_server.listen(port)
+ print("Tornado server starting on port {}".format(port))
+ tornado.ioloop.IOLoop.instance().start()
+
+
+def start_from_terminal(app):
+ """
+ Parse command line options and start the server.
+ """
+ parser = optparse.OptionParser()
+ parser.add_option(
+ '-d', '--debug',
+ help="enable debug mode",
+ action="store_true", default=False)
+ parser.add_option(
+ '-p', '--port',
+ help="which port to serve content on",
+ type='int', default=5000)
+ opts, args = parser.parse_args()
+
+ # Initialize classifier
+ app.clf = ImagenetClassifier(**ImagenetClassifier.default_args)
+
+ if opts.debug:
+ app.run(debug=True, host='0.0.0.0', port=opts.port)
+ else:
+ start_tornado(app, opts.port)
+
+
+if __name__ == '__main__':
+ logging.getLogger().setLevel(logging.INFO)
+ if not os.path.exists(UPLOAD_FOLDER):
+ os.makedirs(UPLOAD_FOLDER)
+ start_from_terminal(app)
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="Caffe demos">
+ <meta name="author" content="BVLC (http://bvlc.eecs.berkeley.edu/)">
+
+ <title>Caffe Demos</title>
+
+ <link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
+
+ <script type="text/javascript" src="//code.jquery.com/jquery-2.1.1.js"></script>
+ <script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
+
+ <!-- Script to instantly classify an image once it is uploaded. -->
+ <script type="text/javascript">
+ $(document).ready(
+ function(){
+ $('#classifyfile').attr('disabled',true);
+ $('#imagefile').change(
+ function(){
+ if ($(this).val()){
+ $('#formupload').submit();
+ }
+ }
+ );
+ }
+ );
+ </script>
+
+ <style>
+ body {
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ line-height:1.5em;
+ color: #232323;
+ -webkit-font-smoothing: antialiased;
+ }
+
+ h1, h2, h3 {
+ font-family: Times, serif;
+ line-height:1.5em;
+ border-bottom: 1px solid #ccc;
+ }
+ </style>
+ </head>
+
+ <body>
+ <!-- Begin page content -->
+ <div class="container">
+ <div class="page-header">
+ <h1><a href="/">Caffe Demos</a></h1>
+ <p>
+ The <a href="http://caffe.berkeleyvision.org">Caffe</a> neural network library makes implementing state-of-the-art computer vision systems easy.
+ </p>
+ </div>
+
+ <div>
+ <h2>Classification</h2>
+ <a href="/classify_url?imageurl=http%3A%2F%2Fi.telegraph.co.uk%2Fmultimedia%2Farchive%2F02351%2Fcross-eyed-cat_2351472k.jpg">Click for a Quick Example</a>
+ </div>
+
+ {% if has_result %}
+ {% if not result[0] %}
+ <!-- we have error in the result. -->
+ <div class="alert alert-danger">{{ result[1] }} Did you provide a valid URL or a valid image file? </div>
+ {% else %}
+ <div class="media">
+ <a class="pull-left" href="#"><img class="media-object" width="192" height="192" src={{ imagesrc }}></a>
+ <div class="media-body">
+ <div class="bs-example bs-example-tabs">
+ <ul id="myTab" class="nav nav-tabs">
+ <li class="active"><a href="#infopred" data-toggle="tab">Maximally accurate</a></li>
+ <li><a href="#flatpred" data-toggle="tab">Maximally specific</a></li>
+ </ul>
+ <div id="myTabContent" class="tab-content">
+ <div class="tab-pane fade in active" id="infopred">
+ <ul class="list-group">
+ {% for single_pred in result[2] %}
+ <li class="list-group-item">
+ <span class="badge">{{ single_pred[1] }}</span>
+ <h4 class="list-group-item-heading">
+ <a href="https://www.google.com/#q={{ single_pred[0] }}" target="_blank">{{ single_pred[0] }}</a>
+ </h4>
+ </li>
+ {% endfor %}
+ </ul>
+ </div>
+ <div class="tab-pane fade" id="flatpred">
+ <ul class="list-group">
+ {% for single_pred in result[1] %}
+ <li class="list-group-item">
+ <span class="badge">{{ single_pred[1] }}</span>
+ <h4 class="list-group-item-heading">
+ <a href="https://www.google.com/#q={{ single_pred[0] }}" target="_blank">{{ single_pred[0] }}</a>
+ </h4>
+ </li>
+ {% endfor %}
+ </ul>
+ </div>
+ </div>
+ </div>
+
+ </div>
+ </div>
+ <p> CNN took {{ result[3] }} seconds. </p>
+ {% endif %}
+ <hr>
+ {% endif %}
+
+ <form role="form" action="classify_url" method="get">
+ <div class="form-group">
+ <div class="input-group">
+ <input type="text" class="form-control" name="imageurl" id="imageurl" placeholder="Provide an image URL">
+ <span class="input-group-btn">
+ <input class="btn btn-primary" value="Classify URL" type="submit" id="classifyurl"></input>
+ </span>
+ </div><!-- /input-group -->
+ </div>
+ </form>
+
+ <form id="formupload" class="form-inline" role="form" action="classify_upload" method="post" enctype="multipart/form-data">
+ <div class="form-group">
+ <label for="imagefile">Or upload an image:</label>
+ <input type="file" name="imagefile" id="imagefile">
+ </div>
+ <!--<input type="submit" class="btn btn-primary" value="Classify File" id="classifyfile"></input>-->
+ </form>
+ </div>
+
+ <hr>
+ <div id="footer">
+ <div class="container">
+ <p>© BVLC 2014</p>
+ </div>
+ </div>
+ </body>
+</html>