Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix end position bug #9

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open

Fix end position bug #9

wants to merge 1 commit into from

Conversation

nathanvogt
Copy link

If multiple different rules begin at the same start position, the wrong end position can be chosen. Changed so that the chosen position rule is taken into account when setting the end position.

@revalo
Copy link
Owner

revalo commented Jun 27, 2024

Thanks for the contribution! Do you have any examples where this is actually a problem, just so I can reproduce the bug on my end?

@nathanvogt
Copy link
Author

This is a slightly contrived example but modifying tinysvgoffset.py to the following will eventually cause an error when running eval_td.py. I added a custom_color rule that can cause conflicts with the start position.

from typing import Tuple

import iceberg as ice
from lark import Transformer, Tree
from lark.visitors import v_args

from td.environments.environment import Environment
from td.environments.goal_checker import GaussianImageGoalChecker
from td.grammar import Compiler, Grammar

_grammar_spec = r"""
custom_color: number " " number " " number " " number | color // RGBA or color
// s: arrange | move | pad | compose | rect | ellipse
s: arrange | rect | ellipse
direction: "v" -> v | "h" -> h
color: "red" -> red | "green" -> green | "blue" -> blue | "yellow" -> yellow | "purple" -> purple | "orange" -> orange | "black" -> black | "white" -> white | "none" -> none
number: "0" -> zero | "1" -> one | "2" -> two | "3" -> three | "4" -> four | "5" -> five | "6" -> six | "7" -> seven | "8" -> eight | "9" -> nine
sign: "+" -> plus | "-" -> minus
boolean: "true" -> true | "false" -> false

// Rectangle w h fillcolor strokecolor strokewidth ox oy
rect: "(" "Rectangle" " " number " " number " " custom_color " " custom_color " " number " " sign number " " sign number ")"

// Ellipse w h fillcolor strokecolor strokewidth ox oy
ellipse: "(" "Ellipse" " " number " " number " " custom_color " " custom_color " " number " " sign number " " sign number ")"

// Arrange direction left right gap
arrange: "(" "Arrange" " " direction " " s " " s " " number ")"

// Move x y negx negy
// move: "(" "Move" " " s " " number " " number " " boolean " " boolean ")"

// Pad t r b l
// pad: "(" "Pad" " " s " " number " " number " " number " " number ")"

// Compose without arranging
// compose: "(" "Compose" " " s " " s ")"

%ignore /[\t\n\f\r]+/ 
"""

_CANVAS_WIDTH = 224
_CANVAS_HEIGHT = 224

_ice_renderer = ice.Renderer(gpu=False)
_ice_canvas = ice.Blank(
    ice.Bounds(size=(_CANVAS_WIDTH, _CANVAS_HEIGHT)), ice.Colors.WHITE
)


class _Move(ice.Drawable):
    child: ice.Drawable
    x: float
    y: float

    def setup(self):
        self._child_bounds = self.child.bounds
        self._moved = self.child.move(self.x, self.y)

    @property
    def bounds(self):
        return self._child_bounds

    def draw(self, canvas):
        self._moved.draw(canvas)

    @property
    def children(self):
        return [self._moved]


