Skip to content

Commit 17f84a5

Browse files
committed
Conflicts: qsr_lib/scripts/mwe.py
2 parents 4bf210d + c313a68 commit 17f84a5

10 files changed

Lines changed: 781 additions & 11 deletions

File tree

124 KB
Loading

docs/rsts/handwritten/qstag.rst

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
QSTAG
2+
======
3+
**Qualitative Spatio-Temporal Activity Graph**
4+
5+
A QSTAG provides a compact and efficient graph structure to represent both qualitative spatial
6+
and temporal information about entities, allowing the use of standard graph comparison techniques.
7+
8+
Structure
9+
---------
10+
The structure of a QSTAG consists of three layers:
11+
12+
• The objects layer consists of nodes representing the objects in the scene.
13+
14+
• The spatial relations layer consists of nodes that represent QSR intervals between the object layer nodes.
15+
These QSR intervals are generated by merging together repeated QSR values over the time interval they hold.
16+
17+
• The temporal relations layer consists of nodes that encode Allen interval algebra (IA) relations
18+
between the QSR intervals.
19+
20+
.. image:: images/qstag.png
21+
:height: 100px
22+
:width: 200 px
23+
24+
Episodes
25+
---------
26+
To begin, the QSR_World_Trace is converted into a QSR Episode representation. A QSR Episode can be thought of as an
27+
interval of time where a QSR holds between some objects.
28+
29+
**The structure of an Episode:**
30+
31+
.. code:: bash
32+
33+
[ [objects], {spatial relations}, (duration) ]
34+
35+
where:
36+
37+
[objects] is a list of objects,
38+
39+
{spatial relations} is a dictionary of spatial relations which hold where the Key is the Type of QSR Relation,
40+
and the value is QSR relation, i.e. {"rcc3" : "Dc"}
41+
42+
(duration) is a tuple containing the interval of discrete time that the spatial relations hold for,
43+
comprising of (start_frame, end_frame).
44+
45+
46+
.. _usage:
47+
48+
Usage
49+
-----
50+
51+
To use, first create a QSR_World_Trace (qsrlib_response_message) in the normal way.
52+
53+
**Standard Steps for Creating QSR_World_Trace:**
54+
55+
• Create a QSRlib object
56+
57+
• Convert your data in to QSRlib standard input format
58+
59+
• Make a request to QSRlib (or a request to QSRlib using ROS)
60+
61+
62+
**Steps for Creating a QSTAG:**
63+
64+
.. code:: python
65+
66+
qstag = qsrlib_response_message.qstag
67+
68+
Note: If you want your object nodes to contain *object types* make sure to include this in the dynamic_args:
69+
70+
Also, if you want the spatial relations only created for certain objects, use the `qsr_for` dictionary.
71+
72+
.. code:: python
73+
74+
object_types = {"o1": "Human",
75+
"o2": "Chair"}
76+
77+
which_qsr = ["qtcbs", "argd", "mos"]
78+
79+
dynamic_args = {"qtcbs": {"quantisation_factor": args.quantisation_factor,
80+
"validate": args.validate,
81+
"no_collapse": args.no_collapse,
82+
"qsrs_for": [("o1", "o2"),("o1", "o3")]},
83+
84+
"argd": {"qsr_relations_and_values": args.distance_threshold,
85+
"qsrs_for": [("o1", "o2")]},
86+
87+
"mos": {"qsrs_for": [("o1"), ("o2")]},
88+
89+
"qstag": {"object_types" : object_types} }
90+
91+
92+
Visualize the QSTAG
93+
------------------------------
94+
95+
A method to save the QSTAG as a dot file, and convert it to a png image.
96+
97+
.. code:: python
98+
99+
qstag = qsrlib_response_message.qstag
100+
101+
qstag.graph2dot('/tmp/act_gr.dot')
102+
os.system('dot -Tpng /tmp/act_gr.dot -o /tmp/act_gr.png')
103+
104+
105+
106+
Parse the Episodes and QSTAG
107+
------------------------------
108+
109+
.. code:: python
110+
111+
qstag = qsrlib_response_message.qstag
112+
113+
print("All the Episodes...")
114+
for episode in qstag.episodes:
115+
print(episode)
116+
117+
print("The QSTAG Graph: \n", qstag.graph)
118+
119+
print("All the Graph NODES:")
120+
for node in qstag.graph.vs():
121+
print(node)
122+
123+
print("All the Graph EDGES:")
124+
for edge in qstag.graph.es():
125+
print(edge, " from: ", edge.source, " to: ", edge.target)
126+
127+
128+
Example QSTAG code
129+
------------------------------
130+
131+
An example script for generating a simple QSTAG is available in `/strands_qsr_lib/qsr_lib/scripts/`:
132+
133+
.. code:: bash
134+
135+
./qstag_example.py <qsr_name>
136+
137+
e.g.
138+
139+
.. code:: bash
140+
141+
./qstag_example.py qtcbs

