def test_byhand_f2v(): # VELO-F2V CRVAL3F = 1.37847121643E+09 CDELT3F = 9.764775E+04 RESTFRQV = 1.420405752E+09 CRVAL3V = 8.98134229811E+06 CDELT3V = -2.1217551E+04 CUNIT3V = 'm/s' CUNIT3F = 'Hz' crvalf = CRVAL3F * u.Unit(CUNIT3F) crvalv = CRVAL3V * u.Unit(CUNIT3V) restfreq = RESTFRQV * u.Unit(CUNIT3F) cdeltf = CDELT3F * u.Unit(CUNIT3F) cdeltv = CDELT3V * u.Unit(CUNIT3V) # (Pdb) crval_in,crval_lin1,crval_lin2,crval_out # (<Quantity 1378471216.43 Hz>, <Quantity 1378471216.43 Hz>, <Quantity # 8981342.29795544 m / s>, <Quantity 8981342.29795544 m / s>) (Pdb) # cdelt_in, cdelt_lin1, cdelt_lin2, cdelt_out # (<Quantity 97647.75 Hz>, <Quantity 97647.75 Hz>, <Quantity # -21217.552294728768 m / s>, <Quantity -21217.552294728768 m / s>) crvalv_computed = crvalf.to(CUNIT3V, u.doppler_relativistic(restfreq)) cdeltv_computed = -4 * constants.c * cdeltf * crvalf * restfreq**2 / ( crvalf**2 + restfreq**2)**2 cdeltv_computed_byfunction = cdelt_derivative(crvalf, cdeltf, intype='frequency', outtype='speed', rest=restfreq) # this should be EXACT assert cdeltv_computed == cdeltv_computed_byfunction assert_allclose(crvalv_computed, crvalv, rtol=1.e-3) assert_allclose(cdeltv_computed, cdeltv, rtol=1.e-3) # round trip # (Pdb) crval_in,crval_lin1,crval_lin2,crval_out # (<Quantity 8981342.29795544 m / s>, <Quantity 8981342.29795544 m / s>, # <Quantity 1377852479.159838 Hz>, <Quantity 1377852479.159838 Hz>) # (Pdb) cdelt_in, cdelt_lin1, cdelt_lin2, cdelt_out # (<Quantity -21217.552294728768 m / s>, <Quantity -21217.552294728768 m / # s>, <Quantity 97647.74999999997 Hz>, <Quantity 97647.74999999997 Hz>) crvalf_computed = crvalv_computed.to(CUNIT3F, u.doppler_relativistic(restfreq)) cdeltf_computed = -(cdeltv_computed * constants.c * restfreq / ((constants.c + crvalv_computed) * (constants.c**2 - crvalv_computed**2)**0.5)) assert_allclose(crvalf_computed, crvalf, rtol=1.e-2) assert_allclose(cdeltf_computed, cdeltf, rtol=1.e-2) cdeltf_computed_byfunction = cdelt_derivative(crvalv_computed, cdeltv_computed, intype='speed', outtype='frequency', rest=restfreq) # this should be EXACT assert cdeltf_computed == cdeltf_computed_byfunction
def test_byhand_f2v(): # VELO-F2V CRVAL3F = 1.37847121643E+09 CDELT3F = 9.764775E+04 RESTFRQV= 1.420405752E+09 CRVAL3V = 8.98134229811E+06 CDELT3V = -2.1217551E+04 CUNIT3V = 'm/s' CUNIT3F = 'Hz' crvalf = CRVAL3F * u.Unit(CUNIT3F) crvalv = CRVAL3V * u.Unit(CUNIT3V) restfreq = RESTFRQV * u.Unit(CUNIT3F) cdeltf = CDELT3F * u.Unit(CUNIT3F) cdeltv = CDELT3V * u.Unit(CUNIT3V) # (Pdb) crval_in,crval_lin1,crval_lin2,crval_out # (<Quantity 1378471216.43 Hz>, <Quantity 1378471216.43 Hz>, <Quantity # 8981342.29795544 m / s>, <Quantity 8981342.29795544 m / s>) (Pdb) # cdelt_in, cdelt_lin1, cdelt_lin2, cdelt_out # (<Quantity 97647.75 Hz>, <Quantity 97647.75 Hz>, <Quantity # -21217.552294728768 m / s>, <Quantity -21217.552294728768 m / s>) crvalv_computed = crvalf.to(CUNIT3V, u.doppler_relativistic(restfreq)) cdeltv_computed = -4*constants.c*cdeltf*crvalf*restfreq**2 / (crvalf**2+restfreq**2)**2 cdeltv_computed_byfunction = cdelt_derivative(crvalf, cdeltf, intype='frequency', outtype='speed', rest=restfreq) # this should be EXACT assert cdeltv_computed == cdeltv_computed_byfunction assert_allclose(crvalv_computed, crvalv, rtol=1.e-3) assert_allclose(cdeltv_computed, cdeltv, rtol=1.e-3) # round trip # (Pdb) crval_in,crval_lin1,crval_lin2,crval_out # (<Quantity 8981342.29795544 m / s>, <Quantity 8981342.29795544 m / s>, # <Quantity 1377852479.159838 Hz>, <Quantity 1377852479.159838 Hz>) # (Pdb) cdelt_in, cdelt_lin1, cdelt_lin2, cdelt_out # (<Quantity -21217.552294728768 m / s>, <Quantity -21217.552294728768 m / # s>, <Quantity 97647.74999999997 Hz>, <Quantity 97647.74999999997 Hz>) crvalf_computed = crvalv_computed.to(CUNIT3F, u.doppler_relativistic(restfreq)) cdeltf_computed = -(cdeltv_computed * constants.c * restfreq / ((constants.c+crvalv_computed)*(constants.c**2 - crvalv_computed**2)**0.5)) assert_allclose(crvalf_computed, crvalf, rtol=1.e-2) assert_allclose(cdeltf_computed, cdeltf, rtol=1.e-2) cdeltf_computed_byfunction = cdelt_derivative(crvalv_computed, cdeltv_computed, intype='speed', outtype='frequency', rest=restfreq) # this should be EXACT assert cdeltf_computed == cdeltf_computed_byfunction
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 center_wavelength(self, idx): j = idx * self.npars lam = (self.em_model[j + 1] * units.km / units.s).to( units.angstrom, equivalencies=units.doppler_relativistic(self.feature_wl[idx] * units.angstrom)) return lam.value
def w80eval(wl: np.ndarray, spec: np.ndarray, wl0: float, smooth: float = None) -> tuple: """ Evaluates the W80 parameter of a given emission fature. Parameters ---------- wl : array-like Wavelength vector. spec : array-like Flux vector. wl0 : number Central wavelength of the emission feature. smooth : float Smoothing sigma to apply after the cumulative sum. Returns ------- w80 : float The resulting w80 parameter. r0 : float r1 : float velocity : numpy.ndarray Velocity vector. velocity_spectrum : numpy.ndarray Spectrum in velocity coordinates. Notes ----- W80 is the width in velocity space which encompasses 80% of the light emitted in a given spectral feature. It is widely used as a proxy for identifying outflows of ionized gas in active galaxies. For instance, see Zakamska+2014 MNRAS. """ velocity = (wl * units.angstrom).to( units.km / units.s, equivalencies=units.doppler_relativistic(wl0 * units.angstrom)) if not (spec == 0.0).all() and not np.isnan(spec).all(): y = ma.masked_invalid(spec) if smooth is not None: kernel = Gaussian1DKernel(smooth) y = convolve(y, kernel=kernel, boundary='extend') cumulative = cumtrapz(y[~y.mask], velocity[~y.mask], initial=0) cumulative /= cumulative.max() r0 = velocity[(np.abs(cumulative - 0.1)).argsort()[0]].value r1 = velocity[(np.abs(cumulative - 0.9)).argsort()[0]].value w80 = r1 - r0 else: w80, r0, r1 = np.nan, np.nan, np.nan return w80, r0, r1, velocity, spec
def velo2freq(velo, line_rest_freq): #velo in km/s; freq in MHz rest_freq = line_rest_freq * u.MHz velocity = velo * u.km / u.s relativistic_equiv = u.doppler_relativistic(rest_freq) freq = velocity.to(u.MHz, equivalencies=relativistic_equiv) return freq.value # freq in MHz
def freq2velo(freq, line_rest_freq): # freq in MHz rest_freq = line_rest_freq * u.MHz frequency = freq * u.MHz relativistic_equiv = u.doppler_relativistic(rest_freq) velo = frequency.to(u.km / u.s, equivalencies=relativistic_equiv) return velo.value # km/s
def beta_from_spectralcoord(spectralcoord): # TODO: check target is consistent doppler_equiv = u.doppler_relativistic(self.wcs.restwav * u.m) if observer is None: warnings.warn('No observer defined on WCS, SpectralCoord ' 'will be converted without any velocity ' 'frame change', AstropyUserWarning) return spectralcoord.to_value(u.m / u.s, doppler_equiv) / C_SI else: return spectralcoord.in_observer_velocity_frame(observer).to_value(u.m / u.s, doppler_equiv) / C_SI
def beta_from_spectralcoord(spectralcoord): # TODO: check target is consistent between WCS and SpectralCoord, # if they are not the transformation doesn't make conceptual sense. doppler_equiv = u.doppler_relativistic(self.wcs.restwav * u.m) if (observer is None or spectralcoord.observer is None or spectralcoord.target is None): if observer is None: msg = 'No observer defined on WCS' elif spectralcoord.observer is None: msg = 'No observer defined on SpectralCoord' else: msg = 'No target defined on SpectralCoord' warnings.warn(f'{msg}, SpectralCoord ' 'will be converted without any velocity ' 'frame change', AstropyUserWarning) return spectralcoord.to_value(u.m / u.s, doppler_equiv) / C_SI else: return spectralcoord.with_observer_stationary_relative_to(observer).to_value(u.m / u.s, doppler_equiv) / C_SI
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 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 convert_spectral_axis(mywcs, outunit, out_ctype, rest_value=None): """ Convert a spectral axis from its unit to a specified out unit with a given output ctype Only VACUUM units are supported (not air) Process: 1. Convert the input unit to its equivalent linear unit 2. Convert the input linear unit to the output linear unit 3. Convert the output linear unit to the output unit """ # If the WCS includes a rest frequency/wavelength, convert it to frequency # or wavelength first. This allows the possibility of changing the rest # frequency wcs_rv = get_rest_value_from_wcs(mywcs) inunit = u.Unit(mywcs.wcs.cunit[mywcs.wcs.spec]) outunit = u.Unit(outunit) # If wcs_rv is set and speed -> speed, then we're changing the reference # location and we need to convert to meters or Hz first if inunit.physical_type == "speed" and outunit.physical_type == "speed" and wcs_rv is not None: mywcs = convert_spectral_axis(mywcs, wcs_rv.unit, ALL_CTYPES[wcs_rv.unit.physical_type], rest_value=wcs_rv) inunit = u.Unit(mywcs.wcs.cunit[mywcs.wcs.spec]) elif inunit.physical_type == "speed" and outunit.physical_type == "speed" and wcs_rv is None: # If there is no reference change, we want an identical WCS, since # WCS doesn't know about units *at all* newwcs = mywcs.deepcopy() return newwcs # crval_out = (mywcs.wcs.crval[mywcs.wcs.spec] * inunit).to(outunit) # cdelt_out = (mywcs.wcs.cdelt[mywcs.wcs.spec] * inunit).to(outunit) # newwcs.wcs.cdelt[newwcs.wcs.spec] = cdelt_out.value # newwcs.wcs.cunit[newwcs.wcs.spec] = cdelt_out.unit.to_string(format='fits') # newwcs.wcs.crval[newwcs.wcs.spec] = crval_out.value # newwcs.wcs.ctype[newwcs.wcs.spec] = out_ctype # return newwcs in_spec_ctype = mywcs.wcs.ctype[mywcs.wcs.spec] # Check whether we need to convert the rest value first ref_value = None if outunit.physical_type == "speed": if rest_value is None: rest_value = wcs_rv if rest_value is None: raise ValueError( "If converting from wavelength/frequency to speed, " "a reference wavelength/frequency is required." ) ref_value = rest_value.to(u.Hz, u.spectral()) elif inunit.physical_type == "speed": # The rest frequency and wavelength should be equivalent if rest_value is not None: ref_value = rest_value elif wcs_rv is not None: ref_value = wcs_rv else: raise ValueError( "If converting from speed to wavelength/frequency, " "a reference wavelength/frequency is required." ) # If the input unit is not linearly sampled, its linear equivalent will be # the 8th character in the ctype, and the linearly-sampled ctype will be # the 6th character # e.g.: VOPT-F2V lin_ctype = in_spec_ctype[7] if len(in_spec_ctype) > 4 else in_spec_ctype[:4] lin_cunit = LINEAR_CUNIT_DICT[lin_ctype] if lin_ctype in LINEAR_CUNIT_DICT else mywcs.wcs.cunit[mywcs.wcs.spec] in_vcequiv = _parse_velocity_convention(in_spec_ctype[:4]) out_ctype_conv = out_ctype[7] if len(out_ctype) > 4 else out_ctype[:4] if CTYPE_TO_PHYSICALTYPE[out_ctype_conv] == "air wavelength": raise NotImplementedError("Conversion to air wavelength is not supported.") out_lin_cunit = LINEAR_CUNIT_DICT[out_ctype_conv] if out_ctype_conv in LINEAR_CUNIT_DICT else outunit out_vcequiv = _parse_velocity_convention(out_ctype_conv) # Load the input values crval_in = mywcs.wcs.crval[mywcs.wcs.spec] * inunit # the cdelt matrix may not be correctly populated: need to account for cd, # cdelt, and pc cdelt_in = mywcs.pixel_scale_matrix[mywcs.wcs.spec, mywcs.wcs.spec] * inunit if in_spec_ctype == "AWAV": warnings.warn( "Support for air wavelengths is experimental and only " "works in the forward direction (air->vac, not vac->air)." ) cdelt_in = air_to_vac_deriv(crval_in) * cdelt_in crval_in = air_to_vac(crval_in) in_spec_ctype = "WAVE" # 1. Convert input to input, linear if in_vcequiv is not None and ref_value is not None: crval_lin1 = crval_in.to(lin_cunit, u.spectral() + in_vcequiv(ref_value)) else: crval_lin1 = crval_in.to(lin_cunit, u.spectral()) cdelt_lin1 = cdelt_derivative( crval_in, cdelt_in, # equivalent: inunit.physical_type intype=CTYPE_TO_PHYSICALTYPE[in_spec_ctype[:4]], outtype=lin_cunit.physical_type, rest=ref_value, linear=True, ) # 2. Convert input, linear to output, linear if ref_value is None: if in_vcequiv is not None: pass # consider raising a ValueError here; not clear if this is valid crval_lin2 = crval_lin1.to(out_lin_cunit, u.spectral()) else: # at this stage, the transition can ONLY be relativistic, because the V # frame (as a linear frame) is only defined as "apparent velocity" crval_lin2 = crval_lin1.to(out_lin_cunit, u.spectral() + u.doppler_relativistic(ref_value)) # For cases like VRAD <-> FREQ and VOPT <-> WAVE, this will be linear too: linear_middle = in_vcequiv == out_vcequiv cdelt_lin2 = cdelt_derivative( crval_lin1, cdelt_lin1, intype=lin_cunit.physical_type, outtype=CTYPE_TO_PHYSICALTYPE[out_ctype_conv], rest=ref_value, linear=linear_middle, ) # 3. Convert output, linear to output if out_vcequiv is not None and ref_value is not None: crval_out = crval_lin2.to(outunit, out_vcequiv(ref_value) + u.spectral()) # cdelt_out = cdelt_lin2.to(outunit, out_vcequiv(ref_value) + u.spectral()) cdelt_out = cdelt_derivative( crval_lin2, cdelt_lin2, intype=CTYPE_TO_PHYSICALTYPE[out_ctype_conv], outtype=outunit.physical_type, rest=ref_value, linear=True, ).to(outunit) else: crval_out = crval_lin2.to(outunit, u.spectral()) cdelt_out = cdelt_lin2.to(outunit, u.spectral()) if crval_out.unit != cdelt_out.unit: # this should not be possible, but it's a sanity check raise ValueError("Conversion failed: the units of cdelt and crval don't match.") # A cdelt of 0 would be meaningless if cdelt_out.value == 0: raise ValueError("Conversion failed: the output CDELT would be 0.") newwcs = mywcs.deepcopy() if hasattr(newwcs.wcs, "cd"): newwcs.wcs.cd[newwcs.wcs.spec, newwcs.wcs.spec] = cdelt_out.value # todo: would be nice to have an assertion here that no off-diagonal # values for the spectral WCS are nonzero, but this is a nontrivial # check else: newwcs.wcs.cdelt[newwcs.wcs.spec] = cdelt_out.value newwcs.wcs.cunit[newwcs.wcs.spec] = cdelt_out.unit.to_string(format="fits") newwcs.wcs.crval[newwcs.wcs.spec] = crval_out.value newwcs.wcs.ctype[newwcs.wcs.spec] = out_ctype if rest_value is not None: if rest_value.unit.physical_type == "frequency": newwcs.wcs.restfrq = rest_value.to(u.Hz).value elif rest_value.unit.physical_type == "length": newwcs.wcs.restwav = rest_value.to(u.m).value else: raise ValueError("Rest Value was specified, but not in frequency or length units") return newwcs
def convert_spectral_axis(mywcs, outunit, out_ctype, rest_value=None): """ Convert a spectral axis from its unit to a specified out unit with a given output ctype Only VACUUM units are supported (not air) Process: 1. Convert the input unit to its equivalent linear unit 2. Convert the input linear unit to the output linear unit 3. Convert the output linear unit to the output unit """ # If the WCS includes a rest frequency/wavelength, convert it to frequency # or wavelength first. This allows the possibility of changing the rest # frequency wcs_rv = get_rest_value_from_wcs(mywcs) inunit = u.Unit(mywcs.wcs.cunit[mywcs.wcs.spec]) outunit = u.Unit(outunit) if (inunit.physical_type == 'speed' and outunit.physical_type == 'speed'): mywcs = convert_spectral_axis(mywcs, wcs_rv.unit, ALL_CTYPES[wcs_rv.unit.physical_type], rest_value=wcs_rv) inunit = u.Unit(mywcs.wcs.cunit[mywcs.wcs.spec]) in_spec_ctype = mywcs.wcs.ctype[mywcs.wcs.spec] # Check whether we need to convert the rest value first ref_value = None if outunit.physical_type == 'speed': if rest_value is None: rest_value = wcs_rv if rest_value is None: raise ValueError("If converting from wavelength/frequency to speed, " "a reference wavelength/frequency is required.") ref_value = rest_value.to(u.Hz, u.spectral()) elif inunit.physical_type == 'speed': # The rest frequency and wavelength should be equivalent if rest_value is not None: ref_value = rest_value elif wcs_rv is not None: ref_value = wcs_rv else: raise ValueError("If converting from speed to wavelength/frequency, " "a reference wavelength/frequency is required.") # If the input unit is not linearly sampled, its linear equivalent will be # the 8th character in the ctype, and the linearly-sampled ctype will be # the 6th character # e.g.: VOPT-F2V lin_ctype = (in_spec_ctype[7] if len(in_spec_ctype) > 4 else in_spec_ctype[:4]) lin_cunit = (LINEAR_CUNIT_DICT[lin_ctype] if lin_ctype in LINEAR_CUNIT_DICT else mywcs.wcs.cunit[mywcs.wcs.spec]) in_vcequiv = _parse_velocity_convention(in_spec_ctype[:4]) out_ctype_conv = out_ctype[7] if len(out_ctype) > 4 else out_ctype[:4] out_lin_cunit = (LINEAR_CUNIT_DICT[out_ctype_conv] if out_ctype_conv in LINEAR_CUNIT_DICT else outunit) out_vcequiv = _parse_velocity_convention(out_ctype_conv) # Load the input values crval_in = (mywcs.wcs.crval[mywcs.wcs.spec] * inunit) cdelt_in = (mywcs.wcs.cdelt[mywcs.wcs.spec] * inunit) # 1. Convert input to input, linear if in_vcequiv is not None and ref_value is not None: crval_lin1 = crval_in.to(lin_cunit, u.spectral() + in_vcequiv(ref_value)) else: crval_lin1 = crval_in.to(lin_cunit, u.spectral()) cdelt_lin1 = cdelt_derivative(crval_in, cdelt_in, # equivalent: inunit.physical_type intype=CTYPE_TO_PHYSICALTYPE[in_spec_ctype[:4]], outtype=lin_cunit.physical_type, rest=ref_value, linear=True ) # 2. Convert input, linear to output, linear if ref_value is None: if in_vcequiv is not None: pass # consider raising a ValueError here; not clear if this is valid crval_lin2 = crval_lin1.to(out_lin_cunit, u.spectral()) else: # at this stage, the transition can ONLY be relativistic, because the V # frame (as a linear frame) is only defined as "apparent velocity" crval_lin2 = crval_lin1.to(out_lin_cunit, u.spectral() + u.doppler_relativistic(ref_value)) # For cases like VRAD <-> FREQ and VOPT <-> WAVE, this will be linear too: linear_middle = in_vcequiv == out_vcequiv cdelt_lin2 = cdelt_derivative(crval_lin1, cdelt_lin1, intype=lin_cunit.physical_type, outtype=CTYPE_TO_PHYSICALTYPE[out_ctype_conv], rest=ref_value, linear=linear_middle) # 3. Convert output, linear to output if out_vcequiv is not None and ref_value is not None: crval_out = crval_lin2.to(outunit, out_vcequiv(ref_value) + u.spectral()) #cdelt_out = cdelt_lin2.to(outunit, out_vcequiv(ref_value) + u.spectral()) cdelt_out = cdelt_derivative(crval_lin2, cdelt_lin2, intype=CTYPE_TO_PHYSICALTYPE[out_ctype_conv], outtype=outunit.physical_type, rest=ref_value, linear=True ).to(outunit) else: crval_out = crval_lin2.to(outunit, u.spectral()) cdelt_out = cdelt_lin2.to(outunit, u.spectral()) if crval_out.unit != cdelt_out.unit: # this should not be possible, but it's a sanity check raise ValueError("Conversion failed: the units of cdelt and crval don't match.") # A cdelt of 0 would be meaningless if cdelt_out.value == 0: raise ValueError("Conversion failed: the output CDELT would be 0.") newwcs = mywcs.deepcopy() newwcs.wcs.cdelt[newwcs.wcs.spec] = cdelt_out.value newwcs.wcs.cunit[newwcs.wcs.spec] = cdelt_out.unit.to_string(format='fits') newwcs.wcs.crval[newwcs.wcs.spec] = crval_out.value newwcs.wcs.ctype[newwcs.wcs.spec] = out_ctype if rest_value is not None: if rest_value.unit.physical_type == 'frequency': newwcs.wcs.restfrq = rest_value.to(u.Hz).value elif rest_value.unit.physical_type == 'length': newwcs.wcs.restwav = rest_value.to(u.m).value else: raise ValueError("Rest Value was specified, but not in frequency or length units") return newwcs
def gauss(x1, y1, sigma1, f, ga, redshift, linea, my_file1, my_file2, fr1, fr2): import numpy as np import matplotlib.pyplot as plt import math import astropy.units as u from numpy import std from numpy import loadtxt from lmfit.models import GaussianModel from lmfit.models import ExponentialModel #Defino una recta para restarle al pseudo-continuo def graph(formula, x_range): x = np.array(x_range) y = eval(formula) plt.plot(x, y) plt.show() def fc(x, p): return p[0] * x + p[1] #Defino una funcion que encuentra el valor en la lista que sea mas cercano a un valor dado (find_nearest) def f_n(array, value): idx = (np.abs(array - value)).argmin() return array[idx] #Defino los parametros de la recta lpR = ga.left_point['x'] rpR = ga.right_point['x'] xa = [lpR, rpR] ya = [ga.left_point['y'], ga.right_point['y']] p1 = np.polyfit(xa, ya, deg=1) #Guardo en variables la informacion de los clicks que hice sobre el espectro #cp=int(ga.center_point['x']) lp = f_n(np.array(x1), ga.left_point['x']) rp = f_n(np.array(x1), ga.right_point['x']) cp = (lp + rp) / 2 #Acoto los datos a la region que me interesa del espectro, es decir, la region donde esta la linea recta = [] x = np.array([float(n) for n in x1[x1.index(lp):x1.index(rp)]]) for i in range(0, len(x)): recta.append(fc(x[i], p1)) y = np.array([float(n) for n in y1[x1.index(lp):x1.index(rp)]]) sigma = np.array([float(n) for n in sigma1[x1.index(lp):x1.index(rp)]]) y = y - recta #Para definir la amp inicial me conviene calcularlo como base*altura/2 del 'rectangulo' que involucra la gaussiana #Sigma lo defino mas o menos como el ancho de la linea ga_mod = GaussianModel(prefix='g_') pars = ga_mod.guess(y, x=x) pars.update(ga_mod.make_params()) pars['g_center'].set(cp) pars['g_sigma'].set(lp - rp) pars['g_amplitude'].set(50.) #A modelo lo defino como la combinacion de los distintos ajustes modelo = ga_mod init = modelo.eval(pars, x=x) out = modelo.fit(y, pars, x=x, weights=sigma) print(out.fit_report(min_correl=0.25)) minimo = out.best_values print 'g_center: ', minimo['g_center'] #En caso de que haya muchos ajustes componentes guarda cada uno de los ajustes componentes = out.eval_components() my_file1.write("# Velocidad \n") my_file1.write("Extremos: %.0f %.0f \n " % (lp, rp)) my_file1.write("Longitud de onda central: %.3f \n" % (minimo['g_center'])) my_file1.write(" \n") my_file1.write("Ajuste principal \n") my_file1.write(out.fit_report(min_correl=0.25)) my_file1.write(" \n") my_file2.write(" \n") f4 = plt.figure(4) ax = f4.add_subplot(111) plt.plot(x, out.best_fit, 'r-', label='Best_Fit') plt.plot(x, y, label='Original') plt.legend(loc='best') plt.title('Ajuste_Gaussiano_' + f) plt.xlabel(r'Longitud de onda [$\AA$]', fontsize=18) plt.ylabel('log(Flujo) + cte', fontsize=18) plt.vlines(minimo['g_center'], min(out.best_fit), 1.25 * min(out.best_fit), linestyles='solid') ax.annotate('MINIMO', xy=(minimo['g_center'] - 1, 1.25 * min(out.best_fit)), xytext=(minimo['g_center'], 1.25 * min(out.best_fit))) plt.ion() f4.show() plt.savefig(fr1, transparent=True) #Calculo la velocidad long_onda_medida = minimo['g_center'] / (1 + redshift) if (linea == 'Halfa'): long_onda_reposo = 6563 elif (linea == 'HeI'): long_onda_reposo = 5876 else: long_onda_reposo = 5169 restfreq = long_onda_reposo * u.AA relativistic_equiv = u.doppler_relativistic(restfreq) measured_freq = long_onda_medida * u.AA relativistic_velocity = measured_freq.to(u.km / u.s, equivalencies=relativistic_equiv) my_file1.write("Velocidad : %.10s \n " % (relativistic_velocity)) print 'Velocidad de ', linea, ' : ', relativistic_velocity #Calculo el error sigma en el calculo de lambda0 #WS=Dimension de la ventana dentro de la cual muevo los limites del ajuste gaussiano. Es impar para tomar un ancho simetrico alrededor de los puntos extremos originales ws = 15 mws = (ws - 1) / 2 #A continuacion los vectores ampliados: recta2 = [] a = f_n(np.array(x1), lp - mws) b = f_n(np.array(x1), rp + mws) x2 = np.array([float(n) for n in x1[x1.index(a):x1.index((b))]]) for i in range(0, len(x2)): recta2.append(fc(x2[i], p1)) y2 = np.array([float(n) for n in y1[x1.index(a):x1.index((b))]]) y3 = y2 - recta2 #Ahora itero sobre los valores del ws ajustando una gaussiana por cada posicion del mismo my_file2.write("Ajustes calculo error \n") cont = 0 m = [] f5 = plt.figure(5) plt.plot(x2, y3, label='Original') for j in range(0, ws): for i in range(int(len(x2) - ws), int(len(x2))): cont += 1 #y3=y4 lp2 = x2[j] rp2 = x2[i] recta3 = [] xrecta = [lp2, rp2] yrecta = [y2[j], y2[i]] p2 = np.polyfit(xrecta, yrecta, deg=1) for i in range(0, len(x2)): recta3.append(fc(x2[i], p2)) y4 = y2 - recta3 #z=(y3[j]+y3[len(x2)-1-j])/2 #y3=y3-z cp2 = (lp2 + rp2) / 2 ga2_mod = GaussianModel(prefix='g2_') pars2 = ga2_mod.guess(y4, x=x2) pars2.update(ga2_mod.make_params()) pars2['g2_center'].set(cp2) pars2['g2_sigma'].set(lp2 - rp2) pars2['g2_amplitude'].set(50.) modelo = ga2_mod init = modelo.eval(pars2, x=x2) out = modelo.fit(y4, pars2, x=x2) my_file2.write(out.fit_report(min_correl=0.25)) minimo2 = out.best_values m.append(minimo2['g2_center']) plt.vlines(minimo2['g2_center'], min(out.best_fit), 1.025 * min(out.best_fit), linestyles='solid', label='') componentes2 = out.eval_components() #plt.plot(x2,out.best_fit+z) plt.plot(x2, out.best_fit) print 'Iteraciones: ', cont plt.title('Ajuste_Gaussiano_' + f) plt.xlabel(r'Longitud de onda [$\AA$]', fontsize=18) plt.ylabel('log(Flujo) + cte', fontsize=18) plt.ion() f5.show() plt.savefig(fr2, transparent=True) my_file1.write(" \n") my_file1.write("Desviacion estandar= %.3f \n " % (std(m))) my_file1.write("Iteraciones: %.0f \n " % (cont)) print 'Dispersion: ', std(m) #Paso la dispersion a velocidad c = 300000 deltav = c * std(m) / (long_onda_reposo * (1 + redshift)) print 'Dispersion [km/s]: ', deltav my_file1.write("Dispersion [km/s]: %.3f \n " % (deltav)) return minimo['g_center'], relativistic_velocity, std(m), deltav
def convert_spectral_axis(mywcs, outunit, out_ctype, rest_value=None): """ Convert a spectral axis from its unit to a specified out unit with a given output ctype Only VACUUM units are supported (not air) Process: 1. Convert the input unit to its equivalent linear unit 2. Convert the input linear unit to the output linear unit 3. Convert the output linear unit to the output unit """ outunit = u.Unit(outunit) inunit = u.Unit(mywcs.wcs.cunit[mywcs.wcs.spec]) in_spec_ctype = mywcs.wcs.ctype[mywcs.wcs.spec] # If the input unit is not linearly sampled, its linear equivalent will be # the 8th character in the ctype, and the linearly-sampled ctype will be # the 6th character # e.g.: VOPT-F2V lin_ctype = (in_spec_ctype[7] if len(in_spec_ctype) > 4 else in_spec_ctype[:4]) lin_cunit = (LINEAR_CUNIT_DICT[lin_ctype] if lin_ctype in LINEAR_CUNIT_DICT else mywcs.wcs.cunit[mywcs.wcs.spec]) in_vcequiv = _parse_velocity_convention(in_spec_ctype[:4]) out_ctype_conv = out_ctype[7] if len(out_ctype) > 4 else out_ctype[:4] out_lin_cunit = (LINEAR_CUNIT_DICT[out_ctype_conv] if out_ctype_conv in LINEAR_CUNIT_DICT else outunit) out_vcequiv = _parse_velocity_convention(out_ctype_conv) ref_value = None if outunit.physical_type == 'speed': if rest_value is None: rest_value = get_rest_value_from_wcs(mywcs) if rest_value is None: raise ValueError( "If converting from wavelength/frequency to speed, " "a reference wavelength/frequency is required.") else: warnings.warn( "Using WCS built-in rest frequency even though the " "WCS system was originally {0}".format(in_spec_ctype)) ref_value = rest_value.to(u.Hz, u.spectral()) elif inunit.physical_type == 'speed': # The rest frequency and wavelength should be equivalent wcs_rv = get_rest_value_from_wcs(mywcs) if rest_value is not None: warnings.warn( "Overriding the reference value specified in the WCS.") ref_value = rest_value elif wcs_rv is not None: ref_value = wcs_rv else: raise ValueError( "If converting from speed to wavelength/frequency, " "a reference wavelength/frequency is required.") # Load the input values crval_in = (mywcs.wcs.crval[mywcs.wcs.spec] * inunit) cdelt_in = (mywcs.wcs.cdelt[mywcs.wcs.spec] * inunit) # 1. Convert input to input, linear if in_vcequiv is not None and ref_value is not None: crval_lin1 = crval_in.to(lin_cunit, u.spectral() + in_vcequiv(ref_value)) else: crval_lin1 = crval_in.to(lin_cunit, u.spectral()) cdelt_lin1 = cdelt_derivative( crval_in, cdelt_in, # equivalent: inunit.physical_type intype=CTYPE_TO_PHYSICALTYPE[in_spec_ctype[:4]], outtype=lin_cunit.physical_type, rest=ref_value, linear=True) # 2. Convert input, linear to output, linear if ref_value is None: if in_vcequiv is not None: pass # consider raising a ValueError here; not clear if this is valid crval_lin2 = crval_lin1.to(out_lin_cunit, u.spectral()) else: # at this stage, the transition can ONLY be relativistic, because the V # frame (as a linear frame) is only defined as "apparent velocity" crval_lin2 = crval_lin1.to( out_lin_cunit, u.spectral() + u.doppler_relativistic(ref_value)) # For cases like VRAD <-> FREQ and VOPT <-> WAVE, this will be linear too: linear_middle = in_vcequiv == out_vcequiv cdelt_lin2 = cdelt_derivative( crval_lin1, cdelt_lin1, intype=lin_cunit.physical_type, outtype=CTYPE_TO_PHYSICALTYPE[out_ctype_conv], rest=ref_value, linear=linear_middle) # 3. Convert output, linear to output if out_vcequiv is not None and ref_value is not None: crval_out = crval_lin2.to(outunit, out_vcequiv(ref_value) + u.spectral()) #cdelt_out = cdelt_lin2.to(outunit, out_vcequiv(ref_value) + u.spectral()) cdelt_out = cdelt_derivative( crval_lin2, cdelt_lin2, intype=CTYPE_TO_PHYSICALTYPE[out_ctype_conv], outtype=outunit.physical_type, rest=ref_value, linear=True).to(outunit) else: crval_out = crval_lin2.to(outunit, u.spectral()) cdelt_out = cdelt_lin2.to(outunit, u.spectral()) if crval_out.unit != cdelt_out.unit: # this should not be possible, but it's a sanity check raise ValueError( "Conversion failed: the units of cdelt and crval don't match.") # A cdelt of 0 would be meaningless if cdelt_out.value == 0: raise ValueError("Conversion failed: the output CDELT would be 0.") newwcs = mywcs.deepcopy() newwcs.wcs.cdelt[newwcs.wcs.spec] = cdelt_out.value newwcs.wcs.cunit[newwcs.wcs.spec] = cdelt_out.unit.to_string(format='fits') newwcs.wcs.crval[newwcs.wcs.spec] = crval_out.value newwcs.wcs.ctype[newwcs.wcs.spec] = out_ctype if rest_value is not None: if rest_value.unit.physical_type == 'frequency': newwcs.wcs.restfrq = rest_value.to(u.Hz).value elif rest_value.unit.physical_type == 'length': newwcs.wcs.restwav = rest_value.to(u.m).value else: raise ValueError( "Rest Value was specified, but not in frequency or length units" ) return newwcs
header_out['BSCALE '] = 1.0 header_out['BZERO '] = 0.0 header_out['DATAMIN '] = np.nanmax(spectrum) header_out['DATAMAX '] = np.nanmin(spectrum) header_out['BUNIT '] = 'K' if header['CTYPE1'].strip() == "VRAD": header_out['CDELT1 '] = header[ 'CDELT1 '] * 1E3 # velocity resolution in m/s; header_out['CRVAL1 '] = header[ 'CRVAL1 '] * 1E3 # velocity offset in m/s header_out['CTYPE1 '] = "VELO " header_out['RESTFREQ'] = header[ 'RESTFRQ'] # /(1+header['ZSOURCE']) # header_out['DELTAV'] = header['RESTFRQ']# /(1+header['ZSOURCE']) else: vel_to_freq = u.doppler_relativistic(frequency * u.Hz) freq_step = ((header['CDELT1 ']) * u.km / u.s).to( u.Hz, equivalencies=vel_to_freq) - (0 * u.km / u.s).to( u.Hz, equivalencies=vel_to_freq) header_out['CRVAL1 '] = header[ 'CRVAL1 '] * 1e3 # frequency in the reference channel; header['CRVAL1 ']*1e3 # form km/s to m/s header_out[ 'CDELT1 '] = freq_step.value # freq resolution in Hz; header['CDELT1 ']*1e3 # form km/s to m/s header_out['CTYPE1 '] = "FREQ " header_out['RESTFREQ'] = header['RESTFRQ'] / (1 + header['ZSOURCE']) # print("frequency:", frequency) # print("freq_step:", freq_step) header_out['CRPIX1 '] = header['CRPIX1 '] header_out['CTYPE2 '] = 'RA---GLS'
def convert_spectral_axis(mywcs, outunit, out_ctype, rest_value=None): """ Convert a spectral axis from its unit to a specified out unit with a given output ctype Only VACUUM units are supported (not air) Process: 1. Convert the input unit to its equivalent linear unit 2. Convert the input linear unit to the output linear unit 3. Convert the output linear unit to the output unit """ # If the WCS includes a rest frequency/wavelength, convert it to frequency # or wavelength first. This allows the possibility of changing the rest # frequency wcs_rv = get_rest_value_from_wcs(mywcs) inunit = u.Unit(mywcs.wcs.cunit[mywcs.wcs.spec]) outunit = u.Unit(outunit) # If wcs_rv is set and speed -> speed, then we're changing the reference # location and we need to convert to meters or Hz first if ((inunit.physical_type == 'speed' and outunit.physical_type == 'speed' and wcs_rv is not None)): mywcs = convert_spectral_axis(mywcs, wcs_rv.unit, ALL_CTYPES[wcs_rv.unit.physical_type], rest_value=wcs_rv) inunit = u.Unit(mywcs.wcs.cunit[mywcs.wcs.spec]) elif (inunit.physical_type == 'speed' and outunit.physical_type == 'speed' and wcs_rv is None): # If there is no reference change, we want an identical WCS, since # WCS doesn't know about units *at all* newwcs = mywcs.deepcopy() return newwcs #crval_out = (mywcs.wcs.crval[mywcs.wcs.spec] * inunit).to(outunit) #cdelt_out = (mywcs.wcs.cdelt[mywcs.wcs.spec] * inunit).to(outunit) #newwcs.wcs.cdelt[newwcs.wcs.spec] = cdelt_out.value #newwcs.wcs.cunit[newwcs.wcs.spec] = cdelt_out.unit.to_string(format='fits') #newwcs.wcs.crval[newwcs.wcs.spec] = crval_out.value #newwcs.wcs.ctype[newwcs.wcs.spec] = out_ctype #return newwcs in_spec_ctype = mywcs.wcs.ctype[mywcs.wcs.spec] # Check whether we need to convert the rest value first ref_value = None if outunit.physical_type == 'speed': if rest_value is None: rest_value = wcs_rv if rest_value is None: raise ValueError( "If converting from wavelength/frequency to speed, " "a reference wavelength/frequency is required.") ref_value = rest_value.to(u.Hz, u.spectral()) elif inunit.physical_type == 'speed': # The rest frequency and wavelength should be equivalent if rest_value is not None: ref_value = rest_value elif wcs_rv is not None: ref_value = wcs_rv else: raise ValueError( "If converting from speed to wavelength/frequency, " "a reference wavelength/frequency is required.") # If the input unit is not linearly sampled, its linear equivalent will be # the 8th character in the ctype, and the linearly-sampled ctype will be # the 6th character # e.g.: VOPT-F2V lin_ctype = (in_spec_ctype[7] if len(in_spec_ctype) > 4 else in_spec_ctype[:4]) lin_cunit = (LINEAR_CUNIT_DICT[lin_ctype] if lin_ctype in LINEAR_CUNIT_DICT else mywcs.wcs.cunit[mywcs.wcs.spec]) in_vcequiv = _parse_velocity_convention(in_spec_ctype[:4]) out_ctype_conv = out_ctype[7] if len(out_ctype) > 4 else out_ctype[:4] if CTYPE_TO_PHYSICALTYPE[out_ctype_conv] == 'air wavelength': raise NotImplementedError( "Conversion to air wavelength is not supported.") out_lin_cunit = (LINEAR_CUNIT_DICT[out_ctype_conv] if out_ctype_conv in LINEAR_CUNIT_DICT else outunit) out_vcequiv = _parse_velocity_convention(out_ctype_conv) # Load the input values crval_in = (mywcs.wcs.crval[mywcs.wcs.spec] * inunit) # the cdelt matrix may not be correctly populated: need to account for cd, # cdelt, and pc cdelt_in = (mywcs.pixel_scale_matrix[mywcs.wcs.spec, mywcs.wcs.spec] * inunit) if in_spec_ctype == 'AWAV': warnings.warn( "Support for air wavelengths is experimental and only " "works in the forward direction (air->vac, not vac->air).") cdelt_in = air_to_vac_deriv(crval_in) * cdelt_in crval_in = air_to_vac(crval_in) in_spec_ctype = 'WAVE' # 1. Convert input to input, linear if in_vcequiv is not None and ref_value is not None: crval_lin1 = crval_in.to(lin_cunit, u.spectral() + in_vcequiv(ref_value)) else: crval_lin1 = crval_in.to(lin_cunit, u.spectral()) cdelt_lin1 = cdelt_derivative( crval_in, cdelt_in, # equivalent: inunit.physical_type intype=CTYPE_TO_PHYSICALTYPE[in_spec_ctype[:4]], outtype=lin_cunit.physical_type, rest=ref_value, linear=True) # 2. Convert input, linear to output, linear if ref_value is None: if in_vcequiv is not None: pass # consider raising a ValueError here; not clear if this is valid crval_lin2 = crval_lin1.to(out_lin_cunit, u.spectral()) else: # at this stage, the transition can ONLY be relativistic, because the V # frame (as a linear frame) is only defined as "apparent velocity" crval_lin2 = crval_lin1.to( out_lin_cunit, u.spectral() + u.doppler_relativistic(ref_value)) # For cases like VRAD <-> FREQ and VOPT <-> WAVE, this will be linear too: linear_middle = in_vcequiv == out_vcequiv cdelt_lin2 = cdelt_derivative( crval_lin1, cdelt_lin1, intype=lin_cunit.physical_type, outtype=CTYPE_TO_PHYSICALTYPE[out_ctype_conv], rest=ref_value, linear=linear_middle) # 3. Convert output, linear to output if out_vcequiv is not None and ref_value is not None: crval_out = crval_lin2.to(outunit, out_vcequiv(ref_value) + u.spectral()) #cdelt_out = cdelt_lin2.to(outunit, out_vcequiv(ref_value) + u.spectral()) cdelt_out = cdelt_derivative( crval_lin2, cdelt_lin2, intype=CTYPE_TO_PHYSICALTYPE[out_ctype_conv], outtype=outunit.physical_type, rest=ref_value, linear=True).to(outunit) else: crval_out = crval_lin2.to(outunit, u.spectral()) cdelt_out = cdelt_lin2.to(outunit, u.spectral()) if crval_out.unit != cdelt_out.unit: # this should not be possible, but it's a sanity check raise ValueError( "Conversion failed: the units of cdelt and crval don't match.") # A cdelt of 0 would be meaningless if cdelt_out.value == 0: raise ValueError("Conversion failed: the output CDELT would be 0.") newwcs = mywcs.deepcopy() if hasattr(newwcs.wcs, 'cd'): newwcs.wcs.cd[newwcs.wcs.spec, newwcs.wcs.spec] = cdelt_out.value # todo: would be nice to have an assertion here that no off-diagonal # values for the spectral WCS are nonzero, but this is a nontrivial # check else: newwcs.wcs.cdelt[newwcs.wcs.spec] = cdelt_out.value newwcs.wcs.cunit[newwcs.wcs.spec] = cdelt_out.unit.to_string(format='fits') newwcs.wcs.crval[newwcs.wcs.spec] = crval_out.value newwcs.wcs.ctype[newwcs.wcs.spec] = out_ctype if rest_value is not None: if rest_value.unit.physical_type == 'frequency': newwcs.wcs.restfrq = rest_value.to(u.Hz).value elif rest_value.unit.physical_type == 'length': newwcs.wcs.restwav = rest_value.to(u.m).value else: raise ValueError( "Rest Value was specified, but not in frequency or length units" ) return newwcs
def velocity_width(wavelength: np.ndarray, model: np.ndarray, data: np.ndarray, rest_wavelength: units.Quantity, width: float = 80.0, smooth: float = None, clip_negative_flux: bool = True, sigma_factor: float = 5.0, fractional_pixels: bool = True, oversample: int = 1): """ Evaluates the W80 parameter of a given emission feature. Parameters ---------- wavelength : array-like Wavelength vector. Units for the wavelength are assumed to be the same as the rest_wavelength. model : np.ndarray Modeled spectral feature. data : np.ndarray Observed spectrum. rest_wavelength : astropy.units.Quantity Rest wavelength of the desired spectral feature. Units for this argument must match those of the wavelength vector. width : float Percentile velocity width. For instance width=80 for the W_80 index. smooth : float Smoothing sigma to apply after the cumulative sum. clip_negative_flux : bool Sets negative flux values to zero. sigma_factor : float Radius of integration, from the modeled feature centroid, in units of the distance between the centroid and the 16 and 84 percentiles. fractional_pixels : bool Uses a linear interpolation between adjacent pixels to find the velocity which yields the desired width exactly. Otherwise take the value from the closest pixel. oversample : int Oversample data this number of times. Oversampling is achieved via a cubic spline interpolation. Returns ------- res : dict Dictionary of results. Notes ----- W80 is the width in velocity space which encompasses 80% of the light emitted in a given spectral feature. It is widely used as a proxy for identifying outflows of ionized gas in active galaxies. For instance, see Zakamska+2014 MNRAS. """ res = {} for name in ['model', 'direct']: res[f'{name}_velocity_width'] = np.nan res[f'{name}_centroid_velocity'] = np.nan res[f'{name}_lower_velocity'] = np.nan res[f'{name}_upper_velocity'] = np.nan res[f'{name}_velocities'] = np.array([]) res[f'{name}_spectrum'] = np.array([]) if np.all(model == 0.0) and np.all(np.isnan(data)): return res cumulative = cumtrapz(model, wavelength, initial=0) if cumulative.max(initial=0) <= 0: return res cumulative /= cumulative.max(initial=0) cw = wavelength[np.argsort(np.abs(cumulative - 0.5))[0]] lower_lambda = cw - ( (cw - wavelength[np.argsort(np.abs(cumulative - 0.16))[0]]) * sigma_factor) upper_lambda = cw + ( (wavelength[np.argsort(np.abs(cumulative - 0.84))[0]] - cw) * sigma_factor) window = (wavelength > lower_lambda) & (wavelength < upper_lambda) if not np.any(window): return res wavelength = wavelength[window] model = model[window] data = data[window] first_iteration = True for name, spec in zip(['model', 'direct'], [model, data]): y = ma.masked_invalid(spec) if oversample > 1: if first_iteration: old_wavelength = copy.deepcopy(wavelength) first_iteration = False y2 = interp1d(old_wavelength[~y.mask], y[~y.mask], kind='cubic') m2 = interp1d(old_wavelength, y.mask, kind='nearest') wavelength = np.linspace(old_wavelength[0], old_wavelength[-1], oversample * old_wavelength.size) y = ma.array(data=y2(wavelength), mask=m2(wavelength)) if clip_negative_flux: y = ma.array(data=np.clip(y.data, a_min=0.0, a_max=None), mask=y.mask) if smooth is not None: kernel = Gaussian1DKernel(smooth) y_mask = copy.deepcopy(y.mask) y = ma.array(data=convolve(y, kernel=kernel, boundary='extend'), mask=y_mask) cumulative = cumtrapz(y[~y.mask], wavelength[~y.mask], initial=0) if len(cumulative.shape) > 1: raise ValueError( f'cumulative must have only one dimension, but it has {len(cumulative.shape)}.' ) cumulative /= cumulative.max() velocity = units.Quantity(wavelength, rest_wavelength.unit).to( units.km / units.s, equivalencies=units.doppler_relativistic(rest_wavelength)) if fractional_pixels: r0 = find_intermediary_value(velocity.value, cumulative, (50 - (width / 2)) / 100) r1 = find_intermediary_value(velocity.value, cumulative, (50 + (width / 2)) / 100) centroid = find_intermediary_value(velocity.value, cumulative, 0.5) else: r0 = velocity[np.abs(cumulative - ((50 - (width / 2)) / 100)).argsort()[0]].value r1 = velocity[np.abs(cumulative - ((50 + (width / 2)) / 100)).argsort()[0]].value centroid = velocity[np.abs(cumulative - 0.5).argsort()[0]].value res[f'{name}_velocity_width'] = r1 - r0 res[f'{name}_centroid_velocity'] = centroid res[f'{name}_lower_velocity'] = r0 res[f'{name}_upper_velocity'] = r1 res[f'{name}_velocities'] = velocity res[f'{name}_spectrum'] = y return res