Example #1
0
def pixelwise_peak_fitting(FDF,
                           phiArr,
                           fwhmRMSF,
                           lamSqArr_m2,
                           lam0Sq,
                           product_list,
                           noiseArr=None,
                           stokesIcube=None):
    """
    Performs the 1D FDF peak fitting used in RMsynth/RMclean_1D, pixelwise on
    all pixels in a 3D FDF cube.

    Inputs: 
        FDF: FDF cube (3D array). This is assumed to be in astropy axis ordering
            (Phi, dec, ra)
        phiArr: (1D) array of phi values
        fwhmRMSF: 2D array of RMSF FWHM values
        lamSqArr_m2: 1D array of channel lambda^2 values.
        lam0Sq: scalar value for lambda^2_0, the reference wavelength squared.
        product_list: list containing the names of the fitting products to save.
        dFDF: 2D array of theoretical noise values. If not supplied, the 
            peak fitting will default to using the measured noise.
    Outputs: dictionary of 2D maps, 1 per fit output
    """

    #FDF: output by synth3d or clean3d
    #phiArr: can be generated from FDF cube
    #fwhm: 2D map produced synth3D
    #dFDFth: not currently produced (default mode not to input noise!)
    #   If not present, measure_FDF_parms uses the corMAD noise.
    #
    #lamSqArr is only needed for computing errors in derotated angles
    #   This could be compressed to a map or single value from RMsynth?
    #lam0Sq is necessary for de-rotation

    map_size = FDF.shape[1:]

    #Create pixel location arrays:
    xarr, yarr = np.meshgrid(range(map_size[0]), range(map_size[1]))
    xarr = xarr.ravel()
    yarr = yarr.ravel()

    #Create empty maps:
    map_dict = {}
    for parameter in product_list:
        map_dict[parameter] = np.zeros(map_size)

    freqArr_Hz = C / np.sqrt(lamSqArr_m2)
    freq0_Hz = C / np.sqrt(lam0Sq)
    if stokesIcube is not None:
        idx = np.abs(freqArr_Hz - freq0_Hz).argmin()
        if freqArr_Hz[idx] < freq0_Hz:
            Ifreq0Arr = interp_images(stokesIcube[idx, :, :],
                                      stokesIcube[idx + 1, :, :],
                                      f=0.5)
        elif freqArr_Hz[idx] > freq0_Hz:
            Ifreq0Arr = interp_images(stokesIcube[idx - 1, :, :],
                                      stokesIcube[idx, :, :],
                                      f=0.5)
        else:
            Ifreq0Arr = stokesIcube[idx, :, :]
    else:
        Ifreq0Arr = np.ones(map_size)
        stokesIcube = np.ones((freqArr_Hz.size, map_size[0], map_size[1]))

    #compute weights if needed:
    if noiseArr is not None:
        weightArr = 1.0 / np.power(noiseArr, 2.0)
        weightArr = np.where(np.isnan(weightArr), 0.0, weightArr)
        dFDF = Ifreq0Arr * np.sqrt(
            np.sum(weightArr**2 * np.nan_to_num(noiseArr)**2) /
            (np.sum(weightArr))**2)

    else:
        weightArr = np.ones(lamSqArr_m2.shape, dtype=np.float32)
        dFDF = None

    #Run fitting pixel-wise:
    progress(40, 0)
    for i in range(xarr.size):
        FDF_pix = FDF[:, xarr[i], yarr[i]]
        fwhmRMSF_pix = fwhmRMSF[xarr[i], yarr[i]]
        if type(dFDF) == type(None):
            dFDF_pix = None
        else:
            dFDF_pix = dFDF[xarr[i], yarr[i]]
        try:
            mDict = measure_FDF_parms(FDF_pix,
                                      phiArr,
                                      fwhmRMSF_pix,
                                      dFDF=dFDF_pix,
                                      lamSqArr_m2=lamSqArr_m2,
                                      lam0Sq=lam0Sq,
                                      snrDoBiasCorrect=5.0)
            #Add keywords not included by the above function:
            mDict['lam0Sq_m2'] = lam0Sq
            mDict['freq0_Hz'] = freq0_Hz
            mDict['fwhmRMSF'] = fwhmRMSF_pix
            mDict['Ifreq0'] = Ifreq0Arr[xarr[i], yarr[i]]
            mDict['fracPol'] = mDict["ampPeakPIfit"] / mDict['Ifreq0']
            mDict["min_freq"] = float(np.min(freqArr_Hz))
            mDict["max_freq"] = float(np.max(freqArr_Hz))
            mDict["N_channels"] = lamSqArr_m2.size
            mDict["median_channel_width"] = float(
                np.median(np.diff(freqArr_Hz)))
            if dFDF_pix is not None:
                mDict['dFDFth'] = dFDF_pix
            else:
                mDict['dFDFth'] = np.nan
            for parameter in product_list:
                map_dict[parameter][xarr[i], yarr[i]] = mDict[parameter]
        except:
            for parameter in product_list:
                map_dict[parameter][xarr[i], yarr[i]] = np.nan

        if i % 100 == 0:
            progress(40, i / xarr.size * 100)
    return map_dict
