How to rotate individual columns of an image? - python

Supposing I have an image with motion artifacts such that each column of the image should be rotated by a known amount, what would be the best way to get an output image with motion corrected. For example, take a 2x3 image like
import numpy as np
image = np.array([[1, 2, 3],
[1, 2, 3]])
column_rotation = np.array([0, 45, -45]) # degrees by which to rotate each column
rotation_pivot = np.array([0, 0, 0])
assert dewarp(image, column_rotation, rotation_pivot) == np.array([[1, 2, 3],
[1, 3, 2]])
Notice how the first column remained unchanged, second column was rotated around the 0th element (item in first row) by 45 degrees, and the third column was rotated around the 0th element by -45 degrees.
The best approach I have so far is to use griddata.
def dewarp(image, rotation_degrees, rotation_pivot) --> np.ndarray:
coordinates = get_each_pixel_coordinates(rotation_degrees, rotation_pivot)
return griddata(image.flatten(), coordinates, np.meshgrid(np.arange(image.shape[0]), np.arange(image.shape[1])), method='nearest')
where get_pixel_coordinates() finds the pixel coordinate for each pixel in the original image.
My problem with this approach is that it's too slow for the image sizes I'm working with (which are actually 3D of shape (200, 500, 1000), but I would settle for a fast 2D solution).
If only griddata was implemented in cupyx.scipy with GPU support I suspect this approach would be fast enough.
griddata might also be suboptimal in that it doesn't take advantage of the fact that columns are rotated in bulk, and just treats each pixel as independently warped.

Related

How to extract the mean of pixels in a raster overlapped with a pixel from another raster with a slight geolocation mismatch?

I have two rasters (xarray dataarray) with a slight geolocation mismatch in a way that pixels of raster1 overlap with multiple pixels of raster2. I am trying to make some calculations on overlapped pixels in raster2. The below code produce an example:
import xarray as xr
LC_val = [[4, 4, 3, 4],
[3, 3, 3, 6],
[3, 3, 3, 9]]
VI_val = [[ 10, 20, 30, 40],
[ 50, 60, 70, 80],
[ 90, 100, 110, 120]]
x_lc = [-1974345., -1974315., -1974285., -1974255.]
y_lc = [3754475., 3754445., 3754415.]
x_vi = [-1974371.7598, -1974341.7598, -1974311.7598, -1974281.7598]
y_vi = [3754465.3842, 3754435.3842, 3754405.3842]
raster1 = xr.DataArray(LC_val,dims=('y','x'),coords={'x': x_lc,'y': y_lc})
raster2 = xr.DataArray(VI_val,dims=('y','x'),coords={'x': x_vi,'y': y_vi})
Which if we plot them together it looks like:
As an example lets focus on pixel A in the figure. It is overlapped with four pixels of raster2 (three of them are visible and one is not). I want to take the weighted mean of these four pixels and assign it to new raster with coordinates similar to pixel A in raster1. The weights are based on the fraction of each pixel (pixels 1-4 in raster2) overlapped with pixel A in raster1.
Based on my search so far there are a couple of ways to do it:
create a for loop over each pixel. This is not a good way since I have millions of pixels.
convert raster1 into meshgrid polygon and perform zonal statistics with packages such as rasterio. I am not sure this is an efficient way because then we are dealing with millions of polygons.
Is there a better way to do this? Please note that each raster is millions of pixels and the extents and dimensions are different. However, the pixel sizes (30 m) and projection systems are similar.
Thanks

Sum up data on specific (multiple) ranges

