Python SVD fix the number of eigenvalues to rebuild the image? - python

I am trying to rebuild an image that I previously decomposed with SVD. The image is this:
I successfully decomposed the image with this code:
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
img = Image.open('steve.jpg')
img = np.mean(img, 2)
U,s,V = np.linalg.svd(img)
s an array of the singular values of the image. The more singular values I take, the more the reconstructed image is similar to the original one.
For example, if I take 20 singular values:
n = 20
S = np.zeros(np.shape(img))
for i in range(0, n):
S[i, i] = s[i]
recon_img = U#S#V
plt.imshow(recon_img)
plt.axis('off')
plt.show()
I would like to fix the minumum number of singular values in order to get a good result: an image pretty similary to the original one. Moreover, I would like to see how much the result changes when I take a higher number of singular values. I tried with an animation without success:
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
img = Image.open('steve.jpg')
img = np.mean(img, 2)
U,s,V = np.linalg.svd(img)
fig = plt.figure()
def update(i):
S = np.zeros(np.shape(img))
n = 20
for i in range(0, n):
S[i, i] = s[i]
recon_img = U#S#V
plt.imshow(recon_img)
plt.axis('off')
ani = FuncAnimation(fig = fig, func = update, frames = 20, interval = 10)
plt.show()

If you plot the s singular values you can see a very steep decreasing curve, better if you use a log scale for the y axis:
plt.semilogy(s, 'k-')
As you can see, the first 50 singular values are the most important ones: almost everyone more that 1000. Values from the ~50th to the ~250th are an order of magnitude lower and their values decreases slowly: the slope of the curve is contained (remember the logarithmic y scale). That beeing said I would take the first 50 elements to rebulid your image.
Regarding the animation:
while the animation updates frame by frame, the counter i is increased by 1. In your code, you mistakenly use i to slice the s and define S; you should rename the counter.
Moreover, as animation goes on, you need to take an increasing number of singular values, this is set by n which you keep constant frame by frame. You need to update n at each loop, so you can use it as the counter.
Furthermore, you need the erase the previous plotted image, so you need to add a plt.gca().cla() at the beginning of the update function.
Check the code below for reference:
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
img = Image.open('steve.jpg')
img = np.mean(img, 2)
U,s,V = np.linalg.svd(img)
fig, ax = plt.subplots(1, 2, figsize = (4, 4))
ax[0].imshow(img)
ax[0].axis('off')
ax[0].set_title('Original')
def init():
ax[1].cla()
ax[1].imshow(np.zeros(np.shape(img)))
ax[1].axis('off')
ax[1].set_title('Reconstructed\nn = 00')
def update(n):
ax[1].cla()
S = np.zeros(np.shape(img))
for i in range(0, n):
S[i, i] = s[i]
recon_img = U#S#V
ax[1].imshow(recon_img)
ax[1].axis('off')
ax[1].set_title(f'Reconstructed\nn = {n:02}')
ani = FuncAnimation(fig = fig, func = update, frames = 50, init_func = init, interval = 10)
ani.save('ani.gif', writer = 'imagemagick')
plt.show()
which gives this animation:
As you can see, the first 50 elements are enough to rebuild you image pretty well. The rest of the elements adds some noise and changes a little the background.

Related

How to draw animation by taking snapshot with matplotlib?

