Efficient way to measure region properties using shapely? - python

First of all, I apologize to post this easy question. I have a polygon
from shapely.geometry import Polygon
polygon = Polygon([(560023.4495758876400000 6362057.3904932579000000),(560023.4495758876400000 6362060.3904932579000000),(560024.4495758876400000 6362063.3904932579000000),(560026.9495758876400000 6362068.3904932579000000),(560028.4495758876400000 6362069.8904932579000000),(560034.9495758876400000 6362071.8904932579000000),(560036.4495758876400000 6362071.8904932579000000),(560037.4495758876400000 6362070.3904932579000000),(560037.4495758876400000 6362064.8904932579000000),(560036.4495758876400000 6362063.3904932579000000),(560034.9495758876400000 6362061.3904932579000000),(560026.9495758876400000 6362057.8904932579000000),(560025.4495758876400000 6362057.3904932579000000),(560023.4495758876400000 6362057.3904932579000000)])
My goal is compute the minor and the major axis of this polygon, following the Figure example:
I find this example in scikit-image but before to use a second module I wish to ask if there is in shapely module a method to calculate these indices.
thanks in advance

This question is a bit old but I ran into this myself recently, here's what I did:
from shapely.geometry import Polygon, LineString
polygon = Polygon([(560023.4495758876400000, 6362057.3904932579000000),(560023.4495758876400000, 6362060.3904932579000000),(560024.4495758876400000, 6362063.3904932579000000),(560026.9495758876400000, 6362068.3904932579000000),(560028.4495758876400000, 6362069.8904932579000000),(560034.9495758876400000, 6362071.8904932579000000),(560036.4495758876400000, 6362071.8904932579000000),(560037.4495758876400000, 6362070.3904932579000000),(560037.4495758876400000, 6362064.8904932579000000),(560036.4495758876400000, 6362063.3904932579000000),(560034.9495758876400000, 6362061.3904932579000000),(560026.9495758876400000, 6362057.8904932579000000),(560025.4495758876400000, 6362057.3904932579000000),(560023.4495758876400000, 6362057.3904932579000000)])
# get the minimum bounding rectangle and zip coordinates into a list of point-tuples
mbr_points = list(zip(*polygon.minimum_rotated_rectangle.exterior.coords.xy))
# calculate the length of each side of the minimum bounding rectangle
mbr_lengths = [LineString((mbr_points[i], mbr_points[i+1])).length for i in range(len(mbr_points) - 1)]
# get major/minor axis measurements
minor_axis = min(mbr_lengths)
major_axis = max(mbr_lengths)
Shapely makes it easy to compute the mbr via minimum_rotated_rectangle, but it doesn't appear that the opposite sides are of exact equal length. Because of this, the above calculates the length of each side, then takes the min/max.

First calculate the Minimum Bounding Rectangle of the polygon - see the process described in How to find the minimum-area-rectangle for given points?, except you will start with the convex hull. In Shapely, use the .convex_hull() method to calculate the convex hull of your polygon.
Then once you have the MBR, you can find the major/minor axes.

Related

How to select from the output of scipy Delaunay triangulation only simplices under certain volume (or under total line length)?

I am using the Delaunay triangulation on a set of points, trying to isolate clusters of points in a regular pattern.
My first experience with using the qhull.Delaunay object so bear with me...
from scipy.spatial import Delaunay
tri = Delaunay(array)
Currently looks like:
and I've found I can print (tri.simplices) to get the list. I want to isolate only those that are in the obvious clusters, which I imagine could be done by removing those with line length or volume over a certain threshold, but I'm unsure how to manipulate the result to do this?
Found the answer - posting in case it is useful for others.
The Delaunay output gives you the list of the coordinates for each point, and a nested list of which three points form each triangle.
To access their area, first you convert this into a list of Shapely polygons, then your polygons are your oyster.
from shapely.geometry.polygon import Polygon
coord_groups = [tri.points[x] for x in tri.simplices]
polygons = [Polygon(x) for x in coord_groups]
#area of the first polygon
polygons[0].area

Area of polygon with list of (x,y) coordinates

