Example App Walkthrough

Let's walk through the example REST service app provided with the Loomie SDK, and see how it uses the Loomie API. This example app is written in Python, using the Loomie API's Python bindings. For integration in a mobile app, the C++ and Java APIs are similar; please contact us for more information.

For the full source of the app, please see Example App Full Source.

The app starts by importing the modules it needs and setting up Flask:

import PIL.Image
import flask
import flask_restful.reqparse
import io
import loomai.loomie
import numpy
import os
import werkzeug
import zipfile
# Flask-RESTful setup
app = flask.Flask(__name__)
api = flask_restful.Api(app)
create_loomie_parser = flask_restful.reqparse.RequestParser()
create_loomie_parser.add_argument('image', type=werkzeug.datastructures.FileStorage,
location='files')

The first thing that any Loomie application must do before using the API is to set the license token. The example app does that like this:

loomai.loomie.License.set_license(os.environ.get('LOOMAI_LICENSE'))

In this case the app gets the license from the environment, but you can store it in whatever way makes the most sense for your application.

Next, we need to create a Loomie factory object:

factory = loomai.loomie.LoomieFactory(os.environ.get('LOOMAI_RESOURCE_ROOT'), loomai.loomie.ThreadingMode.DISPATCHED)

The factory object pre-loads certain expensive resources when it is created, and releases them when it is destroyed. You'll generally want to create a factory only once, and keep it around for the life of your process, in order to avoid the cost of re-loading resources. The factory needs to know where to find the resource files included in the SDK. In our docker image, the environment variable LOOMAI_RESOURCE_ROOT is configured to point to that location (and your application can leverage that as well.)

With a factory ready to go, we can create a Loomie from a source image. Here's how the sample app does that:

def make_loomie(source):
image = PIL.Image.open(source)
bytes = image.tobytes()
view = loomai.loomie.InterleavedImageViewU8(image.width, image.height, 3,
loomai.loomie.ByteMutableBuffer(bytes),
loomai.loomie.ComponentsOrder.RGB,
loomai.loomie.ImageOrientation.NORMAL)
return factory.make_loomie(view)
[...]
loomie = make_loomie(args['image'])

The client provides the image to our app, which passes the raw byte stream to this function. It decodes the image file using PIL and extracts the raw image bytes. It wraps these in a loomai.loomie.InterleavedImageViewU8, which references the raw image data and describes essential characteristics of the image. It then passes the image view to the factory, which creates and returns a Loomie.

Now that we have a Loomie, let's look at how to get sticker pack images:

render_buffer = bytearray(loomie.get_images_buffer_size(loomie.sticker_image_index_end() - loomie.sticker_image_index_begin()))
images = loomie.get_images(loomie.sticker_image_index_begin(), loomie.sticker_image_index_end(), loomai.loomie.ByteMutableBuffer(render_buffer))
for index, image in enumerate(images):
assert image.channel_count == 3
# Put it in a PIL image
pil_image = PIL.Image.frombytes("RGB", (image.width, image.height),
numpy.array(image.data))
buffer = io.BytesIO()
pil_image.save(buffer, "PNG")
buffer.seek(0)

First we need to allocate a buffer that image data can be written into. get_images_buffer_size() tells us how large that buffer needs to be. It needs to know how many images we plan to get. We want all the "sticker" images here, which are all the images in the range [loomie.sticker_image_index_begin(), loomie.sticker_image_index_end()) - so we use that range to calculate the image count.

The get_images() method returns a list of images which we can iterate. Each image is represented by an InterleavedImageViewU8, the same way as the image we passed to the Loomie factory. We can use the image metadata and raw bytes (which are located in a part of the buffer we allocated earlier) to create a PIL image, which we can then save to a file format of our choice (and later add that file to the ZIP archive the app returns.)

In addition to the stickers for the solved Loomie, the app can also include stickers for several random variations of that Loomie. It does so by calling apply_random_variation():

loomie.apply_random_variation()

...then repeating the process above to fetch and encode all the sticker images for the modified Loomie. (Note that apply_random_variation() is a mutating operation; the original Loomie is modified.)

The final functionality provided by the example app is the ability to fetch binary GLTF streams for a Loomie (and several variants.) The app does so by calling get_gltf():

data = numpy.array(loomie.get_gltf())

get_gltf() returns a binary buffer; the app wraps that in a NumPy array, which can then be added as a file in the ZIP archive the app returns. As with generating stickers, apply_random_variation() is used to generate variations to include in the archive as well.

That's it! For detailed PyDoc documentation, please refer to /usr/local/share/loomai/doc/python/loomai/loomai.loomie.loomie.html in the Docker image.