In my project, I have many polygons to draw for each time step.
At each step, the number of polygons varies, thus it is difficult to keep Axes.patchs and translate them to make the animation.
I want to create animation with final figures (show after calling matplotlib.pyplot.show()), how to do this?
We take the sin curve as example:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig = plt.figure()
ims = []
x = np.linspace(0, 2 * np.pi, 100)
y = np.sin(x)
z = np.cos(x)
for i in range(1,100):
tmpx = x[:i]
tmpy = y[:i]
tmpz = z[:i]
plt.plot(tmpx, tmpz)
im = plt.plot(tmpx, tmpy)
ims.append(im)
ani = animation.ArtistAnimation(fig, ims, interval=200)
ani.save('/home/test.gif', writer='imagemagick')
plt.show()
There are two curves: animated-sin-curve and static-cos-curve.
the sin-curve is kept as Line2D objects for each step
the cos-curve stay static for each step.
In this way, we show different Artist object for each step.
But I want to keep the rasterized Line2D figure for each step.
I find classes of AxesImage/FigureImage, but I don't know how to save the rasterized figure and make them work.
I tried to convert figure.canvas to AxesImage with following code :
def fig2AxesImage(fig):
import PIL.Image as Image
fig.canvas.draw()
w, h = fig.canvas.get_width_height()
buf = numpy.fromstring(fig.canvas.tostring_argb(), dtype=numpy.uint8)
buf.shape = (w, h, 4)
# canvas.tostring_argb give pixmap in ARGB mode. Roll the ALPHA channel to have it in RGBA mode
buf = numpy.roll(buf, 3, axis=2)
image = Image.frombytes("RGBA", (w, h), buf.tostring())
image = numpy.asarray(image)
return plt.imshow(image, animated=True)
but with this way, I have to clear canvas at start of next frame, which make the final animation a blank video. (but the .jpg figures I output for each step get the right content)
Does anyone have done this before that save rasterized canvas-figures of matplotlib.pyplot.figure() as a animation Vedio?
celluloid for python 2.7
''' copy from celluloid'''
# from typing import Dict, List # not supported by python 2.7. So comment it
from collections import defaultdict
from matplotlib.figure import Figure
from matplotlib.artist import Artist
from matplotlib.animation import ArtistAnimation
__version__ = '0.2.0'
class Camera:
def __init__(self, figure):
self.figure_ = figure
self.offsets_ = { k:defaultdict(int) \
for k in ['collections', 'patches', 'lines', 'texts', 'artists', 'images']
}
self.photos_ = []
def snap(self):
frame_artists = []
for i, axis in enumerate(self.figure_.axes):
if axis.legend_ is not None:
axis.add_artist(axis.legend_)
for name in self.offsets_:
new_artists = getattr(axis, name)[self.offsets_[name][i]:]
frame_artists += new_artists
self.offsets_[name][i] += len(new_artists)
self.photos_.append(frame_artists)
def animate(self):
return ArtistAnimation(self.figure_, self.photos_)

Replacing values in a massive numpy array

Good evening all, I'm really struggling with my code. I've made a 1D spectrum from a fits file. I've extracted the numerical values for each point along the file, but there are vertical lines of overexposed pixel values. I want to replace all values above 3000 with 0. This is what I've done so far:
import astropy as ap
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from astropy.io import fits
from pathlib import Path
from astropy.nddata import CCDData
from ccdproc import ImageFileCollection
import ccdproc as ccdp
from os import listdir, walk
import astropy.units as u
# this function converts the class astropy.io.fits.hdulist.HDUList to a numpy array as ccd data
fitsfile = fits.open("img/HLXSpectrum.fits")
def spec(fitsfile):
specList = fits.open("img/HLXSpectrum.fits", include_path=True)
imgList = []
for img in specList:
ccd = CCDData(fitsfile[0].data, unit="adu")
HLX = ccdp.trim_image(ccd, fits_section="[:2050, 480:840]")
imgList.append(ccd)
fitsfile.close()
specImg = CCDData(ccd, unit="adu")
return specImg
specImg = spec(fitsfile)
skyarray1 = specImg[180:220, 50:2045]
spectrum1 = np.array(skyarray1)
skyarray2 = specImg[220:260, 50:2045]
spectrum2 = np.array(skyarray2)
skyarray3 = specImg[140:180, 50:2045]
spectrum3 = np.array(skyarray3)
spectrumA = spectrum2 - spectrum3
spectrum = spectrumA - spectrum1
flux = []
pixel = []
fix = np.where(spectrum > 3000, spectrum, 0)
for i in range(len(fix[1])): # cropped img in x dimension
flux.append(np.sum(skyarray1[:, i]))
pixel.append(i)
plt.figure(figsize=(20, 16), dpi=800)
plt.plot(pixel, flux, color="red")
fig1 = plt.gcf()
plt.show()
# fig1.savefig("flux.png", dpi=800)
but no matter what I do, the image stays the same, even though the values in the arrays change. Why?
The problem comes down to what you're plotting here:
fix = np.where(spectrum > 3000, spectrum, 0)
for i in range(len(fix[1])): # cropped img in x dimension
flux.append(np.sum(skyarray1[:, i]))
pixel.append(i)
plt.figure(figsize=(20, 16), dpi=800)
plt.plot(pixel, flux, color="red")
fig1 = plt.gcf()
plt.show()
You're plotting flux, which is taking values from skyarray1, which has not been modified. I think you want to replace it with fix like this:
for i in range(len(fix[1])): # cropped img in x dimension
flux.append(np.sum(fix[:, i]))
pixel.append(i)

