import numpy as np
import pandas as pd
import warnings
from .svgload import svg_parser
[docs]def get_shape_areas(df_shapes, shape_i_columns, signed=False):
'''
Return a `pandas.Series` indexed by `shape_i_columns` (i.e., each entry
corresponds to a single shape/polygon), containing the following columns
the area of each shape.
If `signed=True`, a positive area value corresponds to a clockwise loop,
whereas a negative area value corresponds to a counter-clockwise loop.
'''
# Make a copy of the SVG data frame since we need to add columns to it.
df_i = df_shapes.copy()
df_i['vertex_count'] = (df_i.groupby(shape_i_columns)['x']
.transform('count'))
df_i['area_a'] = df_i.x
df_i['area_b'] = df_i.y
# Vector form of [Shoelace formula][1].
#
# [1]: http://en.wikipedia.org/wiki/Shoelace_formula
df_i.loc[df_i.vertex_i == df_i.vertex_count - 1, 'area_a'] *= df_i.loc[df_i.vertex_i == 0, 'y'].values
df_i.loc[df_i.vertex_i < df_i.vertex_count - 1, 'area_a'] *= df_i.loc[df_i.vertex_i > 0, 'y'].values
df_i.loc[df_i.vertex_i == df_i.vertex_count - 1, 'area_b'] *= df_i.loc[df_i.vertex_i == 0, 'x'].values
df_i.loc[df_i.vertex_i < df_i.vertex_count - 1, 'area_b'] *= df_i.loc[df_i.vertex_i > 0, 'x'].values
area_components = df_i.groupby(shape_i_columns)[['area_a', 'area_b']].sum()
shape_areas = .5 * (area_components['area_b'] - area_components['area_a'])
if not signed:
shape_areas.name = 'area'
return shape_areas.abs()
else:
shape_areas.name = 'signed_area'
return shape_areas
[docs]def get_bounding_boxes(df_shapes, shape_i_columns):
'''
Return a `pandas.DataFrame` indexed by `shape_i_columns` (i.e., each row
corresponds to a single shape/polygon), containing the following columns:
- `width`: The width of the widest part of the shape.
- `height`: The height of the tallest part of the shape.
'''
xy_groups = df_shapes.groupby(shape_i_columns)[['x', 'y']]
xy_min = xy_groups.agg('min')
xy_max = xy_groups.agg('max')
shapes = (xy_max - xy_min).rename(columns={'x': 'width', 'y': 'height'})
return xy_min.join(shapes)
[docs]def get_shape_infos(df_shapes, shape_i_columns):
'''
Return a `pandas.DataFrame` indexed by `shape_i_columns` (i.e., each row
corresponds to a single shape/polygon), containing the following columns:
- `area`: The area of the shape.
- `width`: The width of the widest part of the shape.
- `height`: The height of the tallest part of the shape.
'''
shape_areas = get_shape_areas(df_shapes, shape_i_columns)
bboxes = get_bounding_boxes(df_shapes, shape_i_columns)
return bboxes.join(pd.DataFrame(shape_areas))
[docs]def get_bounding_box(df_points):
'''
Calculate the bounding box of all points in a data frame.
'''
xy_min = df_points[['x', 'y']].min()
xy_max = df_points[['x', 'y']].max()
wh = xy_max - xy_min
wh.index = 'width', 'height'
bbox = pd.concat([xy_min, wh])
bbox.name = 'bounding_box'
return bbox
[docs]def close_paths(df_svg):
# Initialize the closing point for each path based on the first point in
# the path.
close_points = df_svg.groupby('path_id').nth(0)
# Set index of closing vertex (point) to next index in sequence for each
# path.
close_points.loc[:, 'vertex_i'] = df_svg.groupby('path_id')['path_id'].count()
return pd.concat([df_svg,
close_points.reset_index()]).sort(df_svg.columns[:3]
.tolist())
[docs]def get_nearest_neighbours(path_centers):
x_m, x_n = np.meshgrid(path_centers.x.values, path_centers.x.values)
y_m, y_n = np.meshgrid(path_centers.y.values, path_centers.y.values)
distances = np.sqrt((x_m - x_n) ** 2 + (y_m - y_n) ** 2)
nearest_neighbour_i = (np.finfo(distances.dtype).max *
np.eye(distances.shape[0]) +
distances).argmin(axis=1)
nearest_centers = path_centers.iloc[nearest_neighbour_i].reset_index()
nearest_centers.index = path_centers.index
nearest_neighbors = path_centers.join(nearest_centers, rsuffix='_closest')
return nearest_neighbors
# ## Deprecated ##
[docs]def get_svg_path_frame(svg_path):
warnings.warn('get_svg_path_frame function is deprecated. Use '
'`svg_model.svg_polygons_to_df`')
frames = []
for i, loop_i in enumerate(svg_path.loops):
verts = pd.DataFrame(loop_i.verts, columns=['x', 'y'])
verts.insert(0, 'vertex_i', np.arange(verts.shape[0], dtype=int))
verts.insert(0, 'loop_i', i)
frames.append(verts)
return pd.concat(frames)
# ## Deprecated ##
[docs]def get_svg_frame(svg_filepath):
warnings.warn('get_svg_frame function is deprecated. Use '
'`svg_model.svg_polygons_to_df`')
parser = svg_parser.SvgParser()
svg = parser.parse_file(svg_filepath, lambda *args: None)
frames = []
for k, p in svg.paths.iteritems():
svg_frame = get_svg_path_frame(p)
svg_frame.insert(0, 'path_id', k)
frames.append(svg_frame)
return pd.concat(frames).reset_index(drop=True)
# ## Deprecated ##
[docs]def triangulate_svg_frame(svg_frame):
warnings.warn('triangulate_svg_frame function is deprecated. Use '
'`svg_model.tesselate.tesselate_shapes_frame`')
from .seidel import Triangulator
triangle_frames = []
for (path_id, loop_i), df_path_i in svg_frame.groupby(['path_id',
'loop_i']):
triangulator = Triangulator(df_path_i[['x', 'y']].values)
for triangle_i, t in enumerate(triangulator.triangles()):
frame = pd.DataFrame(t, columns=['x', 'y'])
frame.insert(0, 'path_id', path_id)
frame.insert(1, 'loop_i', loop_i)
frame.insert(2, 'triangle_i', triangle_i)
frame.insert(3, 'vertex_i', np.arange(frame.shape[0]))
triangle_frames.append(frame)
return pd.concat(triangle_frames)