Skip to content

Commit

Permalink
Merge pull request #117 from davidprueser/doc
Browse files Browse the repository at this point in the history
Added/updated querying guides and fixed orm usage in different places
  • Loading branch information
Tigul authored Dec 13, 2023
2 parents 3422aa2 + b13b5f3 commit be74899
Show file tree
Hide file tree
Showing 9 changed files with 830 additions and 272 deletions.
5 changes: 3 additions & 2 deletions doc/source/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,15 @@ the relations are reflected in the database and fields of the object. An example
seen in :meth:`pycram.task.TaskTreeNode.to_sql` and :meth:`pycram.task.TaskTreeNode.insert`.

When using the ORM to record the experiments a MetaData instance is created. For a clean data management it is important
to fill out the description. For this, check the documentation of :meth:`pycram.orm.base.MetaData`.
to fill out the description. For this, check the documentation of :meth:`pycram.orm.base.ProcessMetaData`.

Example
Examples
--------

.. nbgallery::

notebooks/orm_example
notebooks/orm_querying_examples



1 change: 1 addition & 0 deletions doc/source/notebooks/orm_querying_examples.ipynb
198 changes: 101 additions & 97 deletions examples/custom_resolver.ipynb

Large diffs are not rendered by default.

294 changes: 174 additions & 120 deletions examples/orm_example.ipynb

Large diffs are not rendered by default.

485 changes: 485 additions & 0 deletions examples/orm_querying_examples.ipynb

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/pycram/orm/action_designator.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class Action(MapperArgsMixin, Designator):
"""

id: Mapped[int] = mapped_column(ForeignKey(f'{Designator.__tablename__}.id'), primary_key=True, init=False)
dtype: Mapped[str] = mapped_column(init=False)
dtype: Mapped[str] = mapped_column("action_dtype", init=False)
robot_state_id: Mapped[int] = mapped_column(ForeignKey(f"{RobotState.__tablename__}.id"), init=False)
robot_state: Mapped[RobotState] = relationship(init=False)

Expand Down Expand Up @@ -82,7 +82,7 @@ class TransportAction(PoseMixin, ObjectMixin, Action):
"""ORM Class of pycram.designators.action_designator.TransportAction."""

id: Mapped[int] = mapped_column(ForeignKey(f'{Action.__tablename__}.id'), primary_key=True, init=False)
arm: Mapped[str] = mapped_column(init=False)
arm: Mapped[str]


class LookAtAction(PoseMixin, Action):
Expand Down
6 changes: 4 additions & 2 deletions src/pycram/orm/motion_designator.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy import ForeignKey

from ..enums import ObjectType


class Motion(MapperArgsMixin, Designator):
"""
Expand All @@ -22,7 +24,7 @@ class Motion(MapperArgsMixin, Designator):
"""

id: Mapped[int] = mapped_column(ForeignKey(f'{Designator.__tablename__}.id'), primary_key=True, init=False)
dtype: Mapped[str] = mapped_column(init=False)
dtype: Mapped[str] = mapped_column("motion_dtype", init=False)


class MoveMotion(PoseMixin, Motion):
Expand Down Expand Up @@ -90,7 +92,7 @@ class DetectingMotion(Motion):
"""

id: Mapped[int] = mapped_column(ForeignKey(f'{Motion.__tablename__}.id'), primary_key=True, init=False)
object_type: Mapped[str]
object_type: Mapped[ObjectType]


class WorldStateDetectingMotion(Motion):
Expand Down
53 changes: 20 additions & 33 deletions src/pycram/resolver/location/database_location.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import numpy as np
import sqlalchemy.orm
import sqlalchemy.sql

from tf import transformations
import pycram.designators.location_designator
import pycram.task
from pycram.costmaps import OccupancyCostmap
from pycram.orm.action_designator import PickUpAction
from pycram.orm.action_designator import PickUpAction, Action
from pycram.orm.object_designator import Object
from pycram.orm.base import Position, RobotState
from pycram.orm.base import Position, RobotState, Designator, Base
from pycram.orm.task import TaskTreeNode, Code
from .jpt_location import JPTCostmapLocation
import tf
from ... import orm
from ...pose import Pose


