def collect(chatter): msg = yield marv.pull(chatter) assert msg is not None yield marv.push(msg.data) while True: msg = yield marv.pull(chatter) if msg is None: return yield marv.push(msg.data)
def orientations(imus, navsatorients): while True: tmp = yield marv.pull(imus) if tmp is None: break yield marv.push(tmp) while True: tmp = yield marv.pull(navsatorients) if tmp is None: break yield marv.push(tmp)
def meta_table(dataset): dataset = yield marv.pull(dataset) columns = [ { 'title': 'Name', 'formatter': 'rellink' }, { 'title': 'Size', 'formatter': 'filesize' }, ] # dataset.id is setid here rows = [{ 'id': idx, 'cells': [ { 'link': { 'href': '{}'.format(idx), 'title': os.path.basename(f.path) } }, { 'uint64': f.size }, ] } for idx, f in enumerate(dataset.files)] yield marv.push({'table': {'columns': columns, 'rows': rows}})
def navsatfix(stream): yield marv.set_header(title=stream.topic) pytype = get_message_type(stream) rosmsg = pytype() erroneous = 0 while True: msg = yield marv.pull(stream) if msg is None: break rosmsg.deserialize(msg.data) if not hasattr(rosmsg, 'status') or \ np.isnan(rosmsg.longitude) or \ np.isnan(rosmsg.latitude) or \ np.isnan(rosmsg.altitude): erroneous += 1 continue # TODO: namedtuple? out = { 'status': rosmsg.status.status, 'lon': rosmsg.longitude, 'lat': rosmsg.latitude, 'timestamp': rosmsg.header.stamp.to_time() } yield marv.push(out) if erroneous: log = yield marv.get_logger() log.warn('skipped %d erroneous messages', erroneous)
def connections_section(bagmeta, dataset, title): """Section displaying information about ROS connections.""" dataset, bagmeta = yield marv.pull_all(dataset, bagmeta) if not bagmeta.topics: raise marv.Abort() columns = [ {'title': 'Topic'}, {'title': 'Type'}, {'title': 'MD5'}, {'title': 'Latching'}, {'title': 'Message count', 'align': 'right'} ] rows = [{'id': idx, 'cells': [ {'text': con.topic}, {'text': con.datatype}, {'text': con.md5sum}, {'bool': con.latching}, {'uint64': con.msg_count} ]} for idx, con in enumerate(bagmeta.connections)] widgets = [{'table': {'columns': columns, 'rows': rows}}] # TODO: Add text widget explaining what can be seen here: ROS bag # files store connections. There can be multiple connections for # one topic with the same or different message types and message # types with the same name might have different md5s. For # simplicity connections with the same topic, message type and md5 # are treated as one, within one bag file as well as across bags # of one set. If one of such connections is latching, the # aggregated connection will be latching. yield marv.push({'title': title, 'widgets': widgets})
def filesize_plot_fixed(filesizes): # set_header() helps marv to schedule nodes yield marv.set_header() # Pull all filesizes sizes = [] while True: size = yield marv.pull(filesizes) if size is None: break sizes.append(size) # plot fig = plt.figure() axis = fig.add_subplot(1, 1, 1) axis.plot(sizes, 'bo') #axis.set_xlabel('foo') #axis.set_ylabel('bat') # save figure to file plotfile = yield marv.make_file('filesizes.json') with open(plotfile.path, 'w') as f: json.dump(mpld3.fig_to_dict(fig), f) # create plot widget referencing file widget = { 'title': 'Filesizes', 'mpld3': 'marv-partial:{}'.format(plotfile.relpath), } yield marv.push(widget)
def filesize_plot(filesizes): # Pull all filesizes sizes = [] while True: size = yield marv.pull(filesizes) if size is None: break sizes.append(size) # plot fig = plt.figure() axis = fig.add_subplot(1, 1, 1) axis.plot(sizes, 'bo') # EE: save figure to file plotfile = yield marv.make_file('filesizes.json') with open(plotfile.path, 'w') as f: json.dump(mpld3.fig_to_dict(fig), f) # EE: create plot widget referencing file widget = { 'title': 'Filesizes', 'mpld3': 'marv-partial:{}'.format(plotfile.relpath), } # Alternative code for community edition #plotfile = yield marv.make_file('filesizes.jpg') #fig.savefig(plotfile.path) #widget = { # 'title': 'Filesizes', # 'image': {'src': plotfile.relpath}, #} yield marv.push(widget)
def summary_keyval(dataset): dataset = yield marv.pull(dataset) if len(dataset.files) < 2: return yield marv.push({ 'keyval': { 'items': [ { 'title': 'size', 'formatter': 'filesize', 'list': False, 'cell': { 'uint64': sum(x.size for x in dataset.files) } }, { 'title': 'files', 'list': False, 'cell': { 'uint64': len(dataset.files) } }, ] } })
def nooutput(stream): yield marv.set_header() while True: msg = yield marv.pull(stream) if msg is None: return yield marv.push(msg)
def images(cam): """Extract images from input stream to jpg files. Args: cam: Input stream of raw rosbag messages. Returns: File instances for images of input stream. """ # Set output stream title and pull first message yield marv.set_header(title=cam.topic) # Fetch and process first 20 image messages name_template = '%s-{}.jpg' % cam.topic.replace('/', ':')[1:] while True: idx, msg = yield marv.pull(cam, enumerate=True) if msg is None or idx >= 20: break # Deserialize raw ros message pytype = get_message_type(cam) rosmsg = pytype() rosmsg.deserialize(msg.data) # Write image to jpeg and push it to output stream img = imgmsg_to_cv2(rosmsg, "rgb8") name = name_template.format(idx) imgfile = yield marv.make_file(name) cv2.imwrite(imgfile.path, img) yield marv.push(imgfile)
def image(cam): """Extract first image of input stream to jpg file. Args: cam: Input stream of raw rosbag messages. Returns: File instance for first image of input stream. """ # Set output stream title and pull first message yield marv.set_header(title=cam.topic) msg = yield marv.pull(cam) if msg is None: return # Deserialize raw ros message pytype = get_message_type(cam) rosmsg = pytype() rosmsg.deserialize(msg.data) # Write image to jpeg and push it to output stream name = '{}.jpg'.format(cam.topic.replace('/', ':')[1:]) imgfile = yield marv.make_file(name) img = imgmsg_to_cv2(rosmsg, "rgb8") cv2.imwrite(imgfile.path, img, (cv2.IMWRITE_JPEG_QUALITY, 60)) yield marv.push(imgfile)
def trajectory(navsatfixes): navsatfix = yield marv.pull(navsatfixes) # Only one topic for now if not navsatfix: raise marv.Abort() yield marv.set_header(title=navsatfix.title) features = [] prev_quality = None timestamps = [] while True: msg = yield marv.pull(navsatfix) if msg is None: break dt = msg['timestamp'] timestamps.append(int(dt * 1e9)) # Whether to output an augmented fix is determined by both the fix # type and the last time differential corrections were received. A # fix is valid when status >= STATUS_FIX. # STATUS_NO_FIX = -1 -> unable to fix position -> color id 0 = red # STATUS_FIX = 0 -> unaugmented fix -> color id 1 = orange # STATUS_SBAS_FIX = 1 -> satellite-based augmentation -> color id 2 = blue # STATUS_GBAS_FIX = 2 -> ground-based augmentation -> color id 3 = green # -> unknown status id -> color id 4 = black if -1 <= msg['status'] <= 2: quality = msg['status'] + 1 else: quality = 4 if quality != prev_quality: color = ( (1., 0., 0., 1.), # rgba (1., 0.65, 0., 1.), (0., 0., 1., 1.), (0., 1., 0., 1.))[quality] coords = [] feat = { 'properties': { 'color': color, 'width': 4., 'timestamps': timestamps, 'markervertices': [c * 30 for c in (0., 0., -1., .3, -1., -.3)] }, 'geometry': { 'line_string': { 'coordinates': coords } } } features.append(feat) prev_quality = quality coords.append((msg['lon'], msg['lat'])) if features: out = {'feature_collection': {'features': features}} yield marv.push(out)
def bagmeta_table(bagmeta, dataset): """Table widget listing metadata for each bag of dataset. Useful for detail_summary_widgets. """ dataset, bagmeta = yield marv.pull_all(dataset, bagmeta) columns = [ { 'title': 'Name', 'formatter': 'rellink' }, { 'title': 'Size', 'formatter': 'filesize' }, { 'title': 'Start time', 'formatter': 'datetime' }, { 'title': 'End time', 'formatter': 'datetime' }, { 'title': 'Duration', 'formatter': 'timedelta' }, { 'title': 'Message count', 'align': 'right' }, ] rows = [{ 'id': idx, 'cells': [{ 'link': { 'href': '{}'.format(idx), 'title': os.path.basename(f.path) } }, { 'uint64': f.size }, { 'timestamp': bag.start_time }, { 'timestamp': bag.end_time }, { 'timedelta': bag.duration }, { 'uint64': bag.msg_count }] } for idx, (bag, f) in enumerate(zip(bagmeta.bags, dataset.files))] yield marv.push({'table': {'columns': columns, 'rows': rows}})
def galleries(stream): """Galleries for all images streams. Used by marv_robotics.detail.images_section. """ yield marv.set_header(title=stream.title) images = [] while True: img = yield marv.pull(stream) if img is None: break images.append({'src': img.relpath}) yield marv.push({'title': stream.title, 'gallery': {'images': images}})
def images_section(galleries, title): """Section with galleries of images for each images stream.""" tmp = [] while True: msg = yield marv.pull(galleries) if msg is None: break tmp.append(msg) galleries = tmp galleries = sorted(galleries, key=lambda x: x.title) widgets = yield marv.pull_all(*galleries) if widgets: yield marv.push({'title': title, 'widgets': widgets})
def fulltext_per_topic(stream): yield marv.set_header() # TODO: workaround words = set() pytype = get_message_type(stream) rosmsg = pytype() while True: msg = yield marv.pull(stream) if msg is None: break rosmsg.deserialize(msg.data) words.update(rosmsg.data.split()) if not words: raise marv.Abort() yield marv.push({'words': list(words)})
def filesizes(images): """Stat filesize of files. Args: images: stream of marv image files Returns: Stream of filesizes """ # Pull each image and push its filesize while True: img = yield marv.pull(images) if img is None: break yield marv.push(img.size)
def fulltext(streams): """Extract all text from bag file and store for fulltext search""" tmp = [] while True: stream = yield marv.pull(streams) if stream is None: break tmp.append(stream) streams = tmp if not streams: raise marv.Abort() msgs = yield marv.pull_all(*streams) words = {x for msg in msgs for x in msg.words} yield marv.push({'words': list(words)})
def trajectory(navsatfixes): navsatfix = yield marv.pull(navsatfixes) # Only one topic for now if not navsatfix: raise marv.Abort() yield marv.set_header(title=navsatfix.title) features = [] quality = None coords = [] timestamps = [] while True: msg = yield marv.pull(navsatfix) if msg is None: break dt = msg['timestamp'] timestamps.append(int(dt * 1e9)) # Whether to output an augmented fix is determined by both the fix # type and the last time differential corrections were received. A # fix is valid when status >= STATUS_FIX. # STATUS_NO_FIX = -1 -> unable to fix position -> color id 0 = red # STATUS_FIX = 0 -> unaugmented fix -> color id 1 = orange # STATUS_SBAS_FIX = 1 -> satellite-based augmentation -> color id 2 = blue # STATUS_GBAS_FIX = 2 -> ground-based augmentation -> color id 3 = green # -> unknown status id -> color id 4 = black if -1 <= msg['status'] <= 2: new_quality = msg['status'] + 1 else: new_quality = 4 # start new feature if quality changed if quality != new_quality: if coords: features.append(_create_feature(coords, quality, timestamps)) quality = new_quality coords = [] timestamps = [] coords.append((msg['lon'], msg['lat'])) if coords: features.append(_create_feature(coords, quality, timestamps)) if features: out = {'feature_collection': {'features': features}} yield marv.push(out)
def section_test(node_test): value = yield marv.pull(node_test) value = value.value yield marv.push({ 'title': 'Test', 'widgets': [{ 'keyval': { 'items': [{ 'title': 'value', 'cell': { 'uint64': value } }] } }] })
def summary_keyval(dataset, bagmeta): """Keyval widget summarizing bag metadata. Useful for detail_summary_widgets. """ dataset, bagmeta = yield marv.pull_all(dataset, bagmeta) yield marv.push({ 'keyval': { 'items': [{ 'title': 'size', 'formatter': 'filesize', 'list': False, 'cell': { 'uint64': sum(x.size for x in dataset.files) } }, { 'title': 'files', 'list': False, 'cell': { 'uint64': len(dataset.files) } }, { 'title': 'start time', 'formatter': 'datetime', 'list': False, 'cell': { 'timestamp': bagmeta.start_time } }, { 'title': 'end time', 'formatter': 'datetime', 'list': False, 'cell': { 'timestamp': bagmeta.end_time } }, { 'title': 'duration', 'formatter': 'timedelta', 'list': False, 'cell': { 'timedelta': bagmeta.duration } }] } })
def video_section(videos, title): """Section displaying one video player per image stream.""" tmps = [] while True: tmp = yield marv.pull(videos) if tmp is None: break tmps.append(tmp) videos = sorted(tmps, key=lambda x: x.title) if not videos: raise marv.Abort() videofiles = yield marv.pull_all(*videos) widgets = [{'title': video.title, 'video': {'src': videofile.relpath}} for video, videofile in zip(videos, videofiles)] assert len(set(x['title'] for x in widgets)) == len(widgets) if widgets: yield marv.push({'title': title, 'widgets': widgets})
def positions(stream): yield marv.set_header(title=stream.topic) pytype = get_message_type(stream) rosmsg = pytype() erroneous = 0 e_offset = None n_offset = None u_offset = None positions = [] while True: msg = yield marv.pull(stream) if msg is None: break rosmsg.deserialize(msg.data) if not hasattr(rosmsg, 'status') or \ np.isnan(rosmsg.longitude) or \ np.isnan(rosmsg.latitude) or \ np.isnan(rosmsg.altitude) or \ np.isnan(rosmsg.position_covariance[0]): erroneous += 1 continue e, n, _, _ = utm.from_latlon(rosmsg.latitude, rosmsg.longitude) if e_offset is None: e_offset = e n_offset = n u_offset = rosmsg.altitude e = e - e_offset n = n - n_offset u = rosmsg.altitude - u_offset # TODO: why do we accumulate? positions.append([rosmsg.header.stamp.to_sec(), rosmsg.latitude, rosmsg.longitude, rosmsg.altitude, e, n, u, rosmsg.status.status, np.sqrt(rosmsg.position_covariance[0])]) if erroneous: log = yield marv.get_logger() log.warn('skipped %d erroneous messages', erroneous) if positions: yield marv.push({'values': positions})
def image_section(image, title): """Create detail section with one image. Args: title (str): Title to be displayed for detail section. image: marv image file. Returns One detail section. """ # pull first image img = yield marv.pull(image) if img is None: return # create image widget and section containing it widget = {'title': image.title, 'image': {'src': img.relpath}} section = {'title': title, 'widgets': [widget]} yield marv.push(section)
def combined_section(title, images, filesizes, filesize_plot): # A gallery of images imgs = [] gallery = {'title': images.title, 'gallery': {'images': imgs}} # A table with two columns rows = [] columns = [{ 'title': 'Name', 'formatter': 'rellink' }, { 'title': 'Size', 'formatter': 'filesize' }] table = {'table': {'columns': columns, 'rows': rows}} # pull images and filesizes synchronously while True: img, filesize = yield marv.pull_all(images, filesizes) if img is None: break imgs.append({'src': img.relpath}) rows.append({ 'cells': [ { 'link': { 'href': img.relpath, 'title': os.path.basename(img.relpath) } }, { 'uint64': filesize }, ] }) # pull filesize_plot AFTER individual messages plot = yield marv.pull(filesize_plot) # section containing multiple widgets section = {'title': title, 'widgets': [table, plot, gallery]} yield marv.push(section)
def gnss_section(plots, title): """Section displaying GNSS plots.""" # tmps = [] # tmp = yield marv.pull(plots) # while tmp: # tmps.append(tmp) # tmp = yield marv.pull(plots) # plots = tmps # TODO: no foreaching right now plots = [plots] widgets = [] for plot in plots: plotfile = yield marv.pull(plot) if plotfile: widgets.append({'title': plot.title, 'image': {'src': plotfile.relpath}}) assert len(set(x['title'] for x in widgets)) == len(widgets) if widgets: yield marv.push({'title': title, 'widgets': widgets})
def imus(stream): yield marv.set_header(title=stream.topic) pytype = get_message_type(stream) rosmsg = pytype() erroneous = 0 imus = [] while True: msg = yield marv.pull(stream) if msg is None: break rosmsg.deserialize(msg.data) if np.isnan(rosmsg.orientation.x): erroneous += 1 continue # TODO: why do we accumulate? imus.append([rosmsg.header.stamp.to_sec(), yaw_angle(rosmsg.orientation)]) if erroneous: log = yield marv.get_logger() log.warn('skipped %d erroneous messages', erroneous) yield marv.push({'values': imus})
def navsatorients(stream): log = yield marv.get_logger() yield marv.set_header(title=stream.topic) pytype = get_message_type(stream) rosmsg = pytype() erroneous = 0 navsatorients = [] while True: msg = yield marv.pull(stream) if msg is None: break rosmsg.deserialize(msg.data) if np.isnan(rosmsg.yaw): erroneous += 1 continue # TODO: why do we accumulate? navsatorients.append([rosmsg.header.stamp.to_sec(), rosmsg.yaw]) if erroneous: log.warn('skipped %d erroneous messages', erroneous) yield marv.push({'values': navsatorients})
def gallery_section(images, title): """Create detail section with gallery. Args: title (str): Title to be displayed for detail section. images: stream of marv image files Returns One detail section. """ # pull all images imgs = [] while True: img = yield marv.pull(images) if img is None: break imgs.append({'src': img.relpath}) if not imgs: return # create gallery widget and section containing it widget = {'title': images.title, 'gallery': {'images': imgs}} section = {'title': title, 'widgets': [widget]} yield marv.push(section)
def trajectory_section(geojson, title, minzoom, maxzoom, tile_server_protocol): """Section displaying trajectory on a map. Args: tile_server_protocol (str): Set to ``https:`` if you host marv behind http and prefer the tile requests to be secured. """ geojson = yield marv.pull(geojson) if not geojson: raise marv.Abort() layers = [ {'title': 'Background', 'tiles': [ {'title': 'Roadmap', 'url': '%s//[abc].osm.ternaris.com/styles/osm-bright/rendered/{z}/{x}/{y}.png' % tile_server_protocol, 'attribution': '© <a href="http://openstreetmap.org/copyright">OpenStreetMap</a> contributors', 'retina': 3, 'zoom': {'min': 0, 'max': 20}}, {'title': 'Satellite', 'url': '%s//server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}.png' % tile_server_protocol, 'attribution': 'Sources: Esri, DigitalGlobe, GeoEye, Earthstar Geographics, CNES/Airbus DS, USDA, USGS, AeroGRID, IGN, and the GIS User Community', 'zoom': {'min': 0, 'max': 18}}, ]}, {'title': 'Trajectory', 'color': (0., 1., 0., 1.), 'geojson': geojson}, ] dct = make_map_dict({ 'layers': layers, 'zoom': {'min': minzoom, 'max': maxzoom}, }) jsonfile = yield marv.make_file('data.json') with open(jsonfile.path, 'w') as f: json.dump(dct, f, sort_keys=True) yield marv.push({'title': title, 'widgets': [{'map_partial': 'marv-partial:{}'.format(jsonfile.relpath)}]})