Skip to content
Snippets Groups Projects
Commit c11724aa authored by Julian Tusch's avatar Julian Tusch :no_entry_sign:
Browse files

Merge branch 'master' into dev/fluxio

parents 191cba42 04827d40
No related branches found
No related tags found
1 merge request!68Fluxio preliminary release
Pipeline #22335 failed
Showing
with 906 additions and 14 deletions
# TODO: implement robot state memory
\ No newline at end of file
# TODO: implement vision memory
\ No newline at end of file
def escapeName(s):
return s.replace("/", "|")
def unescapeName(s):
return s.replace("|", "/")
\ No newline at end of file
import datetime
def date_from_ts(timestamp, normalization_factor) -> str:
dt = datetime.datetime.fromtimestamp(timestamp *normalization_factor) # POSIX is seconds
date_dir: str = dt.strftime('%Y-%m-%d')
return date_dir
def seconds_from_ts(timestamp, normalization_factor) -> int:
dt = datetime.datetime.fromtimestamp(timestamp * normalization_factor) # POSIX is seconds
date_seconds: int = round(dt.timestamp())
if dt.timestamp() - date_seconds == 0.5: # ugly hack to solve rounding differences between C++ and python
return date_seconds + 1
return date_seconds
......@@ -723,17 +723,19 @@ class SkillManager:
def abort_all_non_terminated_skills(
self,
abort_statuses=(
es.ExecutionStatus.Constructing,
es.ExecutionStatus.Initializing,
es.ExecutionStatus.Preparing,
es.ExecutionStatus.Running,
es.ExecutionStatus.Constructing,
es.ExecutionStatus.Initializing,
es.ExecutionStatus.Preparing,
es.ExecutionStatus.Running,
),
blocking=True,
log=None,
) -> Dict[sei.SkillExecutionID, asr.AbortSkillResult]:
execution_ids_to_abort = list()
# temporary fix until RobotAPI is merged
"""execution_ids_to_abort = list()
execution_status_map = self.get_skill_execution_statuses()
for execution_id, execution_status in execution_status_map.items():
if execution_status.status in abort_statuses:
log.info(f"{execution_id} RUNNING")
......@@ -750,7 +752,9 @@ class SkillManager:
)
results[execution_id] = result
return results
return results"""
return self.proxy.abortAllSkillsAsync()
@staticmethod
def _ice_unset_to_none(optional):
......
......@@ -121,6 +121,7 @@ class MemoryStopListener:
except Exception as e:
self.log.error(f"Stopping '{hook.name}' failed. Reason: \n{e}")
continue
self.log.info(f"Stopped '{hook.name}' ...")
t_end = time.time()
......
......@@ -19,5 +19,6 @@ class SkillManagerStopHook(StopHook):
if log is not None:
log.info("Failed to connect to skill manager.")
return
print("Stopping")
skill_manager.abort_all_non_terminated_skills(log=log, blocking=False)
print("Stopped")
# Tutorial: Use `armarx_armem` to load data from Long-Term Memory
## Record Data using the Long-Term Memory
To record data using the long-term memory see the How-To in the `Robot_API` section.
## Loading a full Memory
To load data from the file version of the long-term memory you need to know the following things:
- the absolute path to your exported memories
- the name of the export
If you have this information you can use the functionality of a `MemoryServer` to load all references from that memory.
This only loads references, not the actual content yet, for efficiency reasons.
You can use the following code snippet to load the references for a memory located at `testData/2024_07_16/LTM`:
```python
from armarx_memory.ltm.memory_server import MemoryServer
def loadReferencesForServer():
base_path = "testData/2024_07_16"
export_name = "LTM"
server = MemoryServer(base_path, export_name)
server.loadReferences()
return server
```
## Loading an Entity Snapshot
You probably want the content of a specific (or all) Entity Snapshots and their instances to work with the data in your code.
To do so, you need to know the following things:
- the name of the memory servers (e.g. `RobotState`)
- the name of the core segment (e.g. `Localization`)
- the name of the provider segment (e.g. `Armar3`)
- the name of the entity (e.g. `Map,Odom`)
From this information you can then build a MemoryID, which can then be used to retrieve the entity using the `MemoryServer` (from the code snippet above).
This loads the actual content of the entity instead of just the references.
The entity contains all EntitySnapshots with their EntityInstances that were part of this recording.
To get a specific `EntitySnapshot` you need to know the SnapshotID of this specific snapshot. (Or you can iterate over all snapshots/SnapshotIDs if you are interested in all snapshots).
In the example below we just care for the first snapshot and the first instance of this snapshot.
But SnapshotIDs correspond to their timestamps so you can also get the last snapshot or the first five etc. by using the sorted list of SnapshotIDs.
The example loads the data (and metadata) of the chosen instance, converts it's data to an `AronDict` (all instances are an `AronDict`in the first layer) and
accesses the individual elements of the data and chooses the `transform` element:
```python
from armarx_memory.core.MemoryID import MemoryID
from armarx_memory.ltm.base.entity_snapshot import EntitySnapshot
from armarx_memory.ltm.base.entity_instance import EntityInstance
from armarx_memory.aron.data.variant import AronDict, AronNDArray
def robotStateLoading():
server = loadReferencesForServer() # see code snippet from before
mid = MemoryID("RobotState", "Localization", "Armar3", "Map,Odom")
entity = server.get_entity(mid)
snapshot_id = list(entity.snapshots.keys())[0]
snapshot: EntitySnapshot = entity.snapshots[snapshot_id]
instance: EntityInstance = snapshot.instances[0]
instance.load()
instance_data: AronDict = instance.data
transform_data: AronNDArray = instance_data.elements['transform']
```
### Loading metadata
To load the metadata associated with an `EntityInstance` you can use the following code snippet:
```python
from armarx_memory.core.MemoryID import MemoryID
from armarx_memory.ltm.base.entity_snapshot import EntitySnapshot
from armarx_memory.ltm.base.entity_instance import EntityInstance
def loadingMetadata():
server = loadReferencesForServer()
mid_vision = MemoryID("Vision", "ImageRGB", "Armar3WideImageProvider", "images")
entity = server.get_entity(mid_vision)
snapshot_id = list(entity.snapshots.keys())[0]
snapshot: EntitySnapshot = entity.snapshots[snapshot_id]
instance: EntityInstance = snapshot.instances[0]
instance.load()
metadata = instance.metadata
return metadata
```
This example uses an `EntityInstance` of the `VisionMemory` memory server.
## Using the loaded data in your code
Be aware that the data loaded using these methods is in the form of aron objects. You can either continue using these or convert them into standard objects (like integers etc.) using the aron functionality.
......@@ -23,7 +23,7 @@ def read(core_segment_id: mem.MemoryID):
result_data = None
# Process result.
for prov in memory.coreSegments["ExampleData"].providerSegments.values():
for prov in memory.coreSegments["Proprioception"].providerSegments.values():
for entity in prov.entities.values():
for snapshot in entity.history.values():
for instance in snapshot.instances:
......@@ -53,6 +53,8 @@ def write(entity_id: mem.MemoryID, instances_data: List[Dict]):
commit = memcl.Commit()
now = mem.time_usec()
#pythonic_from_to_aron_ice.pythonic_to_aron_ice(instances_data.pop())
# Add a snapshot.
commit.add(
memcl.EntityUpdate(
......@@ -90,15 +92,16 @@ if __name__ == "__main__":
mns = memcl.MemoryNameSystem.wait_for_mns()
# Specify which memory / core segment / ... you want to deal with.
memory_id = mem.MemoryID("Example")
core_segment_id = memory_id.with_core_segment_name("ExampleData")
memory_id = mem.MemoryID("RobotState")
core_segment_id = memory_id.with_core_segment_name("Proprioception")
# Read
example_data = read(core_segment_id=core_segment_id)
# Modify the data.
example_data["the_string"] = "Hello, this string is from python!"
example_data["the_int"] = 42
#example_data["the_string"] = "Hello, this string is from python!"
#example_data["the_int"] = 42
example_data["TestData"] = 42
# Write
entity_id = core_segment_id.with_provider_segment_name(tag).with_entity_name(
......@@ -118,4 +121,4 @@ if __name__ == "__main__":
For example:
"""
from armarx_memory.segments import ObjectInstance
#from armarx_memory.segments import ObjectInstance
[tool.poetry]
name = "armarx"
version = "0.23.3"
version = "0.23.4"
description = "A Python Toolbox for ArmarX"
authors = [
"Markus Grotz <markus.grotz@kit.edu>",
......@@ -42,6 +42,7 @@ pyyaml = "*"
icecream = "*"
rich = "*"
inquirer = "*"
Pillow = "*"
[tool.poetry.dev-dependencies]
pytest = "^5.2"
......
{
"_ARON_DATA": [
125,
12,
137,
62,
147,
168,
118,
191,
0,
0,
0,
0,
193,
56,
97,
69,
147,
168,
118,
63,
125,
12,
137,
62,
0,
0,
0,
0,
73,
100,
206,
69,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
128,
63,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
128,
63
],
"_ARON_DIMESIONS": [
4,
4,
4
],
"_ARON_PATH": [
"motion",
"0",
"robotState",
"globalPose"
],
"_ARON_TYPE": "_ARON_NDARRAY",
"_ARON_USED_TYPE": "float"
}
{
"_ARON_ELEMENTS": {
"header": {
"_ARON_ELEMENTS": {
"agent": {
"_ARON_PATH": [
"header",
"agent"
],
"_ARON_TYPE": "_ARON_STRING",
"_ARON_VALUE": "Armar3"
},
"frame": {
"_ARON_PATH": [
"header",
"frame"
],
"_ARON_TYPE": "_ARON_STRING",
"_ARON_VALUE": "Root"
},
"parentFrame": {
"_ARON_PATH": [
"header",
"parentFrame"
],
"_ARON_TYPE": "_ARON_STRING",
"_ARON_VALUE": "Odom"
},
"timestamp": {
"_ARON_ELEMENTS": {
"clockType": {
"_ARON_PATH": [
"header",
"timestamp",
"clockType"
],
"_ARON_TYPE": "_ARON_INT",
"_ARON_VALUE": 2
},
"hostname": {
"_ARON_PATH": [
"header",
"timestamp",
"hostname"
],
"_ARON_TYPE": "_ARON_STRING",
"_ARON_VALUE": "unknown"
},
"timeSinceEpoch": {
"_ARON_ELEMENTS": {
"microSeconds": {
"_ARON_PATH": [
"header",
"timestamp",
"timeSinceEpoch",
"microSeconds"
],
"_ARON_TYPE": "_ARON_LONG",
"_ARON_VALUE": 1716809170586798
}
},
"_ARON_PATH": [
"header",
"timestamp",
"timeSinceEpoch"
],
"_ARON_TYPE": "_ARON_DICT"
}
},
"_ARON_PATH": [
"header",
"timestamp"
],
"_ARON_TYPE": "_ARON_DICT"
}
},
"_ARON_PATH": [
"header"
],
"_ARON_TYPE": "_ARON_DICT"
},
"transform": {
"_ARON_DATA": [
0,
0,
128,
63,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
128,
63,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
128,
63,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
128,
63
],
"_ARON_DIMESIONS": [
4,
4,
4
],
"_ARON_PATH": [
"transform"
],
"_ARON_TYPE": "_ARON_NDARRAY",
"_ARON_USED_TYPE": "float"
}
},
"_ARON_PATH": [],
"_ARON_TYPE": "_ARON_DICT"
}
\ No newline at end of file
{
"_ARON_ELEMENTS": {
"__ENTITY_METADATA__CONFIDENCE": {
"_ARON_PATH": [
"__ENTITY_METADATA__CONFIDENCE"
],
"_ARON_TYPE": "_ARON_DOUBLE",
"_ARON_VALUE": 1.0
},
"__ENTITY_METADATA__TIME_ARRIVED": {
"_ARON_PATH": [
"__ENTITY_METADATA__TIME_ARRIVED"
],
"_ARON_TYPE": "_ARON_LONG",
"_ARON_VALUE": 1716809170599737
},
"__ENTITY_METADATA__TIME_REFERENCED": {
"_ARON_PATH": [
"__ENTITY_METADATA__TIME_REFERENCED"
],
"_ARON_TYPE": "_ARON_LONG",
"_ARON_VALUE": 1716809170586798
},
"__ENTITY_METADATA__TIME_SENT": {
"_ARON_PATH": [
"__ENTITY_METADATA__TIME_SENT"
],
"_ARON_TYPE": "_ARON_LONG",
"_ARON_VALUE": 1716809170599343
},
"__WRITER_METADATA__TIME_STORED": {
"_ARON_PATH": [
"__WRITER_METADATA__TIME_STORED"
],
"_ARON_TYPE": "_ARON_LONG",
"_ARON_VALUE": 1716809203749597
}
},
"_ARON_PATH": [],
"_ARON_TYPE": "_ARON_DICT"
}
\ No newline at end of file
def test_build_in_type__from_aron_conversions():
# get/make some data in aron format and convert to python, test if types and values are correct
pass
def test_build_in_type_to_aron_conversions():
# get/make some data in python format and convert to aron, test if types and values are correct
pass
\ No newline at end of file
import pytest
import json
import os
import armarx_memory.aron.data.variant as variant
def loadArrayDataFromJson(path_to_file):
with open(path_to_file) as f:
json_data = json.load(f)
globalPose_robotState_motion = json_data
aron_ndarray = variant.AronNDArray.fromJSON(globalPose_robotState_motion)
normal_array = aron_ndarray.toNDArray()
return normal_array
def test_loadArrayDataFromFile():
test_dir = os.path.dirname(os.path.abspath(__file__))
path = os.path.join(test_dir, "test.json")
array = loadArrayDataFromJson(path)
assert array.size == 16
def test_loadIntFromFile():
test_dir = os.path.dirname(os.path.abspath(__file__))
path = os.path.join(test_dir, "testData/RobotStateLoc_instance/0/data.aron.json")
with open(path) as f:
json_data = json.load(f)
int_value = json_data["_ARON_ELEMENTS"]["header"]["_ARON_ELEMENTS"]["timestamp"]["_ARON_ELEMENTS"]["clockType"]
aron_int = variant.AronInt.fromJSON(int_value)
python_int = aron_int.value
assert python_int == 2
def test_loadStringFromFile():
test_dir = os.path.dirname(os.path.abspath(__file__))
path = os.path.join(test_dir, "testData/RobotStateLoc_instance/0/data.aron.json")
with open(path) as f:
json_data = json.load(f)
string_value = json_data["_ARON_ELEMENTS"]["header"]["_ARON_ELEMENTS"]["timestamp"]["_ARON_ELEMENTS"]["hostname"]
aron_string = variant.AronString.fromJSON(string_value)
python_string = aron_string.value
assert python_string == "unknown"
def test_loadLongFromFile():
test_dir = os.path.dirname(os.path.abspath(__file__))
path = os.path.join(test_dir, "testData/RobotStateLoc_instance/0/data.aron.json")
with open(path) as f:
json_data = json.load(f)
long_value = json_data["_ARON_ELEMENTS"]["header"]["_ARON_ELEMENTS"]["timestamp"]["_ARON_ELEMENTS"]["timeSinceEpoch"]["_ARON_ELEMENTS"]["microSeconds"]
aron_long = variant.AronLong.fromJSON(long_value)
python_long = aron_long.value
assert python_long == 1716809170586798
def test_loadDictFromFile():
test_dir = os.path.dirname(os.path.abspath(__file__))
path = os.path.join(test_dir, "testData/RobotStateLoc_instance/0/data.aron.json")
with open(path) as f:
json_data = json.load(f)
dict_value = json_data["_ARON_ELEMENTS"]["header"]["_ARON_ELEMENTS"]["timestamp"]["_ARON_ELEMENTS"]["timeSinceEpoch"]
aron_dict = variant.AronDict.fromJSON(dict_value)
python_dict = aron_dict.elements
assert 'microSeconds' in python_dict
assert len(python_dict.keys()) == 1
l = python_dict['microSeconds'].value
assert l == 1716809170586798
if __name__ == "__main__":
pytest.main()
# TODO: tests for loading float, bool, quaternion, double, image, pointloud, list, tuple, object
\ No newline at end of file
{
"_ARON_ELEMENTS": {
"header": {
"_ARON_ELEMENTS": {
"agent": {
"_ARON_PATH": [
"header",
"agent"
],
"_ARON_TYPE": "_ARON_STRING",
"_ARON_VALUE": "Armar3"
},
"frame": {
"_ARON_PATH": [
"header",
"frame"
],
"_ARON_TYPE": "_ARON_STRING",
"_ARON_VALUE": "Odom"
},
"parentFrame": {
"_ARON_PATH": [
"header",
"parentFrame"
],
"_ARON_TYPE": "_ARON_STRING",
"_ARON_VALUE": "Map"
},
"timestamp": {
"_ARON_ELEMENTS": {
"clockType": {
"_ARON_PATH": [
"header",
"timestamp",
"clockType"
],
"_ARON_TYPE": "_ARON_INT",
"_ARON_VALUE": 2
},
"hostname": {
"_ARON_PATH": [
"header",
"timestamp",
"hostname"
],
"_ARON_TYPE": "_ARON_STRING",
"_ARON_VALUE": "joana-ThinkPad-P16v-Gen-1"
},
"timeSinceEpoch": {
"_ARON_ELEMENTS": {
"microSeconds": {
"_ARON_PATH": [
"header",
"timestamp",
"timeSinceEpoch",
"microSeconds"
],
"_ARON_TYPE": "_ARON_LONG",
"_ARON_VALUE": 1716470597248789
}
},
"_ARON_PATH": [
"header",
"timestamp",
"timeSinceEpoch"
],
"_ARON_TYPE": "_ARON_DICT"
}
},
"_ARON_PATH": [
"header",
"timestamp"
],
"_ARON_TYPE": "_ARON_DICT"
}
},
"_ARON_PATH": [
"header"
],
"_ARON_TYPE": "_ARON_DICT"
},
"transform": {
"_ARON_DATA": [
180,
188,
67,
63,
95,
254,
36,
191,
94,
254,
142,
58,
30,
222,
199,
68,
103,
254,
36,
63,
186,
188,
67,
63,
12,
83,
186,
184,
147,
148,
249,
67,
143,
167,
75,
186,
240,
32,
74,
58,
246,
255,
127,
63,
209,
176,
138,
61,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
128,
63
],
"_ARON_DIMESIONS": [
4,
4,
4
],
"_ARON_PATH": [
"transform"
],
"_ARON_TYPE": "_ARON_NDARRAY",
"_ARON_USED_TYPE": "float"
}
},
"_ARON_PATH": [],
"_ARON_TYPE": "_ARON_DICT"
}
\ No newline at end of file
{
"_ARON_ELEMENTS": {
"__ENTITY_METADATA__CONFIDENCE": {
"_ARON_PATH": [
"__ENTITY_METADATA__CONFIDENCE"
],
"_ARON_TYPE": "_ARON_DOUBLE",
"_ARON_VALUE": 1.0
},
"__ENTITY_METADATA__TIME_ARRIVED": {
"_ARON_PATH": [
"__ENTITY_METADATA__TIME_ARRIVED"
],
"_ARON_TYPE": "_ARON_LONG",
"_ARON_VALUE": 1716470597300822
},
"__ENTITY_METADATA__TIME_REFERENCED": {
"_ARON_PATH": [
"__ENTITY_METADATA__TIME_REFERENCED"
],
"_ARON_TYPE": "_ARON_LONG",
"_ARON_VALUE": 1716470597300654
},
"__ENTITY_METADATA__TIME_SENT": {
"_ARON_PATH": [
"__ENTITY_METADATA__TIME_SENT"
],
"_ARON_TYPE": "_ARON_LONG",
"_ARON_VALUE": 1716470597300694
},
"__WRITER_METADATA__TIME_STORED": {
"_ARON_PATH": [
"__WRITER_METADATA__TIME_STORED"
],
"_ARON_TYPE": "_ARON_LONG",
"_ARON_VALUE": 1716470662255020
}
},
"_ARON_PATH": [],
"_ARON_TYPE": "_ARON_DICT"
}
\ No newline at end of file
{
"_ARON_ELEMENTS": {
"header": {
"_ARON_ELEMENTS": {
"agent": {
"_ARON_PATH": [
"header",
"agent"
],
"_ARON_TYPE": "_ARON_STRING",
"_ARON_VALUE": "Armar3"
},
"frame": {
"_ARON_PATH": [
"header",
"frame"
],
"_ARON_TYPE": "_ARON_STRING",
"_ARON_VALUE": "Odom"
},
"parentFrame": {
"_ARON_PATH": [
"header",
"parentFrame"
],
"_ARON_TYPE": "_ARON_STRING",
"_ARON_VALUE": "Map"
},
"timestamp": {
"_ARON_ELEMENTS": {
"clockType": {
"_ARON_PATH": [
"header",
"timestamp",
"clockType"
],
"_ARON_TYPE": "_ARON_INT",
"_ARON_VALUE": 2
},
"hostname": {
"_ARON_PATH": [
"header",
"timestamp",
"hostname"
],
"_ARON_TYPE": "_ARON_STRING",
"_ARON_VALUE": "joana-ThinkPad-P16v-Gen-1"
},
"timeSinceEpoch": {
"_ARON_ELEMENTS": {
"microSeconds": {
"_ARON_PATH": [
"header",
"timestamp",
"timeSinceEpoch",
"microSeconds"
],
"_ARON_TYPE": "_ARON_LONG",
"_ARON_VALUE": 1716470597311568
}
},
"_ARON_PATH": [
"header",
"timestamp",
"timeSinceEpoch"
],
"_ARON_TYPE": "_ARON_DICT"
}
},
"_ARON_PATH": [
"header",
"timestamp"
],
"_ARON_TYPE": "_ARON_DICT"
}
},
"_ARON_PATH": [
"header"
],
"_ARON_TYPE": "_ARON_DICT"
},
"transform": {
"_ARON_DATA": [
181,
188,
67,
63,
94,
254,
36,
191,
87,
2,
143,
58,
30,
222,
199,
68,
102,
254,
36,
63,
187,
188,
67,
63,
144,
34,
186,
184,
147,
148,
249,
67,
138,
177,
75,
186,
107,
33,
74,
58,
246,
255,
127,
63,
118,
181,
138,
61,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
128,
63
],
"_ARON_DIMESIONS": [
4,
4,
4
],
"_ARON_PATH": [
"transform"
],
"_ARON_TYPE": "_ARON_NDARRAY",
"_ARON_USED_TYPE": "float"
}
},
"_ARON_PATH": [],
"_ARON_TYPE": "_ARON_DICT"
}
\ No newline at end of file
{
"_ARON_ELEMENTS": {
"__ENTITY_METADATA__CONFIDENCE": {
"_ARON_PATH": [
"__ENTITY_METADATA__CONFIDENCE"
],
"_ARON_TYPE": "_ARON_DOUBLE",
"_ARON_VALUE": 1.0
},
"__ENTITY_METADATA__TIME_ARRIVED": {
"_ARON_PATH": [
"__ENTITY_METADATA__TIME_ARRIVED"
],
"_ARON_TYPE": "_ARON_LONG",
"_ARON_VALUE": 1716470597363829
},
"__ENTITY_METADATA__TIME_REFERENCED": {
"_ARON_PATH": [
"__ENTITY_METADATA__TIME_REFERENCED"
],
"_ARON_TYPE": "_ARON_LONG",
"_ARON_VALUE": 1716470597363643
},
"__ENTITY_METADATA__TIME_SENT": {
"_ARON_PATH": [
"__ENTITY_METADATA__TIME_SENT"
],
"_ARON_TYPE": "_ARON_LONG",
"_ARON_VALUE": 1716470597363688
},
"__WRITER_METADATA__TIME_STORED": {
"_ARON_PATH": [
"__WRITER_METADATA__TIME_STORED"
],
"_ARON_TYPE": "_ARON_LONG",
"_ARON_VALUE": 1716470662314564
}
},
"_ARON_PATH": [],
"_ARON_TYPE": "_ARON_DICT"
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment