Example #1
0
File: cube.py Project: heh15/Arp240
def convolve_cube(cube, newbeam, res_tol=0.0, min_coverage=0.8,
                  append_raw=False, verbose=False,
                  suppress_error=False):
    """
    Convolve a spectral cube to a specified beam.

    This function is essentially a wrapper around
    `~spectral_cube.SpectralCube.convolve_to()`, but it treats
    NaN values / edge effect in a more careful way
    (see the documentation of keyword 'min_coverage' below).

    Parameters
    ----------
    cube : ~spectral_cube.SpectralCube object
        Input spectral cube
    newbeam : radio_beam.Beam object
        Target beam to convolve to
    res_tol : float, optional
        Tolerance on the difference between input/output resolution
        By default, a convolution is performed on the input cube
        whenever its native resolution is different from (sharper than)
        the target resolution. Use this keyword to specify a tolerance
        on resolution, within which no convolution will be performed.
        For example, res_tol=0.1 will allow a 10% tolerance.
    min_coverage : float or None, optional
        When the convolution meets NaN values or edges, the output is
        calculated based on beam-weighted average. This keyword specifies
        the minimum beam covering fraction of valid (np.finite) values.
        All pixels with less beam covering fraction will be assigned NaNs.
        Default is 80% beam covering fraction (min_coverage=0.8).
        If the user would rather use the interpolation strategy in
        `astropy.convolution.convolve_fft`, set this keyword to None.
        Note that the NaN pixels will be kept as NaN.
    append_raw : bool, optional
        Whether to append the raw convolved cube and weight cube
        Default is not to append.
    verbose : bool, optional
        Whether to print the detailed processing information in terminal
        Default is to not print.
    suppress_error : bool, optional
        Whether to suppress the error when convolution is unsuccessful
        Default is to not suppress.

    Returns
    -------
    outcube : SpectralCube object or tuple
        Convolved spectral cube (when append_raw=False), or a tuple
        comprising 3 cubes (when append_raw=True)
    """

    from functools import partial
    from astropy.convolution import convolve_fft

    if min_coverage is None:
        # Skip coverage check and preserve NaN values.
        # This uses the default interpolation strategy
        # implemented in 'astropy.convolution.convolve_fft'
        convolve_func = partial(convolve_fft, preserve_nan=True,
                                allow_huge=True, quiet=~verbose)
    else:
        # Do coverage check to determine the mask on the output
        convolve_func = partial(convolve_fft, nan_treatment='fill',
                                boundary='fill', fill_value=0.,
                                allow_huge=True, quiet=~verbose)

    if (res_tol > 0) and (newbeam.major != newbeam.minor):
        raise ValueError("You cannot specify a non-zero resolution "
                         "torelance if the target beam is not round")

    tol = newbeam.major * np.array([1-res_tol, 1+res_tol])
    if ((tol[0] < cube.beam.major < tol[1]) and
        (tol[0] < cube.beam.minor < tol[1])):
        if verbose:
            print("Native resolution within tolerance - "
                  "Copying original cube...")
        my_append_raw = False
        newcube = cube.unmasked_copy().with_mask(cube.mask.include())
    else:
        if verbose:
            print("Convolving cube...")
        try:
            convcube = cube.convolve_to(newbeam,
                                        convolve=convolve_func)
            if min_coverage is not None:
                # divide the raw convolved image by the weight image
                my_append_raw = True
                wtcube = SpectralCube(
                    cube.mask.include().astype('float'),
                    cube.wcs, beam=cube.beam)
                wtcube = wtcube.convolve_to(newbeam,
                                            convolve=convolve_func)
                newcube = convcube / wtcube.unmasked_data[:]
                # mask all pixels w/ weight smaller than min_coverage
                threshold = min_coverage * u.dimensionless_unscaled
                newcube = newcube.with_mask(wtcube >= threshold)
            else:
                my_append_raw = False
                newcube = convcube
            print("")  # force line break after the progress bar
        except ValueError as err:
            if suppress_error:
                return
            else:
                raise ValueError(
                    "Unsuccessful convolution: {}\nOld: {}\nNew: {}"
                    "".format(err, cube.beam, newbeam))

    if append_raw and my_append_raw:
        return newcube, convcube, wtcube
    else:
        return newcube
