예제 #1
0
def pcap_display_xyz_points(source: client.PacketSource,
                            metadata: client.SensorInfo,
                            num: int = 0) -> None:
    """Plot point cloud using matplotlib."""
    import matplotlib.pyplot as plt  # type: ignore

    # [doc-stag-pcap-plot-xyz-points]
    from more_itertools import nth
    scan = nth(client.Scans(source), num)
    if not scan:
        print(f"ERROR: Scan # {num} in not present in pcap file")
        exit(1)

    # set up figure
    plt.figure()
    ax = plt.axes(projection='3d')
    r = 6
    ax.set_xlim3d([-r, r])
    ax.set_ylim3d([-r, r])
    ax.set_zlim3d([-r, r])

    plt.title("3D Points XYZ for scan")

    # transform data to 3d points and graph
    xyzlut = client.XYZLut(metadata)
    xyz = xyzlut(scan)

    key = scan.field(client.ChanField.SIGNAL)

    [x, y, z] = [c.flatten() for c in np.dsplit(xyz, 3)]
    ax.scatter(x, y, z, c=normalize(key.flatten()), s=0.2)
    plt.show()
예제 #2
0
def test_scans_meta(packets: client.PacketSource) -> None:
    """Sanity check metadata and column headers of a batched scan."""
    scans = iter(client.Scans(packets))
    scan = next(scans)

    assert scan.frame_id != -1
    assert scan.h == packets.metadata.format.pixels_per_column
    assert scan.w == packets.metadata.format.columns_per_frame
    assert len(scan.timestamp) == scan.w
    assert len(scan.measurement_id) == scan.w
    assert len(scan.status) == scan.w
    assert len(scan.header(ColHeader.ENCODER_COUNT)) == scan.w

    assert scan._complete()

    # all timestamps valid
    assert np.count_nonzero(scan.timestamp) == scan.w

    if (packets.metadata.format.udp_profile_lidar ==
            client.UDPProfileLidar.PROFILE_LIDAR_LEGACY):
        # check that all columns are valid
        assert (scan.status == 0xffffffff).all()
        # only first encoder count is zero
        assert np.count_nonzero(scan.header(
            ColHeader.ENCODER_COUNT)) == scan.w - 1
    else:
        # only lowest bit indicates valid
        assert (scan.status & 0x1).all()
        # encoder counts zeroed
        assert (scan.header(ColHeader.ENCODER_COUNT) == 0).all()
예제 #3
0
def test_scans_closed(meta: client.SensorInfo) -> None:
    """Check reading from closed scans raises an exception."""
    with closing(client.Sensor("os.invalid", 0, 0, metadata=meta)) as source:
        scans = client.Scans(source)
        scans.close()
        with pytest.raises(ValueError):
            next(iter(scans))
예제 #4
0
def test_scans_first_packet(packet: client.LidarPacket,
                            packets: client.PacketSource) -> None:
    """Check that data in the first packet survives batching to a scan."""
    scans = iter(client.Scans(packets))
    scan = next(scans)

    h = packet._pf.pixels_per_column
    w = packet._pf.columns_per_packet

    assert np.array_equal(packet.field(ChanField.RANGE),
                          scan.field(ChanField.RANGE)[:h, :w])

    assert np.array_equal(packet.field(ChanField.REFLECTIVITY),
                          scan.field(ChanField.REFLECTIVITY)[:h, :w])

    assert np.array_equal(packet.field(client.ChanField.SIGNAL),
                          scan.field(client.ChanField.SIGNAL)[:h, :w])

    assert np.array_equal(packet.field(client.ChanField.NEAR_IR),
                          scan.field(client.ChanField.NEAR_IR)[:h, :w])

    assert np.all(packet.header(ColHeader.FRAME_ID) == scan.frame_id)

    assert np.array_equal(packet.header(ColHeader.TIMESTAMP),
                          scan.header(ColHeader.TIMESTAMP)[:w])

    assert np.array_equal(packet.header(ColHeader.ENCODER_COUNT),
                          scan.header(ColHeader.ENCODER_COUNT)[:w])

    assert np.array_equal(packet.header(ColHeader.STATUS),
                          scan.header(ColHeader.STATUS)[:w])
예제 #5
0
def pcap_show_one_scan(source: client.PacketSource,
                       metadata: client.SensorInfo,
                       num: int = 0,
                       destagger: bool = True) -> None:
    """Plot all channels of one scan in 2D using matplotlib."""
    import matplotlib.pyplot as plt  # type: ignore

    scan = nth(client.Scans(source), num)
    if not scan:
        print(f"ERROR: Scan # {num} in not present in pcap file")
        exit(1)

    # [doc-stag-pcap-show-one]
    fig = plt.figure(constrained_layout=True)
    axs = fig.subplots(len(client.ChanField), 1, sharey=True)

    for ax, field in zip(axs, client.ChanField):
        img = normalize(scan.field(field))
        if destagger:
            img = client.destagger(metadata, img)

        ax.set_title(str(field), fontdict={'fontsize': 10})
        ax.imshow(img, cmap='gray', resample=False)
        ax.set_yticklabels([])
        ax.set_yticks([])
        ax.set_xticks([0, scan.w])
    plt.show()
