I would like to see how an image get deformed if I know how its coordinates are deformed.
for example: here I draw a circle
import numpy as np
import matplotlib.pyplot as plt
from math import *
plane_side = 500.0 #arcsec
y_l, x_l, = np.mgrid[-plane_side:plane_side:1000j, -plane_side:plane_side:1000j]
r = np.sqrt(y_l**2 + x_l**2)
indexes1 = np.where(r<150.0)
indexes2 = np.where(r>160.0)
image = r.copy()
image[:,:] = 1.0
image[indexes1] = 0.0
image[indexes2] = 0.0
imgplot = plt.imshow(image,cmap="rainbow")
plt.colorbar()
plt.show()
If I want to deform the coordinates like this:
y_c = y_lense**3
x_c = x_lense**2
and plot the image distorted, what should I do?
You can use plt.pcolormesh():
y_c = (y_l/plane_side)**3
x_c = (x_l/plane_side)**2
ax = plt.gca()
ax.set_aspect("equal")
plt.pcolormesh(x_c, y_c, image, cmap="rainbow")
ax.set_xlim(0, 0.2)
ax.set_ylim(-0.1, 0.1);
the result:
In general (without using a dedicated library), you should apply an inverse transformation to the coordinates of the new image. Than, you interpolate values from the original image at the calculated coordinates.
For example, if you want to apply the following transformation:
x_new = x**2
y_new = y**2
you would do something like that:
import numpy as np
# some random image
image = np.random.rand(10,10)
# new interpolated image - actually not interpolated yet :p
# TODO: in some cases, this image should be bigger than the original image. You can calculate optimal size from the original image considering the transformation used.
image_new = np.zeros(image.shape)
# get the coordinates
x_new, y_new = np.meshgrid( range(0,image_new.shape[1]), range(0,image_new.shape[0]) )
x_new = np.reshape(x_new, x_new.size)
y_new = np.reshape(y_new, y_new.size)
# do the inverse transformation
x = np.sqrt(x_new)
y = np.sqrt(y_new)
# TODO: here you should check that all x, y coordinates fall inside the image borders
# do the interpolation. The simplest one is nearest neighbour interpolation. For better image quality, bilinear interpolation should be used.
image_new[y_new,x_new] = image[np.round(y).astype(np.int) , np.round(x).astype(np.int)]
Related
I am new to Fourier Transform in Python.
I want to isolate a field on an image thanks to Fourier Transform. Here is my picture :
And here is what I am supposed to obtain :
Here is my code until now :
import numpy as np
import matplotlib.pyplot as plt
import cv2
path = "C:/Users/cecil/OneDrive/Bureau/00_TP_M2TSI/TP Traitement Image/BE1 PDF/BE1 PDF/champs.png"
img = plt.imread(path)
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) #converting to grayscale
plt.set_cmap("gray")
plt.subplot(131)
plt.imshow(img)
plt.axis("off")
# Calculate the Fourier transform of the grating
ft = np.fft.ifftshift(img)
ft = np.fft.fft2(ft)
ft = np.fft.fftshift(ft)
plt.subplot(132)
plt.imshow(np.log(abs(ft)))
plt.axis("off")
#isolating the band we want to keep (we define 2 affine functions to border the band and keep only the
# values that are inbetween)
for x in range(0,512):
y1 = 0.77*x + 55
y2 = 0.77*x + 65
for y in range(0,512):
if not(y > y1 and y < y2) :
ft[y,x] = 0
# Calculate the inverse Fourier transform of
# the Fourier transform
ift = np.fft.ifftshift(ft)
ift = np.fft.ifft2(ift)
ift = np.fft.fftshift(ift)
ift = ift.real # Take only the real part
plt.subplot(133)
plt.imshow(ift)
plt.axis("off")
plt.show()
My problem is I obtain this image instead of the one I want (see above) :
Can anyone help me please ?
So apparently I fixed the problem. It seems I actually needed to do the shift and fft and ishift and ifft in segragated lines like this :
# Calculate the Fourier transform of the grating
spectre = fft2(imgris)
spectre = fftshift(spectre)
plt.imshow(np.log(abs(spectre)))
plt.show()
bande = np.zeros((512,512), dtype=complex)
for x in range(0,512):
y1 = 0.77*x + 40
y2 = 0.77*x + 80
for y in range(0,512):
if y > y1 and y < y2 :
bande[y,x] = spectre[y,x]
if x > (512/2-20) and x < (512/2+20):
bande[y,x] = 0
plt.imshow(abs(bande))
plt.show()
real = bande.real
phases = bande.imag
bande_mod = np.empty(real.shape, dtype=complex)
bande_mod.real = real
bande_mod.imag = phases
champisolé= ifftshift(bande_mod)
champisolé= np.abs(ifft2(champisolé))
plt.imshow(champisolé)
plt.show()
The obtained image (first picture) is kinda blurred and there is quite a lot of unwanted pixels around the field I wanted to isolate but with a few filters I obtain a perfectly isolated field (even better than the teacher's !) (second picture).
I have the 2D coordinates of a geometric shape as x and y arrays. Using a combination of translation and rotation I can get the shape rotated about its geometric center by a given angle alpha (See below for a minimal example).
As shown in the code below, this can be achieved by first shifting the geometric center of the shape to the origin of the coordinates, then applying the rotation (multiplying by the 2D rotation matrix) then translating it back to its original position.
In this example, let's assume that the shape is a rectangle:
import numpy as np
from numpy import cos, sin, linspace, concatenate
import matplotlib.pyplot as plt
def rotate(x, y, alpha):
"""
Rotate the shape by an angle alpha (given in degrees)
"""
# Get the center of the shape
x_center = (x.max() + x.min()) / 2.0
y_center = (y.max() + y.min()) / 2.0
# Shifting the center of the shape to the origin of coordinates
x0 = x - x_center
y0 = y - y_center
angle_rad = np.deg2rad(alpha)
rot_mat = np.array([
[cos(angle_rad), -sin(angle_rad)],
[sin(angle_rad), cos(angle_rad)]
])
xy = np.vstack((x0, y0))
xnew, ynew = rot_mat # xy
# translate it back to its original location
xnew += x_center
ynew += y_center
return xnew, ynew
z0, z1, z2, z3 = 4 + 0.6*1j, 4 + 0.8*1j, 8 + 0.8*1j, 8 + 0.6*1j
xy = concatenate((
linspace(z0, z1, 10, endpoint=False),
linspace(z1, z2, 10, endpoint=False),
linspace(z2, z3, 10, endpoint=False),
linspace(z3, z0, 10, endpoint=True)
))
x = xy.real
y = xy.imag
xrot, yrot = rotate(x, y, alpha=-45.0)
# The x and y limits
xlow, xup = 0, 10
ylow, yup = -1.5, 3.0
plt.plot(x, y, label='original shape')
plt.plot(xrot, yrot, label='rotated shape')
plt.xlim((xlow, xup))
plt.ylim((ylow, yup))
plt.legend()
plt.show()
We get the following plot:
As you can see, the shape gets rotated but it is stretched/skewed as well because the aspect was not set to equal. we could check that by setting:
plt.gca().set_aspect('equal')
And this shows the rotated shape without being skewed:
The problem is that I am plotting this shape with other data that has an x range much larger than the y range. So, setting an equal aspect is not a solution in this case.
To be more precise, I want the rotated shape (orange color) in the first figure to show up correctly like the second figure. My approach is to find the inverse skew matrix in the first figure (resulting from the difference between x and y limits) and multiply it by the rotated shape to get the expected result.
Unfortunately, Using trial and error I couldn't get the correct skew matrix.
Any help is greatly appreciated.
EDIT
From a linear algebra perspective, how to express that deformation of the rotated shape in the first figure in terms of skewing and scaling transformations?
When performing the desired rotation, the vertices of the rectangle will lose their meaning in data coordinates, and the initial rectangle will become a trapezoid. Apparently this is desired. So the question becomes essentially how to perform a rotation in screen coordinates about a given point center in data coordinates.
The solution might look a little complicated, which is due to a callback being used. This is necessary, to keep the center point in screen coordinates synchronized with possible axis limit changes.
from matplotlib import pyplot as plt
from matplotlib.transforms import Affine2D
x, y = (4, 0.6)
dx, dy = (4, 0.2)
fig, ax = plt.subplots()
# The x and y limits
xlow, xup = 0, 10
ylow, yup = -1.5, 3.0
ax.set(xlim=(xlow, xup), ylim=(ylow, yup))
rect1 = plt.Rectangle((x,y), width=dx, height=dy, facecolor="none", edgecolor="C0")
ax.add_patch(rect1)
rect2 = plt.Rectangle((x,y), width=dx, height=dy, facecolor="none", edgecolor="C1")
ax.add_patch(rect2)
def lim_change(evt=None):
center = (x+dx/2, y+dy/2)
trans = ax.transData + Affine2D().rotate_deg_around(*ax.transData.transform_point(center), -45)
rect2.set_transform(trans)
lim_change()
cid = ax.callbacks.connect("xlim_changed", lim_change)
cid = ax.callbacks.connect("ylim_changed", lim_change)
plt.show()
I'd like to plot two profiles through the highest intensity point in a 2D numpy array, which is an image of a blob (i.e. a line through the semi-major axis, and another line through the semi-minor axis). The blob is rotated at an angle theta counterclockwise from the standard x-axis and is asymmetric.
It is a 600x600 array with a max intensity of 1 (at only one pixel) that is located right at the center at (300, 300). The angle rotation from the x-axis (which then gives the location of the semi-major axis when rotated by that angle) is theta = 89.54 degrees. I do not want to use scipy.ndimage.rotate because it uses spline interpolation, and I do not want to change any of my pixel values. But I suppose a nearest-neighbor interpolation method would be okay.
I tried generating lines corresponding to the major and minor axes across the image, but the result was not right at all (the peak was far less than 1), so maybe I did something wrong. The code for this is below:
import numpy as np
import matplotlib.pyplot as plt
from scipy import ndimage
def profiles_at_angle(image, axis, theta):
theta = np.deg2rad(theta)
if axis == 'major':
x_0, y_0 = 0, 300-300*np.tan(theta)
x_1, y_1 = 599, 300+300*np.tan(theta)
elif axis=='minor':
x_0, y_0 = 300-300*np.tan(theta), 599
x_1, y_1 = 300+300*np.tan(theta), -599
num = 600
x, y = np.linspace(x_0, x_1, num), np.linspace(y_0, y_1, num)
z = ndimage.map_coordinates(image, np.vstack((x,y)))
fig, axes = plt.subplots(nrows=2)
axes[0].imshow(image, cmap='gray')
axes[0].axis('image')
axes[1].plot(z)
plt.xlim(250,350)
plt.show()
profiles_at_angle(image, 'major', theta)
Did I do something obviously wrong in my code above? Or how else can I accomplish this? Thank you.
Edit: Here are some example images. Sorry for the bad quality; my browser crashed every time I tried uploading them anywhere so I had to take photos of the screen.
Figure 1: This is the result of my code above, which is clearly wrong since the peak should be at 1. I'm not sure what I did wrong though.
Figure 2: I made this plot below by just taking the profiles through the standard x and y axes, ignoring any rotation (this only looks good coincidentally because the real angle of rotation is so close to 90 degrees, so I was able to just switch the labels and get this). I want my result to look something like this, but taking the correction rotation angle into account.
Edit: It could be useful to run tests on this method using data very much like my own (it's a 2D Gaussian with nearly the same parameters):
image = np.random.random((600,600))
def generate(data_set):
xvec = np.arange(0, np.shape(data_set)[1], 1)
yvec = np.arange(0, np.shape(data_set)[0], 1)
X, Y = np.meshgrid(xvec, yvec)
return X, Y
def gaussian_func(xy, x0, y0, sigma_x, sigma_y, amp, theta, offset):
x, y = xy
a = (np.cos(theta))**2/(2*sigma_x**2) + (np.sin(theta))**2/(2*sigma_y**2)
b = -np.sin(2*theta)/(4*sigma_x**2) + np.sin(2*theta)/(4*sigma_y**2)
c = (np.sin(theta))**2/(2*sigma_x**2) + (np.cos(theta))**2/(2*sigma_y**2)
inner = a * (x-x0)**2
inner += 2*b*(x-x0)*(y-y0)
inner += c * (y-y0)**2
return (offset + amp * np.exp(-inner)).ravel()
xx, yy = generate(image)
image = gaussian_func((xx.ravel(), yy.ravel()), 300, 300, 5, 4, 1, 1.56, 0)
image = np.reshape(image, (600, 600))
This should do it for you. You just did not properly compute your lines.
theta = 65
peak = np.argwhere(image==1)[0]
x = np.linspace(peak[0]-100,peak[0]+100,1000)
y = lambda x: (x-peak[1])*np.tan(np.deg2rad(theta))+peak[0]
y_maj = np.linspace(y(peak[1]-100),y(peak[1]+100),1000)
y = lambda x: -(x-peak[1])/np.tan(np.deg2rad(theta))+peak[0]
y_min = np.linspace(y(peak[1]-100),y(peak[1]+100),1000)
del y
z_min = scipy.ndimage.map_coordinates(image, np.vstack((x,y_min)))
z_maj = scipy.ndimage.map_coordinates(image, np.vstack((x,y_maj)))
fig, axes = plt.subplots(nrows=2)
axes[0].imshow(image)
axes[0].plot(x,y_maj)
axes[0].plot(x,y_min)
axes[0].axis('image')
axes[1].plot(z_min)
axes[1].plot(z_maj)
plt.show()
From a complex 3D shape, I have obtained by tricontourf the equivalent top view of my shape.
I wish now to export this result on a 2D array.
I have tried this :
import numpy as np
from shapely.geometry import Polygon
import skimage.draw as skdraw
import matplotlib.pyplot as plt
x = [...]
y = [...]
z = [...]
levels = [....]
cs = plt.tricontourf(x, y, triangles, z, levels=levels)
image = np.zeros((100,100))
for i in range(len(cs.collections)):
p = cs.collections[i].get_paths()[0]
v = p.vertices
x = v[:,0]
y = v[:,1]
z = cs.levels[i]
# to see polygon at level i
poly = Polygon([(i[0], i[1]) for i in zip(x,y)])
x1, y1 = poly.exterior.xy
plt.plot(x1,y1)
plt.show()
rr, cc = skdraw.polygon(x, y)
image[rr, cc] = z
plt.imshow(image)
plt.show()
but unfortunately, from contours vertices only one polygon is created by level (I think), generated at the end an incorrect projection of my contourf in my 2D array.
Do you have an idea to correctly represent contourf in a 2D array ?
Considering a inner loop with for path in ...get_paths() as suggested by Andreas, things are better ... but not completely fixed.
My code is now :
import numpy as np
import matplotlib.pyplot as plt
import cv2
x = [...]
y = [...]
z = [...]
levels = [....]
...
cs = plt.tricontourf(x, y, triangles, z, levels=levels)
nbpixels = 1024
image = np.zeros((nbpixels,nbpixels))
pixel_size = 0.15 # relation between a pixel and its physical size
for i,collection in enumerate(cs.collections):
z = cs.levels[i]
for path in collection.get_paths():
verts = path.to_polygons()
for v in verts:
v = v/pixel_size+0.5*nbpixels # to centered and convert vertices in physical space to image pixels
poly = np.array([v], dtype=np.int32) # dtype integer is necessary for the next instruction
cv2.fillPoly( image, poly, z )
The final image is not so far from the original one (retunred by plt.contourf).
Unfortunately, some empty little spaces still remains in the final image.(see contourf and final image)
Is path.to_polygons() responsible for that ? (considering only array with size > 2 to build polygons, ignoring 'crossed' polygons and passing through isolated single pixels ??).
My code takes an image of a pinhole aperture and fits the data to a Gaussian. Using the Gaussian fit it calculates the Full-Width at Half Maximum. This tells me the resolution of my imaging system.
Here is the fit I get with my code right now:
According to the theory for pinhole diffraction images, the data should correspond to an Airy disk function. For completeness I want to fit the data to a Bessel function or Airy disk pattern. I cannot find any packages that will fit these functions.
Here is the picture I am using:
You can just make out the outer fringes around the central bright spot. Those are the fringes I want to account for in my fit.
import numpy as np
import scipy.optimize as opt
import PIL
from PIL import ImageFilter
from pylab import *
#defining the Gaussian
def gauss(x, p): # p[0]==mean, p[1]==stdev
return 1.0/(p[1]*np.sqrt(2*np.pi))*np.exp(-(x-p[0])**2/(2*p[1]**2))
im = PIL.Image.open('C:/Documents/User/3000.bmp').convert("L") #convert to array
imArr = np.array(im, dtype=float)
bg = np.average(imArr) #find the background, subtract it
imArr = imArr - bg
#get the approx coordinates of brightest spot by filtering
im2 = im.filter(ImageFilter.GaussianBlur(radius=2))
imArr2 = np.array(im2, dtype=float)
tuple = unravel_index(imArr2.argmax(), imArr2.shape)
#find and plot FWHM for the brightest spot
x = np.arange(tuple[1] - 100, tuple[1] + 100, dtype=np.float)
y = imArr[tuple[0], tuple[1] - 100:tuple[1] + 100]
y /= ((max(x) - min(x)) / len(x)) * np.sum(y) # renormalize to a proper Gaussian
p0 = [tuple[1], tuple[0]]
errfunc = lambda p, x, y: gauss(x, p) - y # distance to the target function
p1, success = opt.leastsq(errfunc, p0[:], args=(x, y))
fit_mu, fit_stdev = p1
FWHM = 2*np.sqrt(2*np.log(2))*fit_stdev
print "FWHM", FWHM
plt.plot(x,y)
plt.plot(x, gauss(x,p1), lw=3, alpha=.5, color='r')
plt.axvspan(fit_mu-FWHM/2, fit_mu+FWHM/2, facecolor='g', alpha=0.5)
plt.show()