Example #2
0
def cleansplit(filename,
               galaxy=None,
               Vwindow=650 * u.km / u.s,
               Vgalaxy=300 * u.km / u.s,
               blorder=3,
               HanningLoops=0,
               maskfile=None,
               circleMask=True,
               edgeMask=False,
               weightCut=0.2,
               spectralSetup=None,
               spatialSmooth=1.0):
    """
    Takes a raw DEGAS cube and produces individual cubes for each
    spectral line.
    
    Paramters
    ---------
    filename : str
        The file to split.
    
    Keywords
    --------
    galaxy : Galaxy object
        Currently unused
    Vwindow : astropy.Quantity
        Width of the window in velocity units
    Vgalaxy : astropy.Quantity
        Line of sight velocity of the galaxy centre
    blorder : int
        Baseline order
    HanningLoops : int
        Number of times to smooth and resample the data
    edgeMask : bool
        Determine whether to apply an edgeMask
    weightCut : float
        Minimum weight value to include in the data
    spatialSmooth : float
        Factor to increase the (linear) beam size by in a convolution.
    spectralSetup : str
        String to determine how we set up the spectrum
        'hcn_hcop' -- split based on HCN/HCO+ setup
        '13co_c18o' -- split based on 13CO/C18O setup
        '12co' -- don't split; assume single line
    """

    Cube = SpectralCube.read(filename)
    CatalogFile = get_pkg_data_filename('./data/dense_survey.cat',
                                        package='degas')
    Catalog = Table.read(CatalogFile, format='ascii')

    # Find which galaxy in our catalog corresponds to the object we
    # are mapping
    if galaxy is None:
        RABound, DecBound = Cube.world_extrema
        match = np.zeros_like(Catalog, dtype=np.bool)
        for index, row in enumerate(Catalog):
            galcoord = SkyCoord(row['RA'],
                                row['DEC'],
                                unit=(u.hourangle, u.deg))
            if (galcoord.ra < RABound[1] and galcoord.ra > RABound[0]
                    and galcoord.dec < DecBound[1]
                    and galcoord.dec > DecBound[0]):
                match[index] = True
        MatchRow = Catalog[match]
        galcoord = SkyCoord(MatchRow['RA'],
                            MatchRow['DEC'],
                            unit=(u.hourangle, u.deg))
        Galaxy = MatchRow['NAME'].data[0]
        print("Catalog Match with " + Galaxy)
        V0 = MatchRow['CATVEL'].data[0] * u.km / u.s

    # Check spectral setups.  Use the max frequencies present to
    # determine which spectral setup we used if not specifed.
    if spectralSetup is None:
        if (Cube.spectral_axis.max() > 105 * u.GHz
                and Cube.spectral_axis.max() < 113 * u.GHz):
            warnings.warn("assuming 13CO/C18O spectral setup")
            spectralSetup = '13CO_C18O'
            filestr = '13co_c18o'
        if (Cube.spectral_axis.max() > 82 * u.GHz
                and Cube.spectral_axis.max() < 90 * u.GHz):
            warnings.warn("assuming HCN/HCO+ spectral setup")
            spectralSetup = 'HCN_HCO+'
            filestr = 'hcn_hcop'
        if (Cube.spectral_axis.max() > 113 * u.GHz):
            warnings.warn("assuming 12CO spectral setup")
            spectralSetup = '12CO'
            filestr = '12co'

    if spectralSetup == '13CO_C18O':
        CEighteenO = Cube.with_spectral_unit(u.km / u.s,
                                             velocity_convention='radio',
                                             rest_value=109.78217 * u.GHz)
        ThirteenCO = Cube.with_spectral_unit(u.km / u.s,
                                             velocity_convention='radio',
                                             rest_value=110.20135 * u.GHz)
        CubeList = (CEighteenO, ThirteenCO)
        LineList = ('C18O', '13CO')

    elif spectralSetup == 'HCN_HCO+':
        HCN = Cube.with_spectral_unit(u.km / u.s,
                                      velocity_convention='radio',
                                      rest_value=88.631847 * u.GHz)
        HCOp = Cube.with_spectral_unit(u.km / u.s,
                                       velocity_convention='radio',
                                       rest_value=89.188518 * u.GHz)
        CubeList = (HCN, HCOp)
        LineList = ('HCN', 'HCOp')

    elif spectralSetup == '12CO':
        TwelveCO = Cube.with_spectral_unit(u.km / u.s,
                                           velocity_convention='radio',
                                           rest_value=115.27120180 * u.GHz)
        CubeList = (TwelveCO, )
        LineList = ('12CO', )

    for ThisCube, ThisLine in zip(CubeList, LineList):
        if circleMask:
            x0, y0, _ = ThisCube.wcs.wcs_world2pix(galcoord.ra, galcoord.dec,
                                                   0, 0)
            ThisCube = circletrim(ThisCube,
                                  filename.replace('.fits', '_wts.fits'),
                                  x0,
                                  y0,
                                  weightCut=weightCut)
        if edgeMask:
            ThisCube = edgetrim(ThisCube,
                                filename.replace('.fits', '_wts.fits'),
                                weightCut=weightCut)

        # Trim each cube to the specified velocity range
        ThisCube = ThisCube.spectral_slab(V0 - Vwindow, V0 + Vwindow)
        ThisCube.write(Galaxy + '_' + ThisLine + '.fits', overwrite=True)
        StartChan = ThisCube.closest_spectral_channel(V0 - Vgalaxy)
        EndChan = ThisCube.closest_spectral_channel(V0 + Vgalaxy)

        if maskfile is not None:
            maskLookup = buildMaskLookup(maskfile)
            shp = ThisCube.shape
            TmpCube = ThisCube.with_spectral_unit(u.Hz)
            spaxis = TmpCube.spectral_axis
            spaxis = spaxis.value
            data = ThisCube.filled_data[:].value
            for y in np.arange(shp[1]):
                for x in np.arange(shp[2]):
                    spectrum = data[:, y, x]
                    if np.any(np.isnan(spectrum)):
                        continue
                    coords = ThisCube.world[:, y, x]
                    mask = maskLookup(coords[2].value, coords[1].value, spaxis)
                    spectrum = robustBaseline(spectrum,
                                              blorder=blorder,
                                              baselineIndex=~mask)
                    data[:, y, x] = spectrum
            ThisCube = SpectralCube(data * ThisCube.unit,
                                    ThisCube.wcs,
                                    header=ThisCube.header,
                                    meta={'BUNIT': ThisCube.header['BUNIT']})
            ThisCube.write(Galaxy + '_' + ThisLine +
                           '_rebase{0}.fits'.format(blorder),
                           overwrite=True)
        else:
            gbtpipe.Baseline.rebaseline(Galaxy + '_' + ThisLine + '.fits',
                                        baselineRegion=[
                                            slice(0, StartChan, 1),
                                            slice(EndChan, ThisCube.shape[0],
                                                  1)
                                        ],
                                        blorder=blorder)
        ThisCube = SpectralCube.read(Galaxy + '_' + ThisLine +
                                     '_rebase{0}'.format(blorder) + '.fits')
        # Smooth
        Kern = Kernel1D(array=np.array([0.5, 1.0, 0.5]))
        for i in range(HanningLoops):
            ThisCube.spectral_smooth(Kern)
            ThisCube = ThisCube[::2, :, :]

        # Spatial Smooth
        if spatialSmooth > 1.0:
            newBeam = Beam(major=ThisCube.beam.major * spatialSmooth,
                           minor=ThisCube.beam.minor * spatialSmooth)
            ThisCube.convolve_to(newBeam)
            smoothstr = '_smooth{0}'.format(spatialSmooth)
        else:
            smoothstr = ''

        # Final Writeout
        ThisCube.write(Galaxy + '_' + ThisLine + '_rebase{0}'.format(blorder) +
                       smoothstr + '_hanning{0}.fits'.format(HanningLoops),
                       overwrite=True)