I'm certain there's a good way to do this but I'm blanking on the right search terms to google, so I'll ask here instead. My problem is this:
I have 2 2-dimensional array, both with the same dimensions. One array (array 1) is the accumulated precipitation at (x,y) points. The other (array 2) is the topographic height of the same (x,y) grid. I want to sum up array 1 between specific heights of array 2, and create a bar graph with topographic height bins a the x-axis and total accumulated precipitation on the y axis.
So I want to be able to declare a list of heights (say [0, 100, 200, ..., 1000]) and for each bin, sum up all precipitation that occurred within that bin.
I can think of a few complicated ways to do this, but I'm guessing there's probably an easier way that I'm not thinking of. My gut instinct is to loop through my list of heights, mask anything outside of that range, sum up remaining values, add those to a new array, and repeat.
I'm wondering is if there's a built-in numpy or similar library that can do this more efficiently.
This code shows what you're asking for, some explanation in comments:
import numpy as np
def in_range(x, lower_bound, upper_bound):
# returns wether x is between lower_bound (inclusive) and upper_bound (exclusive)
return x in range(lower_bound, upper_bound)
# vectorize allows you to easily 'map' the function to a numpy array
vin_range = np.vectorize(in_range)
# representing your rainfall
rainfall = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
# representing your height map
height = np.array([[1, 2, 1], [2, 4, 2], [3, 6, 3]])
# the bands of height you're looking to sum
bands = [[0, 2], [2, 4], [4, 6], [6, 8]]
# computing the actual results you'd want to chart
result = [(band, sum(rainfall[vin_range(height, *band)])) for band in bands]
print(result)
The next to last line is where the magic happens. vin_range(height, *band) uses the vectorized function to create a numpy array of boolean values, with the same dimensions as height, that has True if a value of height is in the range given, or False otherwise.
By using that array to index the array with the target values (rainfall), you get an array that only has the values for which the height is in the target range. Then it's just a matter of summing those.
In more steps than result = [(band, sum(rainfall[vin_range(height, *band)])) for band in bands] (but with the same result):
result = []
for lower, upper in bands:
include = vin_range(height, lower, upper)
values_to_include = rainfall[include]
sum_of_rainfall = sum(values_to_include)
result.append(([lower, upper], sum_of_rainfall))
You can use np.bincount together with np.digitize. digitize creates an array of bin indices from the height array height and the bin boundaries bins. bincount then uses the bin indices to sum the data in array rain.
# set up
rain = np.random.randint(0,100,(5,5))/10
height = np.random.randint(0,10000,(5,5))/10
bins = [0,250,500,750,10000]
# compute
sums = np.bincount(np.digitize(height.ravel(),bins),rain.ravel(),len(bins)+1)
# result
sums
# array([ 0. , 37. , 35.6, 14.6, 22.4, 0. ])
# check against direct method
[rain[(height>=bins[i]) & (height<bins[i+1])].sum() for i in range(len(bins)-1)]
# [37.0, 35.6, 14.600000000000001, 22.4]
An example using the numpy ma module which allows to make masked arrays. From the docs:
A masked array is the combination of a standard numpy.ndarray and a mask. A mask is either nomask, indicating that no value of the associated array is invalid, or an array of booleans that determines for each element of the associated array whether the value is valid or not.
which seems what you need in this case.
import numpy as np
pr = np.random.randint(0, 1000, size=(100, 100)) #precipitation map
he = np.random.randint(0, 1000, size=(100, 100)) #height map
bins = np.arange(0, 1001, 200)
values = []
for vmin, vmax in zip(bins[:-1], bins[1:]):
#creating the masked array, here minimum included inside bin, maximum excluded.
maskedpr = np.ma.masked_where((he < vmin) | (he >= vmax), pr)
values.append(maskedpr.sum())
values is the list of values for each bin, which you can plot.
The numpy.ma.masked_where function returns an array masked where condition is True. So you need to set the condition to be True outside the bins.
The sum() method performs the sum only where the array is not masked.

Python - argmax on a histogram