qsr_lib/package.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
<build_depend>std_msgs</build_depend>
1818
<build_depend>rostest</build_depend>
1919
<build_depend>roslib</build_depend>
20+
21+
<run_depend>igraph</run_depend>
2022
<run_depend>message_runtime</run_depend>
2123
<run_depend>rospy</run_depend>
2224
<run_depend>std_msgs</run_depend>

qsr_lib/scripts/mwe.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ def pretty_print_world_qsr_trace(which_qsr, qsrlib_response_message):
2626
# ****************************************************************************************************
2727
# parse command line arguments
2828
options = sorted(qsrlib.qsrs_registry.keys())
29+
print(options)
2930
parser = argparse.ArgumentParser()
3031
parser.add_argument("qsr", help="choose qsr: %s" % options, type=str)
3132
args = parser.parse_args()
@@ -37,22 +38,30 @@ def pretty_print_world_qsr_trace(which_qsr, qsrlib_response_message):
3738
# ****************************************************************************************************
3839
# make some input data
3940
world = World_Trace()
40-
o1 = [Object_State(name="o1", timestamp=0, x=1., y=1., xsize=5., ysize=8.),
41-
Object_State(name="o1", timestamp=1, x=1., y=2., xsize=5., ysize=8.)]
42-
43-
o2 = [Object_State(name="o2", timestamp=0, x=11., y=1., xsize=5., ysize=8.),
44-
Object_State(name="o2", timestamp=1, x=1., y=2., xsize=5., ysize=8.)]
45-
41+
o1 = [Object_State(name="o1", timestamp=0, x=1., y=10., xsize=5., ysize=8.),
42+
Object_State(name="o1", timestamp=1, x=1., y=10., xsize=5., ysize=8.),
43+
Object_State(name="o1", timestamp=2, x=3., y=1., xsize=5., ysize=8.),
44+
Object_State(name="o1", timestamp=3, x=6., y=1., xsize=5., ysize=8.),
45+
Object_State(name="o1", timestamp=4, x=6., y=1., xsize=5., ysize=8.)]
46+
o2 = [Object_State(name="o2", timestamp=0, x=1., y=1., xsize=5., ysize=8.),
47+
Object_State(name="o2", timestamp=1, x=2., y=1., xsize=5., ysize=8.),
48+
Object_State(name="o2", timestamp=2, x=1., y=1., xsize=5., ysize=8.),
49+
Object_State(name="o2", timestamp=3, x=1., y=1., xsize=5., ysize=8.),
50+
Object_State(name="o2", timestamp=4, x=1., y=2., xsize=5., ysize=8.)]
4651
o3 = [Object_State(name="o3", timestamp=0, x=5., y=2., xsize=5.2, ysize=8.5),
47-
Object_State(name="o3", timestamp=1, x=6., y=4., xsize=5.2, ysize=8.5)]
52+
Object_State(name="o3", timestamp=1, x=6., y=4., xsize=5.2, ysize=8.5),
53+
Object_State(name="o3", timestamp=2, x=2., y=4., xsize=5.2, ysize=8.5),
54+
Object_State(name="o3", timestamp=3, x=1., y=4., xsize=5.2, ysize=8.5),
55+
Object_State(name="o3", timestamp=4, x=0., y=4., xsize=5.2, ysize=8.5)]
4856

4957
world.add_object_state_series(o1)
5058
world.add_object_state_series(o2)
5159
world.add_object_state_series(o3)
5260

5361
# ****************************************************************************************************
62+
dynammic_args = {"qsr_relations_and_values" : {"Touch": 0.5, "Near": 1, "Far": 3}}
5463
# make a QSRlib request message
55-
qsrlib_request_message = QSRlib_Request_Message(which_qsr=which_qsr, input_data=world)
64+
qsrlib_request_message = QSRlib_Request_Message(which_qsr, world, dynammic_args["qsr_relations_and_values"])
5665
# request your QSRs
5766
qsrlib_response_message = qsrlib.request_qsrs(req_msg=qsrlib_request_message)
5867