Example #3
0
def convolve_cube(incube,
                  newbeam,
                  mode='datacube',
                  res_tol=0.0,
                  min_coverage=0.8,
                  nan_treatment='fill',
                  boundary='fill',
                  fill_value=0.,
                  append_raw=False,
                  verbose=False,
                  suppress_error=False):
    """
    Convolve a spectral cube or an rms noise cube to a specified beam.

    'datacube' mode: This is basically a wrapper around
    `~spectral_cube.SpectralCube.convolve_to()`,
    but it treats NaN values / edge effect in a more careful way
    (see the description of keyword 'min_coverage' below).

    'noisecube' mode: It handles rms noise cubes in a way that it
    correctly predicts the rms noise for the corresponding data cube
    convolved to the same specified beam.

    Parameters
    ----------
    incube : FITS HDU object or SpectralCube object
        Input spectral cube
    newbeam : radio_beam.Beam object
        Target beam to convolve to
    mode : {'datacube', 'noisecube'}, optional
        Whether the input cube is a data cube or an rms noise cube.
        In the former case, a direct convolution is performed;
        in the latter case, the convolution attempts to mimic the
        error propagation process to the specified lower resolution.
        (Default: 'datacube')
    res_tol : float, optional
        Tolerance on the difference between input/output resolution
        By default, a convolution is performed on the input cube
        when its native resolution is different from (sharper than)
        the target resolution. Use this keyword to specify a tolerance
        on resolution, within which no convolution will be performed.
        For example, ``res_tol=0.1`` will allow a 10% tolerance.
    min_coverage : float or None, optional
        This keyword specifies a minimum beam covering fraction of
        valid pixels for convolution (Default: 0.8).
        Locations with a beam covering fraction less than this value
        will be overwritten to "NaN" in the convolved cube.
        If the user would rather use the ``preserve_nan`` mode in
        `astropy.convolution.convolve_fft`, set this keyword to None.
    nan_treatment: {'interpolate', 'fill'}, optional
        To be passed to `astropy.convolution.convolve_fft`.
        (Default: 'fill')
    boundary: {'fill', 'wrap'}, optional
        To be passed to `astropy.convolution.convolve_fft`.
        (Default: 'fill')
    fill_value : float, optional
        To be passed to `astropy.convolution.convolve_fft`.
        (Default: 0)
    append_raw : bool, optional
        Whether to append the raw convolved cube and weight cube.
        Default is not to append.
    verbose : bool, optional
        Whether to print the detailed processing log in terminal.
        Default is to not print.
    suppress_error : bool, optional
        Whether to suppress the error message when convolution is
        unsuccessful. Default is to not suppress.

    Returns
    -------
    outcube : FITS HDU objects or SpectralCube objects
        Convolved spectral cubes (when append_raw=False), or a 3-tuple
        including a masked verson, an unmaked version, and a coverage
        fraction cube (when append_raw=True).
        The output will be the same type of objects as the input.
    """

    if isinstance(incube, SpectralCube):
        cube = incube
    elif isinstance(incube, (fits.PrimaryHDU, fits.ImageHDU)):
        if 'BUNIT' in incube.header:
            unit = u.Unit(incube.header['BUNIT'], parse_strict='warn')
            if isinstance(unit, u.UnrecognizedUnit):
                unit = u.dimensionless_unscaled
        else:
            unit = u.dimensionless_unscaled
        cube = SpectralCube(data=incube.data * unit,
                            wcs=WCS(incube.header),
                            header=incube.header,
                            allow_huge_operations=True).with_mask(
                                np.isfinite(incube.data))
    else:
        raise ValueError("`incube` needs to be either a SpectralCube object "
                         "or a FITS HDU object")

    if (res_tol > 0) and (newbeam.major != newbeam.minor):
        raise ValueError("Cannot handle a non-zero resolution torelance "
                         "when the target beam is not round")

    if min_coverage is None:
        # Skip coverage check and preserve NaN values.
        # This uses the default 'preserve_nan' scheme
        # implemented in 'astropy.convolution.convolve_fft'
        convolve_func = partial(convolve_fft,
                                fill_value=fill_value,
                                nan_treatment=nan_treatment,
                                boundary=boundary,
                                preserve_nan=True,
                                allow_huge=True)
    else:
        # Do coverage check to determine the mask on the output
        convolve_func = partial(convolve_fft,
                                fill_value=fill_value,
                                nan_treatment=nan_treatment,
                                boundary=boundary,
                                allow_huge=True)
    convolve_func_w = partial(convolve_fft,
                              fill_value=0.,
                              boundary='fill',
                              allow_huge=True)

    tol = newbeam.major * np.array([1 - res_tol, 1 + res_tol])
    if ((tol[0] < cube.beam.major < tol[1])
            and (tol[0] < cube.beam.minor < tol[1])):
        if verbose:
            print("Native resolution within tolerance - "
                  "copying original cube...")
        my_append_raw = False
        convcube = wtcube = None
        newcube = cube.unmasked_copy().with_mask(cube.mask.include())
    else:
        if verbose:
            print("Deconvolving beam...")
        try:
            beamdiff = newbeam.deconvolve(cube.beam)
        except ValueError as err:
            if suppress_error:
                if verbose:
                    print("Unsuccessful beam deconvolution: "
                          "{}\nOld: {}\nNew: {}"
                          "".format(err, cube.beam, newbeam))
                    print("Exiting...")
                return
            else:
                raise ValueError("Unsuccessful beam deconvolution: "
                                 "{}\nOld: {}\nNew: {}"
                                 "".format(err, cube.beam, newbeam))
        if verbose:
            print("Convolving cube...")
        if mode == 'datacube':
            # do convolution
            convcube = cube.convolve_to(newbeam, convolve=convolve_func)
            if min_coverage is not None:
                my_append_raw = True
                wtcube = SpectralCube(cube.mask.include().astype('float'),
                                      cube.wcs,
                                      beam=cube.beam).with_mask(
                                          np.ones(cube.shape).astype('?'))
                wtcube.allow_huge_operations = (cube.allow_huge_operations)
                wtcube = wtcube.convolve_to(newbeam, convolve=convolve_func_w)
                # divide the raw convolved image by the weight image
                # to correct for filling fraction
                newcube = convcube / wtcube.unmasked_data[:]
                # mask all pixels w/ weight smaller than min_coverage
                threshold = min_coverage * u.dimensionless_unscaled
                newcube = newcube.with_mask(wtcube >= threshold)
            else:
                my_append_raw = False
                newcube = convcube
                wtcube = None
        elif mode == 'noisecube':
            # Empirically derive a noise cube at the lower resolution
            # Step 1: square the high resolution noise cube
            cubesq = cube**2
            # Step 2: convolve the squared noise cube with a kernel
            #         that is sqrt(2) times narrower than the one
            #         used for data cube convolution (this is because
            #         the Gaussian weight needs to be squared in
            #         error propagation)
            beamdiff_small = Beam(major=beamdiff.major / np.sqrt(2),
                                  minor=beamdiff.minor / np.sqrt(2),
                                  pa=beamdiff.pa)
            newbeam_small = cube.beam.convolve(beamdiff_small)
            convcubesq = cubesq.convolve_to(newbeam_small,
                                            convolve=convolve_func)
            del cubesq  # release memory
            if min_coverage is not None:
                my_append_raw = True
                wtcube = SpectralCube(cube.mask.include().astype('float'),
                                      cube.wcs,
                                      beam=cube.beam).with_mask(
                                          np.ones(cube.shape).astype('?'))
                wtcube.allow_huge_operations = (cube.allow_huge_operations)
                # divide the raw convolved cube by the weight cube
                # to correct for filling fraction
                wtcube_d = wtcube.convolve_to(newbeam_small,
                                              convolve=convolve_func_w)
                newcubesq = convcubesq / wtcube_d.unmasked_data[:]
                del convcubesq, wtcube_d  # release memory
                # mask all pixels w/ weight smaller than min_coverage
                # (here I force the masking of the noise cube to be
                #  consistent with that of the data cube)
                wtcube = wtcube.convolve_to(newbeam, convolve=convolve_func_w)
                threshold = min_coverage * u.dimensionless_unscaled
                newcubesq = newcubesq.with_mask(wtcube >= threshold)
            else:
                my_append_raw = False
                newcubesq = convcubesq
                wtcube = None
            # Step 3: find the sqrt of the convolved noise cube
            convcube = np.sqrt(convcubesq)
            del convcubesq  # release memory
            newcube = np.sqrt(newcubesq)
            del newcubesq  # release memory
            # Step 4: apply a multiplicative factor, which accounts
            #         for the decrease in rms noise due to averaging
            convcube *= np.sqrt(cube.beam.sr / newbeam.sr).to('').value
            newcube *= np.sqrt(cube.beam.sr / newbeam.sr).to('').value
        else:
            raise ValueError("Invalid `mode` value: {}".format(mode))

    if isinstance(incube, SpectralCube):
        if append_raw and my_append_raw:
            return newcube, convcube, wtcube
        else:
            return newcube
    elif isinstance(incube, (fits.PrimaryHDU, fits.ImageHDU)):
        if append_raw and my_append_raw:
            return newcube.hdu, convcube.hdu, wtcube.hdu
        else:
            return newcube.hdu
