Logic language

The ShapeLogic logic system is not quite a language, but it has become more organized with v. 0.8, 0.9 and 1.0.

For a broader introduction to the logic system see Declarative logic .

Different types of declarative programming in ShapeLogic

  1. Goal driven tasks, somewhat like Prolog without backtracking and unification
  2. Lazy calculations
  3. Lazy streams
  4. IoC, Inversion of Control using Google Guise, not been started yet

Declarative programming in ShapeLogic 02. to 0.8

  • Goal driven task
  • Artificial Intelligence choice tree integrated with tasks classes
  • Some use of lazy computations
  • Rules were defined using JEXL
  • Rules where mainly defined in Java classes
  • Rules could be saved in a database, but this was not integrated with ImageJ

This was fine for handling simple rules applied to one polygon.

New declarative programming concept in ShapeLogic 0.9 and 1.0

  • Lazy computations was expanded
  • Lazy streams was introduced
  • Lazy streams can use the Java 6 Scripting interface
  • Tested with Groovy, JRuby and JavaScript, but this is supporting 25 languages
  • Lazy streams work with matching several polygons at once
  • Named lazy stream turned out to be very easy for user defined rules
  • Using a scripting language is less essential for rule definition is less essential than with goal driven tasks

IoC hold potential but has not been used yet.

Future plan for declarative programming in ShapeLogic

Make menu items in ImageJ's menu for opening and running a user defined rule database, without user defined Java code. Since it was so easy to define rules in Java this have been given lower priority than originally planned.

Problem with Java 6 Scripting for rule definition in ImageJ

One possible problem with moving rules to Java 6 Scripting, is that this requires that the user downloads engine interface files from Sun. This would make the installation of ShapeLogic more complicated now it is just unpacking a zip and moving it into ImageJ's plugin dir.

The only scripting language that works out of the box is JavaScript. While that is a reasonable language the integration with Java is not so great especially not Java 5.

It is also possible to connect Groovy and Java outside Java 6 Scripting, but then it would not be uniform.

Logic expressions syntax in lazy stream approach

DigitStreamVectorizer is an example showing what is needed to define a match of the numbers working as a plugin from ImageJ.

Define a rule for matching the letter A

rule("A", POINT_COUNT, "==", 5.);
rule("A", HOLE_COUNT, "==", 1.);
rule("A", T_JUNCTION_LEFT_POINT_COUNT,"==", 1.);
rule("A", T_JUNCTION_RIGHT_POINT_COUNT,"==", 1.);
rule("A", END_POINT_BOTTOM_POINT_COUNT, "==", 2.);
rule("A", HORIZONTAL_LINE_COUNT, "==", 1.);
rule("A", VERTICAL_LINE_COUNT, "==", 0.);
rule("A", END_POINT_COUNT, "==", 2.);
rule("A", SOFT_POINT_COUNT, "==", 0.);

The "A" is saying apply this rule to the OH, object hypothesis, the letter A.

The next is just names of streams that generate the point count and hole count for the polygons. Then there is the predicate and the comparison value.

Idea behind lazy streams

Lazy streams have the following features

  • You can define a lazy stream based on other lazy streams
  • They works as a kind of UNIX pipes or Legos
  • They serves as your query construct, you can directly query them

Logic expressions in goal driven tasks approach

There are currently 3 main places where logic expressions can be placed:

  1. The rules are living in a Prolog like hierarchy of tasks and sub tasks
    1. And tasks
    2. Exclusive Or tasks
    3. Or tasks
    4. Not tasks
    5. Simple tasks
  2. The simple tasks

    Found named values are set in a JEXL context and expressions can be evaluated here too.

    1. Parametric Rule Tasks: Any expressions in evaluated to a value that are then compared to an expected value
    2. BooleanTask: Any expressions in evaluated to a boolean value
  3. Filter Tasks

    They are sub class of the Parametric Rule Tasks. They come in 2 flavors now:

    1. Filter that runs over all the points in the polygon
    2. Filter that runs over all the lines in the polygon

    You can also do logical combinations of the filter tasks:

    So if you have one class with a filter criteria that is filtering:

    • Points in the upper half of the bounding box of the polygon
    • Points that are T junctions

You can combine them with and and to get a filter that filters, T junctions in the upper half.

Example of logic language in the ShapeLogic letter matching example

