def get_spectral_limits(cube, freq_range=None, vel_range=None, chan_range=None, chan_halfwidth=None, vlsr=None, linefreq=None): # Turn everything into frequency or velocity spec_unit = cube.spectral_axis.unit rangefmt = '{0.value[0]:.4f} {0.value[1]:.4f} {0.unit}' if freq_range is not None: LOG.info('Using frequency range = %s', rangefmt.format(freq_range.to(u.GHz))) if spec_unit.is_equivalent(freq_range.unit): return freq_range[0], freq_range[1] else: # Convert to spectral units restfreq = get_restfreq(cube) freq_to_vel = u.doppler_radio(restfreq) lim = freq_range.to(spec_unit, equivalencies=freq_to_vel) return np.min(lim), np.max(lim) elif vel_range is not None: LOG.info('Using velocity range = %s', rangefmt.fmt(vel_range.to(u.km / u.s))) if spec_unit.is_equivalent(vel_range.unit): return vel_range[0], vel_range[1] else: # Convert to spectral units restfreq = get_restfreq(cube) freq_to_vel = u.doppler_radio(restfreq) lim = vel_range.to(spec_unit, equivalencies=freq_to_vel) return np.min(lim), np.max(lim) elif chan_range is not None: LOG.info('Channel range = %i %i', *chan_range) return sorted(chan_range) elif chan_halfwidth is not None and linefreq and vlsr: # Get spectral axis spaxis = cube.spectral_axis restfreq = get_restfreq(cube) # Convert to velocity from line vel_to_freq = u.doppler_radio(restfreq) spaxis = spaxis.to(linefreq.unit, equivalencies=vel_to_freq) freq_to_vel = u.doppler_radio(linefreq) spaxis = spaxis.to(vlsr.unit, equivalencies=freq_to_vel) spaxis = spaxis - vlsr # Closest value to vlsr ind = np.nanargmin(np.abs(spaxis.value)) chmin = ind - chan_halfwidth chmax = ind + chan_halfwidth if chmin < 0: chmin = 0 if chmax >= len(spaxis): chmax = len(spaxis) - 1 LOG.info('Using channel range = %i %i', chmin, chmax) return chmin, chmax else: LOG.info('No spectral limit') return None, None
def test_byhand_vrad(): # VRAD CRVAL3F = 1.37847121643E+09 CDELT3F = 9.764775E+04 RESTFRQR= 1.420405752E+09 CRVAL3R = 8.85075090419E+06 CDELT3R = -2.0609645E+04 CUNIT3R = 'm/s' CUNIT3F = 'Hz' crvalf = CRVAL3F * u.Unit(CUNIT3F) crvalv = CRVAL3R * u.Unit(CUNIT3R) restfreq = RESTFRQR * u.Unit(CUNIT3F) cdeltf = CDELT3F * u.Unit(CUNIT3F) cdeltv = CDELT3R * u.Unit(CUNIT3R) # (Pdb) crval_in,crval_lin1,crval_lin2,crval_out # (<Quantity 1378471216.43 Hz>, <Quantity 1378471216.43 Hz>, <Quantity 8850750.904040769 m / s>, <Quantity 8850750.904040769 m / s>) # (Pdb) cdelt_in, cdelt_lin1, cdelt_lin2, cdelt_out # (<Quantity 97647.75 Hz>, <Quantity 97647.75 Hz>, <Quantity -20609.645482954576 m / s>, <Quantity -20609.645482954576 m / s>) crvalv_computed = crvalf.to(CUNIT3R, u.doppler_radio(restfreq)) cdeltv_computed = -(cdeltf / restfreq)*constants.c assert_allclose(crvalv_computed, crvalv, rtol=1.e-3) assert_allclose(cdeltv_computed, cdeltv, rtol=1.e-3) crvalf_computed = crvalv_computed.to(CUNIT3F, u.doppler_radio(restfreq)) cdeltf_computed = -(cdeltv_computed/constants.c) * restfreq assert_allclose(crvalf_computed, crvalf, rtol=1.e-3) assert_allclose(cdeltf_computed, cdeltf, rtol=1.e-3)
def closest_spectral_channel(self, value, rest_frequency=None): """ Find the index of the closest spectral channel to the specified spectral coordinate. Parameters ---------- value : :class:`~astropy.units.Quantity` The value of the spectral coordinate to search for. rest_frequency : :class:`~astropy.units.Quantity` The rest frequency for any Doppler conversions """ # TODO: we have to not compute this every time spectral_axis = self.spectral_axis try: value = value.to(spectral_axis.unit, equivalencies=u.spectral()) except u.UnitsError: if value.unit.is_equivalent(spectral_axis.unit, equivalencies=u.doppler_radio(None)): if rest_frequency is None: raise u.UnitsError("{0} cannot be converted to {1} without a " "rest frequency".format(value.unit, spectral_axis.unit)) else: try: value = value.to(spectral_axis.unit, equivalencies=u.doppler_radio(rest_frequency)) except u.UnitsError: raise u.UnitsError("{0} cannot be converted to {1}".format(value.unit, spectral_axis.unit)) else: raise u.UnitsError("'value' should be in frequency equivalent or velocity units (got {0})".format(value.unit)) # TODO: optimize the next line - just brute force for now return np.argmin(np.abs(spectral_axis - value))
def freqs_to_vel(center_freq, fs, sc): """Convert frequency to radial Doppler velocity. Accounts for movement of the earth relative to the sun and the sun relative to galactic center. Args: center_freq: frequency at zero velocity fs: Quantity object representing frequencies sc: SkyCoord object representing one or many coordinates (must have obstime and location set) """ # Convert from earth reference frame to solar reference frame using # https://docs.astropy.org/en/stable/coordinates/velocities.html#radial-velocity-corrections # Then convert from solar reference frame to Galactic Standard of Rest using # https://docs.astropy.org/en/stable/generated/examples/coordinates/rv-to-gsr.html pos_gal = sc.galactic v_to_bary = pos_gal.radial_velocity_correction(kind='barycentric') # Calculate the sun's velocity projected in the observing direction. v_sun = Galactocentric().galcen_v_sun.to_cartesian() cart_data = pos_gal.data.to_cartesian() unit_vector = cart_data / cart_data.norm() v_proj = v_sun.dot(unit_vector) doppler_shift = u.doppler_radio(center_freq) v_local = fs.to(u.km/u.s, doppler_shift) v_bary = v_local + v_to_bary + v_local * v_to_bary / c # v_bary is now barycentric; now we need to remove the solar system motion as well return (v_bary + v_proj)
def convert_chan_freq_vel(chan_res): """ Takes an input channel resolution, either in freq or velocity Will return matching resolution in other dimensions Presumes radio definition; can consider as a param for future Inputs: chan_res (Quantity): Input resolution, with units Outputs: new_res (Quantity): Output resolution, with units, opp of input """ #define conversion restfreq = 1420.405752 * u.MHz freq_to_vel = u.doppler_radio(restfreq) #check if in frequency; if so, get vel if 'Hz' in chan_res.unit.to_string(): offsetfreq = restfreq - chan_res vel = restfreq.to(u.km / u.s, equivalencies=freq_to_vel) offsetvel = offsetfreq.to(u.km / u.s, equivalencies=freq_to_vel) new_res = offsetvel - vel else: print("Not frequency unit, presuming velocity") offsetfreq = chan_res.to(u.kHz, equivalencies=freq_to_vel) new_res = restfreq - offsetfreq return new_res
def velocity_axis(self): """Obtain spectral axis en velocity units.""" if self.spectral_axis.unit.is_equivalent(u.km / u.s): return self.spectral_axis else: equiv = u.doppler_radio(self.restfreq) return self.spectral_axis.to(u.km / u.s, equivalencies=equiv)
def test_convert_to_unit(run_with_assert=False): for from_unit, to_unit, _, __ in u.doppler_optical(1 * u.Hz): sp = SpectroscopicAxis(np.linspace(-100, 100, 100), unit=from_unit, refX=23.2 * u.Hz, velocity_convention='optical') sp.convert_to_unit(to_unit) if (run_with_assert): assert sp.unit == to_unit for from_unit, to_unit, _, __ in u.doppler_radio(1 * u.eV): sp = SpectroscopicAxis(np.linspace(-100, 100, 100), unit=from_unit, refX=23.2 * u.Hz, velocity_convention='optical') sp.convert_to_unit(to_unit) if (run_with_assert): assert sp.unit == to_unit for from_unit, to_unit, _, __ in u.doppler_relativistic(1 * u.Angstrom): sp = SpectroscopicAxis(np.linspace(-100, 100, 100), unit=from_unit, refX=23.2 * u.Hz, velocity_convention='relativistic') sp.convert_to_unit(to_unit) if (run_with_assert): assert sp.unit == to_unit
def f_kernel(self): """ Returns a function defining the beam amplitude as a function of position. The model implemented is based on an interpolation of an image of the WSRT beam, re-scaled according to declination and frequency. Returns ------- Callable accepting 2 arguments (both float or array) and returning a float or array of corresponding size. """ if self.dec <= 0. * U.deg: raise ValueError('WSRT beam requires positive declination.') freq = self.vel.to(U.GHz, equivalencies=U.doppler_radio(f_HI)) bheader, bdata = self._load_beamfile() centroid = self._centroid() bpx_ra = np.abs(bheader['CDELT1'] * U.deg).to(U.arcsec) bpx_dec = np.abs(bheader['CDELT2'] * U.deg).to(U.arcsec) dRAs = np.arange(-(bdata.shape[0] // 2), bdata.shape[0] // 2 + 1) \ * bpx_ra * (centroid[2] / freq).to(U.dimensionless_unscaled) dDecs = np.arange(-(bdata.shape[1] // 2), bdata.shape[1] // 2 + 1) \ * bpx_dec * np.sin(self.dec) / np.sin(centroid[1]) \ * (centroid[2] / freq)\ .to(U.dimensionless_unscaled) interpolator = RectBivariateSpline( dRAs, dDecs, bdata[..., 0], kx=1, ky=1, s=0) # RectBivariateSpline is a wrapper around Fortran code and causes a # transpose return lambda x, y: interpolator(y, x, grid=False).T
def kernel_size_px(self): """ Returns a 2-tuple specifying the half-size of the beam image to be initialized, in pixels. The interpolation may become unstable if the image is severely undersampled, pixel sizes of more than 8 arcsec are not recommended. Returns ------- out : 2-tuple, each element an integer. """ if self.px_size > 12. * U.arcsec: warnings.warn( "Using WSRT beam with datacube pixel size >> 8 arcsec," " beam interpolation may fail.") freq = self.vel.to(U.GHz, equivalencies=U.doppler_radio(f_HI)) bheader, bdata = self._load_beamfile() centroid = self._centroid() aspect_x, aspect_y = np.floor(bdata.shape[0] // 2 * np.sin(self.dec)), bdata.shape[1] // 2 aspect_x = aspect_x * np.abs((bheader['CDELT1'] * U.deg)).to(U.arcsec)\ * (centroid[2] / freq).to(U.dimensionless_unscaled) aspect_y = aspect_y * (bheader['CDELT2'] * U.deg).to(U.arcsec) \ * (centroid[2] / freq).to(U.dimensionless_unscaled) return tuple([ (a.to(U.pix, U.pixel_scale(self.px_size / U.pix))).value + 1 for a in (aspect_x, aspect_y) ])
def __init__(self, species: str, qns: str, restfreq: u.Quantity, obsfreq: Optional[u.Quantity] = None, vlsr: Optional[u.Quantity] = None, eup: Optional[u.Quantity] = None, logaij: Optional[u.Quantity] = None) -> None: """Initiate a transition object. Args: species: chemical formula. qns: quantum numbers. restfreq: transition rest frequency. obsfreq: optional; observed frequency. vlsr: optional; used to determine `obsfreq` if not given. eup: optional; upper level energy. logaij: optional; log of the Aij coeficient. """ self.species = species self.qns = qns self.restfreq = restfreq.to(u.GHz) self.obsfreq = obsfreq if self.obsfreq is None and vlsr is not None: equiv = u.doppler_radio(self.restfreq) self.obsfreq = to_rest_freq(self.restfreq, -vlsr, equiv) try: self.obsfreq = self.obsfreq.to(u.GHz) except AttributeError: pass self.eup = eup self.logaij = logaij
def redshift_frequency(f, vlsr): """Assumes (for now) rest frequency (f) in GHz and velocity of the source (vlsr) in km/s.""" f_rest = f * u.GHz # rest frequency vlsr = vlsr * u.km / u.s # velocity of the source radio_equiv = u.doppler_radio(f_rest) f_shifted = vlsr.to(u.GHz, equivalencies=radio_equiv) return f_shifted.value
def make_synthspec_cold( velo=np.linspace(-25, 25, 251), tkin=25, column=13, width=0.5, vcen=0.0, lines=('oneone', 'twotwo', 'fourfour'), ): velo = u.Quantity(velo, u.km / u.s) spectra_list = [] for line in lines: cfrq = ammonia_constants.freq_dict[line] * u.Hz frq = velo.to(u.GHz, u.doppler_radio(cfrq)) xarr = units.SpectroscopicAxis(frq, refX=cfrq) data = ammonia.cold_ammonia(xarr, tkin=tkin, width=width, ntot=column, xoff_v=vcen) sp = Spectrum(xarr=xarr, data=data) spectra_list.append(sp) return Spectra(spectra_list)
def col_d_tot(self): ###--return total column density of the line species (cm^-2)--### lwd = np.abs(self.lw) #channel width in Hz ftov = u.doppler_radio(self.ln[self.ll][2]*u.Hz) lw = self.ln[self.ll][2]- \ (lwd*(u.km/u.s)).to(u.Hz,equivalencies=ftov).value #upper column density to = h.value/k_B.value*self.ln[self.ll][2] ex = to/self.Tx n_u = 8*np.pi*self.ln[self.ll][2]**2/(c.value*100.)**2* \ self.f(ex)/self.ln[self.ll][0]* \ np.sum(self.tau())*lw #alternatively, one may use d(nu)/nu_0 = dv/c in linewidth: #n_u = 8*np.pi*self.ln[self.ll][2]/(c.value*100.)*\ # self.f(ex)/self.ln[self.ll][0]* \ # np.sum(self.tau())*lwd #partition function, approximated by integral eu = k_B.value*self.Tx/h.value/self.ln[self.ll][1] zz = eu/(self.ln[self.ll][3]*2+1)* \ np.exp(self.ln[self.ll][3]*(self.ln[self.ll][3]+1)/eu) return n_u*zz
def get_molecule(args: argparse.Namespace) -> Molecule: """Generate a `Molecule` from argparse. Requires the argparse to have `cube` and `log` attributes. The `vlsr` attribute is also needed but does not need to be initialized. """ # Frequency ranges spectral_axis = args.cube.spectral_axis obs_freq_range = spectral_axis[[0,-1]] args.log.info((f'Observed freq. range: {obs_freq_range[0].value} ' f'{obs_freq_range[1].value} {obs_freq_range[1].unit}')) if args.vlsr is not None: equiv = u.doppler_radio(get_restfreq(args.cube)) rest_freq_range = to_rest_freq(obs_freq_range, args.vlsr, equiv) args.log.info((f'Rest freq. range: {rest_freq_range[0].value} ' f'{rest_freq_range[1].value} {rest_freq_range[1].unit}')) else: args.log.warn('Could not determine rest frequency, using observed') rest_freq_range = obs_freq_range # Create molecule if args.onlyj: filter_out = ['F', 'K'] else: filter_out = None mol = Molecule.from_query(f' {args.molecule[0]} ', rest_freq_range, vlsr=args.vlsr, filter_out=filter_out, line_lists=args.line_lists, qns=args.qns) mol.reduce_qns() args.log.info(f'Number of transitions: {len(mol.transitions)}') return mol
def sio_model(xarr, vcen, width, tex, column, background=None, tbg=2.73): if hasattr(tex, 'unit'): tex = tex.value if hasattr(tbg, 'unit'): tbg = tbg.value if hasattr(column, 'unit'): column = column.value if column < 25: column = 10**column if hasattr(vcen, 'unit'): vcen = vcen.value if hasattr(width, 'unit'): width = width.value if background is not None: tbg = background # assume equal-width channels kwargs = dict(rest=ref_freq) equiv = u.doppler_radio(**kwargs) channelwidth = np.abs(xarr[1].to(u.Hz, equiv) - xarr[0].to(u.Hz, equiv)).value velo = xarr.to(u.km / u.s, equiv).value mol_model = np.zeros_like(xarr).value freqs_ = freqs.to(u.Hz).value Q = m.calculate_partitionfunction(result.data['States'], temperature=tex)[sio.Id] jnu_bg = lte_molecule.Jnu_cgs(xarr.to(u.Hz).value, tbg) bg_model = np.ones_like(xarr).value * jnu_bg for voff, A, g, nu, eu in zip(vdiff, aij, deg, freqs_, EU): tau_per_dnu = lte_molecule.line_tau_cgs(tex, column, Q, g, nu, eu, 10**A) s = np.exp(-(velo - vcen - voff)**2 / (2 * width**2)) * tau_per_dnu / channelwidth jnu_mol = lte_molecule.Jnu_cgs(nu, tex) # the "emission" model is generally zero everywhere, so we can just # add to it as we move along mol_model = mol_model + jnu_mol * (1 - np.exp(-s)) # background is assumed to start as a uniform value, then each # absorption line multiplies to reduce it. s is zero for most velocities, # so this is mostly bg_model *= 1 bg_model *= np.exp(-s) if background: # subtract jnu_bg because we *must* rezero for the case of # having multiple components, otherwise the backgrounds add, # which is nonsense model = bg_model + mol_model - jnu_bg else: model = mol_model return model
def closest_spectral_channel(self, value, rest_frequency=None): """ Find the index of the closest spectral channel to the specified spectral coordinate. Parameters ---------- value : :class:`~astropy.units.Quantity` The value of the spectral coordinate to search for. rest_frequency : :class:`~astropy.units.Quantity` The rest frequency for any Doppler conversions """ # TODO: we have to not compute this every time spectral_axis = self.spectral_axis try: value = value.to(spectral_axis.unit, equivalencies=u.spectral()) except u.UnitsError: if value.unit.is_equivalent(spectral_axis.unit, equivalencies=u.doppler_radio(None)): if rest_frequency is None: raise u.UnitsError( "{0} cannot be converted to {1} without a " "rest frequency".format(value.unit, spectral_axis.unit)) else: try: value = value.to( spectral_axis.unit, equivalencies=u.doppler_radio(rest_frequency)) except u.UnitsError: raise u.UnitsError( "{0} cannot be converted to {1}".format( value.unit, spectral_axis.unit)) else: raise u.UnitsError( "'value' should be in frequency equivalent or velocity units (got {0})" .format(value.unit)) # TODO: optimize the next line - just brute force for now return np.argmin(np.abs(spectral_axis - value))
def get_spectral_equivalencies(restfreqs: list, keys: Optional[list] = None) -> dict: """Get spectral equivalencies from rest frequencies. Args: restfreqs: rest frequencies. keys: optional; keys for the output dictionary. Returns: A dictionary with the astropy equivalency functions. """ # Define keys if keys is None: keys = range(len(restfreqs)) # Process restfreqs if len(restfreqs) == 1: equiv = {key: u.doppler_radio(restfreqs[0]) for key in keys} else: if len(keys) != len(restfreqs): raise ValueError('Size of keys and restfreqs do not match') aux = zip(keys, restfreqs) equiv = {key: u.doppler_radio(restfreq) for key, restfreq in aux} return equiv
def _preproc(args: argparse.Namespace) -> None: """Prepare args inputs for analysis.""" # Equivalencies if args.restfreq: if args.table is not None and 'spw' in args.table[0].dtype.names: spws = np.unique(args.table[0]['spw']) args.equivalencies = get_spectral_equivalencies(args.restfreq, keys=spws) else: args.equivalencies['all'] = u.doppler_radio(args.restfreq[0]) # Define filename if args.filename is not None: args.filename = args.filename[0] else: args.filename = pathlib.Path('./results.ecsv').resolve()
def spectral_velocities(data, wcs=None, fqs=None, fqis=None, restfrq=None): """ Get the spectral velocities from frequencies fqs given a rest frequency (by default search for it in the WCS). If fqs is None, then frequencies indices (fqis) need to be given. Parameters ---------- data : (M,N) or (M,N,Z) numpy.ndarray or astropy.nddata.NDData or astropy.nddata.NDDataRef Astronomical data cube. wcs : astropy.wcs.wcs.WCS World Coordinate System to use. fqs : astropy.units.quantity.Quantity Array of frequencies with units. fqis : list of integers Array of frequencies indices restfrq : astropy.units.quantity.Quantity Rest frequency Returns ------- result: astropy.units.quantity.Quantity Array of Spectral velocities. """ if wcs is None: log.error("A world coordinate system (WCS) is needed") return None if data.ndim != 3: log.error("Not spectral axis found.") return None if restfrq is None: restfrq = wcs.wcs.restfrq * u.Hz if fqs is None: dim = wcs.wcs.spec if fqis is None: # Semi Hardconded... fqis = np.arange(data.shape[data.ndim - dim - 1]) idx = np.zeros((fqis.size, data.ndim)) idx[:, dim] = fqis vals = wcs.all_pix2world(idx, 0) fqs = vals[:, dim] * u.Hz eq = u.doppler_radio(restfrq) return fqs.to(u.km / u.s, equivalencies=eq)
def convert_vel_freq(quantity, restfreq=1420.405752 * u.MHz): """ Take an input quantity that is either vel or freq and return the other Default for HI line Use radio convention (could set as param) """ freq_to_vel = u.doppler_radio(restfreq) if 'Hz' in quantity.unit.to_string(): new_quantity = quantity.to(u.km / u.s, equivalencies=freq_to_vel) else: #presume in vel, use try/except in case try: new_quantity = quantity.to(u.MHz, equivalencies=freq_to_vel) except: print("Units of input not recognized") new_quantity = np.nan return new_quantity
def make_synthspec_cold(velo=np.linspace(-25, 25, 251), tkin=25, column=13, width=0.5, vcen=0.0, lines=('oneone', 'twotwo', 'fourfour'), ): velo = u.Quantity(velo, u.km/u.s) spectra_list = [] for line in lines: cfrq = ammonia_constants.freq_dict[line]*u.Hz frq = velo.to(u.GHz, u.doppler_radio(cfrq)) xarr = units.SpectroscopicAxis(frq, refX=cfrq) data = ammonia.cold_ammonia(xarr, tkin=tkin, width=width, ntot=column, xoff_v=vcen) sp = Spectrum(xarr=xarr, data=data) spectra_list.append(sp) return Spectra(spectra_list)
def ch3cn_model(xarr, vcen, width, tex, column, background=None, tbg=2.73): if hasattr(tex,'unit'): tex = tex.value if hasattr(tbg,'unit'): tbg = tbg.value if hasattr(column, 'unit'): column = column.value if column < 25: column = 10**column if hasattr(vcen, 'unit'): vcen = vcen.value if hasattr(width, 'unit'): width = width.value # assume equal-width channels kwargs = dict(rest=ref_freq) equiv = u.doppler_radio(**kwargs) channelwidth = np.abs(xarr[1].to(u.Hz, equiv) - xarr[0].to(u.Hz, equiv)).value velo = xarr.to(u.km/u.s, equiv).value model = np.zeros_like(xarr).value freqs_ = freqs.to(u.Hz).value Q = m.calculate_partitionfunction(result.data['States'], temperature=tex)[ch3cn.Id] for voff, A, g, nu, eu in zip(vdiff, aij, deg, freqs_, EU): tau_per_dnu = lte_molecule.line_tau_cgs(tex, column, Q, g, nu, eu, 10**A) s = np.exp(-(velo-vcen-voff)**2/(2*width**2))*tau_per_dnu/channelwidth jnu = (lte_molecule.Jnu_cgs(nu, tex)-lte_molecule.Jnu_cgs(nu, tbg)) model = model + jnu*(1-np.exp(-s)) if background is not None: return background-model return model
def __init__(self, mode='relativistic'): # HI restframe self.nu0 = 1420.4058 self.nu0_u = self.nu0 * u.MHz # full mode for Minkowski space time if mode.lower() == 'relativistic': self.v_frame = u.doppler_relativistic(self.nu0_u) # radio definition elif mode.lower() == 'radio': self.v_frame = u.doppler_radio(self.nu0_u) # velo = c * (1. - nu/nu0) # optical definition elif mode.lower() == 'optical': self.v_frame = u.doppler_optical(self.nu0_u) self.mode = mode return None
def spectral_velocities(data,wcs=None,fqs=None,fqis=None,restfrq=None): """ Get the spectral velocities from frequencies fqs given a rest frequency (by default search for it in the WCS). If fqs is None, then frequencies indices (fqis) need to be given. Parameters ---------- data : (M,N) or (M,N,Z) numpy.ndarray or astropy.nddata.NDData or astropy.nddata.NDDataRef Astronomical data cube. wcs : astropy.wcs.wcs.WCS World Coordinate System to use. fqs : astropy.units.quantity.Quantity Array of frequencies with units. fqis : list of integers Array of frequencies indices restfrq : astropy.units.quantity.Quantity Rest frequency Returns ------- result: astropy.units.quantity.Quantity Array of Spectral velocities. """ if wcs is None: log.error("A world coordinate system (WCS) is needed") return None if restfrq is None: restfrq=wcs.wcs.restfrq*u.Hz if fqs is None: if fqis is None: return None dim=wcs.wcs.spec idx=np.zeros((fqis.size,data.ndim)) idx[:,dim]=fqis vals=wcs.all_pix2world(idx,0) fqs=vals[:,dim]*u.Hz eq=u.doppler_radio(restfrq) return fqs.to(u.km/u.s, equivalencies=eq)
def get_spectrum(self, region_pix): # import image print("\t>>> Importing fits image...") hdul = fits.open(self.name) data_image = hdul[0].data[0] # get info from header beam_maj = hdul[0].header["BMAJ"] beam_min = hdul[0].header["BMIN"] beam_units = hdul[0].header["BUNIT"] delta_ra = hdul[0].header["CDELT1"] delta_dec = hdul[0].header["CDELT2"] delta_freq = hdul[0].header["CDELT3"] initial_freq = hdul[0].header["CRVAL3"] freq_units = hdul[0].header["CUNIT3"] channels = hdul[0].header["NAXIS3"] rest_freq = hdul[0].header["RESTFRQ"] hdul.close() # calculate beam beam_area = (np.pi / (4 * np.log(2)) * beam_maj * beam_min * 3600**2 ) # arcsec^2 pix_area = abs(delta_ra * delta_dec) * 3600**2 # arcsec^2 beam_pix = beam_area / pix_area # pixels # calculate sum inside mask print("\t>>> Taking spectrum...") spectrum = [] for channel in data_image: intensity = np.nansum(channel * self.mask) spectrum.append(intensity) # generate freq column freq = (np.arange(initial_freq, initial_freq + channels * delta_freq, delta_freq)[:channels] * 10**-9 * u.GHz) # generate velocity column radio_equiv = u.doppler_radio(rest_freq * 10**-9 * u.GHz) velocity = freq.to(u.km / u.s, equivalencies=radio_equiv) # generate channel column chan = np.arange(channels) # generate pixels column pix = np.full(channels, region_pix) return chan, pix, freq.value, velocity.value, spectrum, beam_area, beam_pix
def test_convert_to_unit(run_with_assert=False): for from_unit, to_unit, _, __ in u.doppler_optical(1 * u.Hz): sp = SpectroscopicAxis( np.linspace(-100, 100, 100), unit=from_unit, refX=23.2 * u.Hz, velocity_convention="optical" ) sp.convert_to_unit(to_unit) if run_with_assert: assert sp.unit == to_unit for from_unit, to_unit, _, __ in u.doppler_radio(1 * u.eV): sp = SpectroscopicAxis( np.linspace(-100, 100, 100), unit=from_unit, refX=23.2 * u.Hz, velocity_convention="optical" ) sp.convert_to_unit(to_unit) if run_with_assert: assert sp.unit == to_unit for from_unit, to_unit, _, __ in u.doppler_relativistic(1 * u.Angstrom): sp = SpectroscopicAxis( np.linspace(-100, 100, 100), unit=from_unit, refX=23.2 * u.Hz, velocity_convention="relativistic" ) sp.convert_to_unit(to_unit) if run_with_assert: assert sp.unit == to_unit
def _init_vel(self): """ Converts the spectral coordinate to (radio) velocities. Currently assumes that the spectral coordinate is in units of Hz. FIXME: read unit from fits file FIXME: stolen from here ... https://github.com/astropy/astropy/issues/4529 mabe not the best way to go FIXME: maybe could be generalized """ wcsvel=wcs.WCS(self.header) xv = numpy.repeat(0, self.header['NAXIS3']) yv = numpy.repeat(0, self.header['NAXIS3']) zv = numpy.arange(self.header['NAXIS3']) wx, wy, wz = wcsvel.wcs_pix2world(xv, yv, zv, 0) freq_to_vel = u.doppler_radio(self.header['RESTFRQ']*u.Hz) vel=(wz * u.Hz).to(u.km / u.s, equivalencies=freq_to_vel) return vel
def frequencies(self): if self._frequencies is None: specax = self.wcs.wcs.spec channels = np.arange(self.data.shape[::-1][specax]) spec = self.spec_wcs.wcs_pix2world(channels, 0)[0] specc = spec * self.axis_units[specax] # Convert radio or optical velocities to frequencies if 'VRAD' in self.wcs.wcs.ctype[specax]: eq = u.doppler_radio(self.rest_frequency) elif 'VOPT' in self.wcs.wcs.ctype[specax]: eq = u.doppler_optical(self.rest_frequency) elif 'FREQ' in self.wcs.wcs.ctype[specax]: eq = [] else: raise AttributeError( 'Unsupported spectral type {:s} in header.'.format(self.wcs.wcs.ctype[specax])) self._frequencies = specc.to(u.Hz, eq) return self._frequencies
def test_equivalencies(): """ Testing spectral equivalencies """ # range in "RADIO" with "100 * u.GHz" as rest frequancy range = u.Quantity([-318 * u.km / u.s, -320 * u.km / u.s]) # range in freq r1 = range.to("GHz", equivalencies=u.doppler_radio(100 * u.GHz)) # round conversion for "doppler_z" r2 = r1.to("km/s", equivalencies=doppler_z(100 * u.GHz)) r3 = r2.to("GHz", equivalencies=doppler_z(100*u.GHz)) assert_quantity_allclose(r1, r3) # round conversion for "doppler_beta" r2 = r1.to("km/s", equivalencies=doppler_beta(100 * u.GHz)) r3 = r2.to("GHz", equivalencies=doppler_beta(100 * u.GHz)) assert_quantity_allclose(r1, r3) # round conversion for "doppler_gamma" r2 = r1.to("km/s", equivalencies=doppler_gamma(100 * u.GHz)) r3 = r2.to("GHz", equivalencies=doppler_gamma(100 * u.GHz)) assert_quantity_allclose(r1, r3)
def test_equivalencies(): """ Testing spectral equivalencies """ # range in "RADIO" with "100 * u.GHz" as rest frequancy range = u.Quantity([-318 * u.km / u.s, -320 * u.km / u.s]) # range in freq r1 = range.to("GHz", equivalencies=u.doppler_radio(100 * u.GHz)) # round conversion for "doppler_z" r2 = r1.to("km/s", equivalencies=doppler_z(100 * u.GHz)) r3 = r2.to("GHz", equivalencies=doppler_z(100 * u.GHz)) assert_quantity_allclose(r1, r3) # round conversion for "doppler_beta" r2 = r1.to("km/s", equivalencies=doppler_beta(100 * u.GHz)) r3 = r2.to("GHz", equivalencies=doppler_beta(100 * u.GHz)) assert_quantity_allclose(r1, r3) # round conversion for "doppler_gamma" r2 = r1.to("km/s", equivalencies=doppler_gamma(100 * u.GHz)) r3 = r2.to("GHz", equivalencies=doppler_gamma(100 * u.GHz)) assert_quantity_allclose(r1, r3)
def convert_to_ms(q): return q.to(U.m / U.s, equivalencies=U.doppler_radio(HIfreq))
def find_linefree_freq(ms_name, vel_map, spw_dict, vel_width=40 * u.km / u.s, field_name='M33', pb_size=0 * u.deg, debug_printing=False): ''' Use a velocity map (e.g., from HI) to define line-free channels in an MS. Parameters ---------- ms_name : str Name of MS file to open. vel_map : spectral_cube.Projection Line velocity map to use extents from. spw_dict : dict Dictionary of SPWs numbers (as keys) and the rest frequency. Continuum SPWs can be set to the brightest line (e.g., CO) to exclude those channels. ''' try: # CASA 6 import casatools # iatool = casatools.image() tb = casatools.table() except ImportError: try: from taskinit import tbtool # iatool = iatool() tb = tbtool() except ImportError: raise ImportError("Could not import CASA (casac).") # Get channel frequencies. tb.open(os.path.join(ms_name, 'SPECTRAL_WINDOW')) chanfreqs = tb.getvarcol('CHAN_FREQ') tb.close() # Get science field positions to define enclosing box of mosaic. tb.open(os.path.join(ms_name, 'FIELD')) field_names = tb.getcol('NAME') phase_dirns = tb.getcol('PHASE_DIR').squeeze() tb.close() # Screen out non-science fields selected_fields = np.array([True if field_name in name else False for name in field_names]) ras, decs = (phase_dirns * u.rad).to(u.deg) ras = ras[selected_fields] decs = decs[selected_fields] pointings = SkyCoord(ras, decs, frame='icrs') # Make bounding box. min_ra = pointings.ra.min() - 0.5 * pb_size.to(u.deg) max_ra = pointings.ra.max() + 0.5 * pb_size.to(u.deg) min_dec = pointings.dec.min() - 0.5 * pb_size.to(u.deg) max_dec = pointings.dec.max() + 0.5 * pb_size.to(u.deg) vel_map_box = vel_map.subimage(ylo=min_dec, yhi=max_dec, xlo=max_ra, xhi=min_ra).to(u.km / u.s) if vel_map_box.ndim != 2: raise ValueError("Spatial slicing failed. Don't think this should happen?") linefree_range = dict.fromkeys(spw_dict.keys()) for line_name in spw_dict: spw_props = spw_dict[line_name] spw_num = spw_props['spw_num'] restfreq = spw_props['restfreq'].to(u.Hz) # Check if a velocity padding is defined if 'vel_pad' in spw_props: vel_pad = spw_props['vel_pad'] else: vel_pad = vel_width if debug_printing: print('Velocity padding used for {0} is {1}'.format(spw_num, vel_pad)) # Get the channel frequencies key_name = "r{0}".format(int(spw_num) + 1) chanfreqs_spw = chanfreqs[key_name].squeeze() * u.Hz # Convert velocity extrema to freq. vel_min = np.nanmin(vel_map_box.quantity) - vel_pad.to(u.km / u.s) / 2. vel_max = np.nanmax(vel_map_box.quantity) + vel_pad.to(u.km / u.s) / 2. freq_min = vel_min.to(u.Hz, u.doppler_radio(restfreq)) freq_max = vel_max.to(u.Hz, u.doppler_radio(restfreq)) if debug_printing: print("Min velocity of spw {}".format(chanfreqs_spw.to(u.km / u.s, u.doppler_radio(restfreq)).min())) print("Max velocity of spw {}".format(chanfreqs_spw.to(u.km / u.s, u.doppler_radio(restfreq)).max())) print("Min velocity of line-free {}".format(vel_min)) print("Max velocity of line-free {}".format(vel_max)) # Switch if needed if freq_max < freq_min: freq_max, freq_min = freq_min, freq_max line_chans = np.logical_and(chanfreqs_spw > freq_min, chanfreqs_spw < freq_max) if debug_printing: print("Valid line channels found: {0}".format(line_chans)) if not line_chans.any(): raise ValueError("Invalid range found for {}".format(line_name)) chan_min = np.where(line_chans)[0].min() chan_max = np.where(line_chans)[0].max() # Make the line-free channel ranges linefree_range[line_name] = [] if debug_printing: print("Chan min/max for {0} are {1}/{2}".format(spw_num, chan_min, chan_max)) if chan_min > 0: lows = [0, chan_min] linefree_range[line_name].append(lows) if chan_max < chanfreqs_spw.size - 1: highs = [chan_max, chanfreqs_spw.size] linefree_range[line_name].append(highs) return linefree_range
"J3-2_38": 0.016574, "J3-2_39": 0.009776, "J3-2_40": 0.000995, "J3-2_41": 0.000491, "J3-2_42": 0.000067, "J3-2_43": 0.000039, "J3-2_44": 0.000010, } # freq_dict = { # 'J2-1': (voff_lines_dict['J2-1']*u.km/u.s).to(u.GHz, equivalencies=u.doppler_radio(freq_dict_cen['J2-1']*u.Hz)).value, # 'J3-2': (voff_lines_dict['J3-2']*u.km/u.s).to(u.GHz, equivalencies=u.doppler_radio(freq_dict_cen['J3-2']*u.Hz)).value, # } # Get frequency dictionary in Hz based on the offset velocity and rest frequency conv_J21 = u.doppler_radio(freq_dict_cen["J2-1"] * u.Hz) conv_J32 = u.doppler_radio(freq_dict_cen["J3-2"] * u.Hz) freq_dict = { name: ((voff_lines_dict[name] * u.km / u.s).to(u.Hz, equivalencies=conv_J21).value) for name in voff_lines_dict.keys() if "J2-1" in name } freq_dict.update( { name: ((voff_lines_dict[name] * u.km / u.s).to(u.Hz, equivalencies=conv_J32).value) for name in voff_lines_dict.keys() if "J3-2" in name } ) # I don't know yet how to use this parameter... in CLASS it does not exist
from __future__ import print_function import pyspeckit from astropy import units as u sp = pyspeckit.Spectrum('G203.04+1.76_h2co.fits',wcstype='D') sp.xarr.center_frequency._unit = u.Hz sp.xarr = sp.xarr.as_unit('km/s', equivalencies=u.doppler_radio(sp.xarr.center_frequency)) sp.specfit(fittype='formaldehyde',multifit=True,usemoments=True,guesses=[-0.6,4,0.2],equivalencies=u.doppler_optical(sp.xarr.center_frequency)) sp.plotter(figure=2) sp.crop(-5*u.km/u.s,15*u.km/u.s) sp.specfit(fittype='formaldehyde',multifit=True,usemoments=True,guesses=[-0.6,4,0.2]) sp.specfit(fittype='formaldehyde',multifit=True,usemoments=True,guesses=[-0.6,4,0.2]) sp.specfit.plot_fit(show_components=True) sp.plotter.savefig("h2co_11_h2cofit.png") print("Line integral (Formaldehyde,11): ",sp.specfit.integral()) print("Direct Line integral (Formaldehyde,11): ",sp.specfit.integral(direct=True,return_error=True)) sp2 = pyspeckit.Spectrum('G203.04+1.76_h2co.fits',wcstype='V') sp2.xarr.convert_to_unit('km/s') sp2.crop(-5*u.km/u.s,15*u.km/u.s) sp2.plotter(figure=3) sp2.specfit.peakbgfit(negamp=True, vheight=False) sp2.specfit.peakbgfit(negamp=True, vheight=False) sp2.plotter.savefig("h2co_11_gaussfit.png") print("Line integral (Gaussian,11): ",sp2.specfit.integral()) print("Direct Line integral (Gaussian,11): ",sp2.specfit.integral(direct=True,return_error=True)) sp22g = pyspeckit.Spectrum('G203.04+1.76_h2co_Tastar.fits',wcstype='V') sp22g.specfit.peakbgfit(negamp=True) sp22g.xarr = sp22g.xarr.as_unit('km/s') sp22g.plotter(figure=5)
'p-1_01-1_11_03': 0.417, 'p-1_01-1_11_04': 0.083, 'p-1_01-1_11_05': 0.139, 'p-1_01-1_11_06': 0.111, ####### para-NH2D J=1_01-0_00 'p-1_01-0_00_01': 0.111, 'p-1_01-0_00_02': 0.556, 'p-1_01-0_00_03': 0.333, } # freq_dict = { # 'J2-1': (voff_lines_dict['J2-1']*u.km/u.s).to(u.GHz, equivalencies=u.doppler_radio(freq_dict_cen['J2-1']*u.Hz)).value, # 'J3-2': (voff_lines_dict['J3-2']*u.km/u.s).to(u.GHz, equivalencies=u.doppler_radio(freq_dict_cen['J3-2']*u.Hz)).value, # } # Get offset velocity dictionary in km/s based on the lines frequencies and rest frequency conv_o1_1=u.doppler_radio(freq_dict_cen['o-1_01-1_11']*u.Hz) conv_p1_1=u.doppler_radio(freq_dict_cen['p-1_01-1_11']*u.Hz) conv_o1_0=u.doppler_radio(freq_dict_cen['o-1_01-0_00']*u.Hz) conv_p1_0=u.doppler_radio(freq_dict_cen['p-1_01-0_00']*u.Hz) voff_lines_dict = { name: ((freq_dict[name]*u.Hz).to(u.km/u.s, equivalencies=conv_o1_1).value) for name in freq_dict.keys() if "o-1_01-1_11" in name } voff_lines_dict.update({ name: ((freq_dict[name]*u.Hz).to(u.km/u.s, equivalencies=conv_p1_1).value) for name in freq_dict.keys() if "p-1_01-1_11" in name }) voff_lines_dict.update({ name: ((freq_dict[name]*u.Hz).to(u.km/u.s, equivalencies=conv_p1_1).value) for name in freq_dict.keys() if "o-1_01-0_00" in name }) voff_lines_dict.update({ name: ((freq_dict[name]*u.Hz).to(u.km/u.s, equivalencies=conv_p1_1).value) for name in freq_dict.keys() if "p-1_01-0_00" in name
import matplotlib import numpy as np from astropy import units as u from distutils.version import StrictVersion if not 'savedir' in globals(): savedir = '' # load a FITS-compliant spectrum spec = pyspeckit.Spectrum('10074-190_HCOp.fits') # The units are originally frequency (check this by printing spec.xarr.units). # I want to know the velocity. Convert! # Note that this only works because the reference frequency is set in the header # this is no longer necessary! #spec.xarr.frequency_to_velocity() # Default conversion is to m/s, but we traditionally work in km/s spec.xarr = spec.xarr.as_unit('km/s', equivalencies=u.doppler_radio(spec.xarr.center_frequency)) # plot it up! spec.plotter() # Subtract a baseline (the data is only 'mostly' reduced) spec.baseline(interactive=True) # specify x points in data units. We need to transform them to axis units # because the axis are not consistently generated by mpl xpoints = [-270,0,50,218,218] ypoints = [0]*5 buttons = [1,1,1,1,2] transform = spec.plotter.axis.transData.transform_point # this absolutely ridiculous line is to deal with scope changes from py2->py3 # http://stackoverflow.com/questions/13905741/accessing-class-variables-from-a-list-comprehension-in-the-class-definition#comment19179733_13913933 def xy_(transform, xpoints, ypoints): return [transform((xp,yp)) for xp,yp in zip(xpoints,ypoints)]
point = SkyCoord(hdu[0].header['OBSRA'] * u.deg, hdu[0].header['OBSDEC'] * u.deg) point_string = point.to_string(style='hmsdms') # hdu[0].header['CRVAL5'] = hdu[0].header['OBSRA'] # hdu[0].header['CRVAL6'] = hdu[0].header['OBSDEC'] # The ref freq is also confusing. It's the frequency in the rest frame # So it needs to be altered to be in the observed frame. # GILDAS appears to have this conversion built-in everywhere restfreq = hdu[0].header['RESTFREQ'] * u.Hz rest_cent_freq = hdu[0].header['CRVAL4'] * u.Hz source_vel = hdu[0].header['VELO-LSR'] * u.m / u.s # Calculate the frequency shift del_f = rest_cent_freq - source_vel.to(u.Hz, u.doppler_radio(restfreq)) # Adjust CRVAL4 to the observed frame frequency hdu[0].header['CRVAL4'] -= del_f.value # Need to assign the SD visibilities to an ant pair # CASA doesn't barf when doing this, but I can't be sure it worked in # any useful way. So don't use this for any science products, just as # a check against the GILDAS imaging if 'merged' in pref: raise ValueError("This does not work. Don't import merged files " "into CASA!") baselines = hdu[0].data['BASELINE'] baselines[np.where(baselines == 0)] = baselines[0] hdu[0].data['BASELINE'] = baselines
def convert_to_Hz(q): return q.to(U.Hz, equivalencies=U.doppler_radio(HIfreq))
from astropy import units as u rest = 4829.66*u.MHz chwid = (7.8125*u.kHz) chan0 = 4824.861*u.MHz vlsr = 42.27*u.km/u.s firstchan = (((35*u.km/u.s-vlsr).to(u.MHz, u.doppler_radio(rest)) - chan0) / chwid).decompose() lastchan = (((80*u.km/u.s-vlsr).to(u.MHz, u.doppler_radio(rest)) - chan0) / chwid).decompose() print firstchan, lastchan print "Frequency at channel 550: ",(550*chwid + chan0).to(u.GHz) print "Frequency at channel 0: ",(0*chwid + chan0).to(u.GHz) print "Frequency at channel 1024: ",(1024*chwid + chan0).to(u.GHz) print "Frequency at channel 512: ",(512*chwid + chan0).to(u.GHz)," and according to CASA: ",4.82886*u.GHz print "total bw: ",1024*chwid
extent=[minfrq, maxfrq, nfields * nconfigs, 0], interpolation='nearest', cmap='gnuplot') ax.set_aspect((maxfrq - minfrq) * 2 / (nfields * nconfigs)) xmin, xmax = ax.get_xlim() ax.hlines(np.arange(nfields) * 3, xmin, xmax, color='w', linestyle='-') for linename, linefrq in lines_to_overplot.items(): linefrq = u.Quantity(linefrq).to(u.GHz) linefrqval = linefrq.value if (minfrq < linefrqval) & (maxfrq > linefrqval): for fieldnum, field in fields_and_numbers: vlsr = u.Quantity(field_vlsr[field]) shifted_frq = vlsr.to(u.GHz, u.doppler_radio(linefrq)).value if (minfrq < shifted_frq) & (maxfrq > shifted_frq): ax.vlines(shifted_frq, fieldnum * nconfigs, (fieldnum + 1) * nconfigs, color='b') pl.figure(fig.number) pl.tight_layout() pl.subplots_adjust(wspace=0.05, hspace=0) fig.text(0.5, xlabel_offset[band], 'Frequency (GHz)', ha='center') fig.savefig( f"{basepath}/paper_figures/continuum_selection_regions_band{band}.png", bbox_inches='tight')
from __future__ import print_function import pyspeckit import numpy as np from astropy import units as u from pyspeckit.spectrum.models import ammonia xarr = np.linspace(-40, 40, 300) * u.km/u.s oneonemod = ammonia.ammonia(xarr.to(u.GHz, u.doppler_radio(ammonia.freq_dict['oneone']*u.Hz)),) twotwomod = ammonia.ammonia(xarr.to(u.GHz, u.doppler_radio(ammonia.freq_dict['twotwo']*u.Hz)),) sp11 = pyspeckit.Spectrum(xarr=xarr, data=oneonemod, unit=u.K, xarrkwargs={'refX': ammonia.freq_dict['oneone']*u.Hz}, header={}) sp22 = pyspeckit.Spectrum(xarr=xarr, data=twotwomod, unit=u.K, xarrkwargs={'refX': ammonia.freq_dict['twotwo']*u.Hz}, header={}) input_dict={'oneone':sp11, 'twotwo':sp22,} spf, specout = pyspeckit.wrappers.fitnh3.fitnh3tkin(input_dict, dobaseline=False) print(specout.specfit.modelpars) print(specout.specfit.parinfo) spf2, specout2 = pyspeckit.wrappers.fitnh3.fitnh3tkin(input_dict, dobaseline=True, baselinekwargs={'exclude':[-30,30]*u.km/u.s}) print(specout.specfit.modelpars) print(specout.specfit.parinfo)
def calculate_syn(linedata=None, ntot=None, tex=None, source_size=None, beam_size=None, species=None, fwhm=None, usetau=True, returnjy=True, spectra=None, fluxcol=None, ilims=[None,None], vsys=None, modelname="model_flux_1", dnu=0.001 * u.GHz, verbose=True, qrot_method='cdms', use_ilims=True ): # Step 0, sort table according to frequency linedata.sort('freq_rest') # Step 1, calculate integrated synthetic line fluxes # from Ntot and Tex for the given lines in input "model" # This function will calculate Jy if returnjy=True # and then use the input beam and source size to also # account for beam dilution. This is total integrated fluxes. linemodel = utils.calc_line_fluxes(linedata=linedata, ntot=ntot, tex=tex, source=source_size, beam=beam_size, species=species, fwhm=fwhm, usetau=True, ilims=ilims, returnjy=True, verbose=verbose, qrot_method=qrot_method, ) # Step 2, integrate the observed spectrum around frequency for # each line. This step is not crucial, but for fitting Ntot or Tex # it is useful. However, be careful to compare the same intensity. # i.e. integrated vs. fitted Gaussian etc. linemodel = utils.integrate_all_lines(spectra=spectra, model=linemodel, linedata=linedata, fluxcol=fluxcol, ilims=ilims, vsys=vsys, verbose=verbose, ) # Step 3, check the model for line blends linemodel = utils.check_for_blend(model=linemodel, dnu=dnu, verbose=verbose) if linemodel.meta['blends_present']: if verbose: print('Blends present, process blends.') # Step 4, calculate a summed integrated synthetic line flux linemodel = utils.process_model_blends(model=linemodel) # Step 5, calculate the synthetic spectrum and put it in # a new column named as the input parameter "modelname". # note that linemodel_syn is different from the linemodel # data structure. spectra_syn, linemodel_syn = utils.calc_synthetic_spectrum(model=linemodel, spectra=spectra, fluxcol=fluxcol, modelname=modelname, fwhm=fwhm, verbose=verbose, ) linemodel.meta['ilims'] = ilims # add a column with velocity for each line # makes plotting some what easier try: linemodel_syn['vel_rest'] = [i.quantity.to(u.km/u.s, equivalencies=u.doppler_radio(j)) for (i,j) in zip( linemodel_syn['freq_rest'], linemodel['freq_rest'].quantity)] except(AttributeError): # if theres only one entry if verbose: print('WARN:Only one line, ugly hack ahead. Check units.') linemodel_syn['vel_rest'] = [(i*u.GHz).to(u.km/u.s, equivalencies=u.doppler_radio(j*u.GHz)) for (i,j) in zip( linemodel_syn['freq_rest'], linemodel['freq_rest'])] return spectra_syn, linemodel_syn, linemodel
def radio_velocities(self): if self._radio_velocities is None: rad_eq = u.doppler_radio(self.rest_frequency) self._radio_velocities = self.frequencies.to(u.km / u.s, rad_eq) return self._radio_velocities