Example #2
0
def run_rmsynth(dataQ,
                dataU,
                freqArr_Hz,
                dataI=None,
                rmsArr=None,
                phiMax_radm2=None,
                dPhi_radm2=None,
                nSamples=10.0,
                weightType="uniform",
                fitRMSF=False,
                nBits=32,
                verbose=True,
                not_rmsf=False,
                log=print):
    """Run RM-synthesis on 2/3D data.

    Args:
      dataQ (array_like): Stokes Q intensity cube.
      dataU (array_like): Stokes U intensity cube.
      freqArr_Hz (array_like): Frequency of each channel in Hz.

    Kwargs:
        dataI (array_like): Model cube of Stokes I spectra (see do_fitIcube).
        rmsArr (array_like): Cube of RMS spectra.
        phiMax_radm2 (float): Maximum absolute Faraday depth (rad/m^2).
        dPhi_radm2 (float): Faraday depth channel size (rad/m^2).
        nSamples (float): Number of samples across the RMSF.
           weightType (str): Can be "variance" or "uniform"
            "variance" -- Weight by RMS.
            "uniform" -- Weight uniformly (i.e. with 1s)
        fitRMSF (bool): Fit a Gaussian to the RMSF?
        nBits (int): Precision of floating point numbers.
        verbose (bool): Verbosity.
        not_rmsf (bool): Just do RM synthesis and ignore RMSF?
        log (function): Which logging function to use.

    Returns:
      dataArr (list): FDF and RMSF information
        if not_rmsf:
            dataArr = [FDFcube, phiArr_radm2, lam0Sq_m2, lambdaSqArr_m2]

        else:
            dataArr = [FDFcube, phiArr_radm2, RMSFcube, phi2Arr_radm2, fwhmRMSFCube,fitStatArr, lam0Sq_m2, lambdaSqArr_m2]


    """
    # Sanity check on header dimensions

    if not str(dataQ.shape) == str(dataU.shape):
        log("Err: unequal dimensions: Q = " + str(dataQ.shape) + ", U = " +
            str(dataU.shape) + ".")
        sys.exit()

    # Check dimensions of Stokes I cube, if present
    if not dataI is None:
        if not str(dataI.shape) == str(dataQ.shape):
            log("Err: unequal dimensions: Q = " + str(dataQ.shape) + ", I = " +
                str(dataI.shape) + ".")
            sys.exit()

    # Default data types
    dtFloat = "float" + str(nBits)
    dtComplex = "complex" + str(2 * nBits)

    lambdaSqArr_m2 = np.power(C / freqArr_Hz, 2.0)

    dFreq_Hz = np.nanmin(np.abs(np.diff(freqArr_Hz)))
    lambdaSqRange_m2 = (np.nanmax(lambdaSqArr_m2) - np.nanmin(lambdaSqArr_m2))
    dLambdaSqMin_m2 = np.nanmin(np.abs(np.diff(lambdaSqArr_m2)))
    dLambdaSqMax_m2 = np.nanmax(np.abs(np.diff(lambdaSqArr_m2)))

    # Set the Faraday depth range
    fwhmRMSF_radm2 = 2.0 * m.sqrt(3.0) / lambdaSqRange_m2
    if dPhi_radm2 is None:
        dPhi_radm2 = fwhmRMSF_radm2 / nSamples
    if phiMax_radm2 is None:
        phiMax_radm2 = m.sqrt(3.0) / dLambdaSqMax_m2
        phiMax_radm2 = max(phiMax_radm2, 600.0)  # Force the minimum phiMax

    # Faraday depth sampling. Zero always centred on middle channel
    nChanRM = round(abs((phiMax_radm2 - 0.0) / dPhi_radm2)) * 2.0 + 1.0
    startPhi_radm2 = -(nChanRM - 1.0) * dPhi_radm2 / 2.0
    stopPhi_radm2 = +(nChanRM - 1.0) * dPhi_radm2 / 2.0
    phiArr_radm2 = np.linspace(startPhi_radm2, stopPhi_radm2, nChanRM)
    phiArr_radm2 = phiArr_radm2.astype(dtFloat)
    if (verbose):
        log("PhiArr = %.2f to %.2f by %.2f (%d chans)." %
            (phiArr_radm2[0], phiArr_radm2[-1], float(dPhi_radm2), nChanRM))

    # Calculate the weighting as 1/sigma^2 or all 1s (uniform)
    if weightType == "variance" and rmsArr is not None:
        weightArr = 1.0 / np.power(rmsArr, 2.0)
    else:
        weightType = "uniform"
        weightArr = np.ones(freqArr_Hz.shape, dtype=dtFloat)
    if (verbose): log("Weight type is '%s'." % weightType)

    startTime = time.time()

    # Read the Stokes I model and divide into the Q & U data
    if dataI is not None:
        with np.errstate(divide='ignore', invalid='ignore'):
            qArr = np.true_divide(dataQ, dataI)
            uArr = np.true_divide(dataU, dataI)
    else:
        qArr = dataQ
        uArr = dataU

    # Perform RM-synthesis on the cube
    FDFcube, lam0Sq_m2 = do_rmsynth_planes(dataQ=qArr,
                                           dataU=uArr,
                                           lambdaSqArr_m2=lambdaSqArr_m2,
                                           phiArr_radm2=phiArr_radm2,
                                           weightArr=weightArr,
                                           nBits=32,
                                           verbose=verbose)
    # Calculate the Rotation Measure Spread Function cube
    if not_rmsf is not True:
        RMSFcube, phi2Arr_radm2, fwhmRMSFCube, fitStatArr = \
            get_rmsf_planes(lambdaSqArr_m2   = lambdaSqArr_m2,
                            phiArr_radm2     = phiArr_radm2,
                            weightArr        = weightArr,
                            mskArr           = ~np.isfinite(dataQ),
                            lam0Sq_m2        = lam0Sq_m2,
                            double           = True,
                            fitRMSF          = fitRMSF,
                            fitRMSFreal      = False,
                            nBits            = 32,
                            verbose          = verbose,
                            log              = log)
    endTime = time.time()
    cputime = (endTime - startTime)
    if (verbose): log("> RM-synthesis completed in %.2f seconds." % cputime)

    # Determine the Stokes I value at lam0Sq_m2 from the Stokes I model
    # Note: the Stokes I model MUST be continuous throughout the cube,
    # i.e., no NaNs as the amplitude at freq0_Hz is interpolated from the
    # nearest two planes.
    freq0_Hz = C / m.sqrt(lam0Sq_m2)
    if dataI is not None:
        idx = np.abs(freqArr_Hz - freq0_Hz).argmin()
        if freqArr_Hz[idx] < freq0_Hz:
            Ifreq0Arr = interp_images(dataI[idx, :, :],
                                      dataI[idx + 1, :, :],
                                      f=0.5)
        elif freqArr_Hz[idx] > freq0_Hz:
            Ifreq0Arr = interp_images(dataI[idx - 1, :, :],
                                      dataI[idx, :, :],
                                      f=0.5)
        else:
            Ifreq0Arr = dataI[idx, :, :]

        # Multiply the dirty FDF by Ifreq0 to recover the PI
        FDFcube *= Ifreq0Arr

    if not_rmsf:
        dataArr = [FDFcube, phiArr_radm2, lam0Sq_m2, lambdaSqArr_m2]

    else:
        dataArr = [
            FDFcube, phiArr_radm2, RMSFcube, phi2Arr_radm2, fwhmRMSFCube,
            fitStatArr, lam0Sq_m2, lambdaSqArr_m2
        ]

    return dataArr