This is how the rules for matching the letter A looks in ShapeLogic v 0.8

The rules for all the capital letters can be found in the class: LetterTaskFactory.java

So each line will be translated into one task / goal.

new NumericRule("A", POINT_COUNT, polygon, VAR_SIZE_START + POINT_COUNT_EX + VAR_SIZE_END,"==", 5.),
new NumericRule("A", HOLE_COUNT, polygon, VAR + HOLE_COUNT_EX, "==", 1.),
new NumericRule("A", T_JUNCTION_LEFT_POINT_COUNT, polygon, FILTER_START + T_JUNCTION_LEFT_POINT_COUNT_EX + FILTER_END,"==", 1.),
new NumericRule("A", T_JUNCTION_RIGHT_POINT_COUNT, polygon, filter(T_JUNCTION_RIGHT_POINT_COUNT_EX),"==", 1.),
new NumericRule("A", END_POINT_BOTTOM_POINT_COUNT, polygon, filter(END_POINT_BOTTOM_POINT_COUNT_EX), "==", 2.),
new NumericRule("A", HORIZONTAL_LINE_COUNT, polygon, size(HORIZONTAL_LINE_COUNT_EX), "==", 1.),
new NumericRule("A", VERTICAL_LINE_COUNT, polygon, size(VERTICAL_LINE_COUNT_EX), "==", 0.),
new NumericRule("A", END_POINT_COUNT, polygon, VAR + END_POINT_COUNT_EX, "==", 2.),
new NumericRule("A", SOFT_POINT_COUNT, polygon, size(SOFT_POINT_COUNT_ANN_EX), "==", 0.),

All rules work in a JEXL context.

How values are set in the JEXL context when running letter match

Steps when running letter match:

  1. The vectorizer, first finds a raw polygon
  2. Sets the raw polygon in the JEXL context of the root task under the name: "raw_polygon"
  3. Transforms the raw polygon into a cleaned up polygon
  4. Sets the cleaned up polygon in JEXL context of the root task under the name: "polygon"

Example of how a rule is evaluated

Hole count rule
new NumericRule("A", HOLE_COUNT, polygon, VAR + HOLE_COUNT_EX, "==", 1.),
Sequence of actions generated from hole count rule

This will be transformed into a task that:

  1. First the expression is: VAR + HOLE_COUNT_EX = "#.holeCount"
  2. Substituted the third argument, "polygon", for "#", to get expression: "ploygon.holeCount"
  3. Evaluated the resulting expression "ploygon.holeCount" in the JEXL context to a number
  4. Take the expected value, here 1
  5. Use the the given predicate to do a comparison. Here the predicate is: "=="
  6. If the predicate return true then the task succeeds, else it fails
  7. In order for the letter A to be matched all the rules need to succeed

Explanation of each field in the constructor for NumericRule

  1. The name of the OH, Object Hypothesis, they are all A.
  2. That is the name of the rule. This is not used for anything now and has no effect.
  3. Name of the variable in the task's JEXL context that you want the rule to work on.
  4. The is the expression that you want to evaluate the task's JEXL context.

    It is using # as a place holder for the name of the variable from last field.

  5. What relations that you want to check.
  6. The expected value.

The 4 main forms of access in a rule that is translated into a Parametric Rule Task

  1. Raw expressions. If you do not need a variable, then all you need is an expression.

    E.g. "ploygon.holeCount"

  2. VAR + HOLE_COUNT_EX,

    Takes the variable coming in from the third field and add that to the expression in HOLE_COUNT_EX.

  3. Size expressions: E.g. size(VERTICAL_LINE_COUNT_EX)

    Evaluate the expression that the string constant VERTICAL_LINE_COUNT_EX.

    This will return a collection of lines.

    Then does a size() call on that to see how many element there are in it.

  4. Filter expressions: E.g. filter(END_POINT_BOTTOM_POINT_COUNT_EX)

    When you need filter a collection with the criteria expression inside the filter.

    And then see how many elements the filter returns.

    So in this case you start with all the points in the polygon.

    The string: END_POINT_BOTTOM_POINT_COUNT_EX has the value:

    "PointOfTypeFilter(PointType.END_POINT) && PointBelowFilter(0.5)"

    This is a composite criteria that checks that the point is an end point and that it is in the lower half of the bounding box for the polygon.

    You can use the normal boolean operators in the filter expression: and, or, not.