It might seem a bit odd that I am asking for python code to calculate the area of a polygon with a list of (x,y) coordinates given that there have been solutions offered in stackoverflow in the past. However, I have found that all the solutions provided are sensitive to the order of the list of (x,y) coordinates given. For example, with the code below to find an area of a polygon:
def area(p):
return 0.5 * abs(sum(x0*y1 - x1*y0
for ((x0, y0), (x1, y1)) in segments(p)))
def segments(p):
return zip(p, p[1:] + [p[0]])
coordinates1 = [(0.5,0.5), (1.5,0.5), (0.5,1.5), (1.5,1.5)]
coordinates2 = [(0.5,0.5), (1.5,0.5), (1.5,1.5), (0.5,1.5)]
print "coordinates1", area(coordinates1)
print "coordinates2", area(coordinates2)
This returns
coordinates1 0.0
coordinates2 1.0 #This is the correct area
For the same set of coordinates but with a different order. How would I correct this in order to get the area of the non-intersecting full polygon with a list of random (x,y) coordinates that I want to make into a non-intersecting polygon?
EDIT: I realise now that there can be multiple non-intersecting polygons from a set of coodinates. Basically I am using scipy.spatial.Voronoi to create Voronoi cells and I wish to calculate the area of the cells once I've fed the coordinates to the scipy Voronoi function - unfortunately the function doesn't always output the coordinates in the order that will allow me to calculate the correct area.
Several non-intersecting polygons can be created from a random list of coordinates (depending on its order), and each polygon will have a different area, so it is essential that you specify the order of the coordinates to build the polygon (see attached picture for an example).
The Voronoi cells are convex, so that the polygon is unambiguously defined.
You can compute the convex hull of the points, but as there are no reflex vertices to be removed, the procedure is simpler.
1) sort the points by increasing abscissa; in case of ties, sort on ordinates (this is a lexicographical ordering);
2) consider the straight line from the first point to the last and split the point sequence in a left and a right subsequence (with respect to the line);
3) the requested polygon is the concatenation of the left subsequence and the right one, reversed.

Matplotlib Axes3d, line intersect with 2d object

I am trying to do this but in 3d and using a 2d circle instead of a box.
I have a line starting between the two points [ (0,0,0), (3,4,5) ] and I want to see if it intersects through
circle = Circle((2, 1), 0.5)
ax.add_patch(circle)
art3d.pathpatch_2d_to_3d(circle, z=1, zdir="x")
Is it possible to test for a path intersect on a 2d object plotted on 3d axis? From the linked example above, I want to do path.intersects_circle where I define a circle as:
I have had a look through the Bbox documentation and it seems that I can't use this method for a circle?
This sounds more like an algebraic problem than related to matplotlib.
This is how I understand your question:
you have a circle at (x=2,y=1) with a radius of r=0.5
this circle is located in a plane at a constant z=1
1.) You need to determine where your vector pierces the plane which is parallel to the x,y-plane and at z=1. For the vector you specify in your question this intersection is at:
x = 3./(2.**0.5)
y = 4./(2.**0.5)
z = 1.
2.) You need to determine if this intersection falls into the part of the plane covered by the circle. The maximum y-coordinate your circle reaches is 1.5 - the y-coordinate of the intersection is already larger. Hence your straight line does not pierce the circle.
All this being said, I would recommend implementing an algebraic check based on the intersection with the plane and determining if this intersection is part of the circle. And only then using matplotlib.

Width of an arbitrary polygon

I need a way to characterize the size of sets of 2-D points, so I can determine whether to render them as individual points in a space or as representative polygons, dependent on the scale of the viewport. I already have an algorithm to calculate the convex hull of the set to produce the representative polygon, but I need a way to characterize its size. One obvious measure is the maximum distance between points on the convex hull, which is the diameter of the set. But I'm really more interested in the size of its cross-section perpendicular to its diameter, to figure out how narrow the bounding polygon is. Is there a simple way to do this, given the sorted list of vertices and and the indices of the furthest points (ideally in Python)?
Or alternatively, is there an easy way to calculate the radii of the minimal area bounding ellipse of a set of points? I have seen some approaches to this problem, but nothing that I can readily convert to Python, so I'm really looking for something that's turnkey.
You can compute:
the size of its cross-section perpendicular to its diameter
with the following steps:
Find the convex hull
Find the two points a and b which are furthest apart
Find the direction vector d = (a - b).normalized() between those two
Rotate your axes so that this direction vector lies horizontal, using the matrix:
[ d.x, d.y]
[-d.y, d.x]
Find the minimum and maximum y value of points in this new coordinate system. The difference is your "width"
Note that this is not a particularly good definition of "width" - a better one is:
The minimal perpendicular distance between two distinct parallel lines each having at least one point in common with the polygon's boundary but none with the polygon's interior
Another useful definition of size might be twice the average distance between points on the hull and the center
center = sum(convexhullpoints) / len(convexhullpoints)
size = 2 * sum(abs(p - center) for p in convexhullpoints) / len(convexhullpoints)