class DatabaseCostmapLocation(pycram.designators.location_designator.CostmapLocation):
Expand Down Expand Up @@ -42,38 +43,24 @@ def create_query_from_occupancy_costmap(self) -> sqlalchemy.orm.Query:
robot_pos = sqlalchemy.orm.aliased(Position)
object_pos = sqlalchemy.orm.aliased(Position)

# create subqueries, such that filters are executed before joins
# filter for successful tasks
filtered_tasks = self.session.query(TaskTreeNode.code_id).filter(TaskTreeNode.status == "SUCCEEDED").subquery()

# remove all no operation codes
filtered_code = self.session.query(Code.id, Code.designator_id).filter(Code.designator_id != None).subquery()

# join task and code
filtered_code = self.session.query(filtered_code.c.designator).\
join(filtered_tasks, filtered_tasks.c.code == filtered_code.c.id).subquery()

# filter all objects that have the same type as the target
filtered_objects = self.session.query(Object).filter(Object.type == self.target.type).\
subquery()

query = self.session.query(PickUpAction.arm, PickUpAction.grasp,
RobotState.torso_height, robot_pos.x, robot_pos.y, ). \
join(filtered_code, filtered_code.c.designator_id == PickUpAction.id). \
join(PickUpAction, PickUpAction.id == filtered_code.c.designator_id). \
join(RobotState, RobotState.id == PickUpAction.robot_state_id). \
join(robot_pos, RobotState.pose.position_id == robot_pos.id). \
join(filtered_objects, filtered_objects.c.id == PickUpAction.object_id). \
join(object_pos, filtered_objects.c.position == object_pos.id)

# query all relative robot positions in regard to an objects position
# make sure to order the joins() correctly
query = (self.session.query(PickUpAction.arm, PickUpAction.grasp, RobotState.torso_height, Position.x,
Position.y).join(TaskTreeNode.code)
.join(Code.designator.of_type(PickUpAction))
.join(PickUpAction.robot_state)
.join(RobotState.pose)
.join(orm.base.Pose.position)
.join(PickUpAction.object).filter(Object.type == self.target.type)
.filter(TaskTreeNode.status == "SUCCEEDED"))

# create Occupancy costmap for the target object
position, orientation = self.target.get_position_and_orientation()
position, orientation = self.target.pose.to_list()
position = list(position)
position[-1] = 0

ocm = OccupancyCostmap(distance_to_obstacle=0.3, from_ros=False, size=200, resolution=0.02,
origin=(position, orientation))
origin=Pose(position, orientation))
# ocm.visualize()

