00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036 import roslib.message
00037 import rospy
00038 import genpy
00039 import os
00040 import re
00041 import yaml
00042 import uuid
00043 import unique_id
00044 import warehouse_ros as wr
00045
00046 from world_canvas_msgs.msg import *
00047 from world_canvas_msgs.srv import *
00048 from world_canvas_utils.serialization import *
00049
00050
00051 class YAMLDatabase:
00052
00053
00054
00055
00056
00057 def __init__(self, anns_collection, data_collection):
00058
00059 self.anns_collection = anns_collection
00060 self.data_collection = data_collection
00061
00062
00063 self.import_srv = rospy.Service('yaml_import', YAMLImport, self.import_from_yaml)
00064 self.export_srv = rospy.Service('yaml_export', YAMLExport, self.export_to_yaml)
00065
00066
00067
00068
00069
00070
00071 def import_from_yaml(self, request):
00072
00073 response = YAMLImportResponse()
00074
00075 if not os.path.isfile(request.filename):
00076 return self.service_error(response, "File does not exist: %s" % (request.filename))
00077
00078 try:
00079 with open(request.filename, 'r') as f:
00080
00081 yaml_data = yaml.load(f)
00082 if yaml_data is None:
00083 raise yaml.YAMLError("Empty files not allowed")
00084 except yaml.YAMLError as e:
00085 return self.service_error(response, "Invalid YAML file: %s" % (str(e)))
00086
00087
00088 self.anns_collection.remove({})
00089 self.data_collection.remove({})
00090
00091
00092
00093 for t in yaml_data:
00094
00095 try:
00096 anns_list = t['annotations']
00097 except KeyError as e:
00098 return self.service_error(response, "Invalid database file format: %s field not found" % str(e))
00099
00100 if len(anns_list) == 0:
00101
00102 return self.service_error(response, "Invalid database file format: " \
00103 "there must be at least one annotation for database entry")
00104
00105 for a in anns_list:
00106 annotation = Annotation()
00107 try:
00108 genpy.message.fill_message_args(annotation, a)
00109
00110 if 'prev_data_id' in locals() and prev_data_id != annotation.data_id.uuid:
00111
00112 return self.service_error(response, "Invalid database file format: " \
00113 "data ids must be equal for annotations referencing the same data")
00114
00115 prev_data_id = annotation.data_id.uuid
00116
00117
00118 annotation.data_id.uuid = ''.join(chr(x) for x in annotation.data_id.uuid)
00119 annotation.id.uuid = ''.join(chr(x) for x in annotation.id.uuid)
00120 for r in annotation.relationships:
00121 r.uuid = ''.join(chr(x) for x in r.uuid)
00122
00123
00124 metadata = { 'data_id' : unique_id.toHexString(annotation.data_id),
00125 'id' : unique_id.toHexString(annotation.id),
00126 'world' : annotation.world,
00127 'name' : annotation.name,
00128 'type' : annotation.type
00129 }
00130
00131
00132 if len(annotation.keywords) > 0:
00133 metadata['keywords'] = annotation.keywords
00134 if len(annotation.relationships) > 0:
00135 metadata['relationships'] = [unique_id.toHexString(r) for r in annotation.relationships]
00136
00137 rospy.logdebug("Saving annotation %s for world %s" % (annotation.id, annotation.world))
00138
00139
00140
00141 self.anns_collection.insert(annotation, metadata, safe=True)
00142
00143 except (genpy.MessageException, genpy.message.SerializationError) as e:
00144 return self.service_error(response, "Invalid annotation msg format: %s" % str(e))
00145 except Exception as e:
00146
00147 return self.service_error(response, "Insert annotation failed: %s" % str(e))
00148
00149
00150 msg_class = roslib.message.get_message_class(annotation.type)
00151 if msg_class is None:
00152
00153 return self.service_error(response, "Unknown message type: %s" % annotation.type)
00154
00155 data = msg_class()
00156
00157
00158 data_metadata = { 'id' : unique_id.toHexString(annotation.data_id) }
00159
00160 try:
00161 genpy.message.fill_message_args(data, t['data'])
00162 data_msg = AnnotationData()
00163 data_msg.id = annotation.data_id
00164 data_msg.type = annotation.type
00165 data_msg.data = serialize_msg(data)
00166 self.data_collection.insert(data_msg, data_metadata, safe=True)
00167 except (genpy.MessageException, genpy.message.SerializationError) as e:
00168
00169
00170 return self.service_error(response, "Invalid %s msg format: %s" % (annotation.type, str(e)))
00171 except KeyError as e:
00172 return self.service_error(response, "Invalid database file format: %s field not found" % str(e))
00173 except Exception as e:
00174
00175 return self.service_error(response, "Insert annotation data failed: %s" % str(e))
00176
00177
00178
00179 del prev_data_id
00180
00181 return self.service_success(response, "%lu annotations imported on database" % len(yaml_data))
00182
00183
00184 def export_to_yaml(self, request):
00185 response = YAMLExportResponse()
00186
00187
00188 matching_anns = self.anns_collection.query({}, sort_by='data_id')
00189
00190 try:
00191 with open(request.filename, 'w') as f:
00192 entries = []
00193 annotations = []
00194 last = False
00195
00196
00197 while True:
00198 try:
00199 annotation, metadata = matching_anns.next()
00200 except StopIteration:
00201 last = True
00202
00203 if not last and (len(annotations) == 0 or annotation.data_id == annotations[-1].data_id):
00204
00205 annotations.append(annotation)
00206 elif len(annotations) > 0:
00207
00208
00209
00210 data_id_str = unique_id.toHexString(annotations[-1].data_id)
00211 matching_data = self.data_collection.query({'id': {'$in': [data_id_str]}})
00212 try:
00213 d = matching_data.next()[0]
00214 except StopIteration:
00215 return self.service_error(response, "Export to file failed: " \
00216 "No data found with id %s" % data_id_str)
00217
00218
00219
00220
00221 data_class = roslib.message.get_message_class(annotations[0].type)
00222 if data_class is None:
00223 rospy.logerr("Annotation type %s definition not found" % annotation.type)
00224 return False
00225
00226 data = deserialize_msg(d.data, data_class)
00227 entry = dict(
00228 annotations = [yaml.load(genpy.message.strify_message(a)) for a in annotations],
00229 data = yaml.load(genpy.message.strify_message(data))
00230 )
00231 entries.append(entry)
00232
00233 if last:
00234 break;
00235 else:
00236
00237
00238 annotations = [annotation]
00239 else:
00240
00241 rospy.logdebug("Final else: %d %d" % (last, len(entries)))
00242 break;
00243
00244
00245 if len(entries) == 0:
00246
00247 return self.service_success(response, "Database is empty!; nothing to export")
00248 else:
00249
00250
00251
00252
00253
00254 dump = yaml.dump(entries, default_flow_style=False)
00255 dump = self.flow_style_lists(dump)
00256 dump = self.flow_style_occ_grid(dump)
00257
00258
00259
00260 dump = self.fix_exp_numbers(dump)
00261 f.write(dump)
00262 return self.service_success(response, "%lu annotations exported from database" % len(entries))
00263 except Exception as e:
00264 return self.service_error(response, "Export to file failed: %s" % (str(e)))
00265
00266
00267
00268
00269
00270
00271 def service_success(self, response, message = None):
00272 if message is not None:
00273 rospy.loginfo(message)
00274 response.result = True
00275 return response
00276
00277 def service_error(self, response, message):
00278 rospy.logerr(message)
00279 response.message = message
00280 response.result = False
00281 return response
00282
00283 def fix_exp_numbers(self, target):
00284
00285 regex = '( [-+]?\d+[eE][-+]?\d+)'
00286 offset = 0
00287 comp_re = re.compile(regex)
00288 for match in comp_re.finditer(target):
00289
00290 fp_nb = match.group(1)
00291 fp_nb = re.sub('[eE]', '.0e', fp_nb)
00292 target = target[:match.start(1) + offset] + fp_nb + target[match.end(1) + offset:]
00293 offset += 2
00294
00295 return target
00296
00297 def flow_style_lists(self, target):
00298
00299 regex = 'uuid: *\n'
00300 for i in range(0, 16):
00301 regex += ' *- +(\d+)\n'
00302 comp_re = re.compile(regex)
00303 while True:
00304 match = comp_re.search(target)
00305 if match is None:
00306 break;
00307
00308
00309 flow_list = 'uuid: ['
00310 for i in range(1, 16):
00311 flow_list += match.group(i) + ', '
00312 flow_list += match.group(16) + ']\n'
00313
00314 target = comp_re.sub(flow_list, target, 1)
00315
00316
00317 regex = 'covariance: *\n'
00318 for i in range(0, 36):
00319 regex += ' *- +([-+]?\d*\.?\d+|\d+[eE][-+]?\d+)\n'
00320 comp_re = re.compile(regex)
00321 while True:
00322 match = comp_re.search(target)
00323 if match is None:
00324 break;
00325
00326
00327 flow_list = 'covariance: ['
00328 for i in range(1, 36):
00329 flow_list += match.group(i) + ', '
00330 flow_list += match.group(36) + ']\n'
00331
00332 target = comp_re.sub(flow_list, target, 1)
00333
00334 return target
00335
00336 def flow_style_occ_grid(self, target):
00337
00338
00339
00340 regex = 'data: *\n( *- +(\d+)\n){10,}'
00341 comp_re = re.compile(regex)
00342 while True:
00343 data_match = comp_re.search(target)
00344 if data_match is None:
00345 break;
00346
00347 data = target[data_match.start():data_match.end()]
00348 flow_list = 'data: ['
00349 for byte_match in re.finditer(' *- +(\d+)\n', data):
00350 flow_list += byte_match.group(1) + ','
00351 flow_list = flow_list[:-1] + ']\n'
00352
00353 target = comp_re.sub(flow_list, target, 1)
00354
00355 return target