def smooth_cube(
        incube=None,
        outfile=None,
        angular_resolution=None,
        linear_resolution=None,
        distance=None,
        velocity_resolution=None,
        nan_treatment='interpolate', # can also be 'fill'
        tol=None,
        make_coverage_cube=False,
        collapse_coverage=False,
        coveragefile=None,
        coverage2dfile=None,
        dtype=np.float32,
        overwrite=True
    ):
    """
    Smooth an input cube to coarser angular or spectral
    resolution. This lightly wraps spectral cube and some of the error
    checking is left to that.

    tol is a fraction. When the target beam is within tol of the
    original beam, we just copy.

    Optionally, also calculate a coverage footprint in which original
    (finite) cube coverage starts at 1.0 and the output cube shows the
    fraction of finite pixels.
    """

    # &%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%
    # Error checking
    # &%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%

    # Require a valid cube or map input
    if type(incube) is SpectralCube:
        cube = incube
    elif type(incube) == type("hello"):
        cube = SpectralCube.read(incube)
    else:
        logger.error("Input must be a SpectralCube object or a filename.")

    # Allow huge operations. If the speed or segfaults become a huge
    # problem, we will adjust our strategy here.

    cube.allow_huge_operations = True

    # Check that only one target scale is set
    if (angular_resolution is not None) and (linear_resolution is not None):
        logger.error('Only one of angular_resolution or ',
                     'linear_resolution can be set')
        return(None)

    # Work out the target angular resolution
    if angular_resolution is not None:
        if type(angular_resolution) is str:
            angular_resolution = u.Quantity(angular_resolution)

    if linear_resolution is not None:
        if distance is None:
            logger.error('Convolution to linear resolution requires a distance.')
            return(None)

        if type(distance) is str:
            distance = u.Quantity(distance)
        if type(linear_resolution) is str:
            linear_resolution = u.Quantity(linear_resolution)
        angular_resolution = (linear_resolution / distance * u.rad).to(u.arcsec)
        dist_mpc_val = float(distance.to(u.pc).value) / 1e6
        cube._header.append(('DIST_MPC',dist_mpc_val,'Used in convolution'))

    if tol is None:
        tol = 0.0

    # &%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%
    # Convolution to coarser beam
    # &%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%
    
    if angular_resolution is not None:
        logger.info("... convolving from beam: "+str(cube.beam))
        target_beam = Beam(major=angular_resolution,
                           minor=angular_resolution,
                           pa=0 * u.deg)
        logger.info("... convolving to beam: "+str(target_beam))

        new_major = float(target_beam.major.to(u.arcsec).value)
        old_major = float(cube.beam.major.to(u.arcsec).value)        
        delta = (new_major-old_major)/old_major

        logger.info("... fractional change: "+str(delta))
        
        if make_coverage_cube:
            coverage = SpectralCube(np.isfinite(cube.unmasked_data[:])*1.0,
                                    wcs=cube.wcs,
                                    header=cube.header,
                                    meta={'BUNIT': ' ', 'BTYPE': 'Coverage'})
            coverage = coverage.with_mask(LazyMask(np.isfinite,cube=coverage))
            
            # Allow huge operations. If the speed or segfaults become a huge
            # problem, we will adjust our strategy here.

            coverage.allow_huge_operations = True

        if delta > tol:
            logger.info("... proceeding with convolution.")
            cube = cube.convolve_to(target_beam,
                                    nan_treatment=nan_treatment)
            if make_coverage_cube:
                coverage = coverage.convolve_to(target_beam,
                                                nan_treatment=nan_treatment)

        if np.abs(delta) < tol:
            logger.info("... current resolution meets tolerance.")

        if delta < -1.0*tol:
            logger.info("... resolution cannot be matched. Returning")
            return(None)

    # &%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%
    # Spectral convolution
    # &%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%

    # This is only a boxcar smooth right now and does not downsample
    # or update the header.

    if velocity_resolution is not None:
        if type(velocity_resolution) is str:
            velocity_resolution = u.Quantity(velocity_resolution)

        dv = scdr.channel_width(cube)
        nChan = (velocity_resolution / dv).to(u.dimensionless_unscaled).value
        if nChan > 1:
            cube = cube.spectral_smooth(Box1DKernel(nChan))
            if make_coverage_cube:
                coverage = coverage.spectral_smooth(Box1DKernel(nChan))

    # &%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%
    # Write or return as requested
    # &%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%&%

    if outfile is not None:
        # cube.write(outfile, overwrite=overwrite)
        hdu = fits.PrimaryHDU(np.array(cube.filled_data[:], dtype=dtype),
                              header=cube.header)
        hdu.writeto(outfile, overwrite=overwrite)
        if make_coverage_cube:
            if coveragefile is not None:
                hdu = fits.PrimaryHDU(np.array(coverage.filled_data[:], dtype=dtype),
                                      header=coverage.header)
                hdu.writeto(coveragefile, overwrite=overwrite)
            if collapse_coverage:
                if coveragefile and not coverage2dfile:
                    coverage2dfile = coveragefile.replace('.fits','2d.fits')
                coverage_collapser(coverage,
                                   coverage2dfile=coverage2dfile,
                                   overwrite=overwrite)
                # coverage.write(coveragefile, overwrite=overwrite)

    return(cube)
