def _convert_sky_coords(self): """ Convert to sky coordinates """ parsed_angles = [(x, y) for x, y in zip(self.coord[:-1:2], self.coord[1::2]) if (isinstance(x, coordinates.Angle) and isinstance(y, coordinates.Angle))] frame = coordinates.frame_transform_graph.lookup_name(self.coordsys) lon, lat = zip(*parsed_angles) if hasattr(lon, '__len__') and hasattr( lat, '__len__') and len(lon) == 1 and len(lat) == 1: # force entries to be scalar if they are length-1 lon, lat = u.Quantity(lon[0]), u.Quantity(lat[0]) else: # otherwise, they are vector quantities lon, lat = u.Quantity(lon), u.Quantity(lat) sphcoords = coordinates.UnitSphericalRepresentation(lon, lat) coords = [SkyCoord(frame(sphcoords))] if self.region_type != 'polygon': coords += self.coord[len(coords * 2):] return coords
def __call__( self, n, *, frame=None, representation_type=None, random=None, **kwargs ): # Get preferred frames frame = self._infer_frame(frame) representation_type = self._infer_representation(representation_type) if random is None: random = np.random elif isinstance(random, int): random = np.random.default_rng(random) # return rep = coord.UnitSphericalRepresentation( lon=random.uniform(size=n) * u.deg, lat=2 * random.uniform(size=n) * u.deg, ) if representation_type is None: representation_type = rep.__class__ sample = coord.SkyCoord( frame.realize_frame(rep, representation_type=representation_type), copy=False, ) sample.cache["mass"] = np.ones(n) sample.cache["potential"] = object() return sample
def sinehgc_to_hgc(sinehgc_coord, hgc_frame): lat = sinehgc_coord.lat lon = sinehgc_coord.lon lat_out = u.Quantity(np.sin(lat.value), u.deg) return hgc_frame.realize_frame( coord.UnitSphericalRepresentation(lat=lat_out, lon=lon))
def hgc_to_sinehgc(hgc_coord, sinehgc_frame): lat = hgc_coord.lat lon = hgc_coord.lon lat_out = u.Quantity(np.arcsin(lat.value), u.rad) return sinehgc_frame.realize_frame( coord.UnitSphericalRepresentation(lat=lat_out, lon=lon))
def test_pole_from_xyz(): xnew = coord.UnitSphericalRepresentation(185*u.deg, 32.5*u.deg).to_cartesian() ynew = coord.UnitSphericalRepresentation(275*u.deg, 0*u.deg).to_cartesian() znew = xnew.cross(ynew) fr1 = GreatCircleICRSFrame.from_xyz(xnew, ynew, znew) fr2 = GreatCircleICRSFrame.from_xyz(xnew, ynew) fr3 = GreatCircleICRSFrame.from_xyz(xnew, znew=znew) fr4 = GreatCircleICRSFrame.from_xyz(ynew=ynew, znew=znew) for fr in [fr2, fr3, fr4]: assert np.isclose(fr1.pole.ra.degree, fr.pole.ra.degree) assert np.isclose(fr1.pole.dec.degree, fr.pole.dec.degree) assert np.isclose(fr1.center.ra.degree, fr.center.ra.degree) assert np.isclose(fr1.center.dec.degree, fr.center.dec.degree) with pytest.raises(ValueError): GreatCircleICRSFrame.from_xyz(xnew)
def zyx_euler_from_endpoints(lon1, lat1, lon2, lat2): c1 = coord.SkyCoord(lon1 * u.deg, lat1 * u.deg) c2 = coord.SkyCoord(lon2 * u.deg, lat2 * u.deg) fr = gc.GreatCircleICRSFrame.from_endpoints(c1, c2) origin = fr.realize_frame( coord.UnitSphericalRepresentation(0 * u.deg, 0 * u.deg)) gc_icrs = origin.transform_to(coord.ICRS) R = gc.greatcircle.reference_to_greatcircle(coord.ICRS, fr) psi = -np.degrees(np.arctan2(R[2, 1], R[2, 2])) return [gc_icrs.ra.degree, gc_icrs.dec.degree, psi]
def get_rot(frame, x0=0.): trans = coord.frame_transform_graph.get_transform(coord.ICRS, frame.__class__) for t in trans.transforms: if not isinstance(t, coord.transformations.StaticMatrixTransform): R = None break else: # All are static matrix transformations R = np.eye(3) for t in trans.transforms: R = R @ t.matrix origin = frame.realize_frame( coord.UnitSphericalRepresentation(0 * u.deg, 0 * u.deg)) gc_icrs = origin.transform_to(coord.ICRS) if R is None: # no simple matrix transformation: usph_band = coord.UnitSphericalRepresentation( np.random.uniform(0, 360, 10000) * u.deg, np.random.uniform(-1, 1, size=10000) * u.deg) band_icrs = frame.realize_frame(usph_band).transform_to(coord.ICRS) R1 = coord.matrix_utilities.rotation_matrix(gc_icrs.ra, 'z') R2 = coord.matrix_utilities.rotation_matrix(-gc_icrs.dec, 'y') Rtmp = R2 @ R1 psi = get_roll(band_icrs, Rtmp, x0).degree else: origin = frame.realize_frame( coord.UnitSphericalRepresentation(0 * u.deg, 0 * u.deg)) gc_icrs = origin.transform_to(coord.ICRS) psi = -np.degrees(np.arctan2(R[2, 1], R[2, 2])) return [gc_icrs.ra.degree, gc_icrs.dec.degree, psi]
def get_rot(fr, x0=0.): origin = get_origin(fr) usph_band = coord.UnitSphericalRepresentation( np.random.uniform(0, 360, 10000) * u.deg, np.random.uniform(-1, 1, size=10000) * u.deg) band_icrs = fr.realize_frame(usph_band).transform_to(coord.ICRS) R1 = rotation_matrix(origin.ra, 'z') R2 = rotation_matrix(-origin.dec, 'y') Rtmp = R2 @ R1 roll = get_roll(band_icrs, Rtmp, x0) return [origin.ra.degree, origin.dec.degree, roll.degree]
def test_representation_representation(): """ Test that Representations are represented correctly. """ # With no unit we get "None" in the unit row c = coordinates.CartesianRepresentation([0], [1], [0], unit=u.one) t = Table([c]) assert t.pformat() == [' col0 ', '------------', '(0., 1., 0.)'] c = coordinates.CartesianRepresentation([0], [1], [0], unit='m') t = Table([c]) assert t.pformat() == [' col0 ', ' m ', '------------', '(0., 1., 0.)'] c = coordinates.SphericalRepresentation([10]*u.deg, [20]*u.deg, [1]*u.pc) t = Table([c]) assert t.pformat() == [' col0 ', ' deg, deg, pc ', '--------------', '(10., 20., 1.)'] c = coordinates.UnitSphericalRepresentation([10]*u.deg, [20]*u.deg) t = Table([c]) assert t.pformat() == [' col0 ', ' deg ', '----------', '(10., 20.)'] c = coordinates.SphericalCosLatDifferential( [10]*u.mas/u.yr, [2]*u.mas/u.yr, [10]*u.km/u.s) t = Table([c]) assert t.pformat() == [' col0 ', 'mas / yr, mas / yr, km / s', '--------------------------', ' (10., 2., 10.)']
def compute_stream_rotation_matrix(coords, zero_pt='mean'): """ Compute the rotation matrix to go from the frame of the input coordinate to closely align the equator with the stream. .. note:: if using zero_pt='mean' or 'median, the coordinates in the new system might not hit (0,0) because the mean / median is taken across each dimension separately. Parameters ---------- coords : :class:`astropy.coordinate.SkyCoord`, :class:`astropy.coordinate.BaseCoordinateFrame` The coordinates of the stream stars. zero_pt : str,:class:`astropy.coordinate.SkyCoord`, :class:`astropy.coordinate.BaseCoordinateFrame` The origin of the new coordinates in the old coordinates. Returns ------- R : :class:`~numpy.ndarray` A 3 by 3 rotation matrix (has shape ``(3,3)``) to convert heliocentric, Cartesian coordinates in the input coordinate frame to stream coordinates. """ if hasattr(coords, 'spherical'): sph = coords.spherical else: sph = coords if zero_pt == 'mean' or zero_pt == 'median': lon = sph.lon.wrap_at(360 * u.degree) lat = sph.lat.wrap_at(90 * u.degree) if np.any(lon < 10 * u.degree) and np.any( lon > 350 * u.degree): # it's wrapping lon = lon.wrap_at(180 * u.degree) if np.any(lat < -80 * u.degree) and np.any( lat > 80 * u.degree): # it's wrapping lat = lat.wrap_at(180 * u.degree) zero_pt = coord.UnitSphericalRepresentation(lon=getattr(np, zero_pt)(lon), lat=getattr(np, zero_pt)(lat)) elif hasattr(zero_pt, 'spherical'): zero_pt = zero_pt.spherical # first determine rotation matrix to put zero_pt at (0,0) R1 = rotation_matrix(zero_pt.lon, 'z') R2 = rotation_matrix(-zero_pt.lat, 'y') xyz2 = (R2 * R1).dot(sph.represent_as(coord.CartesianRepresentation).xyz) # determine initial guess for angle with some math trickery _r = np.sqrt(xyz2[1]**2 + xyz2[2]**2) ix = _r.argmin() if ix == 0: ix += 1 elif ix == (xyz2[1].size - 1): ix -= 1 guess = 180 * u.degree - np.arctan2(xyz2[2][ix + 1] - xyz2[2][ix], xyz2[1][ix + 1] - xyz2[1][ix]).to( u.degree) res = minimize(_rotation_opt_func, x0=np.cos(guess), args=(xyz2, ), method="powell") if not res.success: raise ValueError("Failed to compute final alignment angle.") if np.allclose(np.abs(res.x), 1., atol=1E-5): guess = 180 * u.degree - guess res = minimize(_rotation_opt_func, x0=np.cos(guess), args=(xyz2, ), method="powell") R3 = rotation_matrix(np.arccos(res.x) * u.radian, 'x') R = R3 * R2 * R1 return R
def line_parser(line, coordsys=None, errors='strict'): """ Parse a single ds9 region line into a string Parameters ---------- line : str A single ds9 region contained in a string coordsys : str The global coordinate system name declared at the top of the ds9 file errors : ``warn``, ``ignore``, ``strict`` The error handling scheme to use for handling skipped entries in a region file that were not parseable. The default is 'strict', which will raise a ``DS9RegionParserError``. ``warn`` will raise a warning, and ``ignore`` will do nothing (i.e., be silent). Returns ------- (region_type, parsed_return, parsed_meta, composite, include) region_type : str coord_list : list of coordinate objects meta : metadata dict composite : bool indicates whether region is a composite region include : bool Whether the region is included (False -> excluded) """ if errors not in ('strict', 'ignore', 'warn'): raise ValueError("``errors`` must be one of strict, ignore, or warn") if '# Region file format' in line and line[0] == '#': # This is just a file format line, we can safely skip it return # special case / header: parse global parameters into metadata if line.lstrip()[:6] == 'global': return global_parser(line) region_type_search = region_type_or_coordsys_re.search(line) if region_type_search: include = region_type_search.groups()[0] region_type = region_type_search.groups()[1] else: # if there's no line, it's just blank, so don't warn if line: # but otherwise, this should probably always raise a warning? # at least until we identify common cases for it warn("No region type found for line '{0}'.".format(line), DS9RegionParserWarning) return if region_type in coordinate_systems: return region_type # outer loop has to do something with the coordinate system information elif region_type in language_spec: if coordsys is None: raise DS9RegionParserError("No coordinate system specified and a" " region has been found.") if "||" in line: composite = True else: composite = False # end_of_region_name is the coordinate of the end of the region's name, e.g.: # circle would be 6 because circle is 6 characters end_of_region_name = region_type_search.span()[1] # coordinate of the # symbol or end of the line (-1) if not found hash_or_end = line.find("#") coords_etc = strip_paren( line[end_of_region_name:hash_or_end].strip(" |")) meta_str = line[hash_or_end:] parsed_meta = meta_parser(meta_str) if coordsys in coordsys_name_mapping: parsed = type_parser(coords_etc, language_spec[region_type], coordsys_name_mapping[coordsys]) # Reset iterator for ellipse annulus if region_type == 'ellipse': language_spec[region_type] = itertools.chain( (coordinate, coordinate), itertools.cycle((radius, ))) parsed_angles = [(x, y) for x, y in zip(parsed[:-1:2], parsed[1::2]) if (isinstance(x, coordinates.Angle) and isinstance(y, coordinates.Angle))] frame = coordinates.frame_transform_graph.lookup_name( coordsys_name_mapping[coordsys]) lon, lat = zip(*parsed_angles) if hasattr(lon, '__len__') and hasattr( lon, '__lat__') and len(lon) == 1 and len(lat == 1): # force entries to be scalar if they are length-1 lon, lat = u.Quantity(lon[0]), u.Quantity(lat[0]) else: # otherwise, they are vector quantitites lon, lat = u.Quantity(lon), u.Quantity(lat) sphcoords = coordinates.UnitSphericalRepresentation(lon, lat) coords = frame(sphcoords) return region_type, [ coords ] + parsed[len(coords) * 2:], parsed_meta, composite, include else: parsed = type_parser(coords_etc, language_spec[region_type], coordsys) if region_type == 'polygon': # have to special-case polygon in the phys coord case # b/c can't typecheck when iterating as in sky coord case coord = PixCoord(parsed[0::2], parsed[1::2]) parsed_return = [coord] else: parsed = [_.value for _ in parsed] coord = PixCoord(parsed[0], parsed[1]) parsed_return = [coord] + parsed[2:] # Reset iterator for ellipse annulus if region_type == 'ellipse': language_spec[region_type] = itertools.chain( (coordinate, coordinate), itertools.cycle((radius, ))) return region_type, parsed_return, parsed_meta, composite, include else: # This will raise warnings even if the first line is acceptable, # e.g. something like: # # Region file format: DS9 version 4.1 # That behavior is unfortunate, but there's not a great workaround # except to let the user set `errors='ignore'` if errors in ('warn', 'strict'): message = ( "Region type '{0}' was identified, but it is not one of " "the known region types.".format(region_type)) if errors == 'strict': raise DS9RegionParserError(message) else: warn(message, DS9RegionParserWarning)
def fit_frame( data: coord.BaseCoordinateFrame, origin: coord.BaseCoordinateFrame, rot0: u.Quantity = 0 * u.deg, bounds: T.Sequence = (-np.inf, np.inf), *, fix_origin: bool = _minimize_defaults["fix_origin"], use_lmfit: T.Optional[bool] = _minimize_defaults["use_lmfit"], leastsquares: bool = _minimize_defaults["leastsquares"], align_v: bool = _minimize_defaults["align_v"], **fit_kwargs, ): """Find Best-Fit Rotated Frame. Parameters ---------- data : :class:`~astropy.coordinates.CartesianRepresentation` If `align_v`, must have attached differentials rot0 : |Quantity| Initial guess for rotation origin : :class:`~astropy.coordinates.BaseCoordinateFrame` location of point on sky about which to rotate bounds : array-like, optional Parameter bounds. See :func:`~trackstream.preprocess.fit_rotated_frame.make_bounds` :: [[rot_low, rot_up], [lon_low, lon_up], [lat_low, lat_up]] Returns ------- res : Any The result of the minimization. Depends on arguments. Dict[str, Any] Has fields "rotation" and "origin". Other Parameters ---------------- use_lmfit : bool, optional, kwarg only Whether to use ``lmfit`` package leastsquares : bool, optional, kwarg only If `use_lmfit` is False, whether to to use :func:`~scipy.optimize.least_square` or :func:`~scipy.optimize.minimize` (default) align_v : bool, optional, kwarg only Whether to align velocity to be in positive direction fit_kwargs: Into whatever minimization package / function is used. Raises ------ ValueError If `use_lmfit` and lmfit is not installed. """ # ------------------------ # Inputs # Data # need to make sure Cartesian representation # data = data.represent_as( # coord.CartesianRepresentation, # differential_class=coord.CartesianDifferential, # ) # Origin # We work with a SphericalRepresentation, but # if isinstance(origin, coord.SkyCoord): # raise TypeError origin_frame = origin.__class__ origin = origin.represent_as(coord.SphericalRepresentation) if use_lmfit is None: use_lmfit = conf.use_lmfit x0 = u.Quantity([rot0, origin.lon, origin.lat]).to_value(u.deg) subsel = fit_kwargs.pop("subsel", Ellipsis) # ------------------------ # Fitting if use_lmfit: # lmfit if not HAS_LMFIT: raise ValueError("`lmfit` package not available.") res, values = _fit_representation_lmfit( data.cartesian, x0=x0, bounds=bounds, fix_origin=fix_origin, **fit_kwargs, ) else: # scipy res, values = _fit_representation_scipy( data.cartesian, x0=x0, bounds=bounds, fix_origin=fix_origin, use_leastsquares=leastsquares, **fit_kwargs, ) # /def # ------------------------ # Return best_rot = values[0] best_origin = coord.UnitSphericalRepresentation( lon=values[1], lat=values[2], # TODO re-add distance ) best_origin = origin_frame(best_origin) values = dict(rotation=best_rot, origin=best_origin) if align_v: values = align_v_positive_lon(data, values, subsel=subsel) return res, values
def test__fix_branch_cuts(self): """Test method ``_fix_branch_cuts``. .. todo:: graphical proof via mpl_test that the point hasn't moved. """ # ------------------------------- # no angular units rep = coord.CartesianRepresentation( x=[1, 2] * u.kpc, y=[3, 4] * u.kpc, z=[5, 6] * u.kpc, ) array = rep._values.view(dtype=np.float64).reshape(rep.shape[0], -1).T got = self.inst._fix_branch_cuts(array, rep.__class__, rep._units) assert got is array # ------------------------------- # UnitSphericalRepresentation # 1) all good rep = coord.UnitSphericalRepresentation( lon=[1, 2] * u.deg, lat=[3, 4] * u.deg, ) array = rep._values.view(dtype=np.float64).reshape(rep.shape[0], -1).T got = self.inst._fix_branch_cuts( array.copy(), rep.__class__, rep._units, ) assert np.allclose(got, array) # 2) needs correction array = np.array([[-360, 0, 360], [-91, 0, 91]]) got = self.inst._fix_branch_cuts( array.copy(), rep.__class__, rep._units, ) assert np.allclose(got, np.array([[-180, 0, 540], [-89, 0, 89]])) # ------------------------------- # SphericalRepresentation # 1) all good rep = coord.SphericalRepresentation( lon=[1, 2] * u.deg, lat=[3, 4] * u.deg, distance=[5, 6] * u.kpc, ) array = rep._values.view(dtype=np.float64).reshape(rep.shape[0], -1).T got = self.inst._fix_branch_cuts( array.copy(), rep.__class__, rep._units, ) assert np.allclose(got, array) # 2) needs correction array = np.array([[-360, 0, 360], [-91, 0, 91], [5, 6, 7]]) got = self.inst._fix_branch_cuts( array.copy(), rep.__class__, rep._units, ) assert np.allclose( got, np.array([[-180, 0, 540], [-89, 0, 89], [5, 6, 7]]), ) # 3) needs correction array = np.array([[-360, 0, 360], [-91, 0, 91], [-5, 6, -7]]) got = self.inst._fix_branch_cuts( array.copy(), rep.__class__, rep._units, ) assert np.allclose( got, np.array([[0, 0, 720], [89, 0, -89], [5, 6, 7]]), ) # ------------------------------- # CylindricalRepresentation # 1) all good rep = coord.CylindricalRepresentation( rho=[5, 6] * u.kpc, phi=[1, 2] * u.deg, z=[3, 4] * u.parsec, ) array = rep._values.view(dtype=np.float64).reshape(rep.shape[0], -1).T got = self.inst._fix_branch_cuts( array.copy(), rep.__class__, rep._units, ) assert np.allclose(got, array) # 2) needs correction array = np.array([[-5, 6, -7], [-180, 0, 180], [-4, 4, 4]]) got = self.inst._fix_branch_cuts( array.copy(), rep.__class__, rep._units, ) assert np.allclose(got, np.array([[5, 6, 7], [0, 0, 360], [-4, 4, 4]])) # ------------------------------- # NotImplementedError with pytest.raises(NotImplementedError): rep = coord.PhysicsSphericalRepresentation( phi=[1, 2] * u.deg, theta=[3, 4] * u.deg, r=[5, 6] * u.kpc, ) array = (rep._values.view(dtype=np.float64).reshape( rep.shape[0], -1).T) self.inst._fix_branch_cuts( array.copy(), coord.PhysicsSphericalRepresentation, rep._units, )
def line_parser(line, coordsys=None): """ Parse a single ds9 region line into a string Parameters ---------- line : str A single ds9 region contained in a string coordsys : str The global coordinate system name declared at the top of the ds9 file Returns ------- (region_type, parsed_return, parsed_meta, composite, include) region_type : str coord_list : list of coordinate objects meta : metadata dict composite : bool indicates whether region is a composite region include : bool Whether the region is included (False -> excluded) """ region_type_search = region_type_or_coordsys_re.search(line) if region_type_search: include = region_type_search.groups()[0] region_type = region_type_search.groups()[1] else: return if region_type in coordinate_systems: return region_type # outer loop has to do something with the coordinate system information elif region_type in language_spec: if coordsys is None: raise ValueError( "No coordinate system specified and a region has been found.") if "||" in line: composite = True else: composite = False # end_of_region_name is the coordinate of the end of the region's name, e.g.: # circle would be 6 because circle is 6 characters end_of_region_name = region_type_search.span()[1] # coordinate of the # symbol or end of the line (-1) if not found hash_or_end = line.find("#") coords_etc = strip_paren( line[end_of_region_name:hash_or_end].strip(" |")) meta_str = line[hash_or_end:] parsed_meta = meta_parser(meta_str) if coordsys in coordsys_name_mapping: parsed = type_parser(coords_etc, language_spec[region_type], coordsys_name_mapping[coordsys]) # Reset iterator for ellipse annulus if region_type == 'ellipse': language_spec[region_type] = itertools.chain( (coordinate, coordinate), itertools.cycle((radius, ))) parsed_angles = [(x, y) for x, y in zip(parsed[:-1:2], parsed[1::2]) if isinstance(x, coordinates.Angle) and isinstance(x, coordinates.Angle)] frame = coordinates.frame_transform_graph.lookup_name( coordsys_name_mapping[coordsys]) lon, lat = zip(*parsed_angles) lon, lat = u.Quantity(lon), u.Quantity(lat) sphcoords = coordinates.UnitSphericalRepresentation(lon, lat) coords = frame(sphcoords) return region_type, [ coords ] + parsed[len(coords) * 2:], parsed_meta, composite, include else: parsed = type_parser(coords_etc, language_spec[region_type], coordsys) if region_type == 'polygon': # have to special-case polygon in the phys coord case b/c can't typecheck when iterating as in sky coord case coord = PixCoord(parsed[0::2], parsed[1::2]) parsed_return = [coord] else: parsed = [_.value for _ in parsed] coord = PixCoord(parsed[0], parsed[1]) parsed_return = [coord] + parsed[2:] # Reset iterator for ellipse annulus if region_type == 'ellipse': language_spec[region_type] = itertools.chain( (coordinate, coordinate), itertools.cycle((radius, ))) return region_type, parsed_return, parsed_meta, composite, include
def test__make_bounds_defaults(): """Test `~trackstream.preprocess.rotated_frame._make_bounds_defaults`.""" expected = set(("rot_lower", "rot_upper", "origin_lim")) assert set(rotated_frame._make_bounds_defaults.keys()) == expected # /def # TODO use hypothesis instead @pytest.mark.parametrize( "origin,rot_lower,rot_upper,origin_lim,expected", [ ( coord.UnitSphericalRepresentation(lon=0 * u.deg, lat=0 * u.deg), -1 * u.deg, 2 * u.deg, 3 * u.deg, np.c_[[-1, 2], [-3, 3], [-3, 3]].T, ), ( coord.UnitSphericalRepresentation(lon=10 * u.deg, lat=11 * u.deg), -1 * u.deg, 2 * u.deg, 3 * u.deg, np.c_[[-1, 2], [7, 13], [8, 14]].T, ), ], ) def test_make_bounds(
def get_origin(fr): usph_origin = coord.UnitSphericalRepresentation(0 * u.deg, 0 * u.deg) return fr.realize_frame(usph_origin).transform_to(coord.ICRS)