예제 #6
0
def pcap_to_las(source: client.PacketSource,
                metadata: client.SensorInfo,
                num: int = 0,
                las_dir: str = ".",
                las_base: str = "las_out",
                las_ext: str = "las") -> None:
    "Write scans from a pcap to las files (one per lidar scan)."

    from itertools import islice
    import laspy  # type: ignore

    # precompute xyzlut to save computation in a loop
    xyzlut = client.XYZLut(metadata)

    # create an iterator of LidarScans from pcap and bound it if num is specified
    scans = iter(client.Scans(source))
    if num:
        scans = islice(scans, num)

    for idx, scan in enumerate(scans):

        xyz = xyzlut(scan)

        las = laspy.create()
        las.x = xyz[:, :, 0].flatten()
        las.y = xyz[:, :, 1].flatten()
        las.z = xyz[:, :, 2].flatten()

        las_path = os.path.join(las_dir, f'{las_base}_{idx:06d}.{las_ext}')
        print(f'write frame #{idx} to file: {las_path}')

        las.write(las_path)
예제 #7
0
def test_scans_meta(packets: client.PacketSource) -> None:
    """Sanity check metadata and column headers of a batched scan."""
    scans = iter(client.Scans(packets))
    scan = next(scans)

    assert scan.frame_id != -1
    assert scan.h == packets.metadata.format.pixels_per_column
    assert scan.w == packets.metadata.format.columns_per_frame
    assert len(scan.header(ColHeader.TIMESTAMP)) == scan.w
    assert len(scan.header(ColHeader.ENCODER_COUNT)) == scan.w
    assert len(scan.header(ColHeader.STATUS)) == scan.w

    assert not scan._complete(), "test data should have missing packet!"

    # check that the scan is missing exactly one packet's worth of columns
    valid_columns = list(scan.header(ColHeader.STATUS)).count(0xffffffff)
    assert valid_columns == (packets.metadata.format.columns_per_frame -
                             packets.metadata.format.columns_per_packet)

    missing_ts = list(scan.header(ColHeader.TIMESTAMP)).count(0)
    assert missing_ts == packets.metadata.format.columns_per_packet

    # extra zero encoder value for first column
    zero_enc = list(scan.header(ColHeader.ENCODER_COUNT)).count(0)
    assert zero_enc == packets.metadata.format.columns_per_packet + 1
예제 #8
0
def test_scans_simple(packets: client.PacketSource) -> None:
    """Test that the test data contains exactly one scan."""
    scans = iter(client.Scans(packets))
    assert next(scans) is not None

    with pytest.raises(StopIteration):
        next(scans)
예제 #9
0
def pcap_3d_one_scan(source: client.PacketSource,
                     metadata: client.SensorInfo,
                     num: int = 0) -> None:
    """Render one scan from a pcap file in the Open3D viewer.

    Args:
        source: PacketSource from pcap
        metadata: associated SensorInfo for PacketSource
        num: scan number in a given pcap file (satrs from *0*)
    """
    try:
        import open3d as o3d  # type: ignore
    except ModuleNotFoundError:
        print(
            "This example requires open3d, which may not be available on all "
            "platforms. Try running `pip3 install open3d` first.")
        exit(1)

    from more_itertools import nth
    # get single scan by index
    scan = nth(client.Scans(source), num)

    if not scan:
        print(f"ERROR: Scan # {num} in not present in pcap file")
        exit(1)

    # [doc-stag-open3d-one-scan]
    # compute point cloud using client.SensorInfo and client.LidarScan
    xyz = client.XYZLut(metadata)(scan)

    # create point cloud and coordinate axes geometries
    cloud = o3d.geometry.PointCloud(
        o3d.utility.Vector3dVector(xyz.reshape((-1, 3))))  # type: ignore
    axes = o3d.geometry.TriangleMesh.create_coordinate_frame(
        1.0)  # type: ignore

    # [doc-etag-open3d-one-scan]

    # initialize visualizer and rendering options
    vis = o3d.visualization.Visualizer()  # type: ignore

    vis.create_window()
    vis.add_geometry(cloud)
    vis.add_geometry(axes)
    ropt = vis.get_render_option()
    ropt.point_size = 1.0
    ropt.background_color = np.asarray([0, 0, 0])

    # initialize camera settings
    ctr = vis.get_view_control()
    ctr.set_zoom(0.1)
    ctr.set_lookat([0, 0, 0])
    ctr.set_up([1, 0, 0])

    # run visualizer main loop
    print("Press Q or Excape to exit")
    vis.run()
    vis.destroy_window()
예제 #10
0
def test_scans_timeout(packets: client.PacketSource) -> None:
    """A zero timeout should deterministically throw.

    TODO: should it, though?
    """
    scans = iter(client.Scans(packets, timeout=0.0))

    with pytest.raises(client.ClientTimeout):
        next(scans)
예제 #11
0
def scan(stream_digest: digest.StreamDigest, meta: client.SensorInfo) -> client.LidarScan:
    bin_path = path.join(DATA_DIR, "os-992011000121_data.bin")

    with open(bin_path, 'rb') as b:
        source = digest.LidarBufStream(b, meta)
        scans = client.Scans(source)
        scan = next(iter(scans))

    return scan
예제 #12
0
def test_scans_one_field(packets: client.PacketSource) -> None:
    """Test batching scans with a single field."""
    fields = {ChanField.FLAGS: np.uint8}
    scan = next(iter(client.Scans(packets, fields=fields)))

    assert set(scan.fields) == {ChanField.FLAGS}
    assert scan.field(ChanField.FLAGS).dtype == np.uint8

    with pytest.raises(ValueError):
        scan.field(ChanField.RANGE)
예제 #13
0
def test_scans_complete(packets: client.PacketSource) -> None:
    """Test built-in filtering for complete scans.

    The test dataset only contains a single incomplete frame. Check that
    specifying ``complete=True`` discards it.
    """
    scans = iter(client.Scans(packets, complete=True))

    with pytest.raises(StopIteration):
        next(scans)
예제 #14
0
def main() -> None:
    descr = """Visualize pcap or sensor data using simple viz bindings."""

    epilog = """When reading data from a sensor, this will autoconfigure the udp
        destination unless -x is used."""

    parser = argparse.ArgumentParser(description=descr, epilog=epilog)

    required = parser.add_argument_group('one of the following is required')
    group = required.add_mutually_exclusive_group(required=True)
    group.add_argument('--sensor', metavar='HOST', help='sensor hostname')
    group.add_argument('--pcap', metavar='PATH', help='path to pcap file')
    parser.add_argument('--meta', metavar='PATH', help='path to metadata json')
    parser.add_argument('--lidar-port', type=int, help='lidar port for sensor')
    parser.add_argument('-x',
                        '--no-auto-dest',
                        action='store_true',
                        help='do not auto configure udp destination')

    args = parser.parse_args()

    if args.sensor:
        hostname = args.sensor
        if args.lidar_port or (not args.no_auto_dest):
            config = client.SensorConfig()
            if args.lidar_port:
                config.udp_port_lidar = args.lidar_port
            print("Configuring sensor...")
            client.set_config(hostname,
                              config,
                              udp_dest_auto=(not args.no_auto_dest))
        config = client.get_config(hostname)

        print("Initializing...")
        scans = client.Scans.stream(hostname,
                                    config.udp_port_lidar or 7502,
                                    complete=False)
        rate = None

    elif args.pcap:
        import ouster.pcap as pcap

        if args.meta:
            metadata_path = args.meta
        else:
            print("Deducing metadata based on pcap name. "
                  "To provide a different metadata path, use --meta")
            metadata_path = os.path.splitext(args.pcap)[0] + ".json"

        with open(metadata_path) as json:
            info = client.SensorInfo(json.read())
        scans = client.Scans(pcap.Pcap(args.pcap, info))
        rate = 1.0

    SimpleViz(scans.metadata, rate).run(scans)
예제 #15
0
def test_scans_complete(packets: client.PacketSource) -> None:
    """Test built-in filtering for complete scans."""

    # make new packet source missing a packet
    ps = list(packets)
    del ps[5]
    dropped = client.Packets(ps, packets.metadata)

    scans = iter(client.Scans(dropped, complete=True))

    with pytest.raises(StopIteration):
        next(scans)
예제 #16
0
def pcap_3d_one_scan(source: client.PacketSource,
                     metadata: client.SensorInfo,
                     num: int = 0) -> None:
    """Render one scan from a pcap file in the Open3D viewer.

    Args:
        pcap_path: path to the pcap file
        metadata_path: path to the .json with metadata (aka :class:`.SensorInfo`)
        num: scan number in a given pcap file (satrs from *0*)
    """
    import open3d as o3d

    # get single scan by index
    scan = nth(client.Scans(source), num)

    if not scan:
        print(f"ERROR: Scan # {num} in not present in pcap file")
        exit(1)

    # [doc-stag-open3d-one-scan]
    # compute point cloud using client.SensorInfo and client.LidarScan
    xyz = client.XYZLut(metadata)(scan)

    # create point cloud and coordinate axes geometries
    cloud = o3d.geometry.PointCloud(
        o3d.utility.Vector3dVector(xyz.reshape((-1, 3))))
    axes = o3d.geometry.TriangleMesh.create_coordinate_frame(1.0)
    # [doc-etag-open3d-one-scan]

    # initialize visualizer and rendering options
    vis = o3d.visualization.Visualizer()
    vis.create_window()
    vis.add_geometry(cloud)
    vis.add_geometry(axes)
    ropt = vis.get_render_option()
    ropt.point_size = 1.0
    ropt.background_color = np.asarray([0, 0, 0])

    # initialize camera settings
    ctr = vis.get_view_control()
    ctr.set_zoom(0.1)
    ctr.set_lookat([0, 0, 0])
    ctr.set_up([1, 0, 0])

    # run visualizer main loop
    print("Press Q or Excape to exit")
    vis.run()
    vis.destroy_window()
예제 #17
0
def pcap_show_one_scan(pcap_path: str,
                       metadata_path: str,
                       num: int = 0,
                       destagger: bool = True) -> None:
    """Show all 4 channels of one scan (*num*) form pcap file (*pcap_path*)

    Args:
        pcap_path: path to the pcap file
        metadata_path: path to the .json with metadata (aka :class:`.SensorInfo`)
        num: scan number in a given pcap file (satrs from *0*)
    """
    import matplotlib.pyplot as plt  # type: ignore

    # [doc-stag-pcap-show-one]
    metadata = read_metadata(metadata_path)

    def prepare_field_image(scan, key, metadata, destagger=True):
        f = ae(scan.field(key))
        if destagger:
            return client.destagger(metadata, f)
        return f

    show_fields = [('range', client.ChanField.RANGE),
                   ('signal', client.ChanField.SIGNAL),
                   ('near_ir', client.ChanField.NEAR_IR),
                   ('reflectivity', client.ChanField.REFLECTIVITY)]

    with closing(pcap.Pcap(pcap_path, metadata)) as source:
        scan = nth(client.Scans(source), num)
        if not scan:
            return

        fields_images = [(sf[0],
                          prepare_field_image(scan, sf[1], source.metadata))
                         for sf in show_fields]

        fig = plt.figure(constrained_layout=True)

        axs = fig.subplots(len(fields_images), 1, sharey=True)

        for ax, field in zip(axs, fields_images):
            ax.set_title(field[0], fontdict={'fontsize': 10})
            ax.imshow(field[1], cmap='gray', resample=False)
            ax.set_yticklabels([])
            ax.set_yticks([])
            ax.set_xticks([0, scan.w])
        plt.show()
예제 #18
0
def pcap_2d_viewer(source: client.PacketSource,
                   metadata: client.SensorInfo,
                   num: int = 0) -> None:
    """Visualize channel fields in 2D using opencv."""
    import cv2  # type: ignore

    # [doc-stag-pcap-display-live]
    print("press ESC from visualization to exit")

    quit = False
    paused = False
    destagger = True
    num = 0
    for scan in client.Scans(source):
        print("frame id: {}, num = {}".format(scan.frame_id, num))

        fields = [scan.field(ch) for ch in client.ChanField]
        if destagger:
            fields = [client.destagger(metadata, f) for f in fields]

        combined_images = np.vstack(
            [np.pad(normalize(f), 2, constant_values=1.0) for f in fields])

        cv2.imshow("4 channels: ", combined_images)

        # handle keys presses
        while True:
            key = cv2.waitKey(1) & 0xFF
            # 100 is d
            if key == 100:
                destagger = not destagger
            # 32 is SPACE
            if key == 32:
                paused = not paused
            # 27 is ESC
            elif key == 27:
                quit = True

            if not paused:
                break
            time.sleep(0.1)

        if quit:
            break
        num += 1

    cv2.destroyAllWindows()
예제 #19
0
def main() -> None:
    import argparse
    import os
    import ouster.pcap as pcap

    descr = """Example visualizer using the open3d library.

    Visualize either pcap data (specified using --pcap) or a running sensor
    (specified using --sensor). If no metadata file is specified, this will look
    for a file with the same name as the pcap with the '.json' extension, or
    query it directly from the sensor.

    Visualizing a running sensor requires the sensor to be configured and
    sending lidar data to the default UDP port (7502) on the host machine.
    """

    parser = argparse.ArgumentParser(description=descr)
    parser.add_argument('--pause', action='store_true', help='start paused')
    parser.add_argument('--start', type=int, help='skip to frame number')
    parser.add_argument('--meta', metavar='PATH', help='path to metadata json')

    required = parser.add_argument_group('one of the following is required')
    group = required.add_mutually_exclusive_group(required=True)
    group.add_argument('--sensor', metavar='HOST', help='sensor hostname')
    group.add_argument('--pcap', metavar='PATH', help='path to pcap file')

    args = parser.parse_args()

    if args.sensor:
        scans = client.Scans.stream(args.sensor, metadata=args.meta)
    elif args.pcap:
        pcap_path = args.pcap
        metadata_path = args.meta or os.path.splitext(pcap_path)[0] + ".json"

        with open(metadata_path, 'r') as f:
            metadata = client.SensorInfo(f.read())

        source = pcap.Pcap(pcap_path, metadata)
        scans = client.Scans(source)
        consume(scans, args.start or 0)

    try:
        viewer_3d(scans, paused=args.pause)
    except (KeyboardInterrupt, StopIteration):
        pass
    finally:
        scans.close()
예제 #20
0
def pcap_query_scan(source: client.PacketSource,
                    metadata: client.SensorInfo,
                    num: int = 0) -> None:
    """
    Example: Query available fields in LidarScan

    Args:
        source: PacketSource from pcap
        metadata: associated SensorInfo for PacketSource
        num: scan number in a given pcap file (satrs from *0*)
    """
    scans = iter(client.Scans(source))

    # [doc-stag-pcap-query-scan]
    scan = next(scans)
    print("Available fields and corresponding dtype in LidarScan")
    for field in scan.fields:
        print('{0:15} {1}'.format(str(field), scan.field(field).dtype))
예제 #21
0
def test_scans_raw(packets: client.PacketSource) -> None:
    """Smoke test reading raw channel field data."""
    fields = {
        ChanField.RAW32_WORD1: np.uint32,
        ChanField.RAW32_WORD2: np.uint32,
        ChanField.RAW32_WORD3: np.uint32
    }

    scans = client.Scans(packets, fields=fields)

    ls = list(scans)
    assert len(ls) == 1
    assert set(ls[0].fields) == {
        ChanField.RAW32_WORD1, ChanField.RAW32_WORD2, ChanField.RAW32_WORD3
    }

    # just check that raw fields are populated?
    for f in ls[0].fields:
        assert np.count_nonzero(ls[0].field(f)) != 0
예제 #22
0
def pcap_display_xyz_points(pcap_path: str,
                            metadata_path: str,
                            num: int = 0) -> None:
    """Display range from a specified scan number (*num*) as 3D points from
    pcap file located at *pcap_path*

    Args:
        pcap_path: path to the pcap file
        metadata_path: path to the .json with metadata (aka :class:`.SensorInfo`)
        num: scan number in a given pcap file (satrs from *0*)
    """
    import matplotlib.pyplot as plt  # type: ignore

    # [doc-stag-pcap-plot-xyz-points]
    metadata = read_metadata(metadata_path)
    source = pcap.Pcap(pcap_path, metadata)

    # get single scan
    scans = client.Scans(source)
    scan = nth(scans, num)
    if not scan:
        print(f'ERROR: Scan # {num} in not present in pcap file: {pcap_path}')
        return

    # set up figure
    plt.figure()
    ax = plt.axes(projection='3d')
    r = 6
    ax.set_xlim3d([-r, r])
    ax.set_ylim3d([-r, r])
    ax.set_zlim3d([-r, r])

    plt.title("3D Points XYZ for scan")

    # transform data to 3d points and graph
    xyzlut = client.XYZLut(metadata)
    xyz = xyzlut(scan)

    key = scan.field(client.ChanField.SIGNAL)

    [x, y, z] = [c.flatten() for c in np.dsplit(xyz, 3)]
    ax.scatter(x, y, z, c=ae(key.flatten()), s=0.2)
    plt.show()
예제 #23
0
def test_scans_dual(packets: client.PacketSource) -> None:
    """Test scans from dual returns data."""
    scans = client.Scans(packets, complete=True)

    assert (scans.metadata.format.udp_profile_lidar ==
            client.UDPProfileLidar.PROFILE_LIDAR_RNG19_RFL8_SIG16_NIR16_DUAL)

    ls = list(scans)

    assert len(ls) == 1
    assert set(ls[0].fields) == {
        ChanField.RANGE,
        ChanField.RANGE2,
        ChanField.REFLECTIVITY,
        ChanField.REFLECTIVITY2,
        ChanField.SIGNAL,
        ChanField.SIGNAL2,
        ChanField.NEAR_IR,
    }
예제 #24
0
def pcap_to_pcd(source: client.PacketSource,
                metadata: client.SensorInfo,
                num: int = 0,
                pcd_dir: str = ".",
                pcd_base: str = "pcd_out",
                pcd_ext: str = "pcd") -> None:
    "Write scans from a pcap to pcd files (one per lidar scan)."

    from itertools import islice
    try:
        import open3d as o3d  # type: ignore
    except ModuleNotFoundError:
        print(
            "This example requires open3d, which may not be available on all "
            "platforms. Try running `pip3 install open3d` first.")
        exit(1)

    if not os.path.exists(pcd_dir):
        os.makedirs(pcd_dir)

    # precompute xyzlut to save computation in a loop
    xyzlut = client.XYZLut(metadata)

    # create an iterator of LidarScans from pcap and bound it if num is specified
    scans = iter(client.Scans(source))
    if num:
        scans = islice(scans, num)

    for idx, scan in enumerate(scans):

        xyz = xyzlut(scan)

        pcd = o3d.geometry.PointCloud()  # type: ignore

        pcd.points = o3d.utility.Vector3dVector(xyz.reshape(-1,
                                                            3))  # type: ignore

        pcd_path = os.path.join(pcd_dir, f'{pcd_base}_{idx:06d}.{pcd_ext}')
        print(f'write frame #{idx} to file: {pcd_path}')

        o3d.io.write_point_cloud(pcd_path, pcd)  # type: ignore
예제 #25
0
def main():
    """PointViz visualizer examples."""

    parser = argparse.ArgumentParser(
        description=main.__doc__,
        formatter_class=argparse.RawTextHelpFormatter)

    parser.add_argument('pcap_path',
                        nargs='?',
                        metavar='PCAP',
                        help='path to pcap file')
    parser.add_argument('meta_path',
                        nargs='?',
                        metavar='METADATA',
                        help='path to metadata json')

    args = parser.parse_args()

    pcap_path = os.getenv("SAMPLE_DATA_PCAP_PATH", args.pcap_path)
    meta_path = os.getenv("SAMPLE_DATA_JSON_PATH", args.meta_path)

    if not pcap_path or not meta_path:
        print("ERROR: Please add SAMPLE_DATA_PCAP_PATH and SAMPLE_DATA_JSON_PATH to" +
            " environment variables or pass <pcap_path> and <meta_path>")
        sys.exit()

    print(f"Using:\n\tjson: {meta_path}\n\tpcap: {pcap_path}")

    # Getting data sources
    meta = client.SensorInfo(open(meta_path).read())
    packets = pcap.Pcap(pcap_path, meta)
    scans = iter(client.Scans(packets))

    # ==============================
    print("Ex 0: Empty Point Viz")

    # [doc-stag-empty-pointviz]
    # Creating a point viz instance
    point_viz = viz.PointViz("Example Viz")
    viz.add_default_controls(point_viz)

    # ... add objects here

    # update internal objects buffers and run visualizer
    point_viz.update()
    point_viz.run()
    # [doc-etag-empty-pointviz]


    # =========================================================================
    print("Ex 1.0:\tImages and Labels: the Image object and 2D Image set_position() - height-normalized screen coordinates")

    label_top = viz.Label("[0, 1]", 0.5, 0.0, align_top=True)
    label_top.set_scale(2)
    point_viz.add(label_top)

    label_bot = viz.Label("[0, -1]", 0.5, 1, align_top=False)
    label_bot.set_scale(2)
    point_viz.add(label_bot)

    # [doc-stag-image-pos-center]
    img = viz.Image()
    img.set_image(np.full((10, 10), 0.5))
    img.set_position(-0.5, 0.5, -0.5, 0.5)
    point_viz.add(img)
    # [doc-etag-image-pos-center]

    # visualize
    point_viz.update()
    point_viz.run()

    # =========================================================================
    print("Ex 1.1:\tImages and Labels: Window-aligned images with 2D Image set_hshift() - width-normalized [-1, 1] horizontal shift")

    # [doc-stag-image-pos-left]
    # move img to the left
    img.set_position(0, 1, -0.5, 0.5)
    img.set_hshift(-1)
    # [doc-etag-image-pos-left]

    # visualize
    point_viz.update()
    point_viz.run()

    # [doc-stag-image-pos-right]
    # move img to the right
    img.set_position(-1, 0, -0.5, 0.5)
    img.set_hshift(1)
    # [doc-etag-image-pos-right]

    # visualize
    point_viz.update()
    point_viz.run()

    # [doc-stag-image-pos-right-bottom]
    # move img to the right bottom
    img.set_position(-1, 0, -1, 0)
    img.set_hshift(1)
    # [doc-etag-image-pos-right-bottom]

    # visualize
    point_viz.update()
    point_viz.run()


    # remove_objs(point_viz, [label_top, label_mid, label_bot, img])
    remove_objs(point_viz, [label_top, label_bot, img])

    # =======================================
    print("Ex 1.2:\tImages and Labels: Lidar Scan Fields as Images")

    # [doc-stag-scan-fields-images]
    scan = next(scans)

    img_aspect = (meta.beam_altitude_angles[0] -
                  meta.beam_altitude_angles[-1]) / 360.0
    img_screen_height = 0.4 # [0..2]
    img_screen_len = img_screen_height / img_aspect

    # prepare field data
    ranges = scan.field(client.ChanField.RANGE)
    ranges = client.destagger(meta, ranges)
    ranges = np.divide(ranges, np.amax(ranges), dtype=np.float32)

    signal = scan.field(client.ChanField.SIGNAL)
    signal = client.destagger(meta, signal)
    signal = np.divide(signal, np.amax(signal), dtype=np.float32)

    # creating Image viz elements
    range_img = viz.Image()
    range_img.set_image(ranges)
    # top center position
    range_img.set_position(-img_screen_len / 2, img_screen_len / 2,
                           1 - img_screen_height, 1)
    point_viz.add(range_img)

    signal_img = viz.Image()
    signal_img.set_image(signal)
    img_aspect = (meta.beam_altitude_angles[0] -
                meta.beam_altitude_angles[-1]) / 360.0
    img_screen_height = 0.4 # [0..2]
    img_screen_len = img_screen_height / img_aspect
    # bottom center position
    signal_img.set_position(-img_screen_len / 2, img_screen_len / 2, -1,
                            -1 + img_screen_height)
    point_viz.add(signal_img)
    # [doc-etag-scan-fields-images]

    # visualize
    point_viz.update()
    point_viz.run()

    print("Ex 1.3:\tImages and Labels: Adding labels")

    # [doc-stag-scan-fields-images-labels]
    range_label = viz.Label(str(client.ChanField.RANGE), 0.5, 0, align_top=True)
    range_label.set_scale(1)
    point_viz.add(range_label)

    signal_label = viz.Label(str(client.ChanField.SIGNAL),
                            0.5, 1 - img_screen_height / 2,
                            align_top=True)
    signal_label.set_scale(1)
    point_viz.add(signal_label)
    # [doc-etag-scan-fields-images-labels]

    # visualize
    point_viz.update()
    point_viz.run()

    # ===============================================================
    print("Ex 2.0:\tPoint Clouds: As Structured Points")

    # [doc-stag-scan-structured]
    cloud_scan = viz.Cloud(meta)
    cloud_scan.set_range(scan.field(client.ChanField.RANGE))
    cloud_scan.set_key(signal)
    point_viz.add(cloud_scan)
    # [doc-etag-scan-structured]

    # visualize
    point_viz.update()
    point_viz.run()

    remove_objs(point_viz, [cloud_scan])

    # ===============================================================
    print("Ex 2.1:\tPoint Clouds: As Unstructured Points")

    # [doc-stag-scan-unstructured]
    # transform scan data to 3d points
    xyzlut = client.XYZLut(meta)
    xyz = xyzlut(scan.field(client.ChanField.RANGE))

    cloud_xyz = viz.Cloud(xyz.shape[0] * xyz.shape[1])
    cloud_xyz.set_xyz(np.reshape(xyz, (-1, 3)))
    cloud_xyz.set_key(signal.ravel())
    point_viz.add(cloud_xyz)
    # [doc-etag-scan-unstructured]

    point_viz.camera.dolly(150)

    # visualize
    point_viz.update()
    point_viz.run()

    # =======================================================
    print("Ex 2.2:\tPoint Clouds: Custom Axes Helper as Unstructured Points")

    # [doc-stag-axes-helper]
    # basis vectors
    x_ = np.array([1, 0, 0]).reshape((-1, 1))
    y_ = np.array([0, 1, 0]).reshape((-1, 1))
    z_ = np.array([0, 0, 1]).reshape((-1, 1))

    axis_n = 100
    line = np.linspace(0, 1, axis_n).reshape((1, -1))

    # basis vector to point cloud
    axis_points = np.hstack((x_ @ line, y_ @ line, z_ @ line)).transpose()

    # colors for basis vectors
    axis_color_mask = np.vstack((
        np.full((axis_n, 4), [1, 0.1, 0.1, 1]),
        np.full((axis_n, 4), [0.1, 1, 0.1, 1]),
        np.full((axis_n, 4), [0.1, 0.1, 1, 1])))

    cloud_axis = viz.Cloud(axis_points.shape[0])
    cloud_axis.set_xyz(axis_points)
    cloud_axis.set_key(np.full(axis_points.shape[0], 0.5))
    cloud_axis.set_mask(axis_color_mask)
    cloud_axis.set_point_size(3)
    point_viz.add(cloud_axis)
    # [doc-etag-axes-helper]

    point_viz.camera.dolly(50)

    # visualize
    point_viz.update()
    point_viz.run()

    remove_objs(point_viz, [
        range_img, range_label, signal_img, signal_label, cloud_axis, cloud_xyz
    ])

    # ===============================================================
    print("Ex 2.3:\tPoint Clouds: the LidarScanViz class")

    # [doc-stag-lidar-scan-viz]
    # Creating LidarScan visualizer (3D point cloud + field images on top)
    ls_viz = viz.LidarScanViz(meta, point_viz)

    # adding scan to the lidar scan viz
    ls_viz.scan = scan

    # refresh viz data
    ls_viz.draw()

    # visualize
    # update() is not needed for LidatScanViz because it's doing it internally
    point_viz.run()
    # [doc-etag-lidar-scan-viz]

    # ===================================================
    print("Ex 3.0:\tAugmenting point clouds with 3D Labels")

    # [doc-stag-lidar-scan-viz-labels]
    # Adding 3D Labels
    label1 = viz.Label("Label1: [1, 2, 4]", 1, 2, 4)
    point_viz.add(label1)

    label2 = viz.Label("Label2: [2, 1, 4]", 2, 1, 4)
    label2.set_scale(2)
    point_viz.add(label2)

    label3 = viz.Label("Label3: [4, 2, 1]", 4, 2, 1)
    label3.set_scale(3)
    point_viz.add(label3)
    # [doc-etag-lidar-scan-viz-labels]

    point_viz.camera.dolly(-100)

    # visualize
    point_viz.update()
    point_viz.run()


    # ===============================================
    print("Ex 4.0:\tOverlay 2D Images and 2D Labels")

    # [doc-stag-overlay-images-labels]
    # Adding image 1 with aspect ratio preserved
    img = viz.Image()
    img_data = make_checker_board(10, (2, 4))
    mask_data = np.zeros((30, 30, 4))
    mask_data[:15, :15] = np.array([1, 0, 0, 1])
    img.set_mask(mask_data)
    img.set_image(img_data)
    ypos = (0, 0.5)
    xlen = (ypos[1] - ypos[0]) * img_data.shape[1] / img_data.shape[0]
    xpos = (0, xlen)
    img.set_position(*xpos, *ypos)
    img.set_hshift(-0.5)
    point_viz.add(img)

    # Adding Label for image 1: positioned at bottom left corner
    img_label = viz.Label("ARRrrr!", 0.25, 0.5)
    img_label.set_rgba((1.0, 1.0, 0.0, 1))
    img_label.set_scale(2)
    point_viz.add(img_label)

    # Adding image 2: positioned to the right of the window
    img2 = viz.Image()
    img_data2 = make_checker_board(10, (4, 2))
    mask_data2 = np.zeros((30, 30, 4))
    mask_data2[15:25, 15:25] = np.array([0, 1, 0, 0.5])
    img2.set_mask(mask_data2)
    img2.set_image(img_data2)
    ypos2 = (0, 0.5)
    xlen2 = (ypos2[1] - ypos2[0]) * img_data2.shape[1] / img_data2.shape[0]
    xpos2 = (-xlen2, 0)
    img2.set_position(*xpos2, *ypos2)
    img2.set_hshift(1.0)
    point_viz.add(img2)

    # Adding Label for image 2: positioned at top left corner
    img_label2 = viz.Label("Second", 1.0, 0.25, align_top=True, align_right=True)
    img_label2.set_rgba((0.0, 1.0, 1.0, 1))
    img_label2.set_scale(1)
    point_viz.add(img_label2)
    # [doc-etag-overlay-images-labels]

    # visualize
    point_viz.update()
    point_viz.run()


    # ===============================================================
    print("Ex 5.0:\tAdding key handlers: 'R' for random camera dolly")

    # [doc-stag-key-handlers]
    def handle_dolly_random(ctx, key, mods) -> bool:
        if key == 82:  # key R
            dolly_num = random.randrange(-15, 15)
            print(f"Random Dolly: {dolly_num}")
            point_viz.camera.dolly(dolly_num)
            point_viz.update()
        return True

    point_viz.push_key_handler(handle_dolly_random)
    # [doc-etag-key-handlers]

    # visualize
    point_viz.update()
    point_viz.run()
