Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python API #32

Merged
merged 9 commits into from
Sep 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- PR #7 Initial code
- PR #18 Python initial unit tests and bindings
- PR #32 Python API first pass

## Improvements

Expand Down
95 changes: 95 additions & 0 deletions python/cuspatial/cuspatial/core/gis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Copyright (c) 2019, NVIDIA CORPORATION.

from cudf import DataFrame
from cuspatial._lib.spatial import (
cpp_directed_hausdorff_distance,
cpp_haversine_distance,
cpp_lonlat2coord,
cpp_point_in_polygon_bitmap
)

def directed_hausdorff_distance(x, y, count):
""" Compute the directed Hausdorff distances between any groupings
of polygons.

params
x: x coordinates
y: y coordinates
count: size of each polygon

Parameters
----------
{params}

returns
DataFrame: 'min', 'max' columns of Hausdorff distances for each polygon
"""
return cpp_directed_hausdorff_distance(x, y, count)

def haversine_distance(p1_lat, p1_lon, p2_lat, p2_lon):
""" Compute the haversine distances between an arbitrary list of lat/lon
pairs

params
p1_lat: latitude of first set of coords
p1_lon: longitude of first set of coords
p2_lat: latitude of second set of coords
p2_lon: longitude of second set of coords

Parameters
----------
{params}

returns
Series: distance between all pairs of lat/lon coords
"""
return cpp_haversine_distance(p1_lat, p1_lon, p2_lat, p2_lon)

def lonlat_to_xy_km_coordinates(camera_lon, camera_lat, lon_coords, lat_coords):
""" Convert lonlat coordinates to km x,y coordinates based on some camera
origin.

params
camera_lon: float64 - longitude camera
camera_lat: float64 - latitude camera
lon_coords: Series of longitude coords to convert to x
lat_coords: Series of latitude coords to convert to y

Parameters
----------
{params}

returns
DataFrame: 'x', 'y' columns for new km positions of coords
"""
result = cpp_lonlat2coord(camera_lon, camera_lat, lon_coords, lat_coords)
return DataFrame({'x': result[0],
'y': result[1]
})

def point_in_polygon_bitmap(x_points, y_points,
polygon_ids, polygon_end_indices, polygons_x, polygons_y):
""" Compute from a set of points and a set of polygons which points fall
within which polygons.

params
x_points: x coordinates of points to test
y_points: y coordinates of points to test
polygon_ids: a unique integer id for each polygon
polygon_end_indices: the (n+1)th vertex of the final coordinate of each
polygon in the next parameters
polygons_x: x coordinates of all polygon points
polygons_y: y coordinates of all polygon points

Parameters
----------
{params}

returns
Series: one int32 for each point. This int32 is a binary bitmap specifying
true or false for each of 32 polygons.
"""
return cpp_point_in_polygon_bitmap(
x_points, y_points,
polygon_ids, polygon_end_indices, polygons_x, polygons_y
)
128 changes: 128 additions & 0 deletions python/cuspatial/cuspatial/core/trajectory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Copyright (c) 2019, NVIDIA CORPORATION.

import cudf
import warnings
from cuspatial._lib.trajectory import (
cpp_subset_trajectory_id,
cpp_trajectory_spatial_bounds,
cpp_derive_trajectories,
cpp_trajectory_distance_and_speed,
)

warnings.warn("Duplicates cuDF functionality", DeprecationWarning)
def subset_trajectory_id(trajectory_ids, in_x, in_y, point_ids, timestamps):
"""
Deprecated
"""
return cpp_subset_trajectory_id(
trajectory_ids,
in_x,
in_y,
point_ids,
timestamps
)

def spatial_bounds(
x_coords, y_coords, trajectory_size, trajectory_end_position):
""" Compute the bounding boxes of sets of trajectories.

Parameters
----------
{params}

Examples
--------
>>> result = trajectory.spatial_bounds(
>>> cudf.Series([0, 2, 1, 3, 2]),
>>> cudf.Series([0, 2, 1, 3, 2]),
>>> cudf.Series([2, 3]),
>>> cudf.Series([2, 5]),
>>> )
>>> print(result)
x1 y1 x2 y2
0 0.0 0.0 2.0 2.0
1 1.0 1.0 3.0 3.0
"""
return cpp_trajectory_spatial_bounds(
x_coords,
y_coords,
trajectory_size,
trajectory_end_position
)

def derive(x_coords, y_coords, object_ids, timestamps):
""" Derive trajectories from points, timestamps, and ids.
Parameters
----------
{params}

Returns
-------
result_tuple : tuple (number of discovered trajectories,
DataFrame
id, length, and positions of trajectories for feeding into
compute_distance_and_speed

Examples
--------
import cudf
num_trajectories, result = trajectory.derive(
cudf.Series([0, 1, 2, 3]),
cudf.Series([0, 0, 1, 1])
cudf.Series([0, 0, 1, 1])
cudf.Series([0, 10, 0, 10])
)
print(num_trajectories)
2
print(result)
trajectory_id length position
0 0 2 2
1 1 2 4)
"""
return cpp_derive_trajectories(
x_coords,
y_coords,
object_ids,
timestamps
)