class TSVGOToIceberg(Transformer):
    def __init__(
        self,
        visit_tokens: bool = True,
        stroke_width_divisor: float = 2.0,
        size_multiplier: float = 6.0,
    ) -> None:
        super().__init__(visit_tokens)
        self._stroke_width_divisor = stroke_width_divisor
        self._size_multiplier = size_multiplier

    def custom_color(self, children):
        if len(children) == 4:
            return ice.Color(
                children[0] / 255,
                children[1] / 255,
                children[2] / 255,
                children[3] / 255,
            )
        else:
            return children[0]

    @v_args(meta=True)
    def rect(self, meta, children):
        w, h, fill_color, stroke_color, stroke_width, signx, ox, signy, oy = children

        stroke_width = stroke_width / self._stroke_width_divisor
        w = w * self._size_multiplier
        h = h * self._size_multiplier

        ox = ox * signx * self._size_multiplier
        oy = oy * signy * self._size_multiplier

        rv = ice.Rectangle(
            ice.Bounds(size=(w, h)),
            fill_color=fill_color,
            border_color=stroke_color,
            border_thickness=stroke_width,
            anti_alias=False,
            dont_modify_bounds=True,
        )
        rv._lark_meta = meta

        rv = _Move(child=rv, x=ox, y=oy)

        return rv

    @v_args(meta=True)
    def ellipse(self, meta, children):
        w, h, fill_color, stroke_color, stroke_width, signx, ox, signy, oy = children
        stroke_width = stroke_width / self._stroke_width_divisor
        w = w * self._size_multiplier
        h = h * self._size_multiplier

        ox = ox * signx * self._size_multiplier
        oy = oy * signy * self._size_multiplier

        rv = ice.Ellipse(
            rectangle=ice.Bounds(size=(w, h)),
            fill_color=fill_color,
            border_color=stroke_color,
            border_thickness=stroke_width,
            anti_alias=True,
            dont_modify_bounds=True,
        )
        rv._lark_meta = meta

        rv = _Move(child=rv, x=ox, y=oy)

        return rv

    def arrange(self, children):
        direction, left, right, gap = children

        return ice.Arrange(
            [left, right],
            arrange_direction=(
                ice.Arrange.Direction.HORIZONTAL
                if direction == "h"
                else ice.Arrange.Direction.VERTICAL
            ),
            gap=gap,
        )

    def move(self, children):
        drawable, x, y, negx, negy = children

        x = x * self._size_multiplier
        y = y * self._size_multiplier

        x = x if not negx else -x
        y = y if not negy else -y
        return _Move(child=drawable, x=x, y=y)

    def s(self, children):
        return children[0]

    def v(self, _):
        return "v"

    def h(self, _):
        return "h"

    def red(self, _):
        return ice.Colors.RED

    def green(self, _):
        return ice.Colors.GREEN

    def blue(self, _):
        return ice.Colors.BLUE

    def yellow(self, _):
        return ice.Colors.YELLOW

    def purple(self, _):
        return ice.Color.from_hex("#800080")

    def orange(self, _):
        return ice.Color.from_hex("#FFA500")

    def black(self, _):
        return ice.Colors.BLACK

    def white(self, _):
        return ice.Colors.WHITE

    def none(self, _):
        return None

    def zero(self, _):
        return 0

    def one(self, _):
        return 1

    def two(self, _):
        return 2

    def three(self, _):
        return 3

    def four(self, _):
        return 4

    def five(self, _):
        return 5

    def six(self, _):
        return 6

    def seven(self, _):
        return 7

    def eight(self, _):
        return 8

    def nine(self, _):
        return 9

    def true(self, _):
        return True

    def false(self, _):
        return False

    def plus(self, _):
        return 1

    def minus(self, _):
        return -1


class TSVGOCompiler(Compiler):
    def __init__(self) -> None:
        super().__init__()
        self._expression_to_iceberg = TSVGOToIceberg()

    def compile(self, expression: Tree):
        drawable = self._expression_to_iceberg.transform(expression)
        scene = ice.Anchor((_ice_canvas, _ice_canvas.add_centered(drawable)))
        # return scene
        _ice_renderer.render(scene)
        rv = _ice_renderer.get_rendered_image()[:, :, :3] / 255.0

        return rv


class TinySVGOffset(Environment):
    def __init__(self) -> None:
        super().__init__()

        self._grammar = Grammar(
            _grammar_spec,
            start="s",
            primitives=["rect", "ellipse"],
        )

        self._compiler = TSVGOCompiler()
        self._goal_checker = GaussianImageGoalChecker(self.compiled_shape, sigma=0.1)

    @property
    def grammar(self) -> Grammar:
        return self._grammar

    @property
    def compiler(self) -> Compiler:
        return self._compiler

    @property
    def compiled_shape(self) -> Tuple[int, ...]:
        return _CANVAS_WIDTH, _CANVAS_HEIGHT, 3

    @classmethod
    def name(self) -> str:
        return "tinysvgoffset"

    def goal_reached(self, compiledA, compiledB) -> bool:
        return self._goal_checker.goal_reached(compiledA, compiledB)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants