Beispiel #1
0
def test_voxel_transform():
    """
    convert integer addresses to point coordinates
    """

    edge_length = 1
    boundary_points = np.asarray([[0, 0, 0], [100, 100, 100]])
    vf = geometry.VoxelFilter(boundary_points, edge_length)

    # we'll use the same address we used before
    known_address = 198026
    known_coordinates = np.arange(3) + 10

    assert np.allclose(
        known_coordinates,
        vf.address_to_coordinate(known_address).flatten()),\
        "failed to recover correct coordinates in 3D"

    # and a quick 2d test
    vf = geometry.VoxelFilter(boundary_points[:, :2], edge_length)
    assert np.allclose(
        known_coordinates[:2],
        vf.address_to_coordinate(
            vf.coordinate_to_address(known_coordinates[:2]).flatten())),\
        "failed to recover correct coordinates in 2D"
Beispiel #2
0
def test_voxel_shift():
    """
    calculate the number of bits each address has to be shifted over to fit in the address integer
    """
    dims = [2, 3]

    def get_widths(point_cloud, edge_length):
        """
        compute the widths
        """
        minimum_corner = point_cloud.min(0) - edge_length / 2
        maximum_corner = point_cloud.max(0) + edge_length / 2
        span = maximum_corner - minimum_corner
        return np.ceil(np.log2(span / edge_length))

    for dim in dims:
        good_edge_length = 0.001
        points = np.asarray([[0, 0, 0], [100, 100, 100]])[:, :dim]

        # first let's compute what it should be (17 bits each)
        shifts = [17, 34][:dim - 1]
        vf = geometry.VoxelFilter(points, good_edge_length)
        assert np.array_equal(shifts, vf.shifts),\
            "computed incorrect shifts for {}d cloud".format(dim)
        widths = [17, 17, 17][:dim]
        assert np.array_equal(
            widths,
            vf.widths), "computed incorrect widths for {}d cloud".format(dim)

        # now, what about a voxel edge length that's incompatible with the point cloud shape?
        # we should detect that and raise a ValueError.
        # well, first we'll need to figure out how small we have to go to make it impossible.
        if dim == 3:
            # this should be too small in 3d. in a test in the repl it yielded 72 address bits.
            bad_edge_length = 0.00001
        else:
            # and 68 here for a 2d cloud
            bad_edge_length = 0.00000001

        # but let's check anyway.
        address_length = sum(get_widths(points, bad_edge_length))
        assert address_length > 64,\
            "our test edge length is not short enough to overflow at dim {}. try div by 10.".\
                format(dim)

        try:
            vf = geometry.VoxelFilter(points, bad_edge_length)
        except ValueError:
            pass
        else:
            raise AssertionError\
            ("built a VoxelFilter for a space we should not be able to voxelize at dim {}".\
                format(dim))
Beispiel #3
0
def test_voxel_address():
    """
    convert point coordinates to integer addresses
    """
    edge_length = 1
    boundary_points = np.asarray([[0, 0, 0], [100, 100, 100]])
    vf = geometry.VoxelFilter(boundary_points, edge_length)

    test_point = np.arange(3) + 10
    # first get the minimum corner.
    minimum_corner = vf.minimum_corner
    # now convert the test point to grid coordinates
    grid_point = np.floor(
        (test_point - minimum_corner) / edge_length).astype(np.int64)
    assert np.array_equal(grid_point,
                          [10, 11, 12]), "got wrong grid coordinate"
    # now the bit shifts
    shifts = vf.shifts
    # this operation is going to look like x ^ (y << y_shift) ^ (z << z_shift)
    address = grid_point[0] ^ (grid_point[1] << shifts[0]) ^ (
        grid_point[2] << shifts[1])
    # if each axes' domain in the bit string is properly separated, we can actually just add the
    # grid coordinates together.
    x = 10
    y = 11 << 7
    z = 12 << 14
    known_address = 198026
    assert known_address == x + y + z
    assert known_address == x ^ y ^ z
    assert address == known_address, "test logic computed wrong address"
    assert address == vf.coordinate_to_address(
        test_point), "VoxelFilter computed wrong address"
Beispiel #4
0
def test_in_bounds():
    """
    assert that boundary testing works
    """
    for dim in [2, 3]:
        boundary_points = np.asarray([[0, 0, 0], [100, 100, 100]])[:, :dim]
        edge_length = 1
        vf = geometry.VoxelFilter(boundary_points, edge_length)

        def check_in_bounds(point):
            """
            try/ catch around the in bounds method. returns True if it works.
            """
            try:
                vf._check_in_bounds(point)
            except ValueError as err:
                # print(err)
                return False
            else:
                return True

        assert check_in_bounds(np.zeros((1, dim)) - 0.5), "should be in bounds"
        assert not check_in_bounds(np.zeros((1, dim)) -
                                   1.5), "should not be in bounds"
        assert check_in_bounds(np.zeros((1, dim)) + 0.5), "should be in bounds"
        assert check_in_bounds(np.zeros((1, dim)) +
                               100.5), "should be in bounds"
        assert not check_in_bounds(np.zeros((1, dim)) +
                                   101.5), "should not be in bounds"
        assert not check_in_bounds(np.zeros(
            (1, dim + 1))), "should not be in bounds"
        assert check_in_bounds(np.zeros(dim)), "should be in bounds"
        assert not check_in_bounds(np.zeros(dim + 1)), "should be in bounds"
Beispiel #5
0
def test_masks():
    """
    test that masks used to decalate addresses into grid coordinates are computed correctly
    """

    for dim in [2, 3]:
        boundary_points = np.asarray([[0, 0, 0], [100, 100, 100]])[:, :dim]
        edge_length = 1
        vf = geometry.VoxelFilter(boundary_points, edge_length)
        # each axis should have 7 bits
        masks = [0b1111111, 0b11111110000000, 0b111111100000000000000][:dim]
        assert np.array_equal(masks, vf.masks), "computed masks incorrectly"
Beispiel #6
0
def test_voxel_unique():
    """
    convert point coordinates to integer addresses, unique and convert back to point coordinates
    """

    for dim in [2, 3]:
        boundary_points = np.asarray([[0, 0, 0], [100, 100, 100]])[:, :dim]
        edge_length = 1
        vf = geometry.VoxelFilter(boundary_points, edge_length)
        # we should have 10 points here
        test_points =\
            np.concatenate([np.zeros((1, dim)) + offset for offset in np.arange(0, 20, 2)])
        # 20, with 10 unique
        duplicated_test_points = np.vstack((test_points, test_points))
        unique_voxels = vf.unique_voxels(duplicated_test_points)
        assert np.array_equal(test_points, unique_voxels),\
            "failed to get correct set of unique voxels at dimension {}".format(dim)
Beispiel #7
0
def one_scale_single_core(query_cloud,
                          search_cloud,
                          edge_length,
                          radius,
                          verbose=False):
    """
    generate a 4d feature vector representing one analysis scale.
    """

    # resample the search cloud
    voxel_filter = geometry.VoxelFilter(search_cloud, edge_length)
    search_voxels = voxel_filter.unique_voxels(search_cloud)

    if verbose:
        print("querying {} points against a search space of {} voxels".format(
            query_cloud.shape[0], search_voxels.shape[0]))
        print("using a voxel edge length of {} and radius of {}".format(
            edge_length, radius))
        print("-------------")

    # build a kdtree for it
    search_tree = cKDTree(search_voxels, leafsize=LEAFSIZE)

    accumulator = []

    # segment the query cloud
    # TODO: this loop is where i would use multiprocessing.pool to parallelize. 4-6 processes would
    # maybe yield a 2-3x speedup. system dependent, of course.
    for num, chunk in enumerate(generic.batcher(query_cloud,
                                                QUERY_CHUNK_SIZE)):

        if verbose and num % VERBOSITY_INTERVAL == 0:
            print("processing chunk {} of {}".format(
                num + 1,
                len(query_cloud) // QUERY_CHUNK_SIZE + 1))

        # build a tree
        chunk_tree = cKDTree(chunk, leafsize=LEAFSIZE)

        # query against the search space
        neighbor_idx = chunk_tree.query_ball_tree(search_tree, radius)

        # find all the neighbors of all the query points
        neighborhoods = [
            features.take(idx, search_voxels) for idx in neighbor_idx
        ]

        # compute the feature vectors
        population = np.array([features.population(this_neighborhood)\
                    for this_neighborhood in neighborhoods]).reshape(-1, 1)

        centroid = np.array([features.centroid(query_point, this_neighborhood)\
                    for query_point, this_neighborhood in zip(chunk, neighborhoods)]).reshape(-1, 1)

        pca = np.array([features.pca(this_neighborhood)\
                    for this_neighborhood in neighborhoods]).reshape(-1, 2)

        new_features = np.concatenate((population, centroid, pca), axis=1)
        accumulator.append(new_features)

    accumulator = np.concatenate(accumulator, axis=0)
    return accumulator
Beispiel #8
0
def test_voxel_init():
    """
    initialize the voxel filter
    """
    num = 1000
    scale = 100
    dims = [2, 3]
    edge_length = 0.5

    for dim in dims:
        # first let's do it with only one point, which makes no sense
        points = np.random.rand(1, dim) * scale
        try:
            vf = geometry.VoxelFilter(points, edge_length)
        except ValueError:
            pass
        else:
            raise AssertionError(
                "built a voxel filter with a single point at dim {}".format(
                    dim))

        # now let's do it for real
        points = np.random.rand(num, dim) * scale

        # the first voxel (all 0s address) should be centered on the *actual* minimum corner of
        # the cloud-- though there likely won't be a point there.
        minimum_corner = points.min(0) - edge_length / 2
        # the maximum corner here doesn't actually correspond to a voxel location. we just don't
        # want to be responsible for addressing points outside the rectangular prism bounding the
        # original point cloud on one side and not the other. that feels weird and arbitrary.
        maximum_corner = points.max(0) + edge_length / 2

        vf = geometry.VoxelFilter(points, edge_length)

        assert np.array_equal(vf.minimum_corner, minimum_corner),\
            "set wrong minimum corner with dim {}".format(dim)
        assert np.array_equal(vf.maximum_corner, maximum_corner),\
            "set wrong maximum corner with dim {}".format(dim)
        assert edge_length == vf.edge_length, "set wrong edge length with dim {}".format(
            dim)

    # now let's try doing it with too many dimensions
    dims = [1, 4]
    for dim in dims:
        points = np.random.rand(num, dim) * scale
        try:
            vf = geometry.VoxelFilter(points, edge_length)
        except ValueError:
            pass
        else:
            raise AssertionError(
                "built a VoxelFilter for a {}D space. that's bad.".format(dim))

    # and some arrays that cannot be  point clouds
    try:
        vf = geometry.VoxelFilter(np.random.rand(10), edge_length)
    except ValueError:
        pass
    else:
        raise AssertionError("built a VoxelFilter on a 1D array. that's bad.")

    try:
        vf = geometry.VoxelFilter(np.random.rand(10, 10, 10), edge_length)
    except ValueError:
        pass
    else:
        raise AssertionError("built a VoxelFilter on a 3D array. that's bad.")