python: boolean array to polygon - python

I have a boolean array with one connected component of True values, the border of which I would like to convert to a polygon, e.g. in shapely.
Assuming my array is img, I can get the border indices like this
import numpy as np
from skimage.morphology binary_erosion
border_indices = np.transpose(np.nonzero(np.logical_xor(binary_erosion(img), img)))
but just feeding those into a shapely.Polygon object does not work because the points are not ordered along the boundary, but in increasing x and y values.
It may be possible to use alpha shapes to solve this (note that I'm not looking for the convex hull), but maybe someone can suggest a simpler way of getting to the bounding polygon, ideally directly operating on the original array.

It sounds like rasterio.features.shapes is what you are looking for. A simple example that should illustrate the procedure:
import rasterio.features
import shapely.geometry
import numpy as np
im = np.zeros([5, 5], dtype=np.uint8)
im[1:-1, 1:-1] = 1
shapes = rasterio.features.shapes(im)
shapes is a generator with pairs of (geometry, value). To get the geometry corresponding to where the value is equal to 1:
polygons = [shapely.geometry.Polygon(shape[0]["coordinates"][0]) for shape in shapes if shape[1] == 1]
This creates a list of shapely polygons corresponding to the areas in the array where the value is equal to 1.
print(polygons)
[<shapely.geometry.polygon.Polygon object at 0x7f64bf9ac9e8>]

Related

Pyvista plotting 3D numpy array, always blank

I am trying to plot a 3D numpy array (256 x 256 x 256) as essentially an array of points, which should be colored by their value. I keep getting something like this
if I multiply by data by 1000, I get this:
My data is a 3D np array composed of slices, and the slices look like this:
There are about 50 or so slices, so the 3D visualization should be showing a sphere. I can visualize the slices just fine
My code is a modified example of this example: https://docs.pyvista.org/examples/02-plot/volume.html using the technique given here in this link to make a pyvista.UniformGrid object out of a 3D numpy array: https://docs.pyvista.org/examples/00-load/create-uniform-grid.html
def plot_3d_pyvista(self):
import pyvista as pv
values = self.reconstructed_source_3D
# Create the spatial reference
grid = pv.UniformGrid()
# Set the grid dimensions: shape because we want to inject our values on the
# POINT data
grid.dimensions = values.shape
# Edit the spatial reference
#grid.origin = (1, 1, 1) # The bottom left corner of the data set
#grid.spacing = (1, 1, 1) # These are the cell sizes along each axis
# Add the data values to the cell data
grid.point_data["values"] = values.flatten(order="F")
p = pv.Plotter()
p.add_volume(grid)
p.show()
If I use a sphere, eg, a 3D numpy array of zeros with only the center spherical elements set to 1, I get something like this which clearly shows a sphere but is way too transparent:
Alternatively, if anyone knows a way using a different package to plot a volume like this, I could use that. I just need a way to visualize what the 3D looks like.

Calculate pixel distance from centre of image

import numpy as np
import pandas as pd
Problem
I have an image, n pixel wide, m pixel tall. Both of these numbers are even. Pixels are squares. Pixel values are in a numpy array.
I need to calculate the distance from each pixel's centre to the centre of the image. Ie the pixels just next to the centre should have associated values sqrt(2)/2. If the image is like a chess-board, the pixel corresponding to g6 square should have associated distance value should be (2.5^2+1.5^2)^0.5=2.91
My solution
I have achieved the task by the following code:
image=np.zeros([2,4]) # let's say this is my image
df = pd.DataFrame(image)
distances = \
pd.DataFrame(
df.apply(
lambda row: (row.index+0.5-len(df.index)/2)**2,axis=0).to_numpy()+\
df.T.apply(
lambda row: (row.index+0.5-len(df.columns)/2)**2,axis=0).T.to_numpy())\
.apply(np.sqrt).to_numpy()
distances will be:
array([[1.58113883, 0.70710678, 0.70710678, 1.58113883],
[1.58113883, 0.70710678, 0.70710678, 1.58113883]])
As expected.
Question
Is there a better way? I would appreciate a shorter, more-numpy oriented, or more transparent method.
A more transparent method would be first to define the center of your image, i.e something like
Read your array as an image in openCV :
img = cv2.imread("inputImage")
height, width = img.shape
x_center=(width/2)
y_center=(height/2)
Then for each pixels in your numpy/image array you can compute the distance between each pixel of your numpy array and the above center by computing the euclidean distance:
D = dist.euclidean((xA, yA), (x_center, y_center))
PS : You can simply use img.shape from numpy but openCV gives you many method related to distance computing.
I do not know any specific algorithms to do this except this straightforward implementation in Numpy, that is, using the indices (creates array of indices from shape of array) and linalg.norm (calculates norms) functions. Note we also use broadcasting by indexing new dimensions in center[:,None,None] (necessary because of indices intrinsic output shape).
import numpy as np
import pandas as pd
# Numpy function
def np_function(image):
center = np.array([(image.shape[0])/2, (image.shape[1])/2])
distances = np.linalg.norm(np.indices(image.shape) - center[:,None,None] + 0.5, axis = 0)
return distances
# Pandas function
def pd_function(image):
df = pd.DataFrame(image)
distances = \
pd.DataFrame(
df.apply(
lambda row: (row.index+0.5-len(df.index)/2)**2,axis=0).to_numpy()+\
df.T.apply(
lambda row: (row.index+0.5-len(df.columns)/2)**2,axis=0).T.to_numpy())\
.apply(np.sqrt).to_numpy()
return distances
For a 4000x6000 image, Numpy's method is more than one order of magnitude faster than the original function in my computer. You could also just compute the distances from the center to one octant and then copy the results conveniently to the remaining octants (exploiting simmetry), but this will probably be only worth for big images imho.

Determine Coordinates on Gridded Data to Find Lengths of Objects

I'm in the process of identifying objects whose float value is greater than a certain threshold in a 2-D numpy array. I then need to determine the length of the major axis of each object and make sure that the object's major axis length satisfies a certain threshold in kilometers.
I am able to identify the objects I want in my 2-D numpy array by using the scipy.ndimage.measurements.label module. I then am able to determine the length of each object's major axis using the scikit-image regionprops module (skimage.measure.regionprops).
However, I am unsure about what the units of the object's length are as the 2-D numpy array by itself does not have any information about coordinates. The 2-D numpy array is essentially a dataset that maps to a subdomain on the surface of the globe. Additionally, I have two other 2-D numpy arrays that are the same size as my data array with one array containing the latitude coordinates for each grid point and the other containing the longitude coordinates. I believe I somehow need to use the lat/lon arrays to determine the length of the major axis of my objects but I have no idea how.
This is the code I have so far:
from scipy import ndimage
from skimage.measure import regionprops
import numpy as np
# 2-D numpy array with data.
data
# 2-D numpy arrays with latitude and longitude coordinates that are same grid as data array.
lat
lon
# Allow label module to have diagonal object matching.
struct = np.ones((3,3), dtype=bool)
# Find objects in data array.
labl, n_features = ndimage.measurements.label(data>=35,structure=struct)
# Find major axis length in labl array for each object found.
props = regionprops(labl)
# Loop through each object.
for p in props:
# Find object's major axis length.
length = p.major_axis_length
(some code to compute major axis length in kilometers?)
if length < 125: #(125 is in km)
(keep object)
Any help would be greatly appreciated. Thanks!

Filtering the voronoi diagram from python scipy

I am using the python scipy to compute the voronoi diagram from a set of points in 2-dimensions as follows:
import numpy as np
from scipy.spatial import Voronoi
x = np.random.uniform(-1, 1, (1000, 2))
v = Voronoi(x)
I got quite confused with the different attributes of the voronoi object. What I basically want to do now is filter out all the vertices which are beyond -0.5 and 0.5 in both the dimensions.
You'll have to explain what you mean by "filter" (filter out?). In any case you can obtain the vertices and several types of ridges of the voronoi shapes:
verts = v.vertices
, this will give an array with two columns (x and y coordinates for the vertices). You can mask them the same way you do with numpy arrays (like verts[:,0] > -0.5) and use them for whatever you wish.
I'm not entirely sure if this answers your question but you can find a pretty good tutorial here, including plotting.

OpenCV cv2.fillPoly vs. cv2.fillConvexPoly: expected data type for array of polygon vertices?

I have the following code:
import cv2
import numpy
ar = numpy.zeros((10,10))
triangle = numpy.array([ [1,3], [4,8], [1,9] ], numpy.int32)
If I use cv2.fillConvexPoly like so:
cv2.fillConvexPoly(ar, triangle, 1)
then the results are as expected. If, however, I try:
cv2.fillPoly(ar, triangle, 1)
then I get a failed assertion. This seems to be identical to the assertion that fails if I use a numpy array for cv2.fillConvexPoly that does not have dtype numpy.int32. Do cv2.fillPoly and cv2.fillConvexPoly expect different data types for their second argument? If so, what should I be using for cv2.fillPoly?
cv2.fillPoly and cv2.fillConvexPoly use different data types for their point arrays, because fillConvexPoly draws only one polygon and fillPoly draws a (python) list of them. Thus,
cv2.fillConvexPoly(ar, triangle, 1)
cv2.fillPoly(ar, [triangle], 1)
are the correct ways to call these two methods. If you had square and hexagon point arrays, you could use
cv2.fillPoly(ar, [triangle, square, hexagon], 1)
to draw all three.

Categories

Resources