Building Blocks

As explained in the User Guide, Aliquis is based on some key concepts: patch, stage, pipeline, host. While patches are core objects that cannot be customized and pipelines are easy to write also for non-programmers, new stages and hosts could be created with Aliquis’ APIs to accomplish custom tasks.

This section of the documentation will explain Aliquis’ API to allow developers to build custom stages and hosts. For further information please see the Python references.

Patch

A patch is an istance of the Python class Patch. In your code, you will want to create patches and make use of patch’s data and meta-data.

Aliquis’ API expose several members of the Patch class:

  • Patch.im: a numpy.ndarray containing the main signal, tipically an image
  • Patch.colorfmt: the color format of the image (from one of those available at ColorFormat)
  • Patch.shapes: a list of shapes associated to the patch
  • Patch.val_range: the range of values of the signal (used mainly for adequate displaying)
  • Patch.values: the classification values
  • Patch.tr: the transformation matrix with respect to parent patch

Source patches can be created by defining at least the im data (e.g. image ndarray), other arguments are optional and are mapped to istance variables of the same name:

from aliquis import Patch
p = Patch(im, values, tr, colorfmt, val_range, shapes)

Patches are generally created as children of another patch. In order to allow Aliquis to know the exact patch hierarchy, sub-patches should be created with these useful methods:

# Add a generic sub-patch
child_patch = p.addSubpatch(Patch(img))

# Add a sub-patch from a crop of parent patch's image
child_patch = p.addSubpatchFromRect((y,x),(h,w))

# Add a sub-patch with same data but new shapes
child_patch = p.addSubpatchWithShapes(list_of_shapes)

# Add a sub-patch with same data but different classification values
child_patch = p.addSubpatchWithValues(array_of_values)

Patch history can be climbed up with Patch.getParent and Patch.getRoot methods or descended with Patch.subpatches variable:

parent = p.getParent()
root = p.getRoot()
children = p.children

The geometric transformation of a patch can be obtained with Patch.getTransformTo if needed with respect a specific ancestor or with Patch.getGlobalTransform if needed with respect to root patch:

tr1 = p.getTransofrmTo(p.getParent().getParent())
tr2 = p.getGlobalTransform()

All of these variables and methods can be used in a Stage or in a Host.

Stage

Several stage are available in Aliquis Core (see StageParameter), but if you don’t find the right stage for your needing, you can write your own custom stage!

A stage is a Python object, usually a class object, that exposes a feed method. The method could accept one or more input lists of patches by requiring arguments. After elaborating the data, it could output a list of patches by returing it.

Let’s try to explain this with an example.

my_custom_stage.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from aliquis import Patch, aliquis_pb2 as pb2

class SplitChannelsStage:
  def feed(self, patches):
    ops = []
    for p in patches:
      for c in range(p.im.shape[2]):
        new_patch = Patch(p.im[:,:,c], colorfmt=pb2.GRAY)
        new_patch = p.addSubpatch(new_patch)
        ops.append(new_patch)
    return ops
Custom stage defined in the pipeline
stages {
  name: "channels"
  type: CUSTOM
  input: "src"
  custom_param {
    proxy: "my_custom_stages:SplitChannelsStage"
  }
}

In this example we want to extract each channel of the input image. The class implements a single method feed (line 4) that is called by Aliquis with a single argument, that is the list of ouput patches from the stage “src”. The code create an empty list that will collect output patches (line 5). Than it iterates over each input patch (line 6) and for each channel of the image (line 7) it creates a new patch with relevand data and meta-data (line 8). This new patch is then linked to its parent (line 9) and appended to the output list (line 10). The feed method ends by returning the list of output patches.

Best practice:

  • always add the subpatches to the hierarchy with parent_patch util methods, unless your stage is a source stage.
  • remember that input patches images could be of different shape and colorfmt, handle them in the right way
  • don’t save any reference to patches in instance variable as this could cause memory leaks

Pipeline

A pipeline defined in a .apl file can be instantiated with Aliquis’ API in a Host:

from aliquis import pipeline

pl = Pipeline('/path/to/pipeline.apl')

The Pipeline is an iterable and calling the respective method Pipeline.run returns a iterator that can be run through in a for loop. Each returned item is a dictionary where keys are the name of the stages of the pipeline and respective value are lists of output patches from each stage:

for outputs in pl.run():
      out_patches = outputs['channels']

A Pipeline instance has several variables (see the Reference for Pipeline). Some worth of noting are:

  • Pipeline.stages: a dictionary of all stages istances of the pipeline (e.g. to extract some internal variables)
  • Pipeline.output_stages: a dictionary of only output stages (i.e. stages who are not an input for any other stage)

Host

An host is a script that run a Pipeline and handle the results. All the machine logic of the application is included in the host.

For example, here is a simple host that instantiate and run a pipeline and then print an output message basing on the classification values of the output patch.

classification_host.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from aliquis import Pipeline
import numpy as np

labels = ("dog", "cat")

pl = Pipeline('./cat_or_dog.apl')
output_stage_name = pl.output_stages.keys()[0]
for outputs in pl.run():
  class_values = outputs[output_stage_name][0].values
  max_class = np.argmax(class_values)
  print "It's a {}!".format(labels[max_class])