Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: apassant/plstkbtl
base: ebd126e691
...
head fork: apassant/plstkbtl
compare: 072a057cf8
  • 4 commits
  • 16 files changed
  • 0 commit comments
  • 1 contributor
View
1  .gitignore
@@ -0,0 +1 @@
+*.pyc
View
0  mixture/__init__.py
No changes.
View
0  mixture/image/__init__.py
No changes.
View
BIN  mixture/image/__init__.pyc
Binary file not shown
View
3  mixture/image/models.py
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
View
16 mixture/image/tests.py
@@ -0,0 +1,16 @@
+"""
+This file demonstrates writing tests using the unittest module. These will pass
+when you run "manage.py test".
+
+Replace this with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+
+class SimpleTest(TestCase):
+ def test_basic_addition(self):
+ """
+ Tests that 1 + 1 always equals 2.
+ """
+ self.assertEqual(1 + 1, 2)
View
8 mixture/image/views.py
@@ -0,0 +1,8 @@
+# Create your views here.
+from django.http import HttpResponse
+
+from pyiqe import Api as IQEngine
+iqe = IQEngine('e15511587ea4414f901b9bc1dbaa444a', '46e9fc3bd4684dd6903525fd16da9b74')
+
+def index(request):
+ return HttpResponse('hello, world')
View
BIN  mixture/image/views.pyc
Binary file not shown
View
14 mixture/manage.py
@@ -0,0 +1,14 @@
+#!/usr/bin/env python
+from django.core.management import execute_manager
+import imp
+try:
+ imp.find_module('settings') # Assumed to be in the same directory.
+except ImportError:
+ import sys
+ sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__)
+ sys.exit(1)
+
+import settings
+
+if __name__ == "__main__":
+ execute_manager(settings)
View
29 mixture/pyiqe/__init__.py
@@ -0,0 +1,29 @@
+# -*- coding: UTF-8 -*-
+
+__all__ = ['Api']
+import apis.api1_2
+STABLE_API_VERSION = "1.2"
+
+def Api(*args, **kwargs):
+ """
+ This is a thin wrapper around the Actual API Instances.
+
+ Usage
+ -----
+ iqe = Api(API_KEY, API_SECRET, version="1.2")
+
+ You can obtain api credentials at http://developer.iqengines.com/accounts/register/
+
+ For further information see the docstrings in the following class:
+
+ pyiqe.apis.api1_2.Api
+
+ """
+
+ version = kwargs.pop('version', STABLE_API_VERSION)
+
+ api_map = {"1.2" : apis.api1_2.Api}
+ if not version in api_map:
+ raise Exception("Invalid Api Version")
+ ApiClass = api_map[version]
+ return ApiClass(*args, **kwargs)
View
3  mixture/pyiqe/apis/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: UTF-8 -*-
+
+__all__ = ["api1_2"]
View
361 mixture/pyiqe/apis/api1_2/__init__.py
@@ -0,0 +1,361 @@
+# -*- coding: UTF-8 -*-
+import os
+try:
+ import simplejson
+except:
+ import json as simplejson
+import hmac
+from hashlib import sha1
+from datetime import datetime
+from utils import multipart_call
+
+class __BaseAPI__(object):
+ """
+ Handles requests to the IQ Engines API. An API object is
+ initialized using the API key and secret::
+ """
+
+ host = "api.iqengines.com"
+ protocol = "http"
+ selector = "/v1.2/%s/"
+
+ def __init__(self, key=None, secret=None):
+ super(__BaseAPI__,self).__init__()
+ self.key = key if key else os.environ.get('IQE_KEY')
+ self.secret = secret if secret else os.environ.get('IQE_SECRET')
+ assert self.secret and self.key, "Please provide BOTH a secret and a key"
+
+ def _now(self):
+ t = datetime.utcnow()
+ return t.strftime("%Y%m%d%H%M%S")
+
+ def _build_signature(self, fields, files=None):
+ params = [(key, value) for key, value in fields]
+
+ # put the parameters in a dictionary
+ if files:
+ for f in files:
+ param_name = f[0]
+ filename = os.path.split(f[1])[1]
+ params.append((param_name, filename))
+
+ # reorder the parameters and join key value pairs together
+ sorted_params = sorted( ( key,value ) for key,value in params )
+ raw_string = u"".join([u"".join(x) for x in sorted_params])
+
+ # compute secret
+ digest_maker = hmac.new(self.secret, raw_string.encode("utf-8"), sha1)
+ return digest_maker.hexdigest()
+
+
+ def _signed_call(self, method, selector, fields=None,
+ files=None, json=False):
+
+ fields = fields if fields else []
+ files = files if files else []
+ fields.append(("time_stamp", self._now()))
+ fields.append(("api_key", self.key))
+ if json:
+ fields += [("json", "1")]
+
+ # build signature
+ sig = self._build_signature(fields, files)
+ fields.append(("api_sig", sig))
+
+ # unicode these fields
+ fields = [(i.encode("utf-8"), j.encode("utf-8")) for i,j in fields]
+ files = [(i.encode("utf-8"), j.encode("utf-8"), k) for i,j,k in files]
+
+ # POST the form
+ res = multipart_call( method,
+ self.host,
+ self.protocol,
+ self.selector % selector,
+ fields,
+ files).read()
+ if json:
+ res = simplejson.loads(res)
+ return res, sig
+
+ def _signed_get(self, method, selector, fields=None,
+ files=None, json=False):
+
+ fields = fields if fields else []
+ files = files if files else []
+ fields.append(("time_stamp", self._now()))
+ fields.append(("api_key", self.key))
+ if json: fields.append(("json", "1"))
+
+ # build signature
+ sig = self._build_signature(fields, files)
+ fields.append(("api_sig", sig))
+
+ import urllib
+ params = urllib.urlencode(dict(fields))
+
+ # SEND the formdata
+ url = self.protocol
+ url += "://"
+ url += self.host
+ url += self.selector % selector
+ url += "?"
+ url += params
+ res = urllib.urlopen(url).read()
+ print res
+ if json:
+ res = simplejson.loads(res)
+ return res, sig
+
+
+
+
+class __IQObjects__(__BaseAPI__):
+ """\
+ This class is a handle to the restful objects upload API
+
+ USAGE
+ =====
+
+ >>> from pyiqe import Api
+ >>> iqe = Api(version="1.2", key="blah", secret="blah")
+ >>> iqe.objects.create(
+ name = "Back to the future DVD"
+ images = [
+ "a.jpg",
+ "b.jpg",
+ "c.jpg",
+ ],
+ meta = {
+ sku: "123123",
+ url: "http://retailer.com/123123/"
+ }
+ collection = "DVDs",
+ custom_id = "123123"
+ )
+ [out] : {"obj_id": "foobar"}
+
+ # Equivalent Get statements
+
+ >>> obj = iqe.objects.get(custom_id="123123", collection="default")
+ >>> obj = iqe.objects.get(id="foobar")
+ """
+
+
+ def __init__(self, key, secret):
+ super(__IQObjects__, self).__init__(key=key, secret=secret)
+
+
+ def create(self, images, name="", meta=None, collection=None, custom_id=None):
+ """
+ This api-call will upload an image to the IQ Engines servers
+ where it will be indexed by the computer vision system. Multiple
+ images may be associated to a single object by making repeated
+ upload calls using the same 'name' attribute.
+
+ :type name: string
+ :param name: This is a *unique* name for the object you want to
+ associate to the image
+
+ :type images: list
+ :param imgpath: A list of paths to the images you want to have indexed
+
+ :type meta: dictionary
+ :param meta: A dictionary holding extra meta information about the object
+
+ :type collection: string
+ :param meta: A name for the collection you want to upload to, by default
+ it is set to "_default"
+
+ :type custom_id: string
+ :param custom_id: An id that you can assign to this object that you can
+ later use to look up the object.
+
+ Submit an image for Indexing by the IQE Computer Vision System ::
+ """
+
+ files = [("images", i, open(i).read()) for i in images]
+ fields = [("name", name)]
+
+ if meta:
+ meta = meta or {}
+ meta_json = simplejson.dumps(meta)
+ fields += [("meta",meta_json)]
+
+ if collection:
+ fields += [("collection", collection)]
+
+ if custom_id:
+ fields += [("custom_id", custom_id)]
+
+ res, sig = self._signed_call( method="POST",
+ selector="object",
+ fields=fields,
+ files=files,
+ json=True)
+ return res
+
+ def get(self, obj_id=None, custom_id=None, collection=None):
+ """
+ Retrieves an object using either the object_id OR a custom_id
+ and a collection name
+ """
+
+ assert obj_id or (custom_id and collection), \
+ "Either provide an obj_id or a custom_id and a collection name"
+
+ fields = []
+ if obj_id:
+ selector = "object/%s" % obj_id
+ elif custom_id and collection:
+ selector = "object"
+ fields += [("custom_id", custom_id)]
+ fields += [("collection", collection)]
+
+ res, sig = self._signed_call( method="GET",
+ selector=selector,
+ fields=fields,
+ json=True)
+ return res
+
+ def delete(self, obj_id):
+ """
+ deletes an object
+ """
+ res, sig = self._signed_call(method="DELETE",
+ selector="object/%s/" % obj_id,
+ json=True)
+ return res
+
+class __IQImages__(__BaseAPI__):
+
+
+ def get(self, img_id):
+ """ Retrieves an image and a collection name """
+ fields = []
+ if img_id:
+ selector = "image/%s/" % img_id
+ res, sig = self._signed_call( method="GET",
+ selector=selector,
+ fields=fields,
+ json=True)
+ return res
+
+ def delete(self, obj_id):
+ """
+ deletes an object
+ """
+ res, sig = self._signed_call(method="DELETE",
+ selector="object/%s/" % obj_id,
+ json=True)
+ return res
+
+
+
+class Api(__BaseAPI__):
+ """ Api 1.2 Handle """
+
+
+ def __init__(self, key=None, secret=None):
+ super(Api, self).__init__(key, secret)
+ self.objects = __IQObjects__(key, secret)
+ self.images = __IQImages__(key, secret)
+
+
+ def query(self, imgpath=None, imgdata=None, webhook=None, extra=None, modules=None, json=True, device_id=None, multiple_results=False):
+ """
+ :type imgpath: string
+ :param imgpath: Path to the image you want to have tagged
+
+ :type imgdata: string
+ :param imgpath: binary image data
+
+ :type webhook: string
+ :param webhook: url to post the labels
+
+ :type extra: string
+ :param extra: JSON encoded extra information
+
+ :type device_id: string
+ :param device_id: arbitrary string to represent separate devices
+
+ :type json: boolean
+ :param json: If True the output is a Python dictionary, otherwise XML
+
+ :type multiple_result: boolean
+ :param multiple_result: If True the results will contain all possible matches for the given query
+
+ Submit an image to the IQ Engines image labeling engine using the Query API::
+
+ >>> data, qid = api.query('/path/to/img.jpg')
+ >>> data
+ {u'data': {u'error': 0}}
+ >>> qid
+ '74235664e1f1fc643a15e44517a4cf3d3cbd6874'
+
+ """
+ assert imgpath is not None or imgdata is not None, "either imgpath or imgdata required!"
+ if imgdata is None: imgdata = open(imgpath).read()
+ files = [ ("img", imgpath or sha1(imgdata).hexdigest(), imgdata) ]
+ fields = []
+ if webhook:
+ fields += [("webhook", webhook)]
+ if modules:
+ fields += [("modules", simplejson.dumps(modules))]
+ if extra:
+ fields += [("extra", simplejson.dumps(extra))]
+ if device_id:
+ fields += [("device_id", device_id)]
+ if multiple_results:
+ fields += [("multiple_results", "1")]
+
+
+ fields = fields if fields else None
+ data, sig = self._signed_call(method="POST", selector="query", fields=fields, files=files, json=json)
+ return data, sig
+
+
+ def update(self, json=True, device_id=None):
+ """
+ :type json: boolean
+ :param json: If True the output is a Python dictionary, otherwise XML
+
+ Start a long-polling request to wait for resutls using the Update API::
+
+ >>> results = api.update()
+ >>> results
+ {u'data': {u'error': 0,
+ u'results': [{u'qid': u'74235664e1f1fc643a15e44517a4cf3d3cbd6874',
+ u'qid_data': {u'color': u'Mostly brown orange, with some yellow blue black.',
+ u'labels': u'Duracell Batteries'}}]}}
+
+ """
+ fields = []
+ if device_id:
+ fields=[("device_id", device_id)]
+ data, _ = self._signed_call(method="POST", selector="update", fields=fields, json=json)
+ return data
+
+
+ def result(self, qid, json=True):
+ """
+ :type qid: string
+ :param qid: The QID corresponding to the image for which you want to retrieve the labels
+
+ :type json: boolean
+ :param json: If True the output is a Python dictionary, otherwise XML
+
+ Retrieve the results for a specific QID using the Result API::
+
+ >>> result = api.result(qid="74235664e1f1fc643a15e44517a4cf3d3cbd6874")
+ >>> result
+ {u'data': {u'error': 0,
+ u'results': {u'color': u'Mostly brown orange, with some yellow blue black.',
+ u'labels': u'Duracell Batteries'}}}
+
+ """
+ data, _ = self._signed_call(method="POST",
+ selector="result",
+ fields=[("qid", qid)],
+ json=json)
+ return data
+
View
12 mixture/pyiqe/apis/api1_2/errors.py
@@ -0,0 +1,12 @@
+# -*- coding: UTF-8 -*-
+
+from textwrap import dedent
+class APIError(Exception):
+ """Base Class for All API Errors"""
+ pass
+
+class APIDuplicateObjectCustomIDError(APIError):
+ """You are trying to assign a custom_id to an object. However there is already another object in the same collection that has this custom_id"""
+ pass
+
+
View
59 mixture/pyiqe/apis/api1_2/utils.py
@@ -0,0 +1,59 @@
+# -*- coding: UTF-8 -*-
+
+import httplib, mimetypes
+import urllib2
+
+
+def multipart_call(method, host, protocol, selector, fields, files=None):
+ """
+ Post fields and files to an http host as multipart/form-data.
+ fields is a sequence of (name, value) elements for regular form fields.
+ files is a sequence of (name, filename, value) elements for data to be uploaded as files
+ Return the server's response page.
+ """
+ files = files or []
+ content_type, body = encode_multipart_formdata(fields, files)
+ if protocol == "https":
+ h = httplib.HTTPSConnection(host=host)
+ elif protocol == "http":
+ h = httplib.HTTPConnection(host=host)
+ else:
+ raise NameError, "unknown protocol"
+ headers = {
+ 'User-Agent': 'INSERT USERAGENTNAME',
+ 'Content-Type': content_type
+ }
+ h.request(method, selector, body, headers)
+ res = h.getresponse()
+ return res
+
+def encode_multipart_formdata(fields, files):
+ """
+ fields is a sequence of (name, value) elements for regular form fields.
+ files is a sequence of (name, filename, value) elements for data to be uploaded as files
+ Return (content_type, body) ready for httplib.HTTP instance
+ """
+ BOUNDARY = '----------ThIs_Is_tHe_bouNdaRY_$'
+ CRLF = '\r\n'
+ L = []
+ for (key, value) in fields:
+ L.append('--' + BOUNDARY)
+ L.append('Content-Disposition: form-data; name="%s"' % key)
+ L.append('')
+ L.append(value)
+ for (key, filename, value) in files:
+ L.append('--' + BOUNDARY)
+ L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename))
+ L.append('Content-Type: %s' % get_content_type(filename))
+ L.append('')
+ L.append(value)
+ L.append('--' + BOUNDARY + '--')
+ L.append('')
+ body = CRLF.join(L)
+ content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
+ return content_type, body
+
+def get_content_type(filename):
+ return mimetypes.guess_type(filename)[0] or 'application/octet-stream'
+
+
View
145 mixture/settings.py
@@ -0,0 +1,145 @@
+# Django settings for mixture project.
+
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+
+ADMINS = (
+ # ('Your Name', 'your_email@example.com'),
+)
+
+MANAGERS = ADMINS
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
+ 'NAME': '', # Or path to database file if using sqlite3.
+ 'USER': '', # Not used with sqlite3.
+ 'PASSWORD': '', # Not used with sqlite3.
+ 'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
+ 'PORT': '', # Set to empty string for default. Not used with sqlite3.
+ }
+}
+
+# Local time zone for this installation. Choices can be found here:
+# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
+# although not all choices may be available on all operating systems.
+# On Unix systems, a value of None will cause Django to use the same
+# timezone as the operating system.
+# If running in a Windows environment this must be set to the same as your
+# system time zone.
+TIME_ZONE = 'America/Chicago'
+
+# Language code for this installation. All choices can be found here:
+# http://www.i18nguy.com/unicode/language-identifiers.html
+LANGUAGE_CODE = 'en-us'
+
+SITE_ID = 1
+
+# If you set this to False, Django will make some optimizations so as not
+# to load the internationalization machinery.
+USE_I18N = True
+
+# If you set this to False, Django will not format dates, numbers and
+# calendars according to the current locale
+USE_L10N = True
+
+# Absolute filesystem path to the directory that will hold user-uploaded files.
+# Example: "/home/media/media.lawrence.com/media/"
+MEDIA_ROOT = ''
+
+# URL that handles the media served from MEDIA_ROOT. Make sure to use a
+# trailing slash.
+# Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
+MEDIA_URL = ''
+
+# Absolute path to the directory static files should be collected to.
+# Don't put anything in this directory yourself; store your static files
+# in apps' "static/" subdirectories and in STATICFILES_DIRS.
+# Example: "/home/media/media.lawrence.com/static/"
+STATIC_ROOT = ''
+
+# URL prefix for static files.
+# Example: "http://media.lawrence.com/static/"
+STATIC_URL = '/static/'
+
+# URL prefix for admin static files -- CSS, JavaScript and images.
+# Make sure to use a trailing slash.
+# Examples: "http://foo.com/static/admin/", "/static/admin/".
+ADMIN_MEDIA_PREFIX = '/static/admin/'
+
+# Additional locations of static files
+STATICFILES_DIRS = (
+ # Put strings here, like "/home/html/static" or "C:/www/django/static".
+ # Always use forward slashes, even on Windows.
+ # Don't forget to use absolute paths, not relative paths.
+)
+
+# List of finder classes that know how to find static files in
+# various locations.
+STATICFILES_FINDERS = (
+ 'django.contrib.staticfiles.finders.FileSystemFinder',
+ 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
+# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
+)
+
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = 'nbi$$%w9=qmd*7mvazk76@n8eo20in#g*rvtor*uzwmm3vc2%#'
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+ 'django.template.loaders.filesystem.Loader',
+ 'django.template.loaders.app_directories.Loader',
+# 'django.template.loaders.eggs.Loader',
+)
+
+MIDDLEWARE_CLASSES = (
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+)
+
+ROOT_URLCONF = 'mixture.urls'
+
+TEMPLATE_DIRS = (
+ # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
+ # Always use forward slashes, even on Windows.
+ # Don't forget to use absolute paths, not relative paths.
+)
+
+INSTALLED_APPS = (
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
+ 'django.contrib.messages',
+ 'django.contrib.staticfiles',
+ # Uncomment the next line to enable the admin:
+ # 'django.contrib.admin',
+ # Uncomment the next line to enable admin documentation:
+ # 'django.contrib.admindocs',
+)
+
+# A sample logging configuration. The only tangible logging
+# performed by this configuration is to send an email to
+# the site admins on every HTTP 500 error.
+# See http://docs.djangoproject.com/en/dev/topics/logging for
+# more details on how to customize your logging configuration.
+LOGGING = {
+ 'version': 1,
+ 'disable_existing_loggers': False,
+ 'handlers': {
+ 'mail_admins': {
+ 'level': 'ERROR',
+ 'class': 'django.utils.log.AdminEmailHandler'
+ }
+ },
+ 'loggers': {
+ 'django.request': {
+ 'handlers': ['mail_admins'],
+ 'level': 'ERROR',
+ 'propagate': True,
+ },
+ }
+}
View
14 mixture/urls.py
@@ -0,0 +1,14 @@
+from django.conf.urls.defaults import patterns, include, url
+
+urlpatterns = patterns('',
+ # Examples:
+ # url(r'^$', 'mixture.views.home', name='home'),
+ # url(r'^mixture/', include('mixture.foo.urls')),
+
+ # Uncomment the admin/doc line below to enable admin documentation:
+ # url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
+
+ # Uncomment the next line to enable the admin:
+ # url(r'^admin/', include(admin.site.urls)),
+ url(r'^image/', 'image.views.index'),
+)

No commit comments for this range

Something went wrong with that request. Please try again.