Combining imshow with a user input - python

I am trying to use trackpy (henceforth tp) for particle tracking. I have a series of images of cell samples. Naturally, there is some noise in the image. The first step in tracking is to choose, from the first image of the series, which clusters are cells and which clusters are not. This is done in large part by tp.locate. This is not perfect. I would like to be able to go through the 'candidates' chosen by tp.locate and indicate if each is a cell or not a cell.
I created the function ID in order to do this. The goal is to go through the list of 'candidates' generated by tp.locate. I wanted to do this by displaying (via matplotlib's imshow function) each 'candidate' while simultaneously prompting a user input to indicate whether or not the 'candidate' is a cell.
The problem is that asking for a user input seems to suppress the output from the imshow function. Each pass through the for loop asks about a different candidate but the imshow window never actually shows the candidate. I do not know how to work around this and I feel that I am VERY CLOSE to my end goal so I would really appreciate input.
I do not need a GUI, but is there some way I could handle this using tkinter? I am not familiar with tkinter, but I have read some things which make me think I may be able to solve this problem with it.
import numpy as np
import matplotlib.pyplot as plt
def framer(f,image,windowsize=(60,100)):
arr = image[:,:] #This makes a copy of image, so that when the buffers are
#added for the following process, the input image (image)
#is not modified.
h = windowsize[0]
w = windowsize[1]
hbuffer = np.zeros((h/2,arr.shape[1]))
arr = np.concatenate((hbuffer,arr,hbuffer),axis=0) #Buffer takes care of situations
#where the crop window extends
#beyond input image dimensions
wbuffer = np.zeros((arr.shape[0],w/2))
arr = np.concatenate((wbuffer,arr,wbuffer),axis=1)
narr = np.zeros((f.shape[0],h,w)) #Initialize array of crop windows
for i in range(f.shape[0]):
crop_arr = arr[f.get_value(i,'y'):f.get_value(i,'y') + h,f.get_value(i,'x'):f.get_value(i,'x') + w] #THIS MIGHT BE BACKWARDS
narr[i] = crop_arr
return narr
def ID(f,image,windowsize=(60,100)):
arr = framer(f,image,windowsize=(60,100))
f_cop = f[:]
reslist = np.zeros((arr.shape[0]))
for i in range(arr.shape[0]):
plt.imshow(arr[i],cmap='gray')
plt.annotate('particle '+repr(i),xy=(f.get_value(i,'x'),\
f.get_value(i,'y')),xytext=(f.get_value(i,'x')+20,f.get_value(i,'y')+20),\
arrowprops=dict(facecolor='red', shrink=0.05),fontsize=12,color='r')
res = input('Is this a cell? 1 for yes, 0 for no, 5 to exit')
if res == 1 or res == 0:
reslist[i] = res
if res == 5:
break
else:
print('Must give a valid input! (0,1 or 5)')
f_cop['res'] = reslist
return f_cop[f_cop.res == 1]

Try something like:
fig, ax = plt.subplots(1, 1)
for i in range(arr.shape[0]):
ax.cla()
im = ax.imshow(arr[i], cmap='gray', interpolation='nearest')
plt.annotate('particle ' + repr(i), xy=(f.get_value(i, 'x'), f.get_value(i,'y')),
xytext=(f.get_value(i,'x')+20, f.get_value(i,'y')+20),
arrowprops=dict(facecolor='red', shrink=0.05),
fontsize=12,color='r')
fig.canvas.draw()
res = None
while res not in (0, 1, 5):
res = input('Is this a cell? 1 for yes, 0 for no, 5 to exit')
if res == 1 or res == 0:
reslist[i] = res
elif res == 5:
break
else:
print "should never get here"
You should avoid using pyplot is scripting as much as possible, the state machine can cause you great trouble.

Related

python numpy image arrays: Take a list of horizontally concatenated images and arrange them in a grid

I am trying to take a list of images from a source folder and put them together in a 2x2 grid, with some text underneath each image.
My code so far can scan a folder, add the appropriate text under each image, add a border, and display the images in a line, I'll omit that for brevity. I am making them all a line because later down the line I want to extend it to further grid sizes, so my theory was all images into a big line, then sort into a grid afterwards:
This line of images is a numpy array, concatenated together like so:
images_line = np.hstack( (np.asarray(i) for i in list_images_with_text ) )
print(images.ndim) > 3
print(images.size) > 1326000
print(images.shape) > (325, 1360, 3)
I have tried numpy's images.reshape() to take the array and turn it into a 2x2 grid of images ie:
ASIMOV DOOM
DUNKIRK METALLICA
single_width = horiz_img_size;
single_height = vert_img_size + info_box_height;
images = images.reshape(2*single_height,2*single_width,3)
but that didn't quite work out right:
I feel like I am close, but I can't quite get my head around 3d arrays. I'd like to extend this to more grid sizes, 4x4, 5x5, 5x4, etc. User-enterable if possible.
Here is a minimum reproducible example if you download the original image
import numpy as np
import glob
import PIL
from PIL import Image
single_width = 340
single_height = 325
im=Image.open('TDYZ0.jpg')
images = np.asarray(im)
images = images.reshape(2*single_height,2*single_width,3) # not quite right
images = PIL.Image.fromarray(images)
images.save('outImage.png')
Thanks all
For a MxN tiling of the images, perhaps you could try (I haven't tested):
images = images.reshape(M, N, single_height, single_width, 3)
So, for the 2x2 that you showed the output for, it would be:
images = images.reshape(2, 2, single_height, single_width, 3)
I solved it, albeit in a disgusting fashion. A string is built to form the correct heirarchy of np.block() commands, then is executed . Building on Daniel F's comment:
M = 2
N = 2
string = "images = np.block([["
maxpos = numfiles-1;
pos = 0;
try:
for j in range(0, N):
for i in range(0, M):
if pos > maxpos:
string+= f"[np.asarray(null_image)],"
else:
string += f"[list_images_with_text[{pos}]]"
pos=pos+1
if i < M-1:
string+=f","
if j < N-1:
string+=f"],["
string+="]])"
except:
print("oops")
exec(string);

"Button_Press_Event" Coordinates Being Called Before I Actually Click

I am plotting a fits image of a field of stars. I am then displaying circular apertures across the image on sufficiently bright stars. Next, I am trying to click on the star that I am interested in, and getting the brightness measurement out of the nearest circle. Finally, I want to use that brightness measurement for other calculations.
My problem is that the variable I declare to store the x,y coordinates of my click ("coords") seems to be getting called before I actually click, resulting in an empty array and errors.
A co-worker sent it to me many months ago, and once upon a time it worked flawlessly. But it seems that it's stopped working. It may be a result of updating some libraries/modules, but I can't be sure.
I have tried various combinations, including declaring "coords" in other function, in other locations, etc. I have tried changing the order of certain lines to perhaps get "coords" to be called later. I've also tried writing the code from the ground up, and have had many of the same errors. Because this specific code once worked, I attach it instead of my attempts.
To be honest, I don't fully understand the code, as I didn't write it. As a result, I don't understand what is being called when, or what any changes I've made actually do.
def plot_image(file, vmin=5, vmax=99, threshold=5, radius=25):
hdu=fits.open(file)
image = hdu[0].data
exptime = hdu[0].header['EXPTIME']
band = hdu[0].header['FILTERS']
airmass = hdu[0].header['AIRMASS']
readnoise = hdu[0].header['RN_01']
gain = hdu[0].header['GAIN_01']
obj = hdu[0].header['OBJECT']
sub = image[1500:2000,1500:2000]
bkg_sigma = mad_std(sub)
mean, median, std = sigma_clipped_stats(sub, sigma=3.0, maxiters=5)
daofind = photutils.DAOStarFinder(fwhm=2., threshold=threshold*bkg_sigma)
sources = daofind(sub - median)
positions = (sources['xcentroid'], sources['ycentroid'])
apertures = photutils.CircularAperture(positions, r=radius)
phot_table = photutils.aperture_photometry(sub - median, apertures)
pix = select(image, apertures)
print(pix)
if len(pix) == 2 or len(coords) == 2:
distance = np.sqrt((np.array(phot_table['xcenter'])-pix[0])**2 + (np.array(phot_table['ycenter'])-pix[1])**2)
star = np.argmin(dist)
counts = phot_table[star]['aperture_sum']
fluxfile = open('testfile.txt')
signal = (counts * gain) / exptime
err = np.sqrt(counts*gain + (readnoise**2*np.pi*radius**2))
else:
print('Pix length = 0')
def select(image, apertures, vmin = 5, vmax = 99):
global coords
coords = []
fig = plt.figure(figsize = (9,9))
ax = fig.add_subplot(111)
ax.imshow(image, cmap = 'gist_gray_r', origin='lower', vmin = np.percentile(image, vmin), vmax = np.percentile(image, vmax), interpolation='none')
apertures.plot(color='blue', lw=1.5, alpha=0.5, ax = ax)
ax.set_title('Hello')#label='Object: '+obj+'\nFilter: '+band)
cid = fig.canvas.mpl_connect('button_press_event', onclick)
plt.show()
fig.canvas.mpl_disconnect(cid)
if None in coords:
return [np.nan,np.nan]
else:
return np.round(coords)
def onclick(event):
x = event.xdata
y = event.ydata
global coords
coords = [x, y]
plt.close()
return
def closeonclick(event):
print('Close On Click')
plt.close()
return
Expected Result: The image is displayed with blue apertures overlaid. Then, I click on the desired star and the coordinates I click are stored to "coords" and printed to the console. The window displaying the image is closed alongside the previous step, as well. Finally, using those coordinates, it finds the nearest aperture and does some science with the resulting brightness.
Actual Result: "coords" is immediately printed (an empty list). Immediately after, the image is displayed. Clicking it does nothing. It doesn't change the value of "coords", nothing else is printed, nor does the window close.
I'll come back and delete this if it isn't right (I'd comment if I had the reputation), but it looks like you have to define a global variable outside any functions first, then use the keyword before the variable name inside a function to change its scope. Try moving "coords = []" outside your functions (the list will no longer be empty after the first call to "onclick", but each new click should replace the coordinates so it shouldn't be an issue).
Ref: https://www.programiz.com/python-programming/global-keyword

Plotting a new image without using the old one in matplotlib?

I'm new to python and matplotlib.
I have implemented the k means algorithm in order to compress and image to
clusters and then plotting the changed image.
my question is: I was not able to plot the new image without using
the old one as a base, I tried a few things but could not quite get the result I want. and it's bad programming if I pass the old image as argument when I can definitely not use it.
Can someone please help?
I tried to create a new ndarray but it did not work.
Here is my function:
def changePic(newPixelList, oldPixel, image_size):
index = 0
new_pixels = []
for pixel in newPixelList:
oldPixel[index] = pixel.classification
index+=1
l = oldPixel.reshape(image_size)
plt.imshow(l)
plt.grid(False)
plt.show()
As you can see I don't really use the oldPixel values, just its structure.
now I'll show you the type of oldPixel:
Here is my loadPic method where X.copy is the argument oldPixel:
def loadPic():
"""
Load pic to array
:return: copy of original X, new lisf of pixels, image size
"""
# data preperation (loading, normalizing, reshaping)
path = 'dog.jpeg'
A = imread(path)
A = A.astype(float) / 255.
img_size = A.shape
X = A.reshape(img_size[0] * img_size[1], img_size[2])
listOfPixel= []
for pixel in X:
listOfPixel.append(Pixel(pixel))
return X.copy(), listOfPixel,img_size
Try this:
def changePic(newPixelList, oldPixel, image_size, picture_num):
index = 0
new_pixels = []
for pixel in newPixelList:
oldPixel[index] = pixel.classification
index+=1
l = oldPixel.reshape(image_size)
plt.figure(picture_num)
plt.imshow(l)
plt.grid(False)
plt.show()
Every plot that you generate should have a different picture_num in order to have separate plots.

Creating moving images in python

I am wondering what's the best approach to turn a large number of images into a moving one in Python. A lot of examples I've found seem to deal with actual videos or video games, such as pygame, which seems over complicated for what I'm looking to do.
I have created a loop, and would like the image to update every time the code runs through the loop. Is there a possibly a method in python to overplot each image and erase the previous image with each iteration?
sweeps_no = 10
for t in range(sweeps_no):
i = np.random.randint(N)
j = np.random.randint(N)
arr = nearestneighbours(lat, N, i, j)
energy = delta_E(lat[i,j], arr, J)
if energy <= 0:
matrix[i,j] *= matrix[i,j]
elif np.exp(energy/T) >= np.random.random():
matrix[i,j] *= -matrix[i,j]
else:
matrix[i,j] = matrix[i,j]
t +=1
print t
res.append(switch)
image = plt.imshow(lat)
plt.show()
Also, I can't understand why the loop above doesn't result in 10 different images showing up when the image is contained in the loop.
You can update a single figure using fig.canvas.draw() after your call to imshow(). It is important to include a pause i.e. plt.pause(2), so that you can see the changes to your figure.
The following is a runnable example:
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure() # create the figure
for i in range(10):
data = np.random.randn(25).reshape(5,5) # some fake data
plt.imshow(data)
fig.canvas.draw()
plt.pause(2) # pause for 2 seconds

Python: Calculating the area enclosed by a contour using multi-threading friendly code

I would like to extend this question to find the area of a contour in a multi-threading friendly way.
I've tried the following code:
c = cntr.Cntr(Z, X_, radial)
# Trace a contour at calc_levels
for loop in range(len(calc_levels)):
res = c.trace(calc_levels[loop])
# result is a list of arrays of vertices and path codes
# (see docs for matplotlib.path.Path)
nseg = len(res) // 2
segments = res[:nseg]
area = PolyArea(segments[0][:,0], segments[0][:,1])
def PolyArea(x,y):
return 0.5*np.abs(np.dot(x,np.roll(y,1))-np.dot(y,np.roll(x,1)))
In which I've tried adapting the code from here and where the code for PolyArea comes from this question But 1) the calculation of the area is not quite correct and 2) it slows down my multi-threaded code.
I say that the area is not correct because with the following code, which I spent a long time checking, I get completely different answers. However, this code is DEFINITELY NOT multi-threading compatible and causes the whole code to grind to a halt
fig1 = plt.figure()
ax1 = fig1.add_subplot(111)
cs = ax1.contour(Z,X_,radial, levels = calc_levels,colors='k')
for loop in range(len(calc_levels)):
vs = None
contour_ = None
contour_ = cs.collections[loop]
vs = contour_.get_paths()[0].vertices
# Compute area enclosed by vertices
features['Radial_Area_' + mystr + str(int(thisLevels[loop]*100))] = area(vs)
Is anyone able to help me either debug the first section of code, or write something different that will work better?
Thanks
Sorry, I wasn't comparing like with like. Now looking at the same file I see that the calculated areas are the same! So the code
c = cntr.Cntr(Z, X_, radial)
# Trace a contour at calc_levels
for loop in range(len(calc_levels)):
res = c.trace(calc_levels[loop])
# result is a list of arrays of vertices and path codes
# (see docs for matplotlib.path.Path)
nseg = len(res) // 2
segments = res[:nseg]
area = PolyArea(segments[0][:,0], segments[0][:,1])
def PolyArea(x,y):
return 0.5*np.abs(np.dot(x,np.roll(y,1))-np.dot(y,np.roll(x,1)))
Does work how I thought it should and is much faster than the other code.
I will delete this post later giving those who are looking at time to see my answer

Categories

Resources