How to use numpy.polyfit to fit an graph? - python

I have an image below. Its shape is 720x1280. I want to draw a line to fit this white pattern.
I used y range instead of x is because y is more easy to fit as 2nd order polynomial.
y_range = np.linspace(0, 719, num=720) # to cover same y-range as image
fit = np.polyfit(y_range, image, 2) # image.shape = (720, 1280)
print(fit.shape) # (3, 1280)
I expect fit.shape = (3,), but it's not.
Can np.polyfit() be used in this situation?
If 1. is true, how to do this? I want to use fit to calculate curve as following.
f = fit[0]*y_range**2 + fit[1]*y_range + fit[2]
Thank you.

Your image is 2-D, that is the problem. The 2-D image contains information about the coordinates of each point, so you only have to put it into a suitable format.
Since it seems that you are interested only in the location of the white pixels (and not the particular value of each pixel), convert the image into binary values. I don't know particular values of your image but you could do for example:
import numpy as np
curoff_value = 0.1 # this is particular to your image
image[image > cutoff_value] = 1 # for white pixel
image[image <= cutoff_value] = 0 # for black pixel
Get the coordinates of the white pixels:
coordinates = np.where(image == 1)
y_range = coordinates[0]
x_range = coordinates[1]
fit = np.polyfit(y_range, x_range, 2)
print(fit.shape)
Returns (3, ) as you would expect.

Related

Workaround for storing an image in very low resolution with matplotlib and python

I am trying to create an occupancy grid map by exporting an higher resolution image of the map to a very low resolution.
In most basic form an occupancy grid is a 2 dimensional binary array. The values stored in array denotes free(0) or occupied(1). Each value corresponds to a discrete location of the physical map (the following image depicts an area)
As seen in the above image each array location is a cell of physical world.
I have a 5 meter x 5 meter World, it is then discretized into cells of 5cm x 5cm. The world is thus 100 x 100 cells corresponding to 5m x 5m physical world.
The obstacle re randomly generated circular disks at location (x,y) and of a random radius r like follows:
I need to covert this (above) image into an array of size 100x100. That means evaluating if each cell is actually in the region of a obstacle or free.
To speed things, I have found the following workaround:
Create matplotlib figure populated with obstacles with figsize=(5,5) and save the image with dpi=20 in bmp format and finally import the bmp image as an numpy array. Alas, matplotlib does not support bmp. If I save the image in jpeg using plt.savefig('map.jpg', dpi=20, quality=100) or other formats then the cell's boundary becomes blurred and flows into other cells. Shown in this image :
So my question: How to save a scaled-down image from matplotlib that preserves the cell sharpness of image (akin to bmp).
Nice hack. However, I would rather compute the boolean mask corresponding to your discretized circles explicitly. One simple way to get such a boolean map is by using the contains_points method of matplotlib artists such as a Circle patch.
#!/usr/bin/env python
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
world_width = 100 # x
world_height = 100 # y
minimum_radius = 1
maximum_radius = 10
total_circles = 5
# create circle patches
x = np.random.randint(0, world_width, size=total_circles)
y = np.random.randint(0, world_height, size=total_circles)
r = minimum_radius + (maximum_radius - minimum_radius) * np.random.rand(total_circles)
circles = [Circle((xx,yy), radius=rr) for xx, yy, rr in zip(x, y, r)]
# for each circle, create a boolean mask where each cell element is True
# if its center is within that circle and False otherwise
X, Y = np.meshgrid(np.arange(world_width) + 0.5, np.arange(world_height) + 0.5)
masks = np.zeros((total_circles, world_width, world_height), dtype=bool)
for ii, circle in enumerate(circles):
masks[ii] = circle.contains_points(np.c_[X.ravel(), Y.ravel()]).reshape(world_width, world_height)
combined_mask = np.sum(masks, axis=0)
plt.imshow(combined_mask, cmap='gray_r')
plt.show()
If I have understood correctly, I think this can be done quite simply with PIL, specifically with the Image.resize fucntion. For example, does this do what you asked:
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image, ImageDraw
# Make a dummy image with some black circles on a white background
image = Image.new('RGBA', (1000, 1000), color="white")
draw = ImageDraw.Draw(image)
draw.ellipse((20, 20, 180, 180), fill = 'black', outline ='black')
draw.ellipse((500, 500, 600, 600), fill = 'black', outline ='black')
draw.ellipse((100, 800, 250, 950), fill = 'black', outline ='black')
draw.ellipse((750, 300, 800, 350), fill = 'black', outline ='black')
image.save('circles_full_res.png')
# Resize the image with nearest neighbour interpolation to preserve grid sharpness
image_lo = image.resize((100,100), resample=0)
image_lo.save("circles_low_res.png")

