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
Related
Two sections of my code are giving me trouble, I am trying to get the basemap created in this first section here:
#Basemap
epsg = 6060; width = 2000.e3; height = 2000.e3 #epsg 3413. 6062
m=Basemap(epsg=epsg,resolution='l',width=width,height=height) #lat_ts=(90.+35.)/2.
m.drawcoastlines(color='white')
m.drawmapboundary(fill_color='#99ffff')
m.fillcontinents(color='#cc9966',lake_color='#99ffff')
m.drawparallels(np.arange(10,70,20),labels=[1,1,0,0])
m.drawmeridians(np.arange(-100,0,20),labels=[0,0,0,1])
plt.title('ICESAT2 Tracks in Greenland')
plt.figure(figsize=(20,10))
Then my next section is meant to plot the data its getting from a file, and plot these tracks on top of the Basemap. Instead, it creates a new plot entirely. I have tried rewording the secondary plt.scatter to match Basemap, such as m.scatter, m.plt, etc. But it only returns with “RuntimeError: Can not put single artist in more than one figure” when I do so.
Any ideas on how to get this next section of code onto the basemap? Here is the next section, focus on the end to see where it is plotting.
icesat2_data[track] = dict() # creates a sub-dictionary, track
icesat2_data[track][year+month+day] = dict() # and one layer more for the date under the whole icesat2_data dictionary
icesat2_data[track][year+month+day] = dict.fromkeys(lasers)
for laser in lasers: # for loop, access all the gt1l, 2l, 3l
if laser in f:
lat = f[laser]["land_ice_segments"]["latitude"][:] # data for a particular laser's latitude.
lon = f[laser]["land_ice_segments"]["longitude"][:] #data for a lasers longitude
height = f[laser]["land_ice_segments"]["h_li"][:] # data for a lasers height
quality = f[laser]["land_ice_segments"]["atl06_quality_summary"][:].astype('int')
# Quality filter
idx1 = quality == 0 # data dictionary to see what quality summary is
#print('idx1', idx1)
# Spatial filter
idx2 = np.logical_and( np.logical_and(lat>=lat_min, lat<=lat_max), np.logical_and(lon>=lon_min, lon<=lon_max) )
idx = np.where( np.logical_and(idx1, idx2) ) # combines index 1 and 2 from data quality filter. make sure not empty. if empty all data failed test (low quality or outside box)
icesat2_data[track][year+month+day][laser] = dict.fromkeys(['lat','lon','height']) #store data, creates empty dictionary of lists lat, lon, hi, those strings are the keys to the dict.
icesat2_data[track][year+month+day][laser]['lat'] = lat[idx] # grabbing only latitudes using that index of points with good data quality and within bounding box
icesat2_data[track][year+month+day][laser]['lon'] = lon[idx]
icesat2_data[track][year+month+day][laser]['height'] = height[idx]
if lat[idx].any() == True and lon[idx].any() == True:
x, y = transformer.transform(icesat2_data[track][year+month+day][laser]['lon'], \
icesat2_data[track][year+month+day][laser]['lat'])
plt.scatter(x, y, marker='o', color='#000000')
Currently, they output separately, like this:
Not sure if you're still working on this, but here's a quick example I put together that you might be able to work with (obviously I don't have the data you're working with). A couple things that might not be self-explanatory - I used m() to transform the coordinates to map coordinates. This is Basemap's built-in transformation method so you don't have to use PyProj. Also, setting a zorder in the scatter function ensures that your points are plotted above the countries layer and don't get hidden underneath.
#Basemap
epsg = 6060; width = 2000.e3; height = 2000.e3 #epsg 3413. 6062
plt.figure(figsize=(20,10))
m=Basemap(epsg=epsg,resolution='l',width=width,height=height) #lat_ts=(90.+35.)/2.
m.drawcoastlines(color='white')
m.drawmapboundary(fill_color='#99ffff')
m.fillcontinents(color='#cc9966',lake_color='#99ffff')
m.drawparallels(np.arange(10,70,20),labels=[1,1,0,0])
m.drawmeridians(np.arange(-100,0,20),labels=[0,0,0,1])
plt.title('ICESAT2 Tracks in Greenland')
for coord in [[68,-39],[70,-39]]:
lat = coord[0]
lon = coord[1]
x, y = m(lon,lat)
m.scatter(x,y,color='red',s=100,zorder=10)
plt.show()
I think you might need:
plt.figure(figsize(20,10))
before creating the basemap, not after. As it stands it's creating a map and then creating a new figure after that which is why you're getting two figures.
Then your plotting line should be m.scatter() as you mentioned you tried before.
Or more specifically, how can I change the [659] on the upper-right corner to '659 degrees' or something like that ?
I have checked all the threads mentioned in the following reply: matplotlib values under cursor. However, all of them seem to address the x,y location of the cursor. I am interested in changing the data-value. I could not find a reply or related documentation in the api.
I have tried both format_coord(x, y) and format_cursor_data(data) but neither of them seem to be working.
Thanks,
Sarith
PS: My code is in multiple modules and is a part of gui application. I can share relevant sections if that would be of any help in answering this.
One line solution:
ax.format_coord = lambda x, y: 'x={:.2f}, y={:.2f}, z={:.2f}'.format(x,y,data[int(y + 0.5),int(x + 0.5)])
I had the same problem (I wanted to get rid of the data and send it to somewhere else in a tkinter widget).
I figured out the ax.format_coord was'nt being called, the one you have to change is the one at matplotlib.artist.Artist
this worked for me:
def format_cursor_data(self,data):
return 'new_data'
matplotlib.artist.Artist.format_cursor_data=format_cursor_data
By modifying an example from matplotlib I got this code:
This displays x,y, and z value with a degrees after z.
You should be able to easily modify it, or copy the relevant functions to make it work on your side.
You said you already tried format_coord, maybe you forgot to set the funtion? (second last line)
"""
Show how to modify the coordinate formatter to report the image "z"
value of the nearest pixel given x and y
"""
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
X = 10*np.random.rand(5, 3)
fig, ax = plt.subplots()
ax.imshow(X, cmap=cm.jet, interpolation='nearest')
numrows, numcols = X.shape
def format_coord(x, y):
col = int(x + 0.5)
row = int(y + 0.5)
if col >= 0 and col < numcols and row >= 0 and row < numrows:
#get z from your data, given x and y
z = X[row, col]
#this only leaves two decimal points for readability
[x,y,z]=map("{0:.2f}".format,[x,y,z])
#change return string of x,y and z to whatever you want
return 'x='+str(x)+', y='+str(y)+', z='+str(z)+" degrees"
else:
return 'x=%1.4f, y=%1.4f' % (x, y)
#Set the function as the one used for display
ax.format_coord = format_coord
plt.show()
Emmm, Sarith, I am also facing the problem but a little trick helped me out. I still used this function:
your_imshow.format_coord = lambda x, y: 'x={:.5f}, y={:.2f}, amplitude='.format(x,y)
It pretends to add label before the bracket. Yeah, it is an easy but not essential way to change the presentation form, but it works to me. I hope this could also benefit others.
I had to create a contour graph (in python) based on a formula and several other parameters. My graph came out fine. However, my axis labels will not show. I have tried changing the code several times but I am actually a little lost as how to what my real problem is. I know it deals with the command to create the labels but understand the error message
Also, this is my first post and if you have recommendations for how I should ask questions, I would appreciate the help.
def contourf_plot():
T = np.linspace(0,30,50)
P = np.linspace(600,1000,50)
X, Y = np.meshgrid(T,P)
Z = (Y/100)*np.e**((12*X)/(X+243))
Z.shape
plt.figure()
CF = plt.contourf(T,P,Z,50)
plt.colorbar(CF)
plt.set_Tlabel("Temperature[$\degree$C]")
plt.set_Plabel("Pressure[Pa]")
plt.show()
return
if __name__ == "__main__":
contourf_plot()
Error message: 'module' object has no attribute 'set_Xlabel'
All you need to do is a slight change in your code. You are currently trying to add a label to the axes T and P, though they do not exist (it is still the x and y axes). T and P are just the data that you are trying to plot.
def contourf_plot():
T = np.linspace(0,30,50)
P = np.linspace(600,1000,50)
X, Y = np.meshgrid(T,P)
Z = (Y/100)*np.e**((12*X)/(X+243))
Z.shape
fig,ax = plt.subplots() #add this line
CF = plt.contourf(T,P,Z,50)
plt.colorbar(CF)
ax.set_xlabel("Temperature[$\degree$C]") #sets the x and y label
ax.set_ylabel("Pressure[Pa]")
plt.show()
return
if __name__ == "__main__":
contourf_plot()
This gives the image
I am working on a live plot.I am getting data from a spectrum analyser which gives me the value at a certain frequency. But the program becomes slower the longer it runs.
So I hope you have some ideas. I also looked at my activity monitor while running and the RAM isn't full at all.
I tried to comment out ctf = ax.contourf( a, b, B, cmap=cma) which is responsible for plotting and if it don't need to draw it is so fast. But I need the plot so not drawing is not a solution at all.
And ax = plt.subplot( 111, polar = True) for extra information.
Here is my code:
while True :
trace = inst.query(':TRACe:DATA? TRACE1').partition(' ')[2][:-2].split(', ')# the first & last 2 entries are cut off, are random numbers
for value in trace : #write to file
f.write(value)
f.write('\n')
try : #looking if data is alright
trace = np.array(trace, np.float)
except ValueError: #if a ValueError is raised this message is displayed but the loop won't break and the piece is plotted in one color (green)
print'Some wrong data at the', i+1, 'th measurement'
longzeroarray = np.zeros(801)
a = np.linspace(i*np.pi/8-np.pi/16, i*np.pi/8+np.pi/16, 2)#Angle, circle is divided into 16 pieces
b = np.linspace(start -scaleplot, stop,801) #points of the frequency + 200 more points to gain the inner circle
A, B = np.meshgrid(a, longzeroarray)
cma = ListedColormap(['w'])
#actual plotting
ctf = ax.contourf( a, b, B, cmap=cma)
xCooPoint = i*np.pi/8 + np.pi/16 #shows the user the position of the plot
yCooPoint = stop
ax.plot(xCooPoint, yCooPoint, 'or', markersize = 15)
xCooWhitePoint = (i-1) * np.pi/8 + np.pi/16 #this erases the old red points
yCooWhitePoint = stop
ax.plot(xCooWhitePoint, yCooWhitePoint, 'ow', markersize = 15)
plt.draw()
time.sleep(60) #delaying the time to give analyser time to give us new correct data in the next step
i +=1
continue
maximasearch(trace,searchrange)
trace = np.insert(trace,0,zeroarray)
a = np.linspace(i*np.pi/8+np.pi/16-np.pi/8, i*np.pi/8+np.pi/16, 2)#Angle, circle is divided into 16 pieces
b = np.linspace(start -scaleplot, stop,801) #points of the frequency + 200 more points to gain the inner circle
A, B = np.meshgrid(a, trace)
#actual plotting
ctf = ax.contourf(a, b, B, cmap=cm.jet, vmin=-100, vmax=100)
xCooPoint = i*np.pi/8 + np.pi/16 #shows the user the position of the plot
yCooPoint = stop
ax.plot(xCooPoint, yCooPoint, 'or', markersize = 15)
xCooWhitePoint = (i-1) * np.pi/8 + np.pi/16 #this erases the old red points
yCooWhitePoint = stop
ax.plot(xCooWhitePoint, yCooWhitePoint, 'ow', markersize = 15)
plt.draw()
i+=1
Thats how the plot looks like, and with every new step a new piece of the circle is drawn.
EDIT
I found following question here on stack overflow: real-time plotting in while loop with matplotlib
I think the answer with 22 Upvotes could be helpful. Has anyone ever used blit ? I have no idea yet how to combine it with my code.
http://wiki.scipy.org/Cookbook/Matplotlib/Animations
I want to answer my own question again.
The best way to optimize the code is to calculate with modulo 2*pi for the radial values.
I changed my code a bit :
a = np.linspace((i*np.pi/8+np.pi/16-np.pi/8)%(np.pi*2), (i*np.pi/8+np.pi/16)%(np.pi*2), 2)
The problem before was that Python also plotted all the old pieces, because obviously the were still there but only under a layer of newly plotted data pieces. So although you didn't see the old plotted data, it was still drawn. Now only the circle from 0 to 2pi is redrawn.
I use matplotlib's method hexbin to compute 2d histograms on my data.
But I would like to get the coordinates of the centers of the hexagons in order to further process the results.
I got the values using get_array() method on the result, but I cannot figure out how to get the bins coordinates.
I tried to compute them given number of bins and the extent of my data but i don't know the exact number of bins in each direction. gridsize=(10,2) should do the trick but it does not seem to work.
Any idea?
I think this works.
from __future__ import division
import numpy as np
import math
import matplotlib.pyplot as plt
def generate_data(n):
"""Make random, correlated x & y arrays"""
points = np.random.multivariate_normal(mean=(0,0),
cov=[[0.4,9],[9,10]],size=int(n))
return points
if __name__ =='__main__':
color_map = plt.cm.Spectral_r
n = 1e4
points = generate_data(n)
xbnds = np.array([-20.0,20.0])
ybnds = np.array([-20.0,20.0])
extent = [xbnds[0],xbnds[1],ybnds[0],ybnds[1]]
fig=plt.figure(figsize=(10,9))
ax = fig.add_subplot(111)
x, y = points.T
# Set gridsize just to make them visually large
image = plt.hexbin(x,y,cmap=color_map,gridsize=20,extent=extent,mincnt=1,bins='log')
# Note that mincnt=1 adds 1 to each count
counts = image.get_array()
ncnts = np.count_nonzero(np.power(10,counts))
verts = image.get_offsets()
for offc in xrange(verts.shape[0]):
binx,biny = verts[offc][0],verts[offc][1]
if counts[offc]:
plt.plot(binx,biny,'k.',zorder=100)
ax.set_xlim(xbnds)
ax.set_ylim(ybnds)
plt.grid(True)
cb = plt.colorbar(image,spacing='uniform',extend='max')
plt.show()
I would love to confirm that the code by Hooked using get_offsets() works, but I tried several iterations of the code mentioned above to retrieve center positions and, as Dave mentioned, get_offsets() remains empty. The workaround that I found is to use the non-empty 'image.get_paths()' option. My code takes the mean to find centers but which means it is just a smidge longer, but it does work.
The get_paths() option returns a set of x,y coordinates embedded that can be looped over and then averaged to return the center position for each hexagram.
The code that I have is as follows:
counts=image.get_array() #counts in each hexagon, works great
verts=image.get_offsets() #empty, don't use this
b=image.get_paths() #this does work, gives Path([[]][]) which can be plotted
for x in xrange(len(b)):
xav=np.mean(b[x].vertices[0:6,0]) #center in x (RA)
yav=np.mean(b[x].vertices[0:6,1]) #center in y (DEC)
plt.plot(xav,yav,'k.',zorder=100)
I had this same problem. I think what needs to be developed is a framework to have a HexagonalGrid object which can then be applied to many different data sets (and it would be awesome to do it for N dimensions). This is possible and it surprises me that neither Scipy or Numpy has anything for it (furthermore there seems to be nothing else like it except perhaps binify)
That said, I assume you want to use hexbinning to compare multiple binned data sets. This requires some common base. I got this to work using matplotlib's hexbin the following way:
import numpy as np
import matplotlib.pyplot as plt
def get_data (mean,cov,n=1e3):
"""
Quick fake data builder
"""
np.random.seed(101)
points = np.random.multivariate_normal(mean=mean,cov=cov,size=int(n))
x, y = points.T
return x,y
def get_centers (hexbin_output):
"""
about 40% faster than previous post only cause you're not calculating the
min/max every time
"""
paths = hexbin_output.get_paths()
v = paths[0].vertices[:-1] # adds a value [0,0] to the end
vx,vy = v.T
idx = [3,0,5,2] # index for [xmin,xmax,ymin,ymax]
xmin,xmax,ymin,ymax = vx[idx[0]],vx[idx[1]],vy[idx[2]],vy[idx[3]]
half_width_x = abs(xmax-xmin)/2.0
half_width_y = abs(ymax-ymin)/2.0
centers = []
for i in xrange(len(paths)):
cx = paths[i].vertices[idx[0],0]+half_width_x
cy = paths[i].vertices[idx[2],1]+half_width_y
centers.append((cx,cy))
return np.asarray(centers)
# important parts ==>
class Hexagonal2DGrid (object):
"""
Used to fix the gridsize, extent, and bins
"""
def __init__ (self,gridsize,extent,bins=None):
self.gridsize = gridsize
self.extent = extent
self.bins = bins
def hexbin (x,y,hexgrid):
"""
To hexagonally bin the data in 2 dimensions
"""
fig = plt.figure()
ax = fig.add_subplot(111)
# Note mincnt=0 so that it will return a value for every point in the
# hexgrid, not just those with count>mincnt
# Basically you fix the gridsize, extent, and bins to keep them the same
# then the resulting count array is the same
hexbin = plt.hexbin(x,y, mincnt=0,
gridsize=hexgrid.gridsize,
extent=hexgrid.extent,
bins=hexgrid.bins)
# you could close the figure if you don't want it
# plt.close(fig.number)
counts = hexbin.get_array().copy()
return counts, hexbin
# Example ===>
if __name__ == "__main__":
hexgrid = Hexagonal2DGrid((21,5),[-70,70,-20,20])
x_data,y_data = get_data((0,0),[[-40,95],[90,10]])
x_model,y_model = get_data((0,10),[[100,30],[3,30]])
counts_data, hexbin_data = hexbin(x_data,y_data,hexgrid)
counts_model, hexbin_model = hexbin(x_model,y_model,hexgrid)
# if you want the centers, they will be the same for both
centers = get_centers(hexbin_data)
# if you want to ignore the cells with zeros then use the following mask.
# But if want zeros for some bins and not others I'm not sure an elegant way
# to do this without using the centers
nonzero = counts_data != 0
# now you can compare the two data sets
variance_data = counts_data[nonzero]
square_diffs = (counts_data[nonzero]-counts_model[nonzero])**2
chi2 = np.sum(square_diffs/variance_data)
print(" chi2={}".format(chi2))