I'm trying to find peaks on the left and right halves of the image (basically this is a binary image of a road with left and right lanes).
For some reason, the left argmax is giving a value to the right of midpoint, and right is giving beyond the size of the image.
Here's my code
import numpy as np
import cv2
binary_warped = cv2.imread('data\Sobel\warped-example.jpg')
histogram = np.sum(binary_warped[binary_warped.shape[0]//2:,:], axis=0)
plt.plot(histogram)
midpoint = np.int(histogram.shape[0]//2)
leftx_base = np.argmax(histogram[:midpoint])
rightx_base = np.argmax(histogram[midpoint:]) + midpoint
print('Shape {} midpoint {} left peak {} right peak {}'.format(histogram.shape, midpoint, leftx_base, rightx_base))
This is my input
Input with shapes on the axis
Ideally the left peak should be around 370 and right should be arnd 1000, but
Here is my result
Shape (1280, 3) midpoint 640 left peak 981 right peak 1633
Where was the mistake?
The clue is given when you look at the shape of your histogram. It is 2-dimensional as it has a shape of (1280, 3)
When you call np.argmax(histogram[:midpoint]), argmax is called on a 2-d array and will first be unraveled before finding the largest value/index
You can see an example of this in the numpy docs:
>>> a = np.arange(6).reshape(2,3)
>>> a
array([[0, 1, 2],
[3, 4, 5]])
>>> np.argmax(a)
5

Return row/column values from array underneath lines

What I'm trying to do is generate multiple lines on a binary image based on a length and angle. Then return all of the row/column values along with pixel values underneath those lines and place them into a python list.
To generate those lines I wrote a function that outputs start and end coordinates of each line. Using these coordinates I want to generate the lines and extract the values.
To extract values from a horizontal line from pixel (0,1) to (3,1) I can do:
a = np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])
pixels = a[1, 0:3]
or vertical:
pixels = a[0:3, 1]
which returns an array of all the pixel values underneath that line:
array([3, 4, 5])
array([1, 4, 7])
How could I apply this method on lines with an angle? so with an x1,y1 and x2,y2? These return (syntax) errors:
a([0,0], [2,2])
a([0,0]:[2,2])
a[0,0:2,2]
I'm looking for something similiar as 'improfile' in Matlab.
Many thanks for your help!
You can use scikits-image's draw module and the draw.line method:
>>> from skimage.draw import line
>>> y0 = 1; x0 = 1; y1 = 10; x1 = 10;
>>> rr, cc = line(y0, x0, y1, x1)
rr and cc will contain row and column indexes of the values from which the line passes. You can access those values as:
>>> values = img[rr, cc]
Assuming img is the name of your image.
Note that this implementation does not offer interpolation or subpixel accuracy for angles different from 45 degree intervals. It will create a discrete stepped line between points A and B that passes through whole pixels.

How to perform iterative 2D operation on 4D numpy array

Let me preface this post by saying that I'm pretty new to Python and NumPy, so I'm sure I'm overlooking something simple. What I'm trying to do is image processing over a PGM (grayscale) file using a mask (a mask convolution operation); however, I don't want to do it using the SciPy all-in-one imaging processing libraries that are available—I'm trying to implement the masking and processing operations myself. What I want to do is the following:
Iterate a 3x3 sliding window over a 256x256 array
At each iteration, I want to perform an operation with a 3x3 image mask (array that consists of fractional values < 1 ) and the 3x3 window from my original array
The operation is that the image mask gets multiplied by the 3x3 window, and that the results get summed up into one number, which represents a weighted average of the original 3x3 area
This sum should get inserted back into the center of the 3x3 window, with the original surrounding values left untouched
However, the output of one of these operations shouldn't be the input of the next operation, so a new array should be created or the original 256x256 array shouldn't be updated until all operations have completed.
The process is sort of like this, except I need to put the result of the convolved feature back into the center of the window it came from:
(source: stanford.edu)
So, in this above example, the 4 would go back into the center position of the 3x3 window it came from (after all operations had concluded), so it would look like [[1, 1, 1], [0, 4, 1], [0, 0, 1]] and so on for every other convolved feature obtained. A non-referential copy could also be made of the original and this new value inserted into that.
So, this is what I've done so far: I have a 256x256 2D numpy array which is my source image. Using as_strided, I convert it into a 4D numpy array of 3x3 slices. The main problem I'm facing is that I want to execute the operation I've specified over each slice. I'm able to perform it on one slice, but in npsum operations I've tried, it adds up all the slices' results into one value. After this, I either want to create a new 256x256 array with the results, in the fashion that I've described, or iterate over the original, replacing the middle values of each 3x3 window as appropriate. I've tried using ndenumerate to change just the same value (v, x, 1, 1) of my 4D array each time, but since the index returned from my 4D array is of the form (v, x, y, z), I can't seem to figure out how to only iterate through (v, x) and leave the last two parts as constants that shouldn't change at all.
Here's my code thus far:
import numpy as np
from numpy.lib import stride_tricks
# create 256x256 NumPy 2D array from image data and image size so we can manipulate the image data, then create a 4D array of strided windows
# currently, it's only creating taking 10 slices to test with
imageDataArray = np.array(parsedPGMFile.imageData, dtype=int).reshape(parsedPGMFile.numRows, parsedPGMFile.numColumns)
xx = stride_tricks.as_strided(imageDataArray, shape=(1, 10, 3, 3), strides=imageDataArray.strides + imageDataArray.strides)
# create the image mask to be used
mask = [1,2,1,2,4,2,1,2,1]
mask = np.array(mask, dtype=float).reshape(3, 3)/16
# this will execute the operation on just the first 3x3 element of xx, but need to figure out how to iterate through all elements and perform this operation individually on each element
result = np.sum(mask * xx[0,0])
Research from sources like http://wiki.scipy.org/Cookbook/GameOfLifeStrides, http://www.johnvinyard.com/blog/?p=268, and http://chintaksheth.wordpress.com/2013/07/31/numpy-the-tricks-of-the-trade-part-ii/ were very helpful (as well as SO), but they don't seem to address what I'm trying to do exactly (unless I'm missing something obvious). I could probably use a ton of for loops, but I'd rather learn how to do it using these awesome Python libraries we have. I also realize I'm combining a few questions together, but that's only because I have the sneaking suspicion that this can all be done very simply! Thanks in advance for any help!
When you need to multiply element-wise, then reduce with addition, think np.dot or np.einsum:
from numpy.lib.stride_tricks import as_strided
arr = np.random.rand(256, 256)
mask = np.random.rand(3, 3)
arr_view = as_strided(arr, shape=(254, 254, 3, 3), strides=arr.strides*2)
arr[1:-1, 1:-1] = np.einsum('ijkl,kl->ij', arr_view, mask)
Based on the example illustration:
In [1]: import numpy as np
In [2]: from scipy.signal import convolve2d
In [3]: image = np.array([[1,1,1,0,0],[0,1,1,1,0],[0,0,1,1,1],[0,0,1,1,0],[0,1,1,0,0]])
In [4]: m = np.array([[1,0,1],[0,1,0],[1,0,1]])
In [5]: convolve2d(image, m, mode='valid')
Out[5]:
array([[4, 3, 4],
[2, 4, 3],
[2, 3, 4]])
And putting it back where it came from:
In [6]: image[1:-1,1:-1] = convolve2d(image, m, mode='valid')
In [7]: image
Out[7]:
array([[1, 1, 1, 0, 0],
[0, 4, 3, 4, 0],
[0, 2, 4, 3, 1],
[0, 2, 3, 4, 0],
[0, 1, 1, 0, 0]])

Categories