How do I calculate a 3D centroid?

Is there even such a thing as a 3D centroid? Let me be perfectly clear—I've been reading and reading about centroids for the last 2 days both on this site and across the web, so I'm perfectly aware at the existing posts on the topic, including Wikipedia.
That said, let me explain what I'm trying to do. Basically, I want to take a selection of edges and/or vertices, but NOT faces. Then, I want to place an object at the 3D centroid position.
I'll tell you what I don't want:
The vertices average, which would pull too far in any direction that has a more high-detailed mesh.
The bounding box center, because I already have something working for this scenario.
I'm open to suggestions about center of mass, but I don't see how this would work, because vertices or edges alone don't define any sort of mass, especially when I just have an edge loop selected.
For kicks, I'll show you some PyMEL that I worked up, using #Emile's code as reference, but I don't think it's working the way it should:
from pymel.core import ls, spaceLocator
from pymel.core.datatypes import Vector
from pymel.core.nodetypes import NurbsCurve
def get_centroid(node):
if not isinstance(node, NurbsCurve):
raise TypeError("Requires NurbsCurve.")
centroid = Vector(0, 0, 0)
signed_area = 0.0
cvs = node.getCVs(space='world')
v0 = cvs[len(cvs) - 1]
for i, cv in enumerate(cvs[:-1]):
v1 = cv
a = v0.x * v1.y - v1.x * v0.y
signed_area += a
centroid += sum([v0, v1]) * a
v0 = v1
signed_area *= 0.5
centroid /= 6 * signed_area
return centroid
texas = ls(selection=True)[0]
centroid = get_centroid(texas)
print(centroid)
spaceLocator(position=centroid)
In theory centroid = SUM(pos*volume)/SUM(volume) when you split the part into finite volumes each with a location pos and volume value volume.
This is precisely the calculation done for finding the center of gravity of a composite part.
There is not just a 3D centroid, there is an n-dimensional centroid, and the formula for it is given in the "By integral formula" section of the Wikipedia article you cite.
Perhaps you are having trouble setting up this integral? You have not defined your shape.
[Edit] I'll beef up this answer in response to your comment. Since you have described your shape in terms of edges and vertices, then I'll assume it is a polyhedron. You can partition a polyedron into pyramids, find the centroids of the pyramids, and then the centroid of your shape is the centroid of the centroids (this last calculation is done using ja72's formula).
I'll assume your shape is convex (no hollow parts---if this is not the case then break it into convex chunks). You can partition it into pyramids (triangulate it) by picking a point in the interior and drawing edges to the vertices. Then each face of your shape is the base of a pyramid. There are formulas for the centroid of a pyramid (you can look this up, it's 1/4 the way from the centroid of the face to your interior point). Then as was said, the centroid of your shape is the centroid of the centroids---ja72's finite calculation, not an integral---as given in the other answer.
This is the same algorithm as in Hugh Bothwell's answer, however I believe that 1/4 is correct instead of 1/3. Perhaps you can find some code for it lurking around somewhere using the search terms in this description.
I like the question. Centre of mass sounds right, but the question then becomes, what mass for each vertex?
Why not use the average length of each edge that includes the vertex? This should compensate nicely areas with a dense mesh.
You will have to recreate face information from the vertices (essentially a Delauney triangulation).
If your vertices define a convex hull, you can pick any arbitrary point A inside the object. Treat your object as a collection of pyramidal prisms having apex A and each face as a base.
For each face, find the area Fa and the 2d centroid Fc; then the prism's mass is proportional to the volume (== 1/3 base * height (component of Fc-A perpendicular to the face)) and you can disregard the constant of proportionality so long as you do the same for all prisms; the center of mass is (2/3 A + 1/3 Fc), or a third of the way from the apex to the 2d centroid of the base.
You can then do a mass-weighted average of the center-of-mass points to find the 3d centroid of the object as a whole.
The same process should work for non-convex hulls - or even for A outside the hull - but the face-calculation may be a problem; you will need to be careful about the handedness of your faces.

Categories