Creating a colourmap plot in pyplot with random data and positions

I want to plot a map (let's call it testmap) of shape (100,3) with a colourmap. Each row consists of the x-position, y-position and data, all randomly drawn.
map_pos_x = np.random.randint(100, size=100)
map_pos_y = np.random.randint(100, size=100)
map_pos = np.stack((map_pos_x, map_pos_y), axis=-1)
draw = np.random.random(100)
draw = np.reshape(draw, (100,1))
testmap = np.hstack((map_pos, draw))
I do not want to use a scatterplot, since the map positions are supposed to emulate pixels of a camera. If I try something like
plt.matshow(A=testmap)
I get a 100*2 map. However, I want a 100*100 map. Positions with no data can be black. How can I do this?
edit: I have now adopted the following:
grid = np.zeros((100, 100))
i=0
for pixel in map_pos:
grid[pixel[0], pixel[1]] = draw[i]
i=i+1
This produces what I want to have. The reason why I do not draw the random numbers in the loop itself, but iterate over the existing array "draw", is that the numbers that are being drawn are first being operated on, so I want to have the freedom to manipulate "draw" independently of the loop.
This code also produces double entries/non-unique pairs, which is fine by itself, but I would like to identify these double pairs and add up "draw" for these pairs. How can I do that?
You can first create empty pixels, either with zeros (gets the "lowest" color) or NaNs (these pixels would be invisible). Then you can use numpy's smart indexing to fill in the values. For this to work, it is important that the map_pos_x and map_pos_y are integer coordinates in the correct range.
import numpy as np
import matplotlib.pyplot as plt
map_pos_x = np.random.randint(100, size=100)
map_pos_y = np.random.randint(100, size=100)
draw = np.random.random(100)
# testmap = np.full((100,100), np.nan)
testmap = np.zeros((100,100))
testmap[map_pos_x, map_pos_y] = draw
plt.matshow(testmap)
plt.show()
PS: About your new question, to count how many xy positions coincide, np.histogram2d could be used. The result can also be plotting via matshow. A benefit is that the xy values don't need to be integers: they will be summed depending on their rounded values.
If every xy position also has a value, such as the array draw in the question, it can be passed as np.histogram2d(...., weights=draw).
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(1234)
N = 100
map_pos_x = np.random.randint(N, size=10000)
map_pos_y = np.random.randint(N, size=len(map_pos_x))
fig, (ax1, ax2) = plt.subplots(ncols=2)
testmap1, xedges, yedges = np.histogram2d(map_pos_x, map_pos_y, bins=N, range=[[0, N - 1], [0, N - 1]])
ax1.matshow(testmap1)
plt.show()
To show what happens, here is a test with N=10 with the matshow at the left. At right there is a scatter plot with semitransparent dots, making them darker when there are more dots coinciding.
a solution is this:
import numpy as np
import matplotlib.pyplot as plt
import random
import itertools
#gets as input the size of the axis and the number of pairs
def get_random_pairs(axis_range, count):
numbers = [i for i in range(0,axis_range)]
pairs = list(itertools.product(numbers,repeat=2))
return random.choices(pairs, k=count)
object_positions = get_random_pairs(100,100)
grid = np.zeros((100, 100))
for pixel in object_positions:
grid[pixel[0],pixel[1]] = np.random.random()
print(pixel)
plt.matshow(A=grid)
result:
edit:
since the grid is initialized to zero then just add the new value to the old one
n_pixels_x = 100
n_pixels_y = 100
map_pos_x = np.random.randint(100, size=100)
map_pos_y = np.random.randint(100, size=100)
map_pos = np.stack((map_pos_x, map_pos_y), axis=-1)
draw = np.random.random(100)
draw = np.reshape(draw, (100,1))
testmap = np.hstack((map_pos, draw))
grid = np.zeros((n_pixels_x, n_pixels_y))
for pixel in map_pos:
grid[pixel[0], pixel[1]] = grid[pixel[0], pixel[1]] + draw[i]
plt.matshow(A=grid)

How to animate converging iterations

I'm trying to animate the convergence over a number of iterations for the 1-D Gerchberg-Saxton algorithm but haven't managed to get it to work using matplotlib.animation or drawnow. I can plot them all on one graph simultaneously by using a for loop in the following code but would like to see them sequentially. Can anyone help?
import numpy as np
import pyfftw.interfaces.numpy_fft as fft
from matplotlib import pyplot as plt
aperture_width = 10
constraint_factor = 11
num_points = 2**16
aperture_x = np.linspace(-20*aperture_width, 20*aperture_width, num_points)
# object to be recovered
g = np.array( abs(aperture_x) < (aperture_width/2), dtype=np.complex128)
# Field at Image plane
G = fft.fftshift(fft.fft(fft.fftshift(g)))
#Intensity at Image plane
G = abs(G)**2 # Only known value
constraint = np.array( abs(aperture_x) < (constraint_factor/2)) #object constraint - guess of object size
Gp = np.copy(G) # make a copy of G
for i in range(10):
gp = fft.fftshift(fft.ifft(fft.fftshift(np.sqrt(Gp)))) # first object "guess"
gp = gp*constraint # apply constraint to object
Gp= fft.fftshift(fft.fft(fft.fftshift(gp))) # calculate new image plane
Gp = np.sqrt(G)*np.exp(1j*np.angle(Gp)) # apply image domain constraint
plt.plot(aperture_x,abs(G)**2/max(abs(G)**2),aperture_x,abs(Gp)**2/max(abs(Gp)**2))
plt.xlim(-3,3)
plt.pause(0.05)
plt.show()
to plot the animation use
plt.pause(0.05) #0.05 is time in secs
between the plots.
Here is an example code
import matplotlib as plt
listValues = [i for i in range(0,10)] #random list just to show the basic concept
figure = plt.figure() #initialise figure object outside the loop
ax = figure.add_subplot(1,1,1) # get the subplot
for idx in listValues:
ax.scatter(idx,idx) # here is your plotting
plt.pause(0.05)
plt.show()

Plot size = 1/{N∗⌈log2N⌉∗[(1/70)/60]} in matplotlib in python?

Similar with: Plot size = 1/{N∗⌈log2N⌉∗[(1/70)/60]} in R?
But with matplotlib in python (I guess it will be better to plot the function with matplotlib):
size = 1/{N∗⌈log_2(N)⌉∗[(a)/60]}
a = [1/70, 1/60, 1/50, 1/40]
How can I plot this function (for every value in a - it should be one graphic) with matplotlib in python?
(⌈⌉= ceil)
For example:
With label "size" for y-axis and "N" for the x-axis.
N >= 2, N is natural Number (2,3,4,5,6,...) (but it is not necessary to implement this... see picture above)
I have tried this one as a first approach:
import matplotlib.pyplot as plt
import numpy as np
n = np.arange(3,50,0.1)
size = (1)/n*np.ceil(np.log2(n))*((1/70)/60))
plt.plot(n,size)
plt.axis([3,50,0,550])
plt.show()
If you are looking to plot all the distinct segments and not as continuous lines, one way would be to look for discontinuities in the derivative. In this case, the slopes should always be increasing as n increases (n > 0), so you can look for when it violates this condition and then split the lines there.
import matplotlib.pyplot as plt
import numpy as np
from numpy import diff
n = np.arange(3,50,0.1)
a = [1/70,1/60,1/50,1/40]
discont = np.ones(len(n)-1) #array to show discontinuities
discont[1] = 0
for i in a:
size = 1/(n*np.ceil(np.log2(n))*(i/60))
derivs = diff(size)
for k in range(len(derivs)-2):
if derivs[k+1] > derivs[k]:
discont[k+2] = 0
segments = np.squeeze(np.asarray(discont.nonzero()))
for j in range(len(segments)-1):
start, stop = segments[j], segments[j+1]
plt.plot(n[start:stop],size[start:stop], 'b')
plt.axis([0,20,0,300])
plt.xlabel('N')
plt.ylabel('Size')
plt.grid()
plt.show()
This will produce the following plot:

Categories

Resources