# working on a copy of the costmap, since found rectangles are deleted
Expand Down Expand Up @@ -116,12 +103,12 @@ def sample_to_location(self, sample: sqlalchemy.engine.row.Row) -> JPTCostmapLoc
:param sample: The database row.
:return: The costmap location
"""
target_x, target_y, target_z = self.target.pose
target_x, target_y, target_z = self.target.pose.position_as_list()
pose = [target_x + sample.x, target_y + sample.y, 0]
angle = np.arctan2(pose[1] - target_y, pose[0] - target_x) + np.pi
orientation = list(tf.transformations.quaternion_from_euler(0, 0, angle, axes="sxyz"))
orientation = list(transformations.quaternion_from_euler(0, 0, angle, axes="sxyz"))

result = JPTCostmapLocation.Location((pose, orientation), sample.arm, sample.torso_height, sample.grasp)
result = JPTCostmapLocation.Location(Pose(pose, orientation), sample.arm, sample.torso_height, sample.grasp)
return result

def __iter__(self) -> JPTCostmapLocation.Location:
Expand Down
56 changes: 40 additions & 16 deletions test/test_database_resolver.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import os
import unittest

import sqlalchemy
import sqlalchemy.orm

import pycram.plan_failures
from pycram import task
from pycram.bullet_world import BulletWorld, Object
from pycram.designators import action_designator, object_designator
from pycram.orm.base import Base
from pycram.process_module import ProcessModule
from pycram.process_module import simulated_robot
from pycram.pose import Pose
from pycram.robot_descriptions import robot_description
from pycram.task import with_tree
from pycram.enums import ObjectType

# check if jpt is installed
jpt_installed = True
Expand All @@ -29,38 +31,67 @@
@unittest.skipIf(pycrorm_uri is None, "pycrorm database is not available.")
@unittest.skipIf(not jpt_installed, "jpt is not installed but needed for the definition of DatabaseCostmapLocations. "
"Install via 'pip install pyjpt'")
class DatabaseResolverTestCase(unittest.TestCase):
class DatabaseResolverTestCase(unittest.TestCase,):
world: BulletWorld
milk: Object
robot: Object
engine: sqlalchemy.engine.Engine
session: sqlalchemy.orm.Session

@classmethod
def setUpClass(cls) -> None:
global pycrorm_uri
cls.world = BulletWorld("DIRECT")
cls.milk = Object("milk", "milk", "milk.stl", pose=Pose([3, 3, 0.75]))
cls.robot = Object(robot_description.name, "pr2", robot_description.name + ".urdf")
cls.milk = Object("milk", "milk", "milk.stl", pose=Pose([1.3, 1, 0.9]))
cls.robot = Object(robot_description.name, ObjectType.ROBOT, robot_description.name + ".urdf")
ProcessModule.execution_delay = False
engine = sqlalchemy.create_engine(pycrorm_uri)
cls.engine = sqlalchemy.create_engine(pycrorm_uri)
cls.session = sqlalchemy.orm.Session(bind=cls.engine)

def setUp(self) -> None:
self.world.reset_bullet_world()
pycram.orm.base.Base.metadata.create_all(self.engine)
self.session.commit()

def tearDown(self) -> None:
self.world.reset_bullet_world()
pycram.task.reset_tree()

cls.session = sqlalchemy.orm.Session(bind=engine)
@classmethod
def tearDownClass(cls) -> None:
cls.world.exit()
cls.session.commit()
cls.session.close()

@with_tree
def plan(self):
object_description = object_designator.ObjectDesignatorDescription(names=["milk"])
description = action_designator.PlaceAction(object_description, [Pose([1.3, 1, 0.9], [0, 0, 0, 1])], ["left"])
with simulated_robot:
action_designator.NavigateAction.Action(Pose([0.6, 0.4, 0], [0, 0, 0, 1])).perform()
action_designator.MoveTorsoAction.Action(0.3).perform()
action_designator.PickUpAction.Action(object_description.resolve(), "left", "front").perform()
description.resolve().perform()

def test_costmap_no_obstacles(self):
"""Check if grasping a milk in the air works."""
self.plan()
pycram.orm.base.ProcessMetaData().description = "costmap_no_obstacles_test"
pycram.task.task_tree.root.insert(self.session)

cml = DatabaseCostmapLocation(self.milk, self.session, reachable_for=self.robot)
sample = next(iter(cml))

with simulated_robot:
action_designator.NavigateAction.Action(sample.pose).perform()
# action_designator.NavigateAction.Action(sample.pose).perform()
action_designator.MoveTorsoAction.Action(sample.torso_height).perform()
action_designator.PickUpAction.Action(
object_designator.ObjectDesignatorDescription(types=["milk"]).resolve(),
arm=sample.reachable_arm, grasp=sample.grasp).perform()

@unittest.skip
def test_costmap_with_obstacles(self):
kitchen = Object("kitchen", "environment", "kitchen.urdf")
self.milk.set_pose(Pose([-1.2, 1, 0.98]))

cml = DatabaseCostmapLocation(self.milk, self.session, reachable_for=self.robot)

Expand All @@ -78,13 +109,6 @@ def test_costmap_with_obstacles(self):
return
raise pycram.plan_failures.PlanFailure()

def tearDown(self) -> None:
self.world.reset_bullet_world()

@classmethod
def tearDownClass(cls) -> None:
cls.world.exit()


if __name__ == '__main__':
unittest.main()

0 comments on commit be74899

Please sign in to comment.