Example #1
0
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
Example #4
0
 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
Example #5
0
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
Example #6
0
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
Example #7
0
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
Example #8
0
 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
Example #9
0
 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
Example #10
0
    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
Example #13
0
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
Example #14
0
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
Example #15
0
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
Example #16
0
        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'
Example #17
0
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
Example #18
0
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