def distance_and_speed(x_coords, y_coords, timestamps, length, position):
""" Compute the distance travelled and speed of sets of trajectories

Parameters
----------
{params}

Returns
-------
result : DataFrame
meters - travelled distance of trajectory
speed - speed in m/sec of trajectory

Examples
--------
Compute the distance and speed of the above derived trajectories
result = trajectory.distance_and_speed(x, y, timestamps,
result['length'],
result['position'])
print(result)
meters speed
trajectory_id
0 1000.0 100000.000000
1 1000.0 111111.109375
"""
result = cpp_trajectory_distance_and_speed(
x_coords,
y_coords,
timestamps,
length,
position
)
df = cudf.DataFrame({
'meters': result[0],
'speed': result[1]
})
df.index.name = 'trajectory_id'
return df


36 changes: 36 additions & 0 deletions python/cuspatial/cuspatial/io/soa.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Copyright (c) 2019, NVIDIA CORPORATION.

from cudf import Series, DataFrame
from cuspatial._lib.soa_readers import (
cpp_read_uint_soa,
cpp_read_ts_soa,
cpp_read_pnt_lonlat_soa,
cpp_read_pnt_xy_soa,
cpp_read_polygon_soa
)

def read_uint(filename):
return Series(cpp_read_uint_soa(filename))

def read_ts(filename):
return Series(cpp_read_ts_soa(filename))

def read_points_lonlat(filename):
result = cpp_read_pnt_lonlat_soa(filename)
return DataFrame({'lon': result[0],
'lat': result[1]
})

def read_points_xy_km(filename):
result = cpp_read_pnt_xy_soa(filename)
return DataFrame({'x': result[0],
'y': result[1]
})

def read_polygon(filename):
result = cpp_read_polygon_soa(filename)
return DataFrame({'f_pos': result[0],
'r_pos': result[1],
'x': result[2],
'y': result[3]
})
32 changes: 9 additions & 23 deletions python/cuspatial/cuspatial/tests/test_hausdorff_distance.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,15 @@
# Copyright (c) 2019, NVIDIA CORPORATION.

"""
A toy example to demonstrate how to convert python arrays into cuSpatial inputs,
invoke the GPU accelerated directed Hausdorff distance computing function in
cuSpatial, convert the results back to python array(s) again to be feed into
scipy clustering APIs.

For the toy example, by desgin, both AgglomerativeClustering and DBSCAN cluster
the 2nd and third trajectories into one cluster while leaving the first
trajectory as the second cluster.

To run the demo, first install scipy and scikit-learn
by "conda install -c conda-forge scipy scikit-learn" under cudf_dev environment
"""

import pytest
import numpy as np
import time
import cudf
from cudf.tests.utils import assert_eq
from cudf.core import column
import cuspatial.bindings.spatial as gis
from cuspatial.core import gis

def test_zeros():
distance = gis.cpp_directed_hausdorff_distance(
distance = gis.directed_hausdorff_distance(
cudf.Series([0.0]),
cudf.Series([0.0]),
cudf.Series([1])
Expand All @@ -32,23 +18,23 @@ def test_zeros():

def test_empty_x():
with pytest.raises(RuntimeError):
distance = gis.cpp_directed_hausdorff_distance(
distance = gis.directed_hausdorff_distance(
cudf.Series(),
cudf.Series([0]),
cudf.Series([0])
)

def test_empty_y():
with pytest.raises(RuntimeError):
distance = gis.cpp_directed_hausdorff_distance(
distance = gis.directed_hausdorff_distance(
cudf.Series([0]),
cudf.Series(),
cudf.Series([0])
)

def test_empty_counts():
with pytest.raises(RuntimeError):
distance = gis.cpp_directed_hausdorff_distance(
distance = gis.directed_hausdorff_distance(
cudf.Series([0]),
cudf.Series([0]),
cudf.Series()
Expand All @@ -67,22 +53,22 @@ def test_large():
pnt_x = cudf.Series(py_x)
pnt_y = cudf.Series(py_y)
cnt = cudf.Series(py_cnt)
distance=gis.cpp_directed_hausdorff_distance(pnt_x,pnt_y,cnt)
distance=gis.directed_hausdorff_distance(pnt_x,pnt_y,cnt)

num_set=len(cnt)
matrix=distance.data.to_array().reshape(num_set,num_set)
expect = np.array([0, 1, 1, 0])
assert np.allclose(distance.data.to_array(), expect)

def test_count_one():
distance = gis.cpp_directed_hausdorff_distance(
distance = gis.directed_hausdorff_distance(
cudf.Series([0.0, 0.0]),
cudf.Series([0.0, 1.0]),
cudf.Series([1, 1]))
assert_eq(cudf.Series([0, 1.0, 1, 0]), cudf.Series(distance))

def test_count_two():
distance = gis.cpp_directed_hausdorff_distance(
distance = gis.directed_hausdorff_distance(
cudf.Series([0.0, 0.0, 1.0, 0.0]),
cudf.Series([0.0, -1.0, 1.0, -1.0]),
cudf.Series([2, 2]))
Expand All @@ -104,7 +90,7 @@ def test_values():
pnt_x =cudf.Series(py_x)
pnt_y= cudf.Series(py_y)
cnt= cudf.Series(py_cnt)
distance=gis.cpp_directed_hausdorff_distance(pnt_x,pnt_y,cnt)
distance=gis.directed_hausdorff_distance(pnt_x,pnt_y,cnt)

num_set=len(cnt)
matrix=distance.data.to_array().reshape(num_set,num_set)
Expand Down
Loading