Most optimized way to filter patch positions in an image

So my problem is this: I have an RGB image as a numpy array of dimensions (4086, 2048, 3), I split this image dimension into 96x96 patches and get back the positions of these patches in a numpy array. I always get 96x96 patches in every case. If the dimensions of the image can't allow me to create "pure" 96x96 patches on the x or y axis I just add a left padding to it so the last patches overlap a bit with the patch before it.
Now with these positions in hand I want to get rid of all 96x96 patches for which the RGB value is 255 in all three channels for every pixel in the patch, in the fastest way possible and I want to get back all the patches positions which don't have this value.
I would like to know:
What is the fastest way to extract the 96x96 patches positions from the image dimension? (for now I have a for loop)
How can you get rid of pure white patches (with value 255 on the 3 channels) in most optimal way? (for now I have a for loop)
I have a lot of these images to process like that with images resolution going up to (39706, 94762, 3) so my "for loops" becomes quickly inefficient here. Thanks for your help! (I take solutions which make use of the GPU too)
Here is the pseudo code to give you an idea on how it's done for now:
patches = []
patch_y = 0
y_limit = False
slide_width = 4086
slide_height = 2048
# Lets imagine this image_slide has 96x96 patches which value is 255
image_slide = np.random.rand(slide_width, slide_height, 3)
while patch_y < slide_height:
patch_x = 0
x_limit = False
while patch_x < slide_width:
# Extract the patch at the given position and return it or return None if it's 3 RGB
# channels are 255
is_white = PatchExtractor.is_white(patch_x, patch_y, image_slide)
# Add the patches position to the list if it's not None (not white)
if not is_white:
patches.append((patch_x, patch_y))
if not x_limit and patch_x + crop_size > slide_width - crop_size:
patch_x = slide_width - crop_size
x_limit = True
else:
patch_x += crop_size
if not y_limit and patch_y + crop_size > slide_height - crop_size:
patch_y = slide_height - crop_size
y_limit = True
else:
patch_y += crop_size
return patches
Ideally, I would like to get my patches positions outside a "for loop" then once I have them I can test if they are white or not outside a for loop as well with the fewer possible calls to numpy (so the code is processed in the C layer of numpy and doesn't go back and forth to python)
As you suspected you can vectorize all of what you're doing. It takes roughly a small integer multiple of the memory need of your original image. The algorithm is quite straightforward: pad your image so that an integer number of patches fit in it, cut it up into patches, check if each patch is all white, keep the rest:
import numpy as np
# generate some dummy data and shapes
imsize = (1024, 2048)
patchsize = 96
image = np.random.randint(0, 256, size=imsize + (3,), dtype=np.uint8)
# seed some white patches: cut a square hole in the random noise
image[image.shape[0]//2:3*image.shape[0]//2, image.shape[1]//2:3*image.shape[1]//2] = 255
# pad the image to necessary size; memory imprint similar size as the input image
# white pad for simplicity for now
nx,ny = (np.ceil(dim/patchsize).astype(int) for dim in imsize) # number of patches
if imsize[0] % patchsize or imsize[1] % patchsize:
# we need to pad along at least one dimension
padded = np.pad(image, ((0, nx * patchsize - imsize[0]),
(0, ny * patchsize - imsize[1]), (0,0)),
mode='constant', constant_values=255)
else:
# no padding needed
padded = image
# reshape padded image according to patches; doesn't copy memory
patched = padded.reshape(nx, patchsize, ny, patchsize, 3).transpose(0, 2, 1, 3, 4)
# patched is shape (nx, ny, patchsize, patchsize, 3)
# appending .copy() as a last step to the above will copy memory but might speed up
# the next step; time it to find out
# check for white patches; memory imprint the same size as the padded image
filt = ~(patched == 255).all((2, 3, 4))
# filt is a bool, one for each patch that tells us if it's _not_ all white
# (i.e. we want to keep it)
patch_x,patch_y = filt.nonzero() # patch indices of non-whites from 0 to nx-1, 0 to ny-1
patch_pixel_x = patch_x * patchsize # proper pixel indices of each pixel
patch_pixel_y = patch_y * patchsize
patches = np.array([patch_pixel_x, patch_pixel_y]).T
# shape (npatch, 2) which is compatible with a list of tuples
# if you want the actual patches as well:
patch_images = patched[filt, ...]
# shape (npatch, patchsize, patchsize, 3),
# patch_images[i,...] is an image with patchsize * patchsize pixels
As you can see, in the above I used white padding to get a congruent padded image. I believe this is in line with the philosophy of what you're trying to do. If you want to replicate what you're doing in the loop exactly, you can pad your image manually using the overlapping pixels that you'd take into account near the edge. You'd need to allocate a padded image of the right size, then manually slice the overlapping pixels of the original image in order to set the edge pixels in the padded result.
Since you mentioned that your images are huge and consequently padding leads to far too much memory use, you can avoid padding with some elbow grease. You can use slices of your huge image (which doesn't create a copy), but then you have to manually handle the edges where you don't have full slices. Here's how:
def get_patches(img, patchsize):
"""Compute patches on an input image without padding: assume "congruent" patches
Returns an array shaped (npatch, 2) of patch pixel positions"""
mx,my = (val//patchsize for val in img.shape[:-1])
patched = img[:mx*patchsize, :my*patchsize, :].reshape(mx, patchsize, my, patchsize, 3)
filt = ~(patched == 255).all((1, 3, 4))
patch_x,patch_y = filt.nonzero() # patch indices of non-whites from 0 to nx-1, 0 to ny-1
patch_pixel_x = patch_x * patchsize # proper pixel indices of each pixel
patch_pixel_y = patch_y * patchsize
patches = np.stack([patch_pixel_x, patch_pixel_y], axis=-1)
return patches
# fix the patches that fit inside the image
patches = get_patches(image, patchsize)
# fix edge patches if necessary
all_patches = [patches]
if imsize[0] % patchsize:
# then we have edge patches along the first dim
tmp_patches = get_patches(image[-patchsize:, ...], patchsize)
# correct indices
all_patches.append(tmp_patches + [imsize[0] - patchsize, 0])
if imsize[1] % patchsize:
# same along second dim
tmp_patches = get_patches(image[:, -patchsize:, :], patchsize)
# correct indices
all_patches.append(tmp_patches + [0, imsize[1] - patchsize])
if imsize[0] % patchsize and imsize[1] % patchsize:
# then we have a corner patch we still have to fix
tmp_patches = get_patches(image[-patchsize:, -patchsize:, :], patchsize)
# correct indices
all_patches.append(tmp_patches + [imsize[0] - patchsize, imsize[1] - patchsize])
# gather all the patches into an array of shape (npatch, 2)
patches = np.vstack(all_patches)
# if you also want to grab the actual patch values without looping:
xw, yw = np.mgrid[:patchsize, :patchsize]
patch_images = image[patches[:,0,None,None] + xw, patches[:,1,None,None] + yw, :]
# shape (npatch, patchsize, patchsize, 3),
# patch_images[i,...] is an image with patchsize * patchsize pixels
This will also exactly replicate your looping code, since we're explicitly taking the edge patches such that they overlap with the previous patches (there's no spurious white padding). If you want to have the patches in a given order you'll have to sort them now, though.

Extracting windows/subarrays along a binary circular line/path with view_as_windows from skimage

I have an 8bit binary image that shows me the outline of a circle. The outline is only 1 pixel wide. Using the function view_as_windows lets me generate smaller arrays or windows of an input array like this picture, with adjacent overlapping windows. The size of this image is 250×250.
from skimage.io import imread
from skimage.util import view_as_windows
fname = "C:\\Users\\Username\\Desktop\\Circle.tif"
array = imread(fname)
window_shape = (50, 50)
step = 20
new_array = view_as_windows(array, window_shape, step=step)
This gives me 11×11 overplapping windows. However, I want to extract only windows along the line of the circle so that I can reassemble this object at a later time. The line of a each window should be positioned centrally or in a way so that I have access to the information right under the circle.
This is what I have tried so far:
First I replaced the values (0) and (255) with (1) and (0), respectively. This way, math is a bit easier.
array[array==0] = 1
array[array==255] = 0
Then I iterated over the windows in new_array. In this case over the first two dimensions. new_array.shape is (11, 11, 50, 50)
for j in range(new_array.shape[0]):
for i in range(new_array.shape[1]):
Window = new_array[j, i]
SliceOfWindow = Slice[20:30, 20:30]
sumAxis0 = np.sum(Slice, axis=0)
sumSlice = np.sum(sumAxis0)
if sumSlice >= SliceOfWindow.shape[0]
imsave(...)
I created a smaller slice of the shape = (10, 10) within each window, placed in the center. If the sum of each slice >= the length of a slice I have saved that array as an image.
Can this be done in a more precise way? Is there a way to yield better results (better windows!)?
For a convex curve, you could use polar coordinates and sort the edge pixels by their angle through numpy.argsort and numpy.arctan2.
Demo
from skimage import io
import matplotlib.pyplot as plt
import numpy as np
img = io.imread('https://i.stack.imgur.com/r3D6I.png')
# Arbitrary point inside the curve
row_cen, col_cen = 125, 125
# Coordinates of the edge pixels
row, col = np.nonzero(img == 0)
# Put the origin on the lower left corner
x = col - col_cen
y = -(row - row_cen)
# Indices of the centers of the windows
step = 60
idx = np.argsort(np.arctan2(y, x))[::step]
windows = np.zeros_like(img)
size = 15
for _, n in enumerate(idx):
windows[row[n] - size:row[n] + size, col[n] - size:col[n] + size] = 255
plt.imshow(windows, cmap='gray')
for i, n in enumerate(idx):
plt.text(col[n], row[n], i, fontsize='14',
horizontalalignment='center',
verticalalignment='center')
plt.show()

How can I create a circular mask for a numpy array?

I am trying to circular mask an image in Python. I found some example code on the web, but I'm not sure how to change the maths to get my circle in the correct place.
I have an image image_data of type numpy.ndarray with shape (3725, 4797, 3):
total_rows, total_cols, total_layers = image_data.shape
X, Y = np.ogrid[:total_rows, :total_cols]
center_row, center_col = total_rows/2, total_cols/2
dist_from_center = (X - total_rows)**2 + (Y - total_cols)**2
radius = (total_rows/2)**2
circular_mask = (dist_from_center > radius)
I see that this code applies euclidean distance to calculate dist_from_center, but I don't understand the X - total_rows and Y - total_cols part. This produces a mask that is a quarter of a circle, centered on the top-left of the image.
What role are X and Y playing on the circle? And how can I modify this code to produce a mask that is centered somewhere else in the image instead?
The algorithm you got online is partly wrong, at least for your purposes. If we have the following image, we want it masked like so:
The easiest way to create a mask like this is how your algorithm goes about it, but it's not presented in the way that you want, nor does it give you the ability to modify it in an easy way. What we need to do is look at the coordinates for each pixel in the image, and get a true/false value for whether or not that pixel is within the radius. For example, here's a zoomed in picture showing the circle radius and the pixels that were strictly within that radius:
Now, to figure out which pixels lie inside the circle, we'll need the indices of each pixel in the image. The function np.ogrid() gives two vectors, each containing the pixel locations (or indices): there's a column vector for the column indices and a row vector for the row indices:
>>> np.ogrid[:4,:5]
[array([[0],
[1],
[2],
[3]]), array([[0, 1, 2, 3, 4]])]
This format is useful for broadcasting so that if we use them in certain functions, it will actually create a grid of all the indices instead of just those two vectors. We can thus use np.ogrid() to create the indices (or pixel coordinates) of the image, and then check each pixel coordinate to see if it's inside or outside the circle. In order to tell whether it's inside the center, we can simply find the Euclidean distance from the center to every pixel location, and then if that distance is less than the circle radius, we'll mark that as included in the mask, and if it's greater than that, we'll exclude it from the mask.
Now we've got everything we need to make a function that creates this mask. Furthermore we'll add a little bit of nice functionality to it; we can send in the center and the radius, or have it automatically calculate them.
def create_circular_mask(h, w, center=None, radius=None):
if center is None: # use the middle of the image
center = (int(w/2), int(h/2))
if radius is None: # use the smallest distance between the center and image walls
radius = min(center[0], center[1], w-center[0], h-center[1])
Y, X = np.ogrid[:h, :w]
dist_from_center = np.sqrt((X - center[0])**2 + (Y-center[1])**2)
mask = dist_from_center <= radius
return mask
In this case, dist_from_center is a matrix the same height and width that is specified. It broadcasts the column and row index vectors into a matrix, where the value at each location is the distance from the center. If we were to visualize this matrix as an image (scaling it into the proper range), then it would be a gradient radiating from the center we specify:
So when we compare it to radius, it's identical to thresholding this gradient image.
Note that the final mask is a matrix of booleans; True if that location is within the radius from the specified center, False otherwise. So we can then use this mask as an indicator for a region of pixels we care about, or we can take the opposite of that boolean (~ in numpy) to select the pixels outside that region. So using this function to color pixels outside the circle black, like I did up at the top of this post, is as simple as:
h, w = img.shape[:2]
mask = create_circular_mask(h, w)
masked_img = img.copy()
masked_img[~mask] = 0
But if we wanted to create a circular mask at a different point than the center, we could specify it (note that the function is expecting the center coordinates in x, y order, not the indexing row, col = y, x order):
center = (int(w/4), int(h/4))
mask = create_circular_mask(h, w, center=center)
Which, since we're not giving a radius, would give us the largest radius so that the circle would still fit in the image bounds:
Or we could let it calculate the center but use a specified radius:
radius = h/4
mask = create_circular_mask(h, w, radius=radius)
Giving us a centered circle with a radius that doesn't extend exactly to the smallest dimension:
And finally, we could specify any radius and center we wanted, including a radius that extends outside the image bounds (and the center can even be outside the image bounds!):
center = (int(w/4), int(h/4))
radius = h/2
mask = create_circular_mask(h, w, center=center, radius=radius)
What the algorithm you found online does is equivalent to setting the center to (0, 0) and setting the radius to h:
mask = create_circular_mask(h, w, center=(0, 0), radius=h)
I'd like to offer a way to do this that doesn't involve the np.ogrid() function. I'll crop an image called "robot.jpg", which is 491 x 491 pixels. For readability I'm not going to define as many variables as I would in a real program:
Import libraries:
import matplotlib.pyplot as plt
from matplotlib import image
import numpy as np
Import the image, which I'll call "z". This is a color image so I'm also pulling out just a single color channel. Following that, I'll display it:
z = image.imread('robot.jpg')
z = z[:,:,1]
zimg = plt.imshow(z,cmap="gray")
plt.show()
robot.jpg as displayed by matplotlib.pyplot
To wind up with a numpy array (image matrix) with a circle in it to use as a mask, I'm going to start with this:
x = np.linspace(-10, 10, 491)
y = np.linspace(-10, 10, 491)
x, y = np.meshgrid(x, y)
x_0 = -3
y_0 = -6
mask = np.sqrt((x-x_0)**2+(y-y_0)**2)
Note the equation of a circle on that last line, where x_0 and y_0 are defining the center point of the circle in a grid which is 491 elements tall and wide. Because I defined the grid to go from -10 to 10 in both x and y, it is within that system of units that x_0 and x_y set the center point of the circle with respect to the center of the image.
To see what that produces I run:
maskimg = plt.imshow(mask,cmap="gray")
plt.show()
Our "proto" masking circle
To turn that into an actual binary-valued mask, I'm just going to take every pixel below a certain value and set it to 0, and take every pixel above a certain value and set it to 256. The "certain value" will determine the radius of the circle in the same units defined above, so I'll call that 'r'. Here I'll set 'r' to something and then loop through every pixel in the mask to determine if it should be "on" or "off":
r = 7
for x in range(0,490):
for y in range(0,490):
if mask[x,y] < r:
mask[x,y] = 0
elif mask[x,y] >= r:
mask[x,y] = 256
maskimg = plt.imshow(mask,cmap="gray")
plt.show()
The mask
Now I'll just multiply the mask by the image element-wise, then display the result:
z_masked = np.multiply(z,mask)
zimg_masked = plt.imshow(z_masked,cmap="gray")
plt.show()
To invert the mask I can just swap the 0 and the 256 in the thresholding loop above, and if I do that I get:
Masked version of robot.jpg
The other answers work, but they are slow, so I will propose an answer using skimage.draw.disk. Using this is faster and I find it simple to use. Simply specify the center of the circle and radius then use the output to create a mask
from skimage.draw import disk
mask = np.zeros((10, 10), dtype=np.uint8)
row = 4
col = 5
radius = 5
rr, cc = disk(row, col, radius)
mask[rr, cc] = 1

Set masked pixels in a 3D RGB numpy array

I'd like to set all pixels matching some condition in a 3d numpy array (RGB image) using a mask. I have something like this:
def make_dot(img, color, radius):
"""Make a dot of given color in the center of img (rgb numpy array)"""
(ydim,xdim,dummy) = img.shape
# make an open grid of x,y
y,x = np.ogrid[0:ydim, 0:xdim, ]
y -= ydim/2 # centered at the origin
x -= xdim/2
# now make a mask
mask = x**2+y**2 <= radius**2 # start with 2d
mask.shape = mask.shape + (1,) # make it 3d
print img[mask].shape
img[mask] = color
img = np.zeros((100, 200, 3))
make_dot(img, np.array((.1, .2, .3)), 25)
but that gives ValueError: array is not broadcastable to correct shape in this line:
img[mask] = color
because the shape of img[mask] is (1961,); i.e. it's flattened to contain only the "valid" pixels, which makes sense; but how can I make it "write through the mask" as it were to set only the pixels where the mask is 1? Note that I want to write three values at once to each pixel (the last dim).
You almost have it right.
(ydim,xdim,dummy) = img.shape
# make an open grid of x,y
y,x = np.ogrid[0:ydim, 0:xdim, ]
y -= ydim/2 # centered at the origin
x -= xdim/2
# now make a mask
mask = x**2+y**2 <= radius**2 # start with 2d
img[mask,:] = color
the extra ",:" at the end of the assignment lets you assign the color throughout the 3 channels in one shot.

Categories