예제 #26
0
def scan(meta) -> client.LidarScan:
    bin_path = path.join(DATA_DIR, "os-992011000121_data.bin")
    with open(bin_path, 'rb') as b:
        source = digest.LidarBufStream(b, meta)
        scans = client.Scans(source)
        return next(iter(scans))
예제 #27
0
def pcap_to_csv(source: client.PacketSource,
                metadata: client.SensorInfo,
                num: int = 0,
                csv_dir: str = ".",
                csv_base: str = "pcap_out",
                csv_ext: str = "csv") -> None:
    """Write scans from a pcap to csv files (one per lidar scan).

    The number of saved lines per csv file is always H x W, which corresponds to
    a full 2D image representation of a lidar scan.

    Each line in a csv file is (for LEGACY profile):

        TIMESTAMP, RANGE (mm), SIGNAL, NEAR_IR, REFLECTIVITY, X (mm), Y (mm), Z (mm)

    If ``csv_ext`` ends in ``.gz``, the file is automatically saved in
    compressed gzip format. :func:`.numpy.loadtxt` can be used to read gzipped
    files transparently back to :class:`.numpy.ndarray`.

    Args:
        source: PacketSource from pcap
        metadata: associated SensorInfo for PacketSource
        num: number of scans to save from pcap to csv files
        csv_dir: path to the directory where csv files will be saved
        csv_base: string to use as the base of the filename for pcap output
        csv_ext: file extension to use, "csv" by default
    """

    # ensure that base csv_dir exists
    if not os.path.exists(csv_dir):
        os.makedirs(csv_dir)

    # construct csv header and data format
    def get_fields_info(scan: client.LidarScan) -> Tuple[str, List[str]]:
        field_names = 'TIMESTAMP (ns)'
        field_fmts = ['%d']
        for chan_field in scan.fields:
            field_names += f', {chan_field}'
            if chan_field in [client.ChanField.RANGE, client.ChanField.RANGE2]:
                field_names += ' (mm)'
            field_fmts.append('%d')
        field_names += ', X (mm), Y (mm), Z (mm)'
        field_fmts.extend(3 * ['%d'])
        return field_names, field_fmts

    field_names: str = ''
    field_fmts: List[str] = []

    # [doc-stag-pcap-to-csv]
    from itertools import islice
    # precompute xyzlut to save computation in a loop
    xyzlut = client.XYZLut(metadata)

    # create an iterator of LidarScans from pcap and bound it if num is specified
    scans = iter(client.Scans(source))
    if num:
        scans = islice(scans, num)

    for idx, scan in enumerate(scans):

        # initialize the field names for csv header
        if not field_names or not field_fmts:
            field_names, field_fmts = get_fields_info(scan)

        # copy per-column timestamps for each channel
        timestamps = np.tile(scan.timestamp, (scan.h, 1))

        # grab channel data
        fields_values = [scan.field(ch) for ch in scan.fields]

        # use integer mm to avoid loss of precision casting timestamps
        xyz = (xyzlut(scan) * 1000).astype(np.int64)

        # get all data as one H x W x 8 int64 array for savetxt()
        frame = np.dstack((timestamps, *fields_values, xyz))

        # not necessary, but output points in "image" vs. staggered order
        frame = client.destagger(metadata, frame)

        # write csv out to file
        csv_path = os.path.join(csv_dir, f'{csv_base}_{idx:06d}.{csv_ext}')
        print(f'write frame #{idx}, to file: {csv_path}')

        header = '\n'.join([f'frame num: {idx}', field_names])

        np.savetxt(csv_path,
                   frame.reshape(-1, frame.shape[2]),
                   fmt=field_fmts,
                   delimiter=',',
                   header=header)
