Source code for svg_model.shapes_canvas

# coding: utf-8
import types

import numpy as np
import pandas as pd
import pymunk

from . import (svg_polygons_to_df, fit_points_in_bounding_box,
               fit_points_in_bounding_box_params)
from .tesselate import tesselate_shapes_frame
from .point_query import get_shapes_pymunk_space


[docs]def get_transform(offset, scale): return pd.DataFrame([[scale, 0, offset.x], [0, scale, offset.y], [0, 0, 1]], index=['x', 'y', 'w'])
[docs]class ShapesCanvas(object): ''' The `ShapesCanvas` class fits all shapes defined by vertices in a `pandas.DataFrame` (one vertex per row) into a specified canvas shape (with optional padding), while maintaining aspect ratio. The `ShapesCanvas.find_shape` method returns the shape located at the specified *canvas* coordinates (or `None`, if no shape intersects with specified point). ''' def __init__(self, df_shapes, shape_i_columns, canvas_shape=None, padding_fraction=0): ''' Arguments --------- - `df_shapes`: A `pandas.DataFrame`-like object with `x`, `y` columns, containing one row per point. - `shape_i_columns`: Column(s) in `df_shapes` to group points by shape. - `canvas_shape`: A `pandas.Series`-like object with a `width` and a `height`. ''' self.df_shapes = df_shapes if isinstance(shape_i_columns, types.StringType): shape_i_columns = [shape_i_columns] self.shape_i_columns = shape_i_columns # Scale and center source points to canvas shape. self.source_shape = pd.Series(df_shapes[['x', 'y']].max().values, index=['width', 'height']) # Tesselate electrode polygons into convex shapes (triangles), for # compatability with `pymunk`. self.df_tesselations = tesselate_shapes_frame(self.df_shapes, shape_i_columns) # Create `pymunk` space and add a body for each convex shape. Each # body is mapped to the original shape identifier through # `canvas_bodies`. self.space, self.bodies = get_shapes_pymunk_space(self.df_tesselations, shape_i_columns + ['triangle_i']) self.padding_fraction = padding_fraction self.reset_shape(canvas_shape, self.padding_fraction)
[docs] def reset_shape(self, canvas_shape=None, padding_fraction=None): if canvas_shape is None: canvas_shape = self.source_shape.copy() if padding_fraction is None: padding_fraction = self.padding_fraction self.canvas_shape = canvas_shape self.df_canvas_shapes = fit_points_in_bounding_box(self.df_shapes, canvas_shape, padding_fraction= padding_fraction) # Compute shape (i.e., width and height) of bounding box for each # canvas shape. df_bounding_coords = (self.df_canvas_shapes.groupby('id')[['x', 'y']] .agg(['min', 'max'])) bounding_width = df_bounding_coords.x['max'] - df_bounding_coords.x['min'] bounding_height = df_bounding_coords.y['max'] - df_bounding_coords.y['min'] self.df_bounding_shapes = pd.DataFrame(np.column_stack([bounding_width, bounding_height]), columns=['width', 'height'], index=df_bounding_coords.index) if self.df_canvas_shapes.shape[0] == 0: self.canvas_offset = pd.Series([0, 0], index=['x', 'y']) self.canvas_scale = 1. else: # Get x/y-offset and scale for later use. self.canvas_offset, self.canvas_scale = \ fit_points_in_bounding_box_params(self.df_shapes, canvas_shape, padding_fraction= padding_fraction) # Create transformation matrix to map from shapes coordinate space to # canvas coordinate space. self.shapes_to_canvas_transform = get_transform(self.canvas_offset, self.canvas_scale) self.canvas_to_shapes_transform = \ np.linalg.inv(self.shapes_to_canvas_transform)
@classmethod
[docs] def from_svg(cls, svg_filepath, *args, **kwargs): # Read SVG polygons into dataframe, one row per polygon vertex. df_shapes = svg_polygons_to_df(svg_filepath) return cls(df_shapes, 'path_id', *args, **kwargs)
[docs] def find_shape(self, canvas_x, canvas_y): ''' Look up shape based on canvas coordinates. ''' shape_x, shape_y, w = self.canvas_to_shapes_transform.dot([canvas_x, canvas_y, 1]) if hasattr(self.space, 'point_query_first'): # Assume `pymunk<5.0`. shape = self.space.point_query_first((shape_x, shape_y)) else: # Assume `pymunk>=5.0`, where `point_query_first` method has been # deprecated. info = self.space.point_query_nearest((shape_x, shape_y), 0, [pymunk.ShapeFilter .ALL_CATEGORIES]) shape = info.shape if info else None if shape: return self.bodies[shape.body] return None