Example #3
0
def run_rmsynth(dataQ,
                dataU,
                freqArr_Hz,
                headtemplate,
                dataI=None,
                rmsArr=None,
                phiMax_radm2=None,
                dPhi_radm2=None,
                nSamples=10.0,
                weightType="uniform",
                prefixOut="",
                outDir="",
                fitRMSF=False,
                nBits=32,
                write_seperate_FDF=False,
                verbose=True,
                not_rmsf=True,
                log=print):
    """Read the Q & U data and run RM-synthesis."""
    # Sanity check on header dimensions

    if not str(dataQ.shape) == str(dataU.shape):
        log("Err: unequal dimensions: Q = " + str(dataQ.shape) + ", U = " +
            str(dataU.shape) + ".")
        sys.exit()

    # Check dimensions of Stokes I cube, if present
    if not dataI is None:
        if not str(dataI.shape) == str(dataQ.shape):
            log("Err: unequal dimensions: Q = " + str(dataQ.shape) + ", I = " +
                str(dataI.shape) + ".")
            sys.exit()

    # Default data types
    dtFloat = "float" + str(nBits)
    dtComplex = "complex" + str(2 * nBits)

    lambdaSqArr_m2 = np.power(C / freqArr_Hz, 2.0)

    dFreq_Hz = np.nanmin(np.abs(np.diff(freqArr_Hz)))
    lambdaSqRange_m2 = (np.nanmax(lambdaSqArr_m2) - np.nanmin(lambdaSqArr_m2))
    dLambdaSqMin_m2 = np.nanmin(np.abs(np.diff(lambdaSqArr_m2)))
    dLambdaSqMax_m2 = np.nanmax(np.abs(np.diff(lambdaSqArr_m2)))

    # Set the Faraday depth range
    fwhmRMSF_radm2 = 2.0 * m.sqrt(3.0) / lambdaSqRange_m2
    if dPhi_radm2 is None:
        dPhi_radm2 = fwhmRMSF_radm2 / nSamples
    if phiMax_radm2 is None:
        phiMax_radm2 = m.sqrt(3.0) / dLambdaSqMax_m2
        phiMax_radm2 = max(phiMax_radm2, 600.0)  # Force the minimum phiMax

    # Faraday depth sampling. Zero always centred on middle channel
    nChanRM = round(abs((phiMax_radm2 - 0.0) / dPhi_radm2)) * 2.0 + 1.0
    startPhi_radm2 = -(nChanRM - 1.0) * dPhi_radm2 / 2.0
    stopPhi_radm2 = +(nChanRM - 1.0) * dPhi_radm2 / 2.0
    phiArr_radm2 = np.linspace(startPhi_radm2, stopPhi_radm2, nChanRM)
    phiArr_radm2 = phiArr_radm2.astype(dtFloat)
    if (verbose):
        log("PhiArr = %.2f to %.2f by %.2f (%d chans)." %
            (phiArr_radm2[0], phiArr_radm2[-1], float(dPhi_radm2), nChanRM))

    # Calculate the weighting as 1/sigma^2 or all 1s (uniform)
    if weightType == "variance" and rmsArr is not None:
        weightArr = 1.0 / np.power(rmsArr, 2.0)
    else:
        weightType = "uniform"
        weightArr = np.ones(freqArr_Hz.shape, dtype=dtFloat)
    if (verbose): log("Weight type is '%s'." % weightType)

    startTime = time.time()

    # Read the Stokes I model and divide into the Q & U data
    if dataI is not None:
        with np.errstate(divide='ignore', invalid='ignore'):
            qArr = np.true_divide(dataQ, dataI)
            uArr = np.true_divide(dataU, dataI)
    else:
        qArr = dataQ
        uArr = dataU

    # Perform RM-synthesis on the cube
    FDFcube, lam0Sq_m2 = do_rmsynth_planes(dataQ=qArr,
                                           dataU=uArr,
                                           lambdaSqArr_m2=lambdaSqArr_m2,
                                           phiArr_radm2=phiArr_radm2,
                                           weightArr=weightArr,
                                           nBits=32,
                                           verbose=verbose)
    # Calculate the Rotation Measure Spread Function cube
    if not_rmsf is not True:
        RMSFcube, phi2Arr_radm2, fwhmRMSFCube, fitStatArr = \
            get_rmsf_planes(lambdaSqArr_m2   = lambdaSqArr_m2,
                            phiArr_radm2     = phiArr_radm2,
                            weightArr        = weightArr,
                            mskArr           = ~np.isfinite(dataQ),
                            lam0Sq_m2        = lam0Sq_m2,
                            double           = True,
                            fitRMSF          = fitRMSF,
                            fitRMSFreal      = False,
                            nBits            = 32,
                            verbose          = verbose,
                            log              = log)
    endTime = time.time()
    cputime = (endTime - startTime)
    if (verbose): log("> RM-synthesis completed in %.2f seconds." % cputime)
    if (verbose): log("Saving the dirty FDF, RMSF and ancillary FITS files.")

    # Determine the Stokes I value at lam0Sq_m2 from the Stokes I model
    # Note: the Stokes I model MUST be continuous throughout the cube,
    # i.e., no NaNs as the amplitude at freq0_Hz is interpolated from the
    # nearest two planes.
    freq0_Hz = C / m.sqrt(lam0Sq_m2)
    if dataI is not None:
        idx = np.abs(freqArr_Hz - freq0_Hz).argmin()
        if freqArr_Hz[idx] < freq0_Hz:
            Ifreq0Arr = interp_images(dataI[idx, :, :],
                                      dataI[idx + 1, :, :],
                                      f=0.5)
        elif freqArr_Hz[idx] > freq0_Hz:
            Ifreq0Arr = interp_images(dataI[idx - 1, :, :],
                                      dataI[idx, :, :],
                                      f=0.5)
        else:
            Ifreq0Arr = dataI[idx, :, :]

        # Multiply the dirty FDF by Ifreq0 to recover the PI
        FDFcube *= Ifreq0Arr

    # Make a copy of the Q header and alter frequency-axis as Faraday depth
    header = headtemplate.copy()
    Ndim = header['NAXIS']
    freq_axis = Ndim  #If frequency axis not found, assume it's the last one.
    #Check for frequency axes. Because I don't know what different formatting
    #I might get ('FREQ' vs 'OBSFREQ' vs 'Freq' vs 'Frequency'), convert to
    #all caps and check for 'FREQ' anywhere in the axis name.
    for i in range(1, Ndim + 1):
        try:
            if 'FREQ' in header['CTYPE' + str(i)].upper():
                freq_axis = i
        except:
            pass  #The try statement is needed for if the FITS header does not
            # have CTYPE keywords.

    header["NAXIS" + str(freq_axis)] = phiArr_radm2.size
    header["CTYPE" + str(freq_axis)] = "FARADAY DEPTH"
    header["CDELT" + str(freq_axis)] = np.diff(phiArr_radm2)[0]
    header["CRPIX" + str(freq_axis)] = 1.0
    header["CRVAL" + str(freq_axis)] = phiArr_radm2[0]
    header["CUNIT" + str(freq_axis)] = "rad/m^2"
    header["LAMSQ0"] = (lam0Sq_m2, 'Lambda^2_0, in m^2')
    if "DATAMAX" in header:
        del header["DATAMAX"]
    if "DATAMIN" in header:
        del header["DATAMIN"]

    if outDir == '':  #To prevent code breaking if file is in current directory
        outDir = '.'

    #Re-add any initially removed degenerate axes (to match with FITS header)
    #NOTE THIS HAS NOT BEEN RIGOROUSLY TESTED!!!
    output_axes = []
    for i in range(1, Ndim + 1):
        output_axes.append(header['NAXIS' + str(i)])  #Get FITS dimensions
    del output_axes[freq_axis -
                    1]  #Remove frequency axis (since it's first in the array)
    output_axes.reverse()  #To get into numpy order.
    #Put frequency axis first, and reshape to add degenerate axes:
    FDFcube = np.reshape(FDFcube, [FDFcube.shape[0]] + output_axes)
    if not_rmsf is not True:
        RMSFcube = np.reshape(RMSFcube, [RMSFcube.shape[0]] + output_axes)

    #Move Faraday depth axis to appropriate position to match header.
    FDFcube = np.moveaxis(FDFcube, 0, Ndim - freq_axis)
    if not_rmsf is not True:
        RMSFcube = np.moveaxis(RMSFcube, 0, Ndim - freq_axis)

    if (write_seperate_FDF):
        hdu0 = pf.PrimaryHDU(FDFcube.real.astype(dtFloat), header)
        hdu1 = pf.PrimaryHDU(FDFcube.imag.astype(dtFloat), header)
        hdu2 = pf.PrimaryHDU(np.abs(FDFcube).astype(dtFloat), header)
        fitsFileOut = outDir + "/" + prefixOut + "FDF_real_dirty.fits"
        if (verbose): log("> %s" % fitsFileOut)
        hdu0.writeto(fitsFileOut, output_verify="fix", overwrite=True)

        fitsFileOut = outDir + "/" + prefixOut + "FDF_im_dirty.fits"
        if (verbose): log("> %s" % fitsFileOut)
        hdu1.writeto(fitsFileOut, output_verify="fix", overwrite=True)

        fitsFileOut = outDir + "/" + prefixOut + "FDF_tot_dirty.fits"
        if (verbose): log("> %s" % fitsFileOut)
        hdu2.writeto(fitsFileOut, output_verify="fix", overwrite=True)

    else:
        # Save the dirty FDF
        hdu0 = pf.PrimaryHDU(FDFcube.real.astype(dtFloat), header)
        hdu1 = pf.ImageHDU(FDFcube.imag.astype(dtFloat), header)
        hdu2 = pf.ImageHDU(np.abs(FDFcube).astype(dtFloat), header)
        fitsFileOut = outDir + "/" + prefixOut + "FDF_dirty.fits"
        if (verbose): log("> %s" % fitsFileOut)
        hduLst = pf.HDUList([hdu0, hdu1, hdu2])
        hduLst.writeto(fitsFileOut, output_verify="fix", overwrite=True)
        hduLst.close()

    #Header for outputs that are RM maps (peakRM, RMSF_FWHM)

    # Save the RMSF
    if not_rmsf is not True:
        header["NAXIS" + str(freq_axis)] = phi2Arr_radm2.size
        header["CRVAL" + str(freq_axis)] = phi2Arr_radm2[0]
        header["DATAMAX"] = np.max(fwhmRMSFCube) + 1
        header["DATAMIN"] = np.max(fwhmRMSFCube) - 1
        rmheader = header.copy()
        rmheader['BUNIT'] = 'rad/m^2'

        if (write_seperate_FDF):
            hdu0 = pf.PrimaryHDU(RMSFcube.real.astype(dtFloat), header)
            hdu1 = pf.PrimaryHDU(RMSFcube.imag.astype(dtFloat), header)
            hdu2 = pf.PrimaryHDU(np.abs(RMSFcube).astype(dtFloat), header)

            hdu3 = pf.PrimaryHDU(fwhmRMSFCube.astype(dtFloat), rmheader)
            fitsFileOut = outDir + "/" + prefixOut + "RMSF_real.fits"
            if (verbose): log("> %s" % fitsFileOut)
            hdu0.writeto(fitsFileOut, output_verify="fix", overwrite=True)

            fitsFileOut = outDir + "/" + prefixOut + "RMSF_im.fits"
            if (verbose): log("> %s" % fitsFileOut)
            hdu1.writeto(fitsFileOut, output_verify="fix", overwrite=True)

            fitsFileOut = outDir + "/" + prefixOut + "RMSF_tot.fits"
            if (verbose): log("> %s" % fitsFileOut)
            hdu2.writeto(fitsFileOut, output_verify="fix", overwrite=True)

            fitsFileOut = outDir + "/" + prefixOut + "RMSF_FWHM.fits"
            if (verbose): log("> %s" % fitsFileOut)
            hdu3.writeto(fitsFileOut, output_verify="fix", overwrite=True)

        else:
            fitsFileOut = outDir + "/" + prefixOut + "RMSF.fits"
            hdu0 = pf.PrimaryHDU(RMSFcube.real.astype(dtFloat), header)
            hdu1 = pf.ImageHDU(RMSFcube.imag.astype(dtFloat), header)
            hdu2 = pf.ImageHDU(np.abs(RMSFcube).astype(dtFloat), header)
            hdu3 = pf.ImageHDU(fwhmRMSFCube.astype(dtFloat), rmheader)
            hduLst = pf.HDUList([hdu0, hdu1, hdu2, hdu3])
            if (verbose): log("> %s" % fitsFileOut)
            hduLst.writeto(fitsFileOut, output_verify="fix", overwrite=True)
            hduLst.close()

    #Because there can be problems with different axes having different FITS keywords,
    #don't try to remove the FD axis, but just make it degenerate.
    header["NAXIS" + str(freq_axis)] = 1
    header["CRVAL" + str(freq_axis)] = phiArr_radm2[0]
    if "DATAMAX" in header:
        del header["DATAMAX"]
    if "DATAMIN" in header:
        del header["DATAMIN"]

    # Save a maximum polarised intensity map
    fitsFileOut = outDir + "/" + prefixOut + "FDF_maxPI.fits"
    if (verbose): log("> %s" % fitsFileOut)
    pf.writeto(fitsFileOut,
               np.max(np.abs(FDFcube), Ndim - freq_axis).astype(dtFloat),
               header,
               overwrite=True,
               output_verify="fix")

    # Save a peak RM map
    fitsFileOut = outDir + "/" + prefixOut + "FDF_peakRM.fits"
    header["BUNIT"] = "rad/m^2"
    peakFDFmap = np.argmax(np.abs(FDFcube), Ndim - freq_axis).astype(dtFloat)
    peakFDFmap = header["CRVAL" + str(freq_axis)] + (peakFDFmap + 1 - header[
        "CRPIX" + str(freq_axis)]) * header["CDELT" + str(freq_axis)]
    if (verbose): log("> %s" % fitsFileOut)
    pf.writeto(fitsFileOut,
               peakFDFmap,
               header,
               overwrite=True,
               output_verify="fix")