예제 #28
0
- a proxy run()/quit() on ls_viz would be useful
- maybe: ls_viz could initialize underlying viz + expose it
- point_viz.run() twice is broken
- ideally, run() would open/close window
- auto camera movement example?
"""

from ouster import client, pcap
from ouster.sdk import viz

meta_path = "/mnt/aux/test_drives/OS1_128_2048x10.json"
pcap_path = "/mnt/aux/test_drives/OS1_128_2048x10.pcap"

meta = client.SensorInfo(open(meta_path).read())
packets = pcap.Pcap(pcap_path, meta)
scans = iter(client.Scans(packets))

point_viz = viz.PointViz("Example Viz")
ls_viz = viz.LidarScanViz(meta, point_viz)

ls_viz.scan = next(scans)
ls_viz.draw()
print("Showing first frame, close visuzlier window to continue")
point_viz.run()

ls_viz.scan = next(scans)
ls_viz.draw()
print("Showing second frame, close visuzlier window to continue")
point_viz.run()

# won't work on macos, but convenient:
예제 #29
0
def test_scans_multi(lidar_stream: client.PacketSource) -> None:
    """Test batching multiple scans."""
    scans = take(10, client.Scans(lidar_stream))

    assert list(map(lambda s: s.frame_id, scans)) == list(range(10))
예제 #30
0
def pcap_to_csv(pcap_path: str,
                metadata_path: str,
                num: int = 0,
                csv_dir: str = ".",
                csv_prefix: str = "pcap_out",
                csv_ext: str = "csv") -> None:
    """Write scans from pcap file (*pcap_path*) to plain csv files (one per
    lidar scan).

    If the *csv_ext* ends in ``.gz``, the file is automatically saved in
    compressed gzip format. :func:`.numpy.loadtxt` can be used to read gzipped
    files transparently back to :class:`.numpy.ndarray`.

    Number of saved lines per csv file is always [H x W], which corresponds
    to a full 2D image representation of a lidar scan.

    Each line in a csv file is:

        RANGE (mm), SIGNAL, NEAR_IR, REFLECTIVITY, X (m), Y (m), Z (m)

    Args:
        pcap_path: path to the pcap file
        metadata_path: path to the .json with metadata (aka :class:`.SensorInfo`)
        num: number of scans to save from pcap to csv files
        csv_dir: path to the directory where csv files will be saved
        csv_prefix: the filename prefix that will be appended with frame number
                    and *csv_ext*
        csv_ext: file extension to use. If it ends with ``.gz`` the output is
                 gzip compressed

    """
    from itertools import islice

    # ensure that base csv_dir exists
    if not os.path.exists(csv_dir):
        os.makedirs(csv_dir)

    metadata = read_metadata(metadata_path)
    source = pcap.Pcap(pcap_path, metadata)

    # [doc-stag-pcap-to-csv]
    field_names = 'RANGE (mm), SIGNAL, NEAR_IR, REFLECTIVITY, X (m), Y (m), Z (m)'
    field_fmts = ['%d', '%d', '%d', '%d', '%.8f', '%.8f', '%.8f']

    channels = [
        client.ChanField.RANGE, client.ChanField.SIGNAL,
        client.ChanField.NEAR_IR, client.ChanField.REFLECTIVITY
    ]

    with closing(pcap.Pcap(pcap_path, metadata)) as source:

        # precompute xyzlut to save computation in a loop
        xyzlut = client.XYZLut(metadata)

        # create an iterator of LidarScans from pcap and bound it if num is specified
        scans = iter(client.Scans(source))
        if num:
            scans = islice(scans, num)

        for idx, scan in enumerate(scans):

            fields_values = [scan.field(ch) for ch in channels]
            xyz = xyzlut(scan)

            # get lidar data as one frame of [H x W x 7], "fat" 2D image
            frame = np.dstack((*fields_values, xyz))
            frame = client.destagger(metadata, frame)

            csv_path = os.path.join(csv_dir,
                                    f'{csv_prefix}_{idx:06d}.{csv_ext}')

            header = '\n'.join([
                f'pcap file: {pcap_path}', f'frame num: {idx}',
                f'metadata file: {metadata_path}', field_names
            ])

            print(f'write frame #{idx}, to file: {csv_path}')

            np.savetxt(csv_path,
                       np.reshape(frame, (-1, frame.shape[2])),
                       fmt=field_fmts,
                       delimiter=',',
                       header=header)