Example #5
0
def common_cube(filename,
                target_rms=None,
                target_resolution=None,
                datadir='',
                outdir='',
                outname='common.fits',
                cube=None,
                rmsname=None,
                rmsmap=None,
                distance=None,
                overwrite=True,
                return_cube=False,
                huge=True):
    """
        Convolves and adds noise to a data cube to arrive at 
        a target linear resolution and noise level
        
        Parameters:
        
        filename : str
            File name of spectral data cube to process
    
        outname : str
            File name for output cube
            
        target_rms : astropy.Quantity
            Target RMS level in units of cube brightness
            
        target_resolution : astropy.Quantity
            Target resolution expressed as either length (linear 
            resolution) or angle (angular resolution) 
            
        datadir : str
            Directory for input data
        
        outdir : str
            Directory for output data
        
        rmsname : str
            Name for input RMS data cube
        
        rmsmap : np.ndarray
            numpy array of noise values at positions corresponding to 
            the spectral cube.
        
        distance : astropy.Quantity
            Distance to target; required if a linear target 
            resolution is requested
            
        overwrite : bool
            Overwrite output products if they already exist?
        
        return_cube : bool
            Return a SpectralCube?
        """

    if cube is None:
        cube = SpectralCube.read(os.path.join(datadir, filename))

    cube.allow_huge_operations = huge
    orig_beam_area = cube.beam.sr
    target_beam = None
    if target_resolution is not None:
        if target_resolution.unit.is_equivalent(u.pc):
            if distance is None:
                warnings.warn(
                    "A distance with units must be provided to reach a target linear resolution"
                )
                raise ValueError

            target_beam_size = (target_resolution / distance).to(
                u.dimensionless_unscaled) * u.rad
        elif target_resolution.is_equivalent(u.rad):
            target_beam_size = target_resolution
        else:
            warnings.warn(
                "target_resolution must be either linear distance or angle")
            raise ValueError

        target_beam = Beam(major=target_beam_size,
                           minor=target_beam_size,
                           pa=0 * u.deg)
        if target_beam > cube.beam:
            cube = cube.convolve_to(target_beam)
        else:
            warnings.warn('Target linear resolution unreachable.')

    if target_rms is not None:
        if target_beam is None:
            target_beam = cube.beam
        noisevals = np.random.randn(*cube.shape)
        null_beam = Beam(major=1e-7 * u.deg, minor=1e-7 * u.deg, pa=0 * u.deg)
        noisevals[~np.isfinite(cube)] = np.nan
        noisecube = SpectralCube(noisevals,
                                 cube.wcs,
                                 header=cube.header,
                                 beam=null_beam,
                                 meta={'BUNIT': 'K'})
        noisecube = noisecube.with_mask(np.isfinite(cube))
        noisecube.allow_huge_operations = huge
        area_ratio = (orig_beam_area / target_beam.sr).to(
            u.dimensionless_unscaled).value
        noisecube = noisecube.convolve_to(target_beam)

        if (rmsname is not None) and (rmsmap is None):
            rmsmap = fits.getdata(os.path.join(datadir, rmsname)) * u.K
        if rmsmap is None:
            warnings.warn(
                "One of rmsmap or rmsname must be provided for noise homogenization"
            )
            raise FileNotFoundError

        rmsamplitude = rmsmap * area_ratio**0.5
        if np.all(rmsamplitude > target_rms):
            warnings.warn("All noise values larger than target value")
        else:
            addamplitude = (target_rms**2 - rmsamplitude**2)
            addamplitude[addamplitude < 0] = 0.0
            addamplitude.shape = addamplitude.shape
            noisevals = noisecube.filled_data[:]
            noisevals /= np.nanstd(noisevals)
            noisevals *= (addamplitude**0.5)
            cube.allow_huge_operations = huge
            cube += noisevals
    if type(outname) is str:
        cube.write(outdir + outname, overwrite=overwrite)
    if return_cube:
        return (cube)
    else:
        return (True)