nomposstr = '05h34m31.94s 22d00m52.2s' header = fits.getheader(filename) proj = wcs.Projection(header) xc, yc = float(header['NAXIS1']) / 2., float(header['NAXIS2']) / 2. ui.load_image(filename) ui.notice2d('circle({0}, {1}, {2})'.format(xc, yc, float(header['NAXIS2']) / 4.)) ui.set_source(ui.gauss2d.g1 + ui.gauss2d.g2) g1.xpos = xc g1.ypos = yc g2.fwhm = g1.fwhm = 3. ui.link(g2.xpos, g1.xpos) ui.link(g2.ypos, g1.ypos) g2.ampl = 50. g1.ampl = 50. ui.guess() ui.fit() ui.image_fit() ui.covar() conf = ui.get_covar_results() conf_dict = dict([(n, (v, l, h)) for n, v, l, h in zip( conf.parnames, conf.parvals, conf.parmins, conf.parmaxes)]) x, y = proj.toworld((conf_dict['g1.xpos'][0], conf_dict['g1.ypos'][0])) xmin, ymin = proj.toworld((conf_dict['g1.xpos'][0] + conf_dict['g1.xpos'][1], conf_dict['g1.ypos'][0] + conf_dict['g1.ypos'][1])) xmax, ymax = proj.toworld((conf_dict['g1.xpos'][0] + conf_dict['g1.xpos'][2], conf_dict['g1.ypos'][0] + conf_dict['g1.ypos'][2])) nompos = positions.str2pos(nomposstr, proj) print('{0} ({1}-{2}) vs {3}'.format(x, xmin, xmax, nompos[0][0][0])) print('{0} ({1}-{2}) vs {3}'.format(y, ymin, ymax, nompos[0][0][1]))
filename = 'skymap_ex.fits' nomposstr = '05h34m31.94s 22d00m52.2s' header = fits.getheader(filename) proj = wcs.Projection(header) xc, yc = float(header['NAXIS1']) / 2., float(header['NAXIS2']) / 2. ui.load_image(filename) ui.notice2d('circle({0}, {1}, {2})'.format(xc, yc, float(header['NAXIS2']) / 4.)) ui.set_source(ui.gauss2d.g1 + ui.gauss2d.g2) g1.xpos = xc g1.ypos = yc g2.fwhm = g1.fwhm = 3. ui.link(g2.xpos, g1.xpos) ui.link(g2.ypos, g1.ypos) g2.ampl = 50. g1.ampl = 50. ui.guess() ui.fit() ui.image_fit() ui.covar() conf = ui.get_covar_results() conf_dict = dict([(n,(v, l, h)) for n,v,l,h in zip(conf.parnames, conf.parvals, conf.parmins, conf.parmaxes)]) x, y = proj.toworld((conf_dict['g1.xpos'][0], conf_dict['g1.ypos'][0])) xmin, ymin = proj.toworld((conf_dict['g1.xpos'][0] + conf_dict['g1.xpos'][1], conf_dict['g1.ypos'][0] + conf_dict['g1.ypos'][1])) xmax, ymax = proj.toworld((conf_dict['g1.xpos'][0] + conf_dict['g1.xpos'][2], conf_dict['g1.ypos'][0] + conf_dict['g1.ypos'][2])) nompos = positions.str2pos(nomposstr, proj) print('{0} ({1}-{2}) vs {3}'.format(x, xmin, xmax, nompos[0][0][0])) print('{0} ({1}-{2}) vs {3}'.format(y, ymin, ymax, nompos[0][0][1]))
def __init__(self, projection, mixpix, pxlim, pylim, aspectratio=1.0, pos1=None, pos2=None, rulersize=None, rulerangle=None, x1=None, y1=None, x2=None, y2=None, lambda0=0.5, step=None, world=False, angle=None, addangle=0.0, fmt=None, fun=None, units=None, fliplabelside=False, mscale=None, labelsintex=True, gridmode=False, **kwargs): self.ptype = "Ruler" self.x1 = None self.y1 = None self.x2 = None self.y2 = None self.x = [] self.y = [] self.xw = [] self.yw = [] self.stepsizeW = None self.label = [] self.offsets = [] # Store the offsets in degrees self.angle = None self.kwargs = {'clip_on' : True} # clip_on is buggy for plot() in MPL versions <= 0.98.3 change later self.tickdx = None self.tickdy = None self.mscale = None self.fun = None self.fmt = None self.linekwargs = {'color' : 'k'} self.kwargs.update(kwargs) # These are the kwargs for the labels self.aspectratio = aspectratio self.rulertitle = None self.gridmode = gridmode # Recipe: # Are the start and endpoint in world coordinates or pixels? # Convert to pixels. # Calculate the central position in pixels # Calculate the central position in world coordinates (Xw,Yw) # Find a lambda in (x,y) = (x1,y1) + lambda*(x2-x1,y2-x1) # so that, if (x,y) <-> (xw,yw), the distance D((Xw,Yw), (xw,yw)) # is the step size on the ruler. def bisect(offset, lambda_s, Xw, Yw, x1, y1, x2, y2): """ We are looking for a value mu so that mu+lambda_s sets a pixel which corresponds to world coordinates that are 'offset' away from the start point set by lambda_s If lambda_s == 0 then we are in x1, x2. If lambda_s == 1 we are in x2, y2 """ mes = '' if offset >= 0.0: a = 0.0; b = 1.1 else: a = -1.1; b = 0.0 f1 = getdistance(a, lambda_s, Xw, Yw, x1, y1, x2, y2) - abs(offset) f2 = getdistance(b, lambda_s, Xw, Yw, x1, y1, x2, y2) - abs(offset) validconditions = f1*f2 < 0.0 if not validconditions: mes = "Found interval without a root for this step size" return None, mes tol = 1e-12 # Tolerance. Stop iteration if (b-a)/2 < tol N0 = 50 # Stop output with error message if number of iterations # exceeds this number # Initialize i = 0 fa = getdistance(a, lambda_s, Xw, Yw, x1, y1, x2, y2) - abs(offset) # The iteration itself while i <= N0: # The bisection p = a + (b-a)/2.0 fp = getdistance(p, lambda_s, Xw, Yw, x1, y1, x2, y2) - abs(offset) # Did we find a root? i += 1 if fp == 0.0 or (b-a)/2.0 < tol: # print 'Root is: ', p, fp # We found a root # print "Iterations: ", i break # Success..., leave the while loop if fa*fp > 0: a = p fa = fp else: b = p else: mes = 'Ruler bisection failed after %d iterations!' % N0 p = None return p, mes def DV(l1, b1, l2, b2): # Vincenty, Thaddeus, 1975, formula for distance on sphere accurate over entire sphere fac = numpy.pi / 180.0 l1 *= fac; b1 *= fac; l2 *= fac; b2 *= fac dlon = l2 - l1 a1 = numpy.cos(b2)*numpy.sin(dlon) a2 = numpy.cos(b1)*numpy.sin(b2) - numpy.sin(b1)*numpy.cos(b2)*numpy.cos(dlon) a = numpy.sqrt(a1*a1+a2*a2) b = numpy.sin(b1)*numpy.sin(b2) + numpy.cos(b1)*numpy.cos(b2)*numpy.cos(dlon) d = numpy.arctan2(a,b) return d*180.0/numpy.pi def tolonlat(x, y): # This function also sorts the spatial values in order # longitude, latitude if mixpix == None: xw, yw = projection.toworld((x,y)) xwo = xw # Store originals ywo = yw else: W = projection.toworld((x, y, mixpix)) xw = W[projection.lonaxnum-1] yw = W[projection.lataxnum-1] xwo = xw; ywo = yw if projection.lonaxnum > projection.lataxnum: xwo, ywo = ywo, xwo # Swap return xw, yw, xwo, ywo def topixel2(xw, yw): # Note that this conversion is only used to convert # start and end position, given in world coordinates, # to pixels. if mixpix == None: x, y = projection.topixel((xw,yw)) else: unknown = numpy.nan wt = (xw, yw, unknown) pixel = (unknown, unknown, mixpix) (wt, pixel) = projection.mixed(wt, pixel) x = pixel[0]; y = pixel[1] return x, y def getdistance(mu, lambda_s, Xw, Yw, x1, y1, x2, y2): lam = lambda_s + mu x = x1 + lam*(x2-x1) y = y1 + lam*(y2-y1) xw, yw, xw1, yw1 = tolonlat(x,y) return DV(Xw, Yw, xw, yw) def nicestep(x1, y1, x2, y2): # Assume positions in pixels xw1, yw1, dummyx, dummyy = tolonlat(x1,y1) xw2, yw2, dummyx, dummyy = tolonlat(x2,y2) step = None length = DV(xw1, yw1, xw2, yw2) # Nice numbers for dms should also be nice numbers for hms sec = numpy.array([30, 20, 15, 10, 5, 2, 1]) minut = sec deg = numpy.array([60, 30, 20, 15, 10, 5, 2, 1]) nicenumber = numpy.concatenate((deg*3600.0, minut*60.0, sec)) fact = 3600.0 d = length * fact step2 = 0.9*d/3.0 # We want at least four offsets on our ruler for p in nicenumber: k = int(step2/p) if k >= 1.0: step2 = k * p step = step2 break # Stop if we have a candidate # d = x2 - x1 # If nothing suitable then try something else if step == None: f = int(numpy.log10(d)) if d < 1.0: f -= 1 D3 = numpy.round(d/(10.0**f),0) if D3 == 3.0: D3 = 2.0 elif D3 == 6: D3 = 5.0 elif D3 == 7: D3 = 8 elif D3 == 9: D3 = 10 if D3 in [2,4,8]: k = 4 else: k = 5 step = (D3*10.0**f)/k return step/fact spatial = projection.types[0] in ['longitude', 'latitude'] or projection.types[1] in ['longitude', 'latitude'] if not spatial: raise Exception("Rulers only suitable for maps with at least one spatial axis!") # User entered units, then check conversion uf = None if not units is None: uf, errmes = unitfactor('degree', units) if uf is None: raise ValueError(errmes) if not pos1 is None: poswp = str2pos(pos1, projection, mixpix=mixpix, gridmode=self.gridmode) if poswp[3] != "": raise Exception(poswp[3]) # The result of the position parsing of str2pos is stored in 'poswp' # Its second element are the returned pixel coordinates. # (poswp[1]). # Note we required 1 position. Then the pixel coordinate we want is # poswp[1][0]. If we omit the last index then we end up with a sequence (of 1) # which cannot be processed further. Finally the pixel coordinate represents a # position in 2-dim. So the first element represents x (poswp[1][0][0]). pix = poswp[1][0] x1 = pix[0] y1 = pix[1] else: if x1 is None: x1 = pxlim[0]; world = False if y1 is None: y1 = pylim[0]; world = False if world: x1, y1 = topixel2(x1, y1) if not pos2 is None: poswp = str2pos(pos2, projection, mixpix=mixpix, gridmode=self.gridmode) if poswp[3] != "": raise Exception(poswp[3]) pix = poswp[1][0] x2 = pix[0] y2 = pix[1] else: if not rulersize is None: # We have two pixels to start with. Convert to long, lat # which serves as a starting point for the ruler. lon1, lat1, xwo1, ywo1 = tolonlat(x1, y1) swapped = lon1 != xwo1 # Find second point in world coordinates if rulerangle is None: rulerangle = 270.0 if not uf is None: rulersize /= uf # Find end point. Assume cdelt of long. is negative lon2, lat2 = dispcoord(lon1, lat1, rulersize, -1, rulerangle) if swapped: x2 = lat2 y2 = lon2 # Swap back else: x2 = lon2 y2 = lat2 x2, y2 = topixel2(x2, y2) else: if x2 is None: x2 = pxlim[1]; world = False if y2 is None: y2 = pylim[1]; world = False if world: x2, y2 = topixel2(x2, y2) #print "DV", DV(23*15,15, 22*15, 30)*60.0 # Get a step size for nice offsets if step is None: stepsizeW = nicestep(x1, y1, x2, y2) else: stepsizeW = step if step == 0.0: raise Exception("Cannot make ruler with step size equal to zero!") # Look for suitable units (degrees, arcmin, arcsec) if nothing is # specified in the call. Note that 'stepsizeW' is in degrees. uf = None if units != None: uf, errmes = unitfactor('degree', units) if uf is None: raise ValueError(errmes) if uf != 1.0: fun = lambda x: x*uf # Input in 'units' but must be degrees for further processing if not step is None: stepsizeW /= uf # because step was in units of 'units'. Must be deg. if fmt is None: if uf == 1.0: if labelsintex: fmt = r"%4.0f^{\circ}" else: fmt = "%4.0f\u00B0" elif uf == 60.0: # Write labels in arcmin if labelsintex: fmt = r"%4.0f^{\prime}" else: fmt = r"%4.0f'" elif uf == 3600.0: # Write labels in arcsec if labelsintex: fmt = r"%4.0f^{\prime\prime}" else: fmt = r"%4.0f''" else: raise ValueError("Only degree, arcmin and arcsec allowed") if fun is None and fmt is None: if labelsintex: fmt = r"%4.0f^{\circ}" else: fmt = "%4.0f\u00B0" if abs(stepsizeW) < 1.0: # Write labels in arcmin fun = lambda x: x*60.0 if labelsintex: fmt = r"%4.0f^{\prime}" else: fmt = r"%4.0f'" if abs(stepsizeW) < 1.0/60.0: # Write labels in arcsec fun = lambda x: x*3600.0 if labelsintex: fmt = r"%4.0f^{\prime\prime}" else: fmt = r"%4.0f''" elif fmt is None: # A function but not a format. Then a default format fmt = '%g' # Check whether the start- and end point of the ruler are inside the frame start_in = isinside(x1, y1, pxlim, pylim) #start_in = (pxlim[0]-0.5 <= x1 <= pxlim[1]+0.5) and (pylim[0]-0.5 <= y1 <= pylim[1]+0.5) if not start_in: raise Exception("Start point of ruler not in pixel limits!") end_in = isinside(x2, y2, pxlim, pylim) #end_in = (pxlim[0]-0.5 <= x2 <= pxlim[1]+0.5) and (pylim[0]-0.5 <= y2 <= pylim[1]+0.5) if not end_in: raise Exception("End point of ruler not in pixel limits!") # Ticks perpendicular to ruler line. Prependicular is with respect to # square pixels, so correct these first for their aspect ratio to find # the right angle. defangle = 180.0 * numpy.arctan2(y2-y1, (x2-x1)/aspectratio) / numpy.pi - 90.0 l1 = pxlim[1] - pxlim[0] + 1.0; l1 /= 100.0 l2 = pylim[1] - pylim[0] + 1.0; l2 /= 100.0 ll = max(l1,l2) dx = ll*numpy.cos(defangle*numpy.pi/180.0)*aspectratio dy = ll*numpy.sin(defangle*numpy.pi/180.0) if fliplabelside: dx = -dx dy = -dy if angle == None: phi = defangle else: phi = angle phi += addangle defkwargs = {'fontsize':10, 'rotation':phi} if defangle+90.0 in [270.0, 90.0, -90.0, -270.0]: if fliplabelside: defkwargs.update({'va':'center', 'ha':'right'}) else: defkwargs.update({'va':'center', 'ha':'left'}) if mscale == None: mscale = 1.5 elif defangle+90.0 in [0.0, 180.0, -180.0]: if fliplabelside: defkwargs.update({'va':'bottom', 'ha':'center'}) else: defkwargs.update({'va':'top', 'ha':'center'}) mscale = 1.5 else: defkwargs.update({'va':'center', 'ha':'center'}) if mscale == None: mscale = 2.5 defkwargs.update(kwargs) #ruler = Rulerstick(x1, y1, x2, y2, defangle, dx, dy, mscale, **defkwargs) self.x1 = x1; self.x2 = x2; self.y1 = y1; self.y2 = y2 self.angle = defangle self.tickdx = dx; self.tickdy = dy self.mscale = mscale self.kwargs.update(defkwargs) self.fmt = fmt self.fun = fun self.flip = fliplabelside lambda_s = lambda0 x0 = x1 + lambda_s*(x2-x1) y0 = y1 + lambda_s*(y2-y1) Xw, Yw, xw1, yw1 = tolonlat(x0, y0) self.append(x0, y0, 0.0, fmt%0.0) self.appendW(xw1, yw1) # Store in original order i.e. not sorted self.stepsizeW = stepsizeW # Needed elsewhere so store as an attribute # Find the mu on the straight ruler line for which the distance between # the position defined by mu and the center point (lambda0) is 'offset' # Note that these distances are calculated on a sphere for sign in [+1.0, -1.0]: mu = 0.0 offset = 0.0 lamplusmu = lambda_s + mu while mu != None and (0.0 <= lamplusmu <= 1.0): offset += sign*stepsizeW mu, mes = bisect(offset, lambda_s, Xw, Yw, x1, y1, x2, y2) if mu != None: lamplusmu = lambda_s + mu if 0.0 <= lamplusmu <= 1.0: x = x1 + (lamplusmu)*(x2-x1) y = y1 + (lamplusmu)*(y2-y1) if fun != None: off = fun(offset) else: off = abs(offset) self.append(x, y, offset, fmt%off, labelsintex) xw, yw, xw1, yw1 = tolonlat(x, y) self.appendW(xw1, yw1) elif sign == -1.0: break # raise Exception, mes self.pxlim = pxlim self.pylim = pylim