qsr_lib/scripts/qstag_example.py

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
from __future__ import print_function, division
4+
import argparse
5+
import os, sys
6+
import cPickle as pickle
7+
from qsrlib.qsrlib import QSRlib, QSRlib_Request_Message
8+
from qsrlib_io.world_trace import Object_State, World_Trace
9+
10+
11+
12+
def pretty_print_world_qsr_trace(which_qsr, qsrlib_response_message):
13+
print(which_qsr, "request was made at ", str(qsrlib_response_message.req_made_at)
14+
+ " and received at " + str(qsrlib_response_message.req_received_at)
15+
+ " and finished at " + str(qsrlib_response_message.req_finished_at))
16+
print("---")
17+
print(qsrlib_response_message.qsrs.get_sorted_timestamps())
18+
print("Response is:")
19+
for t in qsrlib_response_message.qsrs.get_sorted_timestamps():
20+
foo = str(t) + ": "
21+
for k, v in zip(qsrlib_response_message.qsrs.trace[t].qsrs.keys(),
22+
qsrlib_response_message.qsrs.trace[t].qsrs.values()):
23+
foo += str(k) + ":" + str(v.qsr) + "; "
24+
print(foo)
25+
26+
27+
28+
if __name__ == "__main__":
29+
30+
options = sorted(QSRlib().qsrs_registry.keys()) + ["multiple"]
31+
multiple = options[:]; multiple.remove("multiple"); multiple.remove("argd"); multiple.remove("argprobd")
32+
multiple.remove("ra"); multiple.remove("mwe");
33+
34+
parser = argparse.ArgumentParser()
35+
parser.add_argument("qsr", help="choose qsr: %s" % options, type=str, default='qtcbs')
36+
parser.add_argument("--ros", action="store_true", default=False, help="Use ROS eco-system")
37+
38+
parser.add_argument("--validate", help="validate state chain. Only QTC", action="store_true", default=False)
39+
parser.add_argument("--quantisation_factor", help="quantisation factor for 0-states in qtc, or 's'-states in mos", type=float, default=0.01)
40+
parser.add_argument("--no_collapse", help="does not collapse similar adjacent states. Only QTC", action="store_true", default=True)
41+
#parser.add_argument("--distance_threshold", help="distance threshold for qtcb <-> qtcc transition. Only QTCBC", type=float)
42+
43+
args = parser.parse_args()
44+
args.distance_threshold = {"touch":1, "near":3, "medium":5, "far":10}
45+
46+
qtcbs_qsrs_for = [("o1", "o2"),("o1", "o3")]
47+
argd_qsrs_for = [("o1", "o2")]
48+
mos_qsrs_for = ["o1", "o2"]
49+
50+
object_types = {"o1": "Human",
51+
"o2": "Chair"}
52+
53+
if args.qsr in options:
54+
if args.qsr != "multiple":
55+
which_qsr = args.qsr
56+
else:
57+
which_qsr = multiple
58+
elif args.qsr == "hardcoded":
59+
which_qsr = ["qtcbs", "argd", "mos"]
60+
else:
61+
raise ValueError("qsr not found, keywords: %s" % options)
62+
63+
world = World_Trace()
64+
65+
dynamic_args = {"qtcbs": {"quantisation_factor": args.quantisation_factor,
66+
"validate": args.validate,
67+
"no_collapse": args.no_collapse,
68+
"qsrs_for": qtcbs_qsrs_for},
69+
70+
"argd": {"qsr_relations_and_values": args.distance_threshold,
71+
"qsrs_for": argd_qsrs_for},
72+
73+
"mos": {"qsrs_for": mos_qsrs_for},
74+
75+
"qstag": {"object_types" : object_types} }
76+
77+
78+
o1 = [Object_State(name="o1", timestamp=0, x=2., y=2., object_type="Person"), #accessed first using try: kwargs["object_type"] except:
79+
Object_State(name="o1", timestamp=1, x=1., y=1., object_type="Person")]
80+
#Object_State(name="o1", timestamp=2, x=2., y=2., object_type="Human"),
81+
#Object_State(name="o1", timestamp=3, x=4., y=1., object_type="Human"),
82+
#Object_State(name="o1", timestamp=4, x=4., y=1., object_type="Human"),
83+
#Object_State(name="o1", timestamp=5, x=4., y=2., object_type="Human")]
84+
85+
o2 = [Object_State(name="o2", timestamp=0, x=1., y=1., object_type="Chair"),
86+
Object_State(name="o2", timestamp=1, x=2., y=2., object_type="Chair")]
87+
#Object_State(name="o2", timestamp=2, x=1., y=5., object_type="Chair"),
88+
#Object_State(name="o2", timestamp=3, x=1., y=5., object_type="Chair"),
89+
#Object_State(name="o2", timestamp=4, x=2., y=5., object_type="Chair"),
90+
#Object_State(name="o2", timestamp=5, x=2., y=5., object_type="Chair")]
91+
92+
o3 = [Object_State(name="o3", timestamp=0, x=0., y=0., object_type="Desk"),
93+
Object_State(name="o3", timestamp=1, x=0., y=0., object_type="Desk")]
94+
#Object_State(name="o3", timestamp=2, x=0., y=0., object_type="Desk"),
95+
#Object_State(name="o3", timestamp=3, x=0., y=0., object_type="Desk"),
96+
#Object_State(name="o3", timestamp=4, x=0., y=0., object_type="Desk"),
97+
#Object_State(name="o3", timestamp=5, x=0., y=0., object_type="Desk")]
98+
99+
100+
world.add_object_state_series(o1)
101+
world.add_object_state_series(o2)
102+
world.add_object_state_series(o3)
103+
qsrlib_request_message = QSRlib_Request_Message(which_qsr=which_qsr, input_data=world, dynamic_args=dynamic_args)
104+
105+
106+
if args.ros:
107+
try:
108+
import rospy
109+
from qsrlib_ros.qsrlib_ros_client import QSRlib_ROS_Client
110+
except ImportError:
111+
raise ImportError("ROS not found")
112+
client_node = rospy.init_node("qsr_lib_ros_client_example")
113+
cln = QSRlib_ROS_Client()
114+
req = cln.make_ros_request_message(qsrlib_request_message)
115+
res = cln.request_qsrs(req)
116+
qsrlib_response_message = pickle.loads(res.data)
117+
else:
118+
qsrlib = QSRlib()
119+
qsrlib_response_message = qsrlib.request_qsrs(req_msg=qsrlib_request_message)
120+
121+
pretty_print_world_qsr_trace(which_qsr, qsrlib_response_message)
122+
123+
124+
#params={'MAX_ROWS':1, 'MIN_ROWS':None, 'MAX_EPI':4, 'num_cores':8}
125+
qstag = qsrlib_response_message.qstag
126+
127+
print("Episodes...")
128+
for i in qstag.episodes:
129+
print(i)
130+
131+
qstag.graph2dot('/tmp/act_gr.dot')
132+
os.system('dot -Tpng /tmp/act_gr.dot -o /tmp/act_gr.png')
133+
134+
######## PRINT THE GRAPH #########
135+
print("QSTAG Graph:\n", qstag.graph)
136+
print("NODES:")
137+
for node in qstag.graph.vs():
138+
print(node)
139+
print("EDGES:")
140+
for edge in qstag.graph.es():
141+
print(edge, " from: ", edge.source, " to: ", edge.target)

