def diag_size(self): """return the largest diagonal size""" p1, p2, p3, p4 = self.calc_footprint() return np.max([ angular_separation(*np.concatenate([p1, p3])), angular_separation(*np.concatenate([p2, p4])) ])
def edge_size(self): """return the p1,p2 and p2,p3 size (The order is counter-clockwise starting with the bottom left corner. p1,p2,p3,p4)""" p1, p2, p3, p4 = self.calc_footprint() return angular_separation( *np.concatenate([p1, p2])), angular_separation( *np.concatenate([p2, p3]))
def solid_angle(self): """Solid angle array (`~astropy.units.Quantity` in ``sr``). The array has the same dimension as the WcsGeom object. To return solid angles for the spatial dimensions only use:: WcsGeom.to_image().solid_angle() """ coord = self.get_coord(mode="edges") lon = coord.lon * np.pi / 180.0 lat = coord.lat * np.pi / 180.0 # Compute solid angle across centres of the pixels, approximating it # as a rectangle # First index is "y", second index is "x" # TODO: Calculate actual solid angle between two great circles? Here are two references # suggesting more precise methods: # https://mail.python.org/pipermail/astropy/2013-December/002632.html # https://cta-redmine.irap.omp.eu/issues/1017 lon_centres = (lon[..., :-1, :-1] + lon[..., 1:, 1:]) / 2 lat_centres = (lat[..., :-1, :-1] + lat[..., 1:, 1:]) / 2 ymid_xlo = lon[..., :-1, :-1], lat_centres ymid_xhi = lon[..., :-1, 1:], lat_centres ylo_xmid = lon_centres, lat[..., :-1, 1:] yhi_xmid = lon_centres, lat[..., 1:, :-1] dx = angular_separation(*(ymid_xlo + ymid_xhi)) dy = angular_separation(*(ylo_xmid + yhi_xmid)) return u.Quantity(dx * dy, "sr", copy=False)
def read_and_update_dl2(filepath, tel_id=1, filters=['intensity > 0']): """ read DL2 data from lstchain file and update it to be compliant with irf Maker """ dl2_params_lstcam_key = 'dl2/event/telescope/parameters/LST_LSTCam' # lstchain DL2 files data = pd.read_hdf(filepath, key=dl2_params_lstcam_key) data = deepcopy(data.query(f'tel_id == {tel_id}')) for filter in filters: data = deepcopy(data.query(filter)) # angles are in degrees in protopipe data['xi'] = pd.Series(angular_separation( data.reco_az.values * u.rad, data.reco_alt.values * u.rad, data.mc_az.values * u.rad, data.mc_alt.values * u.rad, ).to(u.deg).value, index=data.index) data['offset'] = pd.Series(angular_separation( data.reco_az.values * u.rad, data.reco_alt.values * u.rad, data.mc_az_tel.values * u.rad, data.mc_alt_tel.values * u.rad, ).to(u.deg).value, index=data.index) for key in [ 'mc_alt', 'mc_az', 'reco_alt', 'reco_az', 'mc_alt_tel', 'mc_az_tel' ]: data[key] = np.rad2deg(data[key]) return data
def solid_angle(self): """Solid angle array (`~astropy.units.Quantity` in ``sr``). The array has the same dimension as the WcsGeom object. To return solid angles for the spatial dimensions only use: WcsGeom.to_image().solid_angle() """ # TODO: Improve by exposing a mode 'edge' for get_coord # Note that edge is applied only to spatial coordinates in the following call # Note also that pix_to_coord is already called in _get_pix_coords. # This should be made more efficient. pix = self._get_pix_coords(mode='edge') coord = self.pix_to_coord(pix) lon = coord[0] * np.pi / 180. lat = coord[1] * np.pi / 180. # Compute solid angle using the approximation that it's # the product between angular separation of pixel corners. # First index is "y", second index is "x" ylo_xlo = lon[..., :-1, :-1], lat[..., :-1, :-1] ylo_xhi = lon[..., :-1, 1:], lat[..., :-1, 1:] yhi_xlo = lon[..., 1:, :-1], lat[..., 1:, :-1] dx = angular_separation(*(ylo_xlo + ylo_xhi)) dy = angular_separation(*(ylo_xlo + yhi_xlo)) return u.Quantity(dx * dy, 'sr')
def coarse_quad_search(self, obsjd, quads, objects, vlim): """Nearest-neighbor search. Parameters ---------- obsjd : float Julian date. quads : list of lists Each item is a list of quadrant parameters: obsjd, pid, ra_c, dec_c, ra1, ra2, ra3, ra4, dec1, dec2, dec3, dec4 where 1..4 are coordinates of the corners. objects : list of string Objects. vlim : float Limiting magnitude to consider. Returns ------- objects : list Found objects. quads : list Nearest 4 quads for each object. """ import numpy as np from astropy.coordinates.angle_utilities import angular_separation from .exceptions import EphemerisError (ra_c, dec_c, ra1, ra2, ra3, ra4, dec1, dec2, dec3, dec4) = tuple(zip(*quads))[2:] found = [] for obj in objects: try: ra, dec, vmag = self._get_ephemeris(obj, obsjd) except EphemerisError as e: self.logger.debug(str(e)) continue # vmag greater than vlim? skip. if vmag > vlim: continue # farther than 12 deg from any corner? forget it if angular_separation(ra, dec, ra_c[0], dec_c[0]) > 0.21: continue # Farther than 1.5 deg from any quad? skip. d = angular_separation(ra, dec, ra_c, dec_c) if min(d) > 0.026: continue found.append((obj, [quads[i] for i in np.argsort(d)[:4]])) return found
def evaluate(self, lon, lat, lon_0, lat_0, semi_major, e, theta, edge): """Evaluate the model (static function).""" # find the foci of the ellipse c = semi_major * e lon_1, lat_1 = self._offset_by(lon_0, lat_0, 90 * u.deg - theta, c) lon_2, lat_2 = self._offset_by(lon_0, lat_0, 270 * u.deg - theta, c) sep_1 = angular_separation(lon, lat, lon_1, lat_1) sep_2 = angular_separation(lon, lat, lon_2, lat_2) in_ellipse = smooth_edge(sep_1 + sep_2 - 2 * semi_major, edge) norm = SkyEllipse.compute_norm(semi_major, e) return u.Quantity(norm * in_ellipse, "sr-1", copy=False)
def evaluate(self, lon, lat, lon_0, lat_0, semi_major, e, theta, edge): """Evaluate the model (static function).""" # find the foci of the ellipse c = semi_major * e lon_1, lat_1 = self._offset_by(lon_0, lat_0, 90 * u.deg - theta, c) lon_2, lat_2 = self._offset_by(lon_0, lat_0, 270 * u.deg - theta, c) sep_1 = angular_separation(lon, lat, lon_1, lat_1) sep_2 = angular_separation(lon, lat, lon_2, lat_2) in_ellipse = smooth_edge(sep_1 + sep_2 - 2 * semi_major, 2 * edge) norm = SkyEllipse.compute_norm(semi_major, e) return u.Quantity(norm * in_ellipse, "sr-1", copy=False)
def __init__(self, ra, dec, trigger_time, ft2file, degree=2): # Open the FT2 file and read the pointing information with pyfits.open(ft2file) as f: # Remember: in the FT2 all quantities (except the livetime) refer to the START time time = f['SC_DATA'].data['START'] # Read the position of the Z-axis (the boresight) and convert it to radians ra_scz_rad = np.deg2rad(f['SC_DATA'].data['RA_SCZ']) dec_scz_rad = np.deg2rad(f['SC_DATA'].data['DEC_SCZ']) # Prepare the interpolator ang_dist_rad = angular_separation(ra_scz_rad, dec_scz_rad, np.deg2rad(ra), np.deg2rad(dec)) self._theta_interpolator_radians = scipy.interpolate.InterpolatedUnivariateSpline(time - trigger_time, ang_dist_rad, w=np.ones_like(ang_dist_rad), k=2, ext=2, check_finite=True) # Store the trigegr time as reference time self._reference_time = float(trigger_time) self._degree = int(degree) self._scales = np.ones(self._degree+1) self.clip_at_zero = False
def calculate_source_fov_offset(events, prefix="true"): """Calculate angular separation between true and pointing positions. Parameters ---------- events : astropy.QTable Astropy Table object containing the reconstructed events information. prefix: str Column prefix for az / alt, can be used to calculate reco or true source fov offset. Returns ------- theta: astropy.units.Quantity Angular separation between the true and pointing positions in the sky. """ theta = angular_separation( events[f"{prefix}_az"], events[f"{prefix}_alt"], events["pointing_az"], events["pointing_alt"], ) return theta.to(u.deg)
def compute_constraint(self, times, observer, targets): moon = get_moon(times, observer.location, observer.pressure) targets = [target.coord if hasattr(target, 'coord') else target for target in targets] moon_separation = Angle([angular_separation(moon.spherical.lon, moon.spherical.lat, target.spherical.lon, target.spherical.lat) for target in targets]) # The line below should have worked, but needs a workaround. # TODO: once bug has been fixed, replace workaround with simpler version. # Relevant PR: https://github.com/astropy/astropy/issues/4033 # moon_separation = Angle([moon.separation(target) for target in targets]) if self.min is None and self.max is not None: mask = self.max > moon_separation elif self.max is None and self.min is not None: mask = self.min < moon_separation elif self.min is not None and self.max is not None: mask = ((self.min < moon_separation) & (moon_separation < self.max)) else: raise ValueError("No max and/or min specified in " "MoonSeparationConstraint.") return mask
def moon_phase_angle(time, location): """ Calculate lunar orbital phase [radians]. Parameters ---------- time : `~astropy.time.Time` Time of observation location : `~astropy.coordinates.EarthLocation` Location of observer Returns ------- i : float Phase angle of the moon [radians] """ # TODO: cache these sun/moon SkyCoord objects sun = get_sun(time).transform_to(AltAz(location=location, obstime=time)) moon = get_moon(time, location) # The line below should have worked, but needs a workaround. # TODO: once bug has been fixed, replace workaround with simpler version. # elongation = sun.separation(moon) elongation = Angle(angular_separation(moon.spherical.lon, moon.spherical.lat, sun.spherical.lon, sun.spherical.lat), u.deg) return np.arctan2(sun.distance*np.sin(elongation), moon.distance - sun.distance*np.cos(elongation))
def triangle_test(p, v): """Test if point p lies within the triangle with vertices v. All points in units of raidans. Edges and vertices are included. Assumes points are located on the unit sphere. http://mathworld.wolfram.com/TriangleInterior.html """ import numpy as np from astropy.coordinates.angle_utilities import ( angular_separation, position_angle) # project coordinate system about p to avoid polar and boundary issues # Zenithal projection th = angular_separation(p[0], p[1], v[:, 0], v[:, 1]) phi = position_angle(p[0], p[1], v[:, 0], v[:, 1]) x = np.c_[th * np.sin(phi), -th * np.cos(phi)] a = x[1] - x[0] b = x[2] - x[0] #s = (np.cross(p, b) - np.cross(v[0], b)) / np.cross(a, b) #t = -(np.cross(p, a) - np.cross(v[0], a)) / np.cross(a, b) s = -np.cross(x[0], b) / np.cross(a, b) t = np.cross(x[0], a) / np.cross(a, b) return (s >= 0) * (t >= 0) * (s + t <= 1)
def compute_constraint(self, times, observer, targets): moon = get_moon(times, observer.location, observer.pressure) targets = [ target.coord if hasattr(target, 'coord') else target for target in targets ] moon_separation = Angle([ angular_separation(moon.spherical.lon, moon.spherical.lat, target.spherical.lon, target.spherical.lat) for target in targets ]) # The line below should have worked, but needs a workaround. # TODO: once bug has been fixed, replace workaround with simpler version. # Relevant PR: https://github.com/astropy/astropy/issues/4033 # moon_separation = Angle([moon.separation(target) for target in targets]) if self.min is None and self.max is not None: mask = self.max >= moon_separation elif self.max is None and self.min is not None: mask = self.min <= moon_separation elif self.min is not None and self.max is not None: mask = ((self.min <= moon_separation) & (moon_separation <= self.max)) else: raise ValueError("No max and/or min specified in " "MoonSeparationConstraint.") return mask
def moon_phase_angle(time, location): """ Calculate lunar orbital phase [radians]. Parameters ---------- time : `~astropy.time.Time` Time of observation location : `~astropy.coordinates.EarthLocation` Location of observer Returns ------- i : float Phase angle of the moon [radians] """ # TODO: cache these sun/moon SkyCoord objects sun = get_sun(time).transform_to(AltAz(location=location, obstime=time)) moon = get_moon(time, location) # The line below should have worked, but needs a workaround. # TODO: once bug has been fixed, replace workaround with simpler version. # elongation = sun.separation(moon) elongation = Angle( angular_separation(moon.spherical.lon, moon.spherical.lat, sun.spherical.lon, sun.spherical.lat), u.deg) return np.arctan2(sun.distance * np.sin(elongation), moon.distance - sun.distance * np.cos(elongation))
def calculate_theta(events, assumed_source_az, assumed_source_alt): """Calculate sky separation between assumed and reconstructed positions. Parameters ---------- events : astropy.QTable Astropy Table object containing the reconstructed events information. assumed_source_az: astropy.units.Quantity Assumed Azimuth angle of the source. assumed_source_alt: astropy.units.Quantity Assumed Altitude angle of the source. Returns ------- theta: astropy.units.Quantity Angular separation between the assumed and reconstructed positions in the sky. """ theta = angular_separation( assumed_source_az, assumed_source_alt, events["reco_az"], events["reco_alt"], ) return theta.to(u.deg)
def f(params, row, col, alt, az): print(params) zenith_row, zenith_col, rotation = params magic_2018.zenith_row = zenith_row + magic_2018.sensor.resolution_row / 2 magic_2018.zenith_col = zenith_col + magic_2018.sensor.resolution_col / 2 magic_2018.rotation = rotation * u.deg magic_2018.lens.mapping = 'spline' r, phi = magic_2018.pixel2polar(row, col) r *= magic_2018.sensor.pixel_width.to(u.mm).value x = np.append(0, r) y = np.append(0, 90 - alt) idx = np.argsort(x) tck = splrep(x[idx], y[idx], t=np.linspace(np.percentile(x, 1), np.percentile(x, 99), 10)) magic_2018.lens.tck_inv = tck coords = magic_2018.pixel2horizontal(row, col) val = angular_separation(alt * u.deg, az * u.deg, coords.alt, coords.az).value.mean() return val
def evaluate(lon, lat, lon_0, lat_0, sigma): """Evaluate the model (static function).""" sep = angular_separation(lon, lat, lon_0, lat_0) a = 1.0 - np.cos(sigma) norm = 1 / (4 * np.pi * a * (1.0 - np.exp(-1.0 / a))) exponent = -0.5 * ((1 - np.cos(sep)) / a) return u.Quantity(norm.value * np.exp(exponent).value, "sr-1", copy=False)
def search_for_maximum(self, ra_center, dec_center, half_side_deg, n_side, proj_name='AIT', verbose=False): """ :param ra_center: R.A. of the center of the map :param dec_center: Dec of the center of the map :param half_size_deg: half size of the side of the TS map ("radius", even though it is a square) :param n_side: number of points on one side. So n_side = 5 means that a 5x5 map will be computed :param stepsize: size of the step, i.e., distance between two adiancent points in the RA or Dec direction :param proj_name: name for the projection (default: AIT). All projections supported by astropy.wcs can be used :return: (max_ts_position, max_ts): returns a tuple of (RA, Dec) and the maximum TS found """ # Figure out step size stepsize = half_side_deg / (n_side / 2.0) # Create WCS object which will allow us to make the grid wcs = pywcs.WCS(naxis=2) wcs.wcs.crpix = [n_side / 2. + 0.5, n_side / 2. + 0.5] wcs.wcs.cdelt = [-stepsize, stepsize] wcs.wcs.crval = [float(ra_center), float(dec_center)] wcs.wcs.ctype = ["RA---%s" % proj_name, "DEC--%s" % proj_name] # Compute all TSs # These two will hold maximum and position of the maximum # Init them with worse case scenario max_ts = 0.0 max_ts_position = (ra_center, dec_center) ang_sep = [] for i in range(n_side): for j in range(n_side): this_ra, this_dec = wcs.wcs_pix2world(i, j, 0) this_TS = self._calc_one_TS(float(this_ra), float(this_dec)) if this_TS >= max_ts: # New maximum max_ts = this_TS max_ts_position = (this_ra, this_dec) if verbose: ang_sep.append(np.rad2deg(angular_separation(*np.deg2rad([ra_center, dec_center, this_ra, this_dec])))) print("(%.3f, %.3f) -> %.2f (%.3f deg away from center)" % (this_ra, this_dec, this_TS, ang_sep[-1])) if verbose: print("Total number of points: %i" % len(ang_sep)) print("Minimum ang. dist: %s deg" % min(ang_sep)) print("Maximum ang. dist: %s deg" % max(ang_sep)) # Find maximum and its position return max_ts_position, max_ts
def const_score(tb, ob, times, site): """ calculates a score for an OB thar represents how well it would fit here, infinity if constraints make it unobservable Remember: Lower numbers are better """ moon = astroplan.get_moon(times, site.location, site.pressure) altaz = site.altaz(times, ob.target) if np.any(altaz.alt < ob.constraints[1].min) or np.any(altaz.alt > ob.constraints[1].max): return float('inf') # altitude constraint if np.any(site.moon_illumination(times) > ob.constraints[2].max): return float('inf') # moon illumination constraint moon_sep = astropy.coordinates.Angle([angular_separation(moon.spherical.lon, moon.spherical.lat, ob.target.coord.spherical.lon, ob.target.coord.spherical.lat)]) if np.any(moon_sep < ob.constraints[0].min): return float('inf') # moon separation constraint score = 0. score += ob.priority*weights.w_priority # the marked priority score += ob.program.rank*weights.w_rank # the rank of the program if tb is not None: # the time to slew score += tb.components.get('slew_time', 0*u.second).value*weights.w_slew # the time to change filter score += tb.components.get('filter_change', 0*u.second).value*weights.w_filterchange return score
def process_mc(simtelfile, dl2_file, mc_type): """ Process the MC simulated and reconstructed to extract the relevant parameters to compute the sensitivity Paramenters --------- simtel: simtelarray file dl2_file: `pandas.DataFrame` dl2 parameters mc_type: 'string' type of particle Returns --------- gammaness: `numpy.ndarray` angdist2: `numpy.ndarray` angular distance squared e_reco: `numpy.ndarray` reconstructed energies n_reco: `int` number of reconstructed events mc_par: `dict` with simulated parameters """ source = SimTelFile(simtelfile) sim_par = read_sim_par(source) events = pd.read_hdf(dl2_file) e_reco = 10**events.mc_energy.to_numpy() * u.GeV gammaness = events.gammaness #Get source position in radians focal_length = source.telescope_descriptions[1]['camera_settings']['focal_length'] * u.m # If the particle is a gamma ray, it returns the squared angular distance # from the reconstructed gamma-ray position and the simulated incoming position if mc_type=='gamma': events = events[events.mc_type==0] alt2 = events.mc_alt az2 = np.arctan(np.tan(events.mc_az)) # If the particle is not a gamma-ray (diffuse protons/electrons), it returns # the squared angular distance of the reconstructed position w.r.t. the # center of the camera else: events = events[events.mc_type!=0] alt2 = events.mc_alt_tel az2 = np.arctan(np.tan(events.mc_az_tel)) src_pos_reco = reco_source_position_sky(events.x.values * u.m, events.y.values * u.m, events.disp_dx_rec.values * u.m, events.disp_dy_rec.values * u.m, focal_length, events.mc_alt_tel.values * u.rad, events.mc_az_tel.values * u.rad) alt1 = src_pos_reco.alt.rad az1 = np.arctan(np.tan(src_pos_reco.az.rad)) angdist2 = (angular_separation(az1, alt1, az2, alt2).to_numpy() * u.rad)**2 return gammaness, angdist2.to(u.deg**2), e_reco, sim_par
def pair_correlation(lon, lat, theta_bins): """Compute pair correlation function for points on the sphere. Parameters ---------- lon, lat : array_like Coordinate arrays theta_bins : array_like Array defining the ``theta`` binning. ``theta`` is the angular offset between positions. unit : {'deg', 'rad'} Units of input and output coordinates Returns ------- counts : array Array of point separations per ``theta`` bin. """ # TODO: Implement speedups: # - use radians # - avoid processing each pair twice (distance a to b and b to a) counts = np.zeros(shape=len(theta_bins) - 1, dtype=int) # If there are many points this should have acceptable performance # because the inner loop is in np.histogram, not in Python for ii in range(len(lon)): theta = angular_separation(lon[ii], lat[ii], lon, lat) hist = np.histogram(theta, theta_bins)[0] counts += hist return counts
def minimum_separation(lon1, lat1, lon2, lat2): """Compute minimum distance of each (lon1, lat1) to any (lon2, lat2). Parameters ---------- lon1, lat1 : array_like Primary coordinates of interest lon2, lat2 : array_like Counterpart coordinate array unit : {'deg', 'rad'} Units of input and output coordinates Returns ------- theta_min : array Minimum distance """ lon1 = np.asanyarray(lon1) lat1 = np.asanyarray(lat1) theta_min = np.empty_like(lon1, dtype=np.float64) for i1 in range(lon1.size): thetas = angular_separation(lon1[i1], lat1[i1], lon2, lat2) theta_min[i1] = thetas.min() return theta_min
def anglesep(lon0: float, lat0: float, lon1: float, lat1: float, deg: bool = True) -> float: """ inputs: DEGREES For reference, this is from astropy astropy/coordinates/angle_utilities.py Angular separation between two points on a sphere. """ if angular_separation is None: raise ImportError('angledist requires AstroPy. Try angledis_meeus') if deg: lon0 = radians(lon0) lat0 = radians(lat0) lon1 = radians(lon1) lat1 = radians(lat1) sep_rad = angular_separation(lon0, lat0, lon1, lat1) if deg: return degrees(sep_rad) else: return sep_rad
def evaluate(lon, lat, lon_0, lat_0, r_0): """Evaluate the model (static function).""" sep = angular_separation(lon, lat, lon_0, lat_0) # Surface area of a spherical cap, see https://en.wikipedia.org/wiki/Spherical_cap norm = 1.0 / (2 * np.pi * (1 - np.cos(r_0))) return u.Quantity(norm.value * (sep <= r_0), "sr-1", copy=False)
def process_mc(dl2_file, mc_type): """ Process the MC simulated and reconstructed to extract the relevant parameters to compute the sensitivity Parameters ---------- dl2_file: dl2 file with mc parameters events: `pandas DataFrame' dl2 events mc_type: 'string' type of particle Returns ------- gammaness: `numpy.ndarray` angdist2: `numpy.ndarray` angular distance squared e_reco: `numpy.ndarray` reconstructed energies n_reco: `int` number of reconstructed events mc_par: `dict` with simulated parameters """ sim_par = read_sim_par(dl2_file) events = pd.read_hdf(dl2_file, key=dl2_params_lstcam_key) # Filters: # TO DO: These cuts must be given in a configuration file # By now: only cut in leakage and intensity # we use all telescopes (number of events needs to be multiplied # by the number of LSTs in the simulation) filter_good_events = ((events.leakage_intensity_width_2 < 0.2) & (events.intensity > 100)) events = events[filter_good_events] e_reco = events.reco_energy.to_numpy() * u.TeV e_true = events.mc_energy.to_numpy() * u.TeV gammaness = events.gammaness # If the particle is a gamma ray, it returns the squared angular distance # from the reconstructed gamma-ray position and the simulated incoming position if mc_type == 'gamma': alt2 = events.mc_alt az2 = events.mc_az # If the particle is not a gamma-ray (diffuse protons/electrons), it returns # the squared angular distance of the reconstructed position w.r.t. the # center of the camera else: alt2 = events.mc_alt_tel az2 = events.mc_az_tel alt1 = events.reco_alt az1 = events.reco_az angdist2 = (angular_separation(az1, alt1, az2, alt2).to_numpy() * u.rad)**2 events['theta2'] = angdist2.to_value(u.deg**2) return gammaness, angdist2.to(u.deg**2), e_reco, e_true, sim_par, events
def angledist(lon1, lat1, lon2, lat2): """ For reference, this is from astropy astropy/coordinates/angle_utilities.py Angular separation between two points on a sphere. """ return degrees( angular_separation(radians(lon1), radians(lat1), radians(lon2), radians(lat2)))
def angledist_astropy(lon1, lat1, lon2, lat2): """ For reference, this is from astropy astropy/coordinates/angle_utilities.py Angular separation between two points on a sphere. """ return degrees(angular_separation(radians(lon1), radians(lat1), radians(lon2), radians(lat2)))
def test_fk4_no_e_fk5(): lines = get_pkg_data_contents('data/fk4_no_e_fk5.csv').split('\n') t = Table.read(lines, format='ascii', delimiter=',', guess=False) if N_ACCURACY_TESTS >= len(t): idxs = range(len(t)) else: idxs = np.random.randint(len(t), size=N_ACCURACY_TESTS) diffarcsec1 = [] diffarcsec2 = [] for i in idxs: # Extract row r = t[int(i)] # int here is to get around a py 3.x astropy.table bug # FK4NoETerms to FK5 c1 = FK4NoETerms(ra=r['ra_in'] * u.deg, dec=r['dec_in'] * u.deg, obstime=Time(r['obstime']), equinox=Time(r['equinox_fk4'])) c2 = c1.transform_to(FK5(equinox=Time(r['equinox_fk5']))) # Find difference diff = angular_separation(c2.ra.radian, c2.dec.radian, np.radians(r['ra_fk5']), np.radians(r['dec_fk5'])) diffarcsec1.append(np.degrees(diff) * 3600.) # FK5 to FK4NoETerms c1 = FK5(ra=r['ra_in'] * u.deg, dec=r['dec_in'] * u.deg, equinox=Time(r['equinox_fk5'])) fk4neframe = FK4NoETerms(obstime=Time(r['obstime']), equinox=Time(r['equinox_fk4'])) c2 = c1.transform_to(fk4neframe) # Find difference diff = angular_separation(c2.ra.radian, c2.dec.radian, np.radians(r['ra_fk4']), np.radians(r['dec_fk4'])) diffarcsec2.append(np.degrees(diff) * 3600.) np.testing.assert_array_less(diffarcsec1, TOLERANCE) np.testing.assert_array_less(diffarcsec2, TOLERANCE)
def evaluate(lon, lat, lon_0, lat_0, r_0, eta, e, phi): sep = angular_separation(lon, lat, lon_0, lat_0) if isinstance(eta, u.Quantity): eta = eta.value # gamma function does not allow quantities minor_axis, r_eff = compute_sigma_eff(lon_0, lat_0, lon, lat, phi, r_0, e) z = sep / r_eff norm = 1 / (2 * np.pi * minor_axis * r_0 * eta * scipy.special.gamma(2 * eta)) return (norm * np.exp(-(z ** (1 / eta)))).to("sr-1")
def evaluate(lon, lat, lon_0, lat_0, r_0, edge): """Evaluate model.""" sep = angular_separation(lon, lat, lon_0, lat_0) # Surface area of a spherical cap, see https://en.wikipedia.org/wiki/Spherical_cap norm = 1.0 / (2 * np.pi * (1 - np.cos(r_0))) in_disk = smooth_edge(sep - r_0, edge) return u.Quantity(norm.value * in_disk, "sr-1", copy=False)
def fov_offset(df): pointing_lat = (90 - df.aux_pointing_position_zd.values) * u.deg pointing_lon = df.aux_pointing_position_az.values * u.deg source_lat = (90 - df.source_position_zd.values) * u.deg source_lon = df.source_position_az.values * u.deg return angular_separation(pointing_lon, pointing_lat, source_lon, source_lat)
def calculate_distance_to_true_source_position(df): source_az = Angle(df.mc_az.values * u.deg).wrap_at(180 * u.deg) source_alt = Angle(df.mc_alt.values * u.deg) az = Angle(df.az.values * u.deg).wrap_at(180 * u.deg) alt = Angle(df.alt.values * u.deg) distance = angular_separation(source_az, source_alt, az, alt).to(u.deg) return distance
def evaluate(lon, lat, lon_0, lat_0, r_0, edge): """Evaluate the model (static function).""" sep = angular_separation(lon, lat, lon_0, lat_0) # Surface area of a spherical cap, see https://en.wikipedia.org/wiki/Spherical_cap norm = 1.0 / (2 * np.pi * (1 - np.cos(r_0))) in_disk = smooth_edge(sep - r_0, edge) return u.Quantity(norm.value * in_disk, "sr-1", copy=False)
def calculate_distance_to_point_source(df, source_alt, source_az): source_az = Angle(source_az).wrap_at(180 * u.deg) source_alt = Angle(source_alt) az = Angle(df.az.values * u.deg).wrap_at(180 * u.deg) alt = Angle(df.alt.values * u.deg) distance = angular_separation(source_az, source_alt, az, alt).to(u.deg) return distance
def test_fk4_no_e_fk5(): lines = get_pkg_data_contents('fk4_no_e_fk5.csv').split('\n') t = Table.read(lines, format='ascii', delimiter=',', guess=False) if N_ACCURACY_TESTS >= len(t): idxs = range(len(t)) else: idxs = np.random.randint(len(t), size=N_ACCURACY_TESTS) diffarcsec1 = [] diffarcsec2 = [] for i in idxs: # Extract row r = t[int(i)] # int here is to get around a py 3.x astropy.table bug # FK4NoETerms to FK5 c1 = FK4NoETerms(ra=r['ra_in']*u.deg, dec=r['dec_in']*u.deg, obstime=Time(r['obstime'], scale='utc'), equinox=Time(r['equinox_fk4'], scale='utc')) c2 = c1.transform_to(FK5(equinox=Time(r['equinox_fk5'], scale='utc'))) # Find difference diff = angular_separation(c2.ra.radian, c2.dec.radian, np.radians(r['ra_fk5']), np.radians(r['dec_fk5'])) diffarcsec1.append(np.degrees(diff) * 3600.) # FK5 to FK4NoETerms c1 = FK5(ra=r['ra_in']*u.deg, dec=r['dec_in']*u.deg, equinox=Time(r['equinox_fk5'], scale='utc')) fk4neframe = FK4NoETerms(obstime=Time(r['obstime'], scale='utc'), equinox=Time(r['equinox_fk4'], scale='utc')) c2 = c1.transform_to(fk4neframe) # Find difference diff = angular_separation(c2.ra.radian, c2.dec.radian, np.radians(r['ra_fk4']), np.radians(r['dec_fk4'])) diffarcsec2.append(np.degrees(diff) * 3600.) np.testing.assert_array_less(diffarcsec1, TOLERANCE) np.testing.assert_array_less(diffarcsec2, TOLERANCE)
def evaluate(lon, lat, lon_0, lat_0, r_0): """Evaluate the model (static function).""" sep = angular_separation(lon, lat, lon_0, lat_0) sep = sep.to('rad').value r_0 = r_0.to('rad').value norm = 1. / (2 * np.pi * (1 - np.cos(r_0))) val = np.where(sep <= r_0, norm, 0) return val * u.Unit('sr-1')
def solid_angle(self): """ Solid angle image (2-dim `~astropy.units.Quantity` in `sr`). """ coordinates = self.coordinates(mode='edges') lon = coordinates.data.lon.radian lat = coordinates.data.lat.radian # Compute solid angle using the approximation that it's # the product between angular separation of pixel corners. # First index is "y", second index is "x" ylo_xlo = lon[:-1, :-1], lat[:-1, :-1] ylo_xhi = lon[:-1, 1:], lat[:-1, 1:] yhi_xlo = lon[1:, :-1], lat[1:, :-1] dx = angular_separation(*(ylo_xlo + ylo_xhi)) dy = angular_separation(*(ylo_xlo + yhi_xlo)) omega = u.Quantity(dx * dy, 'sr') return omega
def _pix_cen(self): """ Offset of every pixel from the origin, along each direction Returns ------- tuple of spectral_offset, y_offset, x_offset, each 3D arrays describing the distance from the origin Notes ----- These arrays are broadcast, and are not memory intensive Each array is in the units of the corresponding wcs.cunit, but this is implicit (e.g., they are not astropy Quantity arrays) """ # Start off by extracting the world coordinates of the pixels _, lat, lon = self.world[0, :, :] spectral, _, _ = self.world[:, 0, 0] # Convert to radians lon = np.radians(lon) lat = np.radians(lat) # Find the dx and dy arrays from astropy.coordinates.angle_utilities import angular_separation dx = angular_separation(lon[:, :-1], lat[:, :-1], lon[:, 1:], lat[:, :-1]) dy = angular_separation(lon[:-1, :], lat[:-1, :], lon[1:, :], lat[1:, :]) # Find the cumulative offset - need to add a zero at the start x = np.zeros(self._data.shape[1:]) y = np.zeros(self._data.shape[1:]) x[:, 1:] = np.cumsum(np.degrees(dx), axis=1) y[1:, :] = np.cumsum(np.degrees(dy), axis=0) x = x.reshape(1, x.shape[0], x.shape[1]) y = y.reshape(1, y.shape[0], y.shape[1]) spectral = spectral.reshape(-1, 1, 1) - spectral.ravel()[0] x, y, spectral = np.broadcast_arrays(x, y, spectral) return spectral, y, x
def angular_separation_deg_to_arcsec(lon1, lat1, lon2, lat2): from astropy.coordinates.angle_utilities import angular_separation from astropy.coordinates import Angle lon1 = np.radians(lon1) lat1 = np.radians(lat1) lon2 = np.radians(lon2) lat2 = np.radians(lat2) separation = angular_separation(lon1, lat1, lon2, lat2) return Angle(separation, 'radian').to('arcsec').value
def test_galactic_fk4(): lines = get_pkg_data_contents('data/galactic_fk4.csv').split('\n') t = Table.read(lines, format='ascii', delimiter=',', guess=False) if N_ACCURACY_TESTS >= len(t): idxs = range(len(t)) else: idxs = np.random.randint(len(t), size=N_ACCURACY_TESTS) diffarcsec1 = [] diffarcsec2 = [] for i in idxs: # Extract row r = t[int(i)] # int here is to get around a py 3.x astropy.table bug # Galactic to FK4 c1 = Galactic(l=r['lon_in']*u.deg, b=r['lat_in']*u.deg) c2 = c1.transform_to(FK4(equinox=Time(r['equinox_fk4'], scale='utc'))) # Find difference diff = angular_separation(c2.ra.radian, c2.dec.radian, np.radians(r['ra_fk4']), np.radians(r['dec_fk4'])) diffarcsec1.append(np.degrees(diff) * 3600.) # FK4 to Galactic c1 = FK4(ra=r['lon_in']*u.deg, dec=r['lat_in']*u.deg, obstime=Time(r['obstime'], scale='utc'), equinox=Time(r['equinox_fk4'], scale='utc')) c2 = c1.transform_to(Galactic) # Find difference diff = angular_separation(c2.l.radian, c2.b.radian, np.radians(r['lon_gal']), np.radians(r['lat_gal'])) diffarcsec2.append(np.degrees(diff) * 3600.) np.testing.assert_array_less(diffarcsec1, TOLERANCE) np.testing.assert_array_less(diffarcsec2, TOLERANCE)
def compute_angletosun(self, meteo): """ Computes the distance to the Sun :param meteo: a Meteo object, whose time attribute has been actualized beforehand """ if SETTINGS["misc"]["singletargetlogs"] == "True": logger.debug("Computing angletosun for {}...".format(self.name)) sunalt, sunaz = meteo.sunalt, meteo.sunaz alt, az = self.altitude, self.azimuth separation = angle_utilities.angular_separation(sunaz, sunalt, az, alt) # Warning, separation is in radian!! angletosun = angles.Angle(separation.value, unit="radian") self.angletosun = angletosun
def test_angsep(): """ Tests that the angular separation object also behaves correctly. """ from astropy.coordinates.angle_utilities import angular_separation # check it both works with floats in radians, Quantities, or Angles for conv in (np.deg2rad, lambda x: u.Quantity(x, "deg"), lambda x: Angle(x, "deg")): for (lon1, lat1, lon2, lat2), corrsep in zip(coords, correct_seps): angsep = angular_separation(conv(lon1), conv(lat1), conv(lon2), conv(lat2)) assert np.fabs(angsep - conv(corrsep)) < conv(correctness_margin)
def evaluate(lon, lat, lon_0, lat_0, radius, width): """Evaluate the model (static function).""" sep = angular_separation(lon, lat, lon_0, lat_0) radius_out = radius + width norm = 3 / (2 * np.pi * (radius_out ** 3 - radius ** 3)) with np.errstate(invalid="ignore"): # np.where and np.select do not work with quantities, so we use the # workaround with indexing value = np.sqrt(radius_out ** 2 - sep ** 2) mask = [sep < radius] value[mask] = (value - np.sqrt(radius ** 2 - sep ** 2))[mask] value[sep > radius_out] = 0 return norm * value
def _compute_kernel_separations(geom, factor): # utility function used for preparing distance to the center of the upsampled geom # TODO : take into account non regular geometry for energy dependent PSF kernel size if geom.is_regular is False: raise ValueError("Non regular geometries are not supported yet.") upsampled_image_geom = geom.to_image().upsample(factor) # get center coordinate center_coord = upsampled_image_geom.center_coord * u.deg # get coordinates map_c = upsampled_image_geom.get_coord() # compute distances to map center separations = angular_separation( center_coord[0], center_coord[1], map_c.lon * u.deg, map_c.lat * u.deg ) # Create map kernel_map = Map.from_geom(geom=upsampled_image_geom.to_cube(axes=geom.axes)) return kernel_map, separations
def compute_angletowind(self, meteo): """ Computes the angle to wind :param meteo: a Meteo object, whose time attribute has been actualized beforehand """ if SETTINGS["misc"]["singletargetlogs"] == "True": logger.debug("Computing angletowind for {}...".format(self.name)) winddirection = meteo.winddirection if winddirection < 0 or winddirection > 360: self.angletowind = None return try: winddirection = angles.Angle(winddirection, unit='degree') self.angletowind = angle_utilities.angular_separation(winddirection, 0., self.azimuth, 0.) self.angletowind = angles.Angle(self.angletowind, unit="radian") except AttributeError: logger.error("{} has no azimuth! \n Compute its azimuth first !".format(self.name)) raise AttributeError("%s has no azimuth! \n Compute its azimuth first !")
def anglesep(lon0: float, lat0: float, lon1: float, lat1: float, deg: bool = True) -> float: """ Parameters ---------- lon0 : float longitude of first point lat0 : float latitude of first point lon1 : float longitude of second point lat1 : float latitude of second point deg : bool, optional degrees input/output (False: radians in/out) Returns ------- sep_rad : float or numpy.ndarray of float angular separation For reference, this is from astropy astropy/coordinates/angle_utilities.py Angular separation between two points on a sphere. """ if angular_separation is None: raise ImportError('angledist requires AstroPy. Try angledis_meeus') if deg: lon0 = radians(lon0) lat0 = radians(lat0) lon1 = radians(lon1) lat1 = radians(lat1) sep_rad = angular_separation(lon0, lat0, lon1, lat1) if deg: return degrees(sep_rad) else: return sep_rad
def pair_correlation(lon, lat, theta_bins): """Compute pair correlation function for points on the sphere. Parameters ---------- lon, lat : array_like Coordinate arrays theta_bins : array_like Array defining the ``theta`` binning. ``theta`` is the angular offset between positions. unit : {'deg', 'rad'} Units of input and output coordinates Returns ------- counts : array Array of point separations per ``theta`` bin. """ separation = angular_separation(lon[:, np.newaxis], lat[:, np.newaxis], lon, lat) pair_correlation, _ = np.histogram(separation.ravel(), theta_bins) return pair_correlation
def minimum_separation(lon1, lat1, lon2, lat2): """Compute minimum distance of each (lon1, lat1) to any (lon2, lat2). Parameters ---------- lon1, lat1 : array_like Primary coordinates of interest lon2, lat2 : array_like Counterpart coordinate array unit : {'deg', 'rad'} Units of input and output coordinates Returns ------- theta_min : array Minimum distance """ lon1 = np.asanyarray(lon1) lat1 = np.asanyarray(lat1) separation = angular_separation( lon1[:, np.newaxis], lat1[:, np.newaxis], lon2, lat2 ) return separation.min(axis=1)
def get_lon_lat_path(ax, transform, lon_lat): """ Draw a curve, taking into account discontinuities. Parameters ---------- ax : ~matplotlib.axes.Axes The axes in which to plot the grid transform : transformation class The transformation between the world and pixel coordinates lon_lat : `~numpy.ndarray` The longitude and latitude values along the curve, given as a (n,2) array. """ # Get pixel limits # xlim = ax.get_xlim() # ylim = ax.get_ylim() # Transform line to pixel coordinates pixel = transform.transform(lon_lat) # In some spherical projections, some parts of the curve are 'behind' or # 'in front of' the plane of the image, so we find those by reversing the # transformation and finding points where the result is not consistent. lon_lat_check = transform.inverted().transform(pixel) sep = angular_separation(np.radians(lon_lat[:, 0]), np.radians(lon_lat[:, 1]), np.radians(lon_lat_check[:, 0]), np.radians(lon_lat_check[:, 1])) sep[sep > np.pi] -= 2. * np.pi mask = np.abs(sep > ROUND_TRIP_TOL) # Mask values with invalid pixel positions mask = mask | np.isnan(pixel[:, 0]) | np.isnan(pixel[:, 1]) # Mask values outside the viewport # This has now been disabled because it assumes specifically rectangular # axes, and also doesn't work if the coordinate direction is flipped. # outside = ((pixel[:, 0] < xlim[0]) | (pixel[:, 0] > xlim[-1]) | # (pixel[:, 1] < ylim[0]) | (pixel[:, 1] > ylim[-1])) # mask[1:-1] = mask[1:-1] | (outside[2:] & outside[:-2]) # We can now start to set up the codes for the Path. codes = np.zeros(lon_lat.shape[0], dtype=np.uint8) codes[:] = Path.LINETO codes[0] = Path.MOVETO codes[mask] = Path.MOVETO # Also need to move to point *after* a hidden value codes[1:][mask[:-1]] = Path.MOVETO # We now go through and search for discontinuities in the curve that would # be due to the curve going outside the field of view, invalid WCS values, # or due to discontinuities in the projection. # We start off by pre-computing the step in pixel coordinates from one # point to the next. The idea is to look for large jumps that might indicate # discontinuities. step = np.sqrt((pixel[1:, 0] - pixel[:-1, 0]) ** 2 + (pixel[1:, 1] - pixel[:-1, 1]) ** 2) # We search for discontinuities by looking for places where the step # is larger by more than a given factor compared to the median # discontinuous = step > DISCONT_FACTOR * np.median(step) discontinuous = step[1:] > DISCONT_FACTOR * step[:-1] # Skip over discontinuities codes[2:][discontinuous] = Path.MOVETO # The above missed the first step, so check that too if step[0] > DISCONT_FACTOR * step[1]: codes[1] = Path.MOVETO # Create the path path = Path(pixel, codes=codes) # And add to the axes return path
def get_lon_lat_path(lon_lat, pixel, lon_lat_check): """ Draw a curve, taking into account discontinuities. Parameters ---------- lon_lat : `~numpy.ndarray` The longitude and latitude values along the curve, given as a (n,2) array. pixel : `~numpy.ndarray` The pixel coordinates corresponding to ``lon_lat`` lon_lat : `~numpy.ndarray` The world coordinates derived from converting from ``pixel``, which is used to ensure round-tripping. """ # In some spherical projections, some parts of the curve are 'behind' or # 'in front of' the plane of the image, so we find those by reversing the # transformation and finding points where the result is not consistent. sep = angular_separation(np.radians(lon_lat[:, 0]), np.radians(lon_lat[:, 1]), np.radians(lon_lat_check[:, 0]), np.radians(lon_lat_check[:, 1])) sep[sep > np.pi] -= 2. * np.pi mask = np.abs(sep > ROUND_TRIP_TOL) # Mask values with invalid pixel positions mask = mask | np.isnan(pixel[:, 0]) | np.isnan(pixel[:, 1]) # We can now start to set up the codes for the Path. codes = np.zeros(lon_lat.shape[0], dtype=np.uint8) codes[:] = Path.LINETO codes[0] = Path.MOVETO codes[mask] = Path.MOVETO # Also need to move to point *after* a hidden value codes[1:][mask[:-1]] = Path.MOVETO # We now go through and search for discontinuities in the curve that would # be due to the curve going outside the field of view, invalid WCS values, # or due to discontinuities in the projection. # We start off by pre-computing the step in pixel coordinates from one # point to the next. The idea is to look for large jumps that might indicate # discontinuities. step = np.sqrt((pixel[1:, 0] - pixel[:-1, 0]) ** 2 + (pixel[1:, 1] - pixel[:-1, 1]) ** 2) # We search for discontinuities by looking for places where the step # is larger by more than a given factor compared to the median # discontinuous = step > DISCONT_FACTOR * np.median(step) discontinuous = step[1:] > DISCONT_FACTOR * step[:-1] # Skip over discontinuities codes[2:][discontinuous] = Path.MOVETO # The above missed the first step, so check that too if step[0] > DISCONT_FACTOR * step[1]: codes[1] = Path.MOVETO # Create the path path = Path(pixel, codes=codes) return path
def _pix_size(self): """ Return the size of each pixel along each direction, in world units Returns ------- dv, dy, dx : tuple of 3D arrays The extent of each pixel along each direction Notes ----- These arrays are broadcast, and are not memory intensive Each array is in the units of the corresponding wcs.cunit, but this is implicit (e.g., they are not astropy Quantity arrays) """ # First, scale along x direction xpix = np.linspace(-0.5, self._data.shape[2] - 0.5, self._data.shape[2] + 1) ypix = np.linspace(0., self._data.shape[1] - 1, self._data.shape[1]) xpix, ypix = np.meshgrid(xpix, ypix) zpix = np.zeros(xpix.shape) lon, lat, _ = self._wcs.all_pix2world(xpix, ypix, zpix, 0) # Convert to radians lon = np.radians(lon) lat = np.radians(lat) # Find the dx and dy arrays from astropy.coordinates.angle_utilities import angular_separation dx = angular_separation(lon[:, :-1], lat[:, :-1], lon[:, 1:], lat[:, :-1]) # Next, scale along y direction xpix = np.linspace(0., self._data.shape[2] - 1, self._data.shape[2]) ypix = np.linspace(-0.5, self._data.shape[1] - 0.5, self._data.shape[1] + 1) xpix, ypix = np.meshgrid(xpix, ypix) zpix = np.zeros(xpix.shape) lon, lat, _ = self._wcs.all_pix2world(xpix, ypix, zpix, 0) # Convert to radians lon = np.radians(lon) lat = np.radians(lat) # Find the dx and dy arrays from astropy.coordinates.angle_utilities import angular_separation dy = angular_separation(lon[:-1, :], lat[:-1, :], lon[1:, :], lat[1:, :]) # Next, spectral coordinates zpix = np.linspace(-0.5, self._data.shape[0] - 0.5, self._data.shape[0] + 1) xpix = np.zeros(zpix.shape) ypix = np.zeros(zpix.shape) _, _, spectral = self._wcs.all_pix2world(xpix, ypix, zpix, 0) dspectral = np.diff(spectral) dx = np.abs(np.degrees(dx.reshape(1, dx.shape[0], dx.shape[1]))) dy = np.abs(np.degrees(dy.reshape(1, dy.shape[0], dy.shape[1]))) dspectral = np.abs(dspectral.reshape(-1, 1, 1)) dx, dy, dspectral = np.broadcast_arrays(dx, dy, dspectral) return dspectral, dy, dx
def diag_size(self): """return the largest diagonal size""" p1,p2,p3,p4 = self.calc_footprint() return np.max([angular_separation(*np.concatenate([p1,p3])), angular_separation(*np.concatenate([p2,p4]))])
# It fails for empty pixels try: hillas_params[tel_id] = hillas_parameters(camgeom, cleaned_image) except: pass if len(hillas_params) < 2: continue reco_result = reco.predict(hillas_params, event.inst, point_altitude, point_azimuth) # get angular offset between reconstructed shower direction and MC # generated shower direction off_angle = angular_separation( event.mc.az, event.mc.alt, reco_result.az, reco_result.alt ) # Appending all estimated off angles off_angles.append(off_angle.to(u.deg).value) # calculate theta square for angles which are not nan off_angles = np.array(off_angles) thetasquare = off_angles[np.isfinite(off_angles)]**2 # To plot thetasquare The number of events in th data files for LSTCam is not # significantly high to give a nice thetasquare plot for gammas One can use # deedicated MC file for LST get nice plot plt.figure(figsize=(10, 8)) plt.hist(thetasquare, bins=np.linspace(0, 1, 50))
# assume the location of the centroids to be the published value # draw values from 4 normal distribution centroid_old = [[NW_cent.ra.deg, NW_cent.dec.deg, 0.87, SE_cent.ra.deg, SE_cent.dec.deg, 0.87]] * no # hopefully that the arrays shape will be broadcast N_halos = [2] * no centroid_steps = [1. / np.sqrt(2) / 60. / 60.] * no cent_new = map(get_new_centroids, N_halos, centroid_old, centroid_steps) cent_new = np.array(cent_new) [NW_ra, NW_dec, NW_z, SE_ra, SE_dec, SE_z] = cent_new.transpose() # inputs of angular separation has to be in units of radians ang_sep = \ ang_util.angular_separation(NW_ra / 180. * np.pi, NW_dec / 180. * np.pi, SE_ra / 180. * np.pi, SE_dec / 180. * np.pi) cosmo = FlatLambdaCDM(H0=70, Om0=0.3) D_proj = cosmo.angular_diameter_distance(0.87) * ang_sep ### # Debug input ### def plot_dproj(): plt.title('D-proj') plt.xlabel('Mpc') plt.hist(D_proj, bins=100, normed=True, histtype='step') plt.savefig(out_prefix + '_D_proj.png') plt.close()