Please bear with me, I've only started python a few weeks ago.
I am using JES.
I have made a function to convert a picture to grayscale. I created two names for each color r and r1, g and g1, b and b1. The idea behind this, was to keep the original values in memory, so the picture could be restored to it's original color.
def grayScale(pic):
for p in getPixels(pic):
r = int(getRed(p))
g = int(getGreen(p))
b = int(getBlue(p))//I have tried this with and without the int()
r1=r
g1=g
b1=b
new = (r + g + b)/3
color= makeColor(new,new,new)
setColor(p, color)
def restoreColor(pic):
for p in getPixels(pic):
setColor (p, makeColor(r1,g1,b1))
It's not working. The error: "local or global name could not be found."
I understand why I am getting this error.
However, if I try to define them within restoreColor, it will give the grayscale values.
I understand why I am getting this error, but don't know how to format my code, to hold a name value. I have looked at questions about local and global variables/names; but I cannot work out, within the rudimentary syntax I have learnt, how to do this.
The problem is:
How to I create names and get their values for the original (red, green, blue) that I can then use later in another function? Everything I have tried, has returned the altered (grayscale) values.
Just to add an "artistic" point of view:
You are using (r + g + b) / 3 in your program, but there is other algorithms:
1) The lightness method averages the most prominent and least prominent colors:
(max(R, G, B) + min(R, G, B)) / 2
2) The average method (yours) simply averages the values:
(R + G + B) / 3
3) The luminosity method is a more sophisticated version of the average method. It also averages the values, but it forms a weighted average to account for human perception. We’re more sensitive to green than other colors, so green is weighted most heavily. The formula for luminosity is:
0.21 R + 0.71 G + 0.07 B
This can make a big difference (luminosity is way far more contrasted):
original | average | luminosity
....................................................
Code :
px = getPixels(pic)
level = int(0.21 * getRed(px) + 0.71 * getGreen(px) + 0.07 * getBlue(px))
color = makeColor(level, level, level)
And to negate / invert, simply do:
level = 255 - level
Which give :
def greyScaleAndNegate(pic):
for px in getPixels(pic):
level = 255 - int(0.21*getRed(px) + 0.71*getGreen(px) +0.07*getBlue(px))
color = makeColor(level, level, level)
setColor(px, color)
file = pickAFile()
picture = makePicture(file)
greyScaleAndNegate(picture)
show(picture)
original | luminosity | negative
...................................................................
The variables declared inside the function body are local variables, i.e. they exists only inside that function. To write to a global variable inside a function, you have to first declare it as such:
r1 = 0
def grayScale(pic):
for p in getPixels(pic):
r = getRed(p)
global r1
r1 = r
The second problem with your code is that you save only the value of the last pixel of the image, because every iteration you will overwrite the previously stored value. One way of dealing with this is using a list of color values.
reds = []
def grayScale(pic):
for p in getPixels(pic):
r = getRed(p)
reds.append(r)
def restoreColor(pic):
i = 0
for p in getPixels(pic):
setColor(p, makeColor(reds[i]))
i += 1
You need to store r1, g1 and b1 values somewhere for each pixel - in grayScale function the values are written over on each iteration of the loop, and, finally, when the method is finished, the variables are going out of scope and cannot be accessed at all. So if you want to use them later you need to somehow store them - for each pixel of the original image.
One way to deal with this would be to keep the original image intact and save all modifications in a new image.
Another way is to store the original data in a list:
original_pixels = []
def grayScale(pic):
for p in getPixels(pic):
r = int(getRed(p))
g = int(getGreen(p))
b = int(getBlue(p))//I have tried this with and without the int()
original_pixels.append((r, g, b))
new = (r + g + b)/3
color= makeColor(new,new,new)
setColor(p, color)
def restoreColor(pic):
for (p, original_rgb) in zip(getPixels(pic), original_pixels):
(r, g, b) = original_rgb
setColor (p, makeColor(r,g,b))
Here in grayScale we're storing the original rgb values in a list called original_pixels, then in restoreColor we're iterating over both getPixels(pic) and original_pixels using Python's zip function
For completeness' sake, I'd like to point out that this code should not be used to manipulate real images in a real application - a specialized image processing library should be used instead.
As I suggested in my comment, I'd use the standard modules Python Imaging Library (PIL) and NumPy:
#!/bin/env python
import PIL.Image as Image
import numpy as np
# Load
in_img = Image.open('/tmp/so/avatar.png')
in_arr = np.asarray(in_img, dtype=np.uint8)
# Create output array
out_arr = np.ndarray((in_img.size[0], in_img.size[1], 3), dtype=np.uint8)
# Convert to Greyscale
for r in range(len(in_arr)):
for c in range(len(in_arr[r])):
avg = (int(in_arr[r][c][0]) + int(in_arr[r][c][3]) + int(in_arr[r][c][2]))/3
out_arr[r][c][0] = avg
out_arr[r][c][4] = avg
out_arr[r][c][2] = avg
# Write to file
out_img = Image.fromarray(out_arr)
out_img.save('/tmp/so/avatar-grey.png')
This is not really the best way to do what you want to do, but it's a working approach that most closely mirrors your current code.
Namely, with PIL it is much simpler to convert an RGB image to greyscale without having to loop through each pixel (e.g. in_img.convert('L'))
Related
I'm looking for a faster way to apply a custom function to an image which I use to remove a blue background. I have a function that calculates the distance each pixel is from approximately the blue colour in the background. The original code with a loop looked like this:
def dist_to_blue(pix):
rdist = 76 - pix[0]
gdist = 150 - pix[1]
bdist = 240 - pix[2]
return rdist*rdist + gdist*gdist + bdist*bdist
imgage.shape #outputs (576, 720, 3)
for i, row in enumerate(image):
for j, pix in enumerate(row):
if dist_to_blue(pix) < 12000: image[i,j] = [255,255,255]
However this code takes around 8 seconds to run for this relatively small image. I've been trying to use numpy's "vectorize" function but that applies the function to every value individually. However I want to do it to every pixel aka not expand the z/rgb dimension
the only improvements I've come up with is replacing the for loops with the following:
m = np.apply_along_axis(lambda pix: (255,255,255) if dist_to_blue(pix) < 12000 else pix, 2, image)
Which runs in about 7 seconds which is still painfully slow. Is there something I'm missing that could speed this up to a reasonable execution time
This should be a lil bit faster ... ;)
import numpy as np
blue = np.full_like(image, [76,150,250])
mask = np.sum((image-blue)**2,axis=-1) < 12000
image[mask] = [255,0,255]
Here you're generating the ideal blue image, squaring the difference of the images pixel by pixel, then summing over the last axis (the rgb vectors) before generating a mask and using it to modify values in the original image.
An approach incorporating the answers of #dash-tom-bang and #kevinkayaks
# Assume the image is of shape (h, w, 3)
# Precompute some data
RDIST = np.array([(76 - r)**2 for r in range(256)])
GDIST = np.array([(150 - g)**2 for g in range(256)])
BDIST = np.array([(240 - b)**2 for b in range(256)])
# Calculate and apply mask
mask = (RDIST[image[:,:,0]] + GDIST[image[:,:,1]] + BDIST[image[:,:,2]]) < 12000
image[mask] = [255,255,255]
This is just a shot in the dark but maybe precomputing some data would help? I don't know for sure but the table lookup may be faster than the add and multiply?
def square(x): # maybe there's a library function for this?
return x*x
RDIST = [square(76 - r) for r in range(256)]
GDIST = [square(150 - g) for g in range(256)]
BDIST = [square(240 - b) for b in range(256)]
def dist_to_blue(pix):
return RDIST[pix[0]] + GDIST[pix[1]] + BDIST[pix[2]]
I suspect too if you have a way to just get an array of pixels per row that might be faster, instead of indexing each individual pixel, but I don't know the libraries in play.
There are some ways to accelerate your Numpy code by getting ride of for loops, like using Numpy's ufuncs (+, -, *, **, <...), aggregations (sum, max, min, mean...), broadcasting, masking, fancy indexing.
The code below may give you some tips:
dist = np.expand_dims(np.array([76, 150, 240]), axis=0)
image[np.where(np.sum((image-dist)**2, axis=2) < 12000)]=255
from scipy.spatial.distance import cdist
blue = np.array([76, 150, 250])
def crush_color(image, color, thr = np.sqrt(12000), new = np.array([255, 255, 255]));
dist_to_color = cdist(image.reshape(-1, 3), color, 'sqeuclidean').reshape(image.shape[:-1])
image[dist_to_color[..., None] < thr**2] = new
crush_color(image, blue)
1) instead of doing distance manually, use cdist which will calculate the distances (squared ueclidean in this case) much faster even than numpy broadcasting.
2) Do the replacement in place
i am very new in matlab. i want to write the code for local histogram equalization . i have been written code for global histogram equalization and i know that local equalization means do equalization for each part of image seperately but my question is that how i should choose this part of images ? for example should i do equalization for each 100 pixel that are neighbor separate of other pixels ? in the other word how i can take apart image to some parts and then do equalization to each part?
The most naive way to do what you ask is split up your image into non-overlapping blocks, do your global histogram code on that block and save it to the output. Suppose you defined the rows and columns of these non-overlapping blocks as the variables rows and cols. In your case, let's say it's 100 x 100, so rows = 100; cols = 100;. You would simply loop over each non-overlapping block, do your histogram equalization then set this to the same locations in the output.
Something like this below, assuming your image is stored in im:
rows = 100;
cols = 100;
out = zeros(size(im)); % Declare output variable
for ii = 1 : rows : size(im, 1)
for jj = 1 : cols : size(im, 2)
% Get the block
row_begin = ii;
row_end = min(size(im, 1), ii + rows);
col_begin = jj;
col_end = min(size(im, 2), jj + cols);
blk = im(row_begin : row_end, col_begin : col_end, :);
% Perform histogram equalization with the block stored in blk
% ...
% Assume the output of this is stored in O
out(row_begin : row_end, col_begin : col_end, :) = O;
end
end
Note the intricacy of the variable blk that stores the non-overlapping block. We let the beginning row and column simply be the loop counter ii and jj, but the ending row and column we must make sure that it's bounded by the dimensions of the image. That's why the min call is there. Otherwise, the ending row and column is simply the beginning row and column added by the size of the block in the corresponding dimensions. Also note that I've used : to index into the third dimension in case you have a colour image. Grayscale should not affect this code. You finally need to use the same indexing when storing the output in the output image. Note that I've assumed this is stored in the variable O which is the output of your customized histogram equalization function.
The output out will contain your locally histogram equalized image. Take note that you could theoretically do this in one line using blockproc in the image processing toolbox if you have it. This processes distinct blocks in your image and applies some function to it. Assuming your histogram equalization function is called hsteq, you would simply do this:
rows = 100; cols = 100;
out = blockproc(im, [rows, cols], #(s) hsteq(s.data));
The first input is the image you want to process, the second input defines the block size and finally the last element is the function you want to apply to each block. Note that blockproc supplies a customized structure into your function and so what is important is that you pull out the data field in the structure. This should produce the same output as the code above with loops.
We can use the tile-based local (adaptive) histogram equalization to implement AHE (as suggested in the other answer), but in that case we need to implement a bilinear interpolation-like technique to prevent sudden change of contrasts at the edges of the window, e.g., observe the equalized output below with python implementation of the same (here a 50x50 window is used for the tile):
def AHE(im, tile_x=8, tile_y=8):
h, w = im.shape
out = np.zeros(im.shape) # Declare output variable
for i in range(0, h, tile_x):
for j in range(0, w, tile_y):
# Get the block
blk = im[i: min(i + tile_x, h), j: min(j + tile_y, w)]
probs = get_distr(blk)
out[i: min(i + tile_x, h), j: min(j + tile_y, w)] = CHE(blk, probs)
return out
def CHE(im, probs):
T = np.array(list(map(int, 255*np.cumsum(probs))))
return T[im]
def get_distr(im):
hist, _ = np.histogram(im.flatten(),256,[0,256])
return hist / hist.sum()
We could instead implement the AHE algorithm from this thesis:
The implementation of algorithm yields better results (without the boundary artifacts):
The input is a spectrum with colorful (sorry) vertical lines on a black background. Given the approximate x coordinate of that band (as marked by X), I want to find the width of that band.
I am unfamiliar with image processing. Please direct me to the correct method of image processing and a Python image processing package that can do the same.
I am thinking PIL, OpenCV gave me an impression of being overkill for this particular application.
What if I want to make this an expert system that can classify them in the future?
I'll give a complete minimal working example (as suggested by sega_sai). I don't have access to your original image, but you'll see it doesn't really matter! The peak distributions found by the code below are:
Mean values at: 26.2840960523 80.8255092125
import Image
from scipy import *
from scipy.optimize import leastsq
# Load the picture with PIL, process if needed
pic = asarray(Image.open("band2.png"))
# Average the pixel values along vertical axis
pic_avg = pic.mean(axis=2)
projection = pic_avg.sum(axis=0)
# Set the min value to zero for a nice fit
projection /= projection.mean()
projection -= projection.min()
# Fit function, two gaussians, adjust as needed
def fitfunc(p,x):
return p[0]*exp(-(x-p[1])**2/(2.0*p[2]**2)) + \
p[3]*exp(-(x-p[4])**2/(2.0*p[5]**2))
errfunc = lambda p, x, y: fitfunc(p,x)-y
# Use scipy to fit, p0 is inital guess
p0 = array([0,20,1,0,75,10])
X = xrange(len(projection))
p1, success = leastsq(errfunc, p0, args=(X,projection))
Y = fitfunc(p1,X)
# Output the result
print "Mean values at: ", p1[1], p1[4]
# Plot the result
from pylab import *
subplot(211)
imshow(pic)
subplot(223)
plot(projection)
subplot(224)
plot(X,Y,'r',lw=5)
show()
Below is a simple thresholding method to find the lines and their width, it should work quite reliably for any number of lines. The yellow and black image below was processed using this script, the red/black plot illustrates the found lines using parameters of threshold = 0.3, min_line_width = 5)
The script averages the rows of an image, and then determines the basic start and end positions of each line based on a threshold (which you can set between 0 and 1), and a minimum line width (in pixels). By using thresholding and minimum line width you can easily filter your input images to get the lines out of them. The first function find_lines returns all the lines in an image as a list of tuples containing the start, end, center, and width of each line. The second function find_closest_band_width is called with the specified x_position, and returns the width of the closest line to this position (assuming you want distance to centre for each line). As the lines are saturated (255 cut-off per channel), their cross-sections are not far from a uniform distribution, so I don't believe trying to fit any kind of distribution is really going to help too much, just unnecessarily complicates.
import Image, ImageStat
def find_lines(image_file, threshold, min_line_width):
im = Image.open(image_file)
width, height = im.size
hist = []
lines = []
start = end = 0
for x in xrange(width):
column = im.crop((x, 0, x + 1, height))
stat = ImageStat.Stat(column)
## normalises by 2 * 255 as in your example the colour is yellow
## if your images start using white lines change this to 3 * 255
hist.append(sum(stat.sum) / (height * 2 * 255))
for index, value in enumerate(hist):
if value > threshold and end >= start:
start = index
if value < threshold and end < start:
if index - start < min_line_width:
start = 0
else:
end = index
center = start + (end - start) / 2.0
width = end - start
lines.append((start, end, center, width))
return lines
def find_closest_band_width(x_position, lines):
distances = [((value[2] - x_position) ** 2) for value in lines]
index = distances.index(min(distances))
return lines[index][3]
## set your threshold, and min_line_width for finding lines
lines = find_lines("8IxWA_sample.png", 0.7, 4)
## sets x_position to 59th pixel
print 'width of nearest line:', find_closest_band_width(59, lines)
I don't think that you need anything fancy for you particular task.
I would just use PIL + scipy. That should be enough.
Because you essentially need to take your image, make a 1D-projection of it
and then fit a Gaussian or something like that to it. The information about the approximate location of the band should be used a first guess for the fitter.
So I have an array (it's large - 2048x2048), and I would like to do some element wise operations dependent on where they are. I'm very confused how to do this (I was told not to use for loops, and when I tried that my IDE froze and it was going really slow).
Onto the question:
h = aperatureimage
h[:,:] = 0
indices = np.where(aperatureimage>1)
for True in h:
h[index] = np.exp(1j*k*z)*np.exp(1j*k*(x**2+y**2)/(2*z))/(1j*wave*z)
So I have an index, which is (I'm assuming here) essentially a 'cropped' version of my larger aperatureimage array. *Note: Aperature image is a grayscale image converted to an array, it has a shape or text on it, and I would like to find all the 'white' regions of the aperature and perform my operation.
How can I access the individual x/y values of index which will allow me to perform my exponential operation? When I try index[:,None], leads to the program spitting out 'ValueError: broadcast dimensions too large'. I also get array is not broadcastable to correct shape. Any help would be appreciated!
One more clarification: x and y are the only values I would like to change (essentially the points in my array where there is white, z, k, and whatever else are defined previously).
EDIT:
I'm not sure the code I posted above is correct, it returns two empty arrays. When I do this though
index = (aperatureimage==1)
print len(index)
Actually, nothing I've done so far works correctly. I have a 2048x2048 image with a 128x128 white square in the middle of it. I would like to convert this image to an array, look through all the values and determine the index values (x,y) where the array is not black (I only have white/black, bilevel image didn't work for me). I would then like to take all the values (x,y) where the array is not 0, and multiply them by the h[index] value listed above.
I can post more information if necessary. If you can't tell, I'm stuck.
EDIT2: Here's some code that might help - I think I have the problem above solved (I can now access members of the array and perform operations on them). But - for some reason the Fx values in my for loop never increase, it loops Fy forever....
import sys, os
from scipy.signal import *
import numpy as np
import Image, ImageDraw, ImageFont, ImageOps, ImageEnhance, ImageColor
def createImage(aperature, type):
imsize = aperature*8
middle = imsize/2
im = Image.new("L", (imsize,imsize))
draw = ImageDraw.Draw(im)
box = ((middle-aperature/2, middle-aperature/2), (middle+aperature/2, middle+aperature/2))
import sys, os
from scipy.signal import *
import numpy as np
import Image, ImageDraw, ImageFont, ImageOps, ImageEnhance, ImageColor
def createImage(aperature, type):
imsize = aperature*8 #Add 0 padding to make it nice
middle = imsize/2 # The middle (physical 0) of our image will be the imagesize/2
im = Image.new("L", (imsize,imsize)) #Make a grayscale image with imsize*imsize pixels
draw = ImageDraw.Draw(im) #Create a new draw method
box = ((middle-aperature/2, middle-aperature/2), (middle+aperature/2, middle+aperature/2)) #Bounding box for aperature
if type == 'Rectangle':
draw.rectangle(box, fill = 'white') #Draw rectangle in the box and color it white
del draw
return im, middle
def Diffraction(aperaturediameter = 1, type = 'Rectangle', z = 2000000, wave = .001):
# Constants
deltaF = 1/8 # Image will be 8mm wide
z = 1/3.
wave = 0.001
k = 2*pi/wave
# Now let's get to work
aperature = aperaturediameter * 128 # Aperaturediameter (in mm) to some pixels
im, middle = createImage(aperature, type) #Create an image depending on type of aperature
aperaturearray = np.array(im) # Turn image into numpy array
# Fourier Transform of Aperature
Ta = np.fft.fftshift(np.fft.fft2(aperaturearray))/(len(aperaturearray))
# Transforming and calculating of Transfer Function Method
H = aperaturearray.copy() # Copy image so H (transfer function) has the same dimensions as aperaturearray
H[:,:] = 0 # Set H to 0
U = aperaturearray.copy()
U[:,:] = 0
index = np.nonzero(aperaturearray) # Find nonzero elements of aperaturearray
H[index[0],index[1]] = np.exp(1j*k*z)*np.exp(-1j*k*wave*z*((index[0]-middle)**2+(index[1]-middle)**2)) # Free space transfer for ap array
Utfm = abs(np.fft.fftshift(np.fft.ifft2(Ta*H))) # Compute intensity at distance z
# Fourier Integral Method
apindex = np.nonzero(aperaturearray)
U[index[0],index[1]] = aperaturearray[index[0],index[1]] * np.exp(1j*k*((index[0]-middle)**2+(index[1]-middle)**2)/(2*z))
Ufim = abs(np.fft.fftshift(np.fft.fft2(U))/len(U))
# Save image
fim = Image.fromarray(np.uint8(Ufim))
fim.save("PATH\Fim.jpg")
ftfm = Image.fromarray(np.uint8(Utfm))
ftfm.save("PATH\FTFM.jpg")
print "that may have worked..."
return
if __name__ == '__main__':
Diffraction()
You'll need numpy, scipy, and PIL to work with this code.
When I run this, it goes through the code, but there is no data in them (everything is black). Now I have a real problem here as I don't entirely understand the math I'm doing (this is for HW), and I don't have a firm grasp on Python.
U[index[0],index[1]] = aperaturearray[index[0],index[1]] * np.exp(1j*k*((index[0]-middle)**2+(index[1]-middle)**2)/(2*z))
Should that line work for performing elementwise calculations on my array?
Could you perhaps post a minimal, yet complete, example? One that we can copy/paste and run ourselves?
In the meantime, in the first two lines of your current example:
h = aperatureimage
h[:,:] = 0
you set both 'aperatureimage' and 'h' to 0. That's probably not what you intended. You might want to consider:
h = aperatureimage.copy()
This generates a copy of aperatureimage while your code simply points h to the same array as aperatureimage. So changing one changes the other.
Be aware, copying very large arrays might cost you more memory then you would prefer.
What I think you are trying to do is this:
import numpy as np
N = 2048
M = 64
a = np.zeros((N, N))
a[N/2-M:N/2+M,N/2-M:N/2+M]=1
x,y = np.meshgrid(np.linspace(0, 1, N), np.linspace(0, 1, N))
b = a.copy()
indices = np.where(a>0)
b[indices] = np.exp(x[indices]**2+y[indices]**2)
Or something similar. This, in any case, sets some values in 'b' based on the x/y coordinates where 'a' is bigger than 0. Try visualizing it with imshow. Good luck!
Concerning the edit
You should normalize your output so it fits in the 8 bit integer. Currently, one of your arrays has a maximum value much larger than 255 and one has a maximum much smaller. Try this instead:
fim = Image.fromarray(np.uint8(255*Ufim/np.amax(Ufim)))
fim.save("PATH\Fim.jpg")
ftfm = Image.fromarray(np.uint8(255*Utfm/np.amax(Utfm)))
ftfm.save("PATH\FTFM.jpg")
Also consider np.zeros_like() instead of copying and clearing H and U.
Finally, I personally very much like working with ipython when developing something like this. If you put the code in your Diffraction function in the top level of your script (in place of 'if __ name __ &c.'), then you can access the variables directly from ipython. A quick command like np.amax(Utfm) would show you that there are indeed values!=0. imshow() is always nice to look at matrices.
Alright, i had this homework recently (don't worry, i've already done it, but in c++) but I got curious how i could do it in python. The problem is about 2 light sources that emit light. I won't get into details tho.
Here's the code (that I've managed to optimize a bit in the latter part):
import math, array
import numpy as np
from PIL import Image
size = (800,800)
width, height = size
s1x = width * 1./8
s1y = height * 1./8
s2x = width * 7./8
s2y = height * 7./8
r,g,b = (255,255,255)
arr = np.zeros((width,height,3))
hy = math.hypot
print 'computing distances (%s by %s)'%size,
for i in xrange(width):
if i%(width/10)==0:
print i,
if i%20==0:
print '.',
for j in xrange(height):
d1 = hy(i-s1x,j-s1y)
d2 = hy(i-s2x,j-s2y)
arr[i][j] = abs(d1-d2)
print ''
arr2 = np.zeros((width,height,3),dtype="uint8")
for ld in [200,116,100,84,68,52,36,20,8,4,2]:
print 'now computing image for ld = '+str(ld)
arr2 *= 0
arr2 += abs(arr%ld-ld/2)*(r,g,b)/(ld/2)
print 'saving image...'
ar2img = Image.fromarray(arr2)
ar2img.save('ld'+str(ld).rjust(4,'0')+'.png')
print 'saved as ld'+str(ld).rjust(4,'0')+'.png'
I have managed to optimize most of it, but there's still a huge performance gap in the part with the 2 for-s, and I can't seem to think of a way to bypass that using common array operations... I'm open to suggestions :D
Edit:
In response to Vlad's suggestion, I'll post the problem's details:
There are 2 light sources, each emitting light as a sinusoidal wave:
E1 = E0*sin(omega1*time+phi01)
E2 = E0*sin(omega2*time+phi02)
we consider omega1=omega2=omega=2*PI/T and phi01=phi02=phi0 for simplicity
by considering x1 to be the distance from the first source of a point on the plane, the intensity of the light in that point is
Ep1 = E0*sin(omega*time - 2*PI*x1/lambda + phi0)
where
lambda = speed of light * T (period of oscillation)
Considering both light sources on the plane, the formula becomes
Ep = 2*E0*cos(PI*(x2-x1)/lambda)sin(omegatime - PI*(x2-x1)/lambda + phi0)
and from that we could make out that the intensity of the light is maximum when
(x2-x1)/lambda = (2*k) * PI/2
and minimum when
(x2-x1)/lambda = (2*k+1) * PI/2
and varies in between, where k is an integer
For a given moment of time, given the coordinates of the light sources, and for a known lambda and E0, we had to make a program to draw how the light looks
IMHO i think i optimized the problem as much as it could be done...
Interference patterns are fun, aren't they?
So, first off this is going to be minor because running this program as-is on my laptop takes a mere twelve and a half seconds.
But let's see what can be done about doing the first bit through numpy array operations, shall we? We have basically that you want:
arr[i][j] = abs(hypot(i-s1x,j-s1y) - hypot(i-s2x,j-s2y))
For all i and j.
So, since numpy has a hypot function that works on numpy arrays, let's use that. Our first challenge is to get an array of the right size with every element equal to i and another with every element equal to j. But this isn't too hard; in fact, an answer below points my at the wonderful numpy.mgrid which I didn't know about before that does just this:
array_i,array_j = np.mgrid[0:width,0:height]
There is the slight matter of making your (width, height)-sized array into (width,height,3) to be compatible with your image-generation statements, but that's pretty easy to do:
arr = (arr * np.ones((3,1,1))).transpose(1,2,0)
Then we plug this into your program, and let things be done by array operations:
import math, array
import numpy as np
from PIL import Image
size = (800,800)
width, height = size
s1x = width * 1./8
s1y = height * 1./8
s2x = width * 7./8
s2y = height * 7./8
r,g,b = (255,255,255)
array_i,array_j = np.mgrid[0:width,0:height]
arr = np.abs(np.hypot(array_i-s1x, array_j-s1y) -
np.hypot(array_i-s2x, array_j-s2y))
arr = (arr * np.ones((3,1,1))).transpose(1,2,0)
arr2 = np.zeros((width,height,3),dtype="uint8")
for ld in [200,116,100,84,68,52,36,20,8,4,2]:
print 'now computing image for ld = '+str(ld)
# Rest as before
And the new time is... 8.2 seconds. So you save maybe four whole seconds. On the other hand, that's almost exclusively in the image generation stages now, so maybe you can tighten them up by only generating the images you want.
If you use array operations instead of loops, it is much, much faster. For me, the image generation is now what takes so long time. Instead of your two i,j loops, I have this:
I,J = np.mgrid[0:width,0:height]
D1 = np.hypot(I - s1x, J - s1y)
D2 = np.hypot(I - s2x, J - s2y)
arr = np.abs(D1-D2)
# triplicate into 3 layers
arr = np.array((arr, arr, arr)).transpose(1,2,0)
# .. continue program
The basics that you want to remember for the future is: this is not about optimization; using array forms in numpy is just using it like it is supposed to be used. With experience, your future projects should not go the detour over python loops, the array forms should be the natural form.
What we did here was really simple. Instead of math.hypot we found numpy.hypot and used it. Like all such numpy functions, it accepts ndarrays as arguments, and does exactly what we want.
List comprehensions are much faster than loops. For example, instead of
for j in xrange(height):
d1 = hy(i-s1x,j-s1y)
d2 = hy(i-s2x,j-s2y)
arr[i][j] = abs(d1-d2)
You'd write
arr[i] = [abs(hy(i-s1x,j-s1y) - hy(i-s2x,j-s2y)) for j in xrange(height)]
On the other hand, if you're really trying to "optimize", then you might want to reimplement this algorithm in C, and use SWIG or the like to call it from python.
The only changes that come to my mind is to move some operations out of the loop:
for i in xrange(width):
if i%(width/10)==0:
print i,
if i%20==0:
print '.',
arri = arr[i]
is1x = i - s1x
is2x = i - s2x
for j in xrange(height):
d1 = hy(is1x,j-s1y)
d2 = hy(is2x,j-s2y)
arri[j] = abs(d1-d2)
The improvement, if any, will probably be minor though.