Example #4
0
def run_rmsynth(fitsQ,
                fitsU,
                freqFile,
                fitsI=None,
                noiseFile=None,
                phiMax_radm2=None,
                dPhi_radm2=None,
                nSamples=10.0,
                weightType="natural",
                prefixOut="",
                outDir="",
                fitRMSF=False,
                nBits=32):
    """Read the Q & U data from the given files and run RM-synthesis."""

    # Default data types
    dtFloat = "float" + str(nBits)
    dtComplex = "complex" + str(2 * nBits)

    # Sanity check on header dimensions
    headQ = pf.getheader(fitsQ, 0)
    headU = pf.getheader(fitsU, 0)
    if not headQ["NAXIS"] == headU["NAXIS"]:
        print "Err: unequal header dimensions: Q = %d, U = %d." % \
              (headQ["NAXIS"], headU["NAXIS"])
        sys.exit()
    if headQ["NAXIS"] < 3 or headQ["NAXIS"] > 4:
        print "Err: only  3 dimensions supported: D = %d." % headQ["NAXIS"]
        sys.exit()
    for i in [str(x + 1) for x in range(3)]:
        if not headQ["NAXIS" + i] == headU["NAXIS" + i]:
            print "Err: Axis %d of data are unequal: Q = %d, U = %d." % \
                  (headQ["NAXIS" + i], headU["NAXIS" + i])
            sys.exit()

    # Check dimensions of Stokes I cube, if present
    if not fitsI is None:
        headI = pf.getheader(fitsI, 0)
        if not headI["NAXIS"] == headQ["NAXIS"]:
            print "Err: unequal header dimensions: I = %d, Q = %d." % \
                  (headI["NAXIS"], headQ["NAXIS"])
            sys.exit()
        for i in [str(x + 1) for x in range(3)]:
            if not headI["NAXIS" + i] == headQ["NAXIS" + i]:
                print "Err: Axis %d of data are unequal: I = %d, Q = %d." % \
                      (headI["NAXIS" + i], headQ["NAXIS" + i])
                sys.exit()

    # Feeback
    print "The first 3 dimensions of the cubes are [X=%d, Y=%d, Z=%d]." % \
          (headQ["NAXIS1"], headQ["NAXIS2"], headQ["NAXIS3"])

    # Read the frequency vector and wavelength sampling
    freqArr_Hz = np.loadtxt(freqFile, dtype=dtFloat)
    lambdaSqArr_m2 = np.power(C / freqArr_Hz, 2.0)
    dFreq_Hz = np.nanmin(np.abs(np.diff(freqArr_Hz)))
    lambdaSqRange_m2 = (np.nanmax(lambdaSqArr_m2) - np.nanmin(lambdaSqArr_m2))
    dLambdaSqMin_m2 = np.nanmin(np.abs(np.diff(lambdaSqArr_m2)))
    dLambdaSqMax_m2 = np.nanmax(np.abs(np.diff(lambdaSqArr_m2)))

    # Set the Faraday depth range
    fwhmRMSF_radm2 = 2.0 * m.sqrt(3.0) / lambdaSqRange_m2
    if dPhi_radm2 is None:
        dPhi_radm2 = fwhmRMSF_radm2 / nSamples
    if phiMax_radm2 is None:
        phiMax_radm2 = m.sqrt(3.0) / dLambdaSqMax_m2
        phiMax_radm2 = max(phiMax_radm2, 600.0)  # Force the minimum phiMax

    # Faraday depth sampling. Zero always centred on middle channel
    nChanRM = round(abs((phiMax_radm2 - 0.0) / dPhi_radm2)) * 2.0 + 1.0
    startPhi_radm2 = -(nChanRM - 1.0) * dPhi_radm2 / 2.0
    stopPhi_radm2 = +(nChanRM - 1.0) * dPhi_radm2 / 2.0
    phiArr_radm2 = np.linspace(startPhi_radm2, stopPhi_radm2, nChanRM)
    phiArr_radm2 = phiArr_radm2.astype(dtFloat)
    print "PhiArr = %.2f to %.2f by %.2f (%d chans)." % (
        phiArr_radm2[0], phiArr_radm2[-1], float(dPhi_radm2), nChanRM)

    # Read the noise vector, if provided
    rmsArr_Jy = None
    if noiseFile is not None and os.path.exists(noiseFile):
        rmsArr_Jy = np.loadtxt(noiseFile, dtype=dtFloat)

    # Calculate the weighting as 1/sigma^2 or all 1s (natural)
    if weightType == "variance" and rmsArr_Jy is not None:
        weightArr = 1.0 / np.power(rmsArr_Jy, 2.0)
    else:
        weightType = "natural"
        weightArr = np.ones(freqArr_Hz.shape, dtype=dtFloat)
    print "Weight type is '%s'." % weightType

    # Read the Stokes data
    fQ = pf.open(fitsQ)
    fU = pf.open(fitsU)
    print "Reading Q data array ...",
    if headQ["NAXIS"] == 4:
        dataQ = fQ[0].data[0]
    else:
        dataQ = fQ[0].data
    print "done."
    print "Reading U data array ...",
    if headU["NAXIS"] == 4:
        dataU = fU[0].data[0]
    else:
        dataU = fU[0].data
    print "done."
    fQ.close()
    fU.close()

    startTime = time.time()

    # Read the Stokes I model and divide into the Q & U data
    if fitsI:
        fI = pf.open(fitsI)
        print "Reading I data array ...",
        if headI["NAXIS"] == 4:
            dataI = fI[0].data[0]
        else:
            dataI = fI[0].data
            print "done."

        with np.errstate(divide='ignore', invalid='ignore'):
            qArr = np.true_divide(dataQ, dataI)
            uArr = np.true_divide(dataU, dataI)
    else:
        qArr = dataQ
        uArr = dataU

    # Perform RM-synthesis on the cube
    FDFcube, lam0Sq_m2 = do_rmsynth_planes(dataQ=qArr,
                                           dataU=uArr,
                                           lambdaSqArr_m2=lambdaSqArr_m2,
                                           phiArr_radm2=phiArr_radm2,
                                           weightArr=weightArr,
                                           nBits=32,
                                           verbose=True)

    # Calculate the Rotation Measure Spread Function cube
    RMSFcube, phi2Arr_radm2, fwhmRMSFCube, fitStatArr = \
        get_rmsf_planes(lambdaSqArr_m2   = lambdaSqArr_m2,
                        phiArr_radm2     = phiArr_radm2,
                        weightArr        = weightArr,
                        mskArr           = np.isnan(dataQ),
                        lam0Sq_m2        = lam0Sq_m2,
                        double           = True,
                        fitRMSF          = fitRMSF,
                        fitRMSFreal      = False,
                        nBits            = 32,
                        verbose          = True)

    endTime = time.time()
    cputime = (endTime - startTime)
    print "> RM-synthesis completed in %.2f seconds." % cputime
    print "Saving the dirty FDF, RMSF and ancillary FITS files."

    # Determine the Stokes I value at lam0Sq_m2 from the Stokes I model
    # Note: the Stokes I model MUST be continuous throughout the cube,
    # i.e., no NaNs as the amplitude at freq0_Hz is interpolated from the
    # nearest two planes.
    freq0_Hz = C / m.sqrt(lam0Sq_m2)
    if fitsI:
        idx = np.abs(freqArr_Hz - freq0_Hz).argmin()
        if freqArr_Hz[idx] < freq0_Hz:
            Ifreq0Arr = interp_images(dataI[idx, :, :],
                                      dataI[idx + 1, :, :],
                                      f=0.5)
        elif freqArr_Hz[idx] > freq0_Hz:
            Ifreq0Arr = interp_images(dataI[idx - 1, :, :],
                                      dataI[idx, :, :],
                                      f=0.5)
        else:
            Ifreq0Arr = dataI[idx, :, :]

        # Multiply the dirty FDF by Ifreq0 to recover the PI in Jy
        FDFcube *= Ifreq0Arr

    # Make a copy of the Q header and alter Z-axis as Faraday depth
    header = headQ.copy()
    header["CTYPE3"] = "FARADAY DEPTH"
    header["CDELT3"] = np.diff(phiArr_radm2)[0]
    header["CRPIX3"] = 1.0
    header["CRVAL3"] = phiArr_radm2[0]
    if "DATAMAX" in header:
        del header["DATAMAX"]
    if "DATAMIN" in header:
        del header["DATAMIN"]

    # Save the dirty FDF
    fitsFileOut = outDir + "/" + prefixOut + "FDF_dirty.fits"
    print "> %s" % fitsFileOut
    hdu0 = pf.PrimaryHDU(FDFcube.real.astype(dtFloat), header)
    hdu1 = pf.ImageHDU(FDFcube.imag.astype(dtFloat), header)
    hdu2 = pf.ImageHDU(np.abs(FDFcube).astype(dtFloat), header)
    hduLst = pf.HDUList([hdu0, hdu1, hdu2])
    hduLst.writeto(fitsFileOut, output_verify="fix", clobber=True)
    hduLst.close()

    # Save a maximum polarised intensity map
    fitsFileOut = outDir + "/" + prefixOut + "FDF_maxPI.fits"
    print "> %s" % fitsFileOut
    pf.writeto(fitsFileOut,
               np.max(np.abs(FDFcube), 0).astype(dtFloat),
               header,
               clobber=True,
               output_verify="fix")

    # Save a peak RM map
    fitsFileOut = outDir + "/" + prefixOut + "FDF_peakRM.fits"
    header["BUNIT"] = "rad/m^2"
    peakFDFmap = np.argmax(np.abs(FDFcube), 0).astype(dtFloat)
    peakFDFmap = header["CRVAL3"] + (peakFDFmap + 1 -
                                     header["CRPIX3"]) * header["CDELT3"]
    print "> %s" % fitsFileOut
    pf.writeto(fitsFileOut,
               peakFDFmap,
               header,
               clobber=True,
               output_verify="fix")

    # Save an RM moment-1 map
    fitsFileOut = outDir + "/" + prefixOut + "FDF_mom1.fits"
    header["BUNIT"] = "rad/m^2"
    mom1FDFmap = (
        np.nansum(np.abs(FDFcube).transpose(1, 2, 0) * phiArr_radm2, 2) /
        np.nansum(np.abs(FDFcube).transpose(1, 2, 0), 2))
    mom1FDFmap = mom1FDFmap.astype(dtFloat)
    print "> %s" % fitsFileOut
    pf.writeto(fitsFileOut,
               mom1FDFmap,
               header,
               clobber=True,
               output_verify="fix")

    # Save the RMSF
    header["CRVAL3"] = phi2Arr_radm2[0]
    fitsFileOut = outDir + "/" + prefixOut + "RMSF.fits"
    hdu0 = pf.PrimaryHDU(RMSFcube.real.astype(dtFloat), header)
    hdu1 = pf.ImageHDU(RMSFcube.imag.astype(dtFloat), header)
    hdu2 = pf.ImageHDU(np.abs(RMSFcube).astype(dtFloat), header)
    header["DATAMAX"] = np.max(fwhmRMSFCube) + 1
    header["DATAMIN"] = np.max(fwhmRMSFCube) - 1
    hdu3 = pf.ImageHDU(fwhmRMSFCube.astype(dtFloat), header)
    hduLst = pf.HDUList([hdu0, hdu1, hdu2, hdu3])
    print "> %s" % fitsFileOut
    hduLst.writeto(fitsFileOut, output_verify="fix", clobber=True)
    hduLst.close()