qsr_lib/setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
d = generate_distutils_setup(
77
# # don't do this unless you want a globally visible script
88
# scripts=['bin/myscript'],
9-
packages=['qsrlib', 'qsrlib_qsrs', 'qsrlib_ros', 'qsrlib_io', 'qsrlib_utils'],
9+
packages=['qsrlib', 'qsrlib_qsrs', 'qsrlib_ros', 'qsrlib_io', 'qsrlib_utils', 'qsrlib_qstag'],
1010
package_dir={'': 'src'}
1111
)
1212

qsr_lib/src/qsrlib/qsrlib.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
from qsrlib_io.world_trace import World_Trace
66
from qsrlib_utils.utils import merge_world_qsr_traces
77
from qsrlib_qsrs import *
8+
from qsrlib_qstag.qstag import Activity_Graph
89

910

1011
class QSRlib_Response_Message(object):
1112
"""The response message of QSRlib containing the QSRs and time processing information."""
12-
13-
def __init__(self, qsrs, req_made_at, req_received_at, req_finished_at):
13+
def __init__(self, qsrs, qstag, req_made_at, req_received_at, req_finished_at):
1414
"""Constructor.
1515
1616
:param qsrs: Computed QSRs in World_QSR_Trace format.
@@ -25,6 +25,9 @@ def __init__(self, qsrs, req_made_at, req_received_at, req_finished_at):
2525
self.qsrs = qsrs
2626
""":class:`World_QSR_Trace <qsrlib_io.world_qsr_trace.World_QSR_Trace>`: Holds the QSRs."""
2727

28+
self.qstag = qstag
29+
"""Activity_Graph: Qualitative Spatio-Temporal Activity Graph"""
30+
2831
self.req_made_at = req_made_at
2932
"""datetime: Time the request was made."""
3033

@@ -153,6 +156,7 @@ def request_qsrs(self, req_msg):
153156
world_qsr_trace = None
154157

155158
qsrlib_response = QSRlib_Response_Message(qsrs=world_qsr_trace,
159+
qstag=Activity_Graph(req_msg.input_data, world_qsr_trace, req_msg.dynamic_args["qstag"]["object_types"] if "object_types" in req_msg.dynamic_args["qstag"] else {}) if "qstag" in req_msg.dynamic_args else None,
156160
req_made_at=req_msg.made_at,
157161
req_received_at=req_received_at,
158162
req_finished_at=datetime.now())

qsr_lib/src/qsrlib_qstag/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)