Exemplo n.º 1
0
def run_rmsynth(dataFile,
                polyOrd=3,
                phiMax_radm2=None,
                dPhi_radm2=None,
                nSamples=10.0,
                weightType="variance",
                fitRMSF=False,
                noStokesI=False,
                phiNoise_radm2=1e6,
                nBits=32,
                showPlots=False,
                debug=False):
    """
    Read the I, Q & U data from the ASCII file and run RM-synthesis.
    """

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

    # Output prefix is derived from the input file name
    prefixOut, ext = os.path.splitext(dataFile)

    # Read the data-file. Format=space-delimited, comments="#".
    print "Reading the data file '%s':" % dataFile
    # freq_Hz, I_Jy, Q_Jy, U_Jy, dI_Jy, dQ_Jy, dU_Jy
    try:
        print "> Trying [freq_Hz, I_Jy, Q_Jy, U_Jy, dI_Jy, dQ_Jy, dU_Jy]",
        (freqArr_Hz, IArr_Jy, QArr_Jy, UArr_Jy,
         dIArr_Jy, dQArr_Jy, dUArr_Jy) = \
         np.loadtxt(dataFile, unpack=True, dtype=dtFloat)
        print "... success."
    except Exception:
        print "...failed."
        # freq_Hz, q_Jy, u_Jy, dq_Jy, du_Jy
        try:
            print "> Trying [freq_Hz, q_Jy, u_Jy,  dq_Jy, du_Jy]",
            (freqArr_Hz, QArr_Jy, UArr_Jy, dQArr_Jy, dUArr_Jy) = \
                         np.loadtxt(dataFile, unpack=True, dtype=dtFloat)
            print "... success."
            noStokesI = True
        except Exception:
            print "...failed."
            if debug:
                print traceback.format_exc()
            sys.exit()
    print "Successfully read in the Stokes spectra."

    # If no Stokes I present, create a dummy spectrum = unity
    if noStokesI:
        print "Warn: no Stokes I data in use."
        IArr_Jy = np.ones_like(QArr_Jy)
        dIArr_Jy = np.zeros_like(QArr_Jy)

    # Convert to GHz and mJy for convenience
    freqArr_GHz = freqArr_Hz / 1e9
    IArr_mJy = IArr_Jy * 1e3
    QArr_mJy = QArr_Jy * 1e3
    UArr_mJy = UArr_Jy * 1e3
    dIArr_mJy = dIArr_Jy * 1e3
    dQArr_mJy = dQArr_Jy * 1e3
    dUArr_mJy = dUArr_Jy * 1e3
    dQUArr_mJy = (dQArr_mJy + dUArr_mJy) / 2.0
    dQUArr_Jy = dQUArr_mJy / 1e3

    # Fit the Stokes I spectrum and create the fractional spectra
    IModArr, qArr, uArr, dqArr, duArr, fitDict = \
             create_frac_spectra(freqArr  = freqArr_GHz,
                                 IArr     = IArr_mJy,
                                 QArr     = QArr_mJy,
                                 UArr     = UArr_mJy,
                                 dIArr    = dIArr_mJy,
                                 dQArr    = dQArr_mJy,
                                 dUArr    = dUArr_mJy,
                                 polyOrd  = polyOrd,
                                 verbose  = True,
                                 debug    = debug)

    # Plot the data and the Stokes I model fit
    if showPlots:
        print "Plotting the input data and spectral index fit."
        freqHirArr_Hz = np.linspace(freqArr_Hz[0], freqArr_Hz[-1], 10000)
        IModHirArr_mJy = poly5(fitDict["p"])(freqHirArr_Hz / 1e9)
        specFig = plt.figure(figsize=(12.0, 8))
        plot_Ipqu_spectra_fig(freqArr_Hz=freqArr_Hz,
                              IArr_mJy=IArr_mJy,
                              qArr=qArr,
                              uArr=uArr,
                              dIArr_mJy=dIArr_mJy,
                              dqArr=dqArr,
                              duArr=duArr,
                              freqHirArr_Hz=freqHirArr_Hz,
                              IModArr_mJy=IModHirArr_mJy,
                              fig=specFig)

        # Use the custom navigation toolbar (does not work on Mac OS X)
        try:
            specFig.canvas.toolbar.pack_forget()
            CustomNavbar(specFig.canvas, specFig.canvas.toolbar.window)
        except Exception:
            pass

        # Display the figure
        specFig.show()

        # DEBUG (plot the Q, U and average RMS spectrum)
        if debug:
            rmsFig = plt.figure(figsize=(12.0, 8))
            ax = rmsFig.add_subplot(111)
            ax.plot(freqArr_Hz / 1e9,
                    dQUArr_mJy,
                    marker='o',
                    color='k',
                    lw=0.5,
                    label='rms <QU>')
            ax.plot(freqArr_Hz / 1e9,
                    dQArr_mJy,
                    marker='o',
                    color='b',
                    lw=0.5,
                    label='rms Q')
            ax.plot(freqArr_Hz / 1e9,
                    dUArr_mJy,
                    marker='o',
                    color='r',
                    lw=0.5,
                    label='rms U')
            xRange = (np.nanmax(freqArr_Hz) - np.nanmin(freqArr_Hz)) / 1e9
            ax.set_xlim(
                np.min(freqArr_Hz) / 1e9 - xRange * 0.05,
                np.max(freqArr_Hz) / 1e9 + xRange * 0.05)
            ax.set_xlabel('$\\nu$ (GHz)')
            ax.set_ylabel('RMS (mJy bm$^{-1}$)')
            ax.set_title("RMS noise in Stokes Q, U and <Q,U> spectra")
            rmsFig.show()

    #-------------------------------------------------------------------------#

    # Calculate some wavelength parameters
    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)

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

    startTime = time.time()

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

    # Calculate the Rotation Measure Spread Function
    RMSFArr, phi2Arr_radm2, fwhmRMSFArr, fitStatArr = \
        get_rmsf_planes(lambdaSqArr_m2  = lambdaSqArr_m2,
                        phiArr_radm2    = phiArr_radm2,
                        weightArr       = weightArr,
                        mskArr          = np.isnan(qArr),
                        lam0Sq_m2       = lam0Sq_m2,
                        double          = True,
                        fitRMSF         = fitRMSF,
                        fitRMSFreal     = False,
                        nBits           = nBits,
                        verbose         = True)
    fwhmRMSF = float(fwhmRMSFArr)

    # ALTERNATE RM-SYNTHESIS CODE --------------------------------------------#

    #dirtyFDF, [phi2Arr_radm2, RMSFArr], lam0Sq_m2, fwhmRMSF = \
    #          do_rmsynth(qArr, uArr, lambdaSqArr_m2, phiArr_radm2, weightArr)

    #-------------------------------------------------------------------------#

    endTime = time.time()
    cputime = (endTime - startTime)
    print "> RM-synthesis completed in %.2f seconds." % cputime

    # Determine the Stokes I value at lam0Sq_m2 from the Stokes I model
    # Multiply the dirty FDF by Ifreq0 to recover the PI in Jy
    freq0_Hz = C / m.sqrt(lam0Sq_m2)
    Ifreq0_mJybm = poly5(fitDict["p"])(freq0_Hz / 1e9)
    dirtyFDF *= (Ifreq0_mJybm / 1e3)  # FDF is in Jy

    # Calculate the theoretical noise in the FDF
    dFDFth_Jybm = np.sqrt(1. / np.sum(1. / dQUArr_Jy**2.))

    # Measure the parameters of the dirty FDF
    # Use the theoretical noise to calculate uncertainties
    mDict = measure_FDF_parms(FDF=dirtyFDF,
                              phiArr=phiArr_radm2,
                              fwhmRMSF=fwhmRMSF,
                              dFDF=dFDFth_Jybm,
                              lamSqArr_m2=lambdaSqArr_m2,
                              lam0Sq=lam0Sq_m2)
    mDict["Ifreq0_mJybm"] = toscalar(Ifreq0_mJybm)
    mDict["polyCoeffs"] = ",".join([str(x) for x in fitDict["p"]])
    mDict["IfitStat"] = fitDict["fitStatus"]
    mDict["IfitChiSqRed"] = fitDict["chiSqRed"]
    mDict["lam0Sq_m2"] = toscalar(lam0Sq_m2)
    mDict["freq0_Hz"] = toscalar(freq0_Hz)
    mDict["fwhmRMSF"] = toscalar(fwhmRMSF)
    mDict["dQU_Jybm"] = toscalar(nanmedian(dQUArr_Jy))
    mDict["dFDFth_Jybm"] = toscalar(dFDFth_Jybm)

    # Measure the complexity of the q and u spectra
    mDict["fracPol"] = mDict["ampPeakPIfit_Jybm"] / (Ifreq0_mJybm / 1e3)
    mD, pD = measure_qu_complexity(freqArr_Hz=freqArr_Hz,
                                   qArr=qArr,
                                   uArr=uArr,
                                   dqArr=dqArr,
                                   duArr=duArr,
                                   fracPol=mDict["fracPol"],
                                   psi0_deg=mDict["polAngle0Fit_deg"],
                                   RM_radm2=mDict["phiPeakPIfit_rm2"])
    mDict.update(mD)

    # Debugging plots for spectral complexity measure
    if debug:
        tmpFig = plot_complexity_fig(xArr=pD["xArrQ"],
                                     qArr=pD["yArrQ"],
                                     dqArr=pD["dyArrQ"],
                                     sigmaAddqArr=pD["sigmaAddArrQ"],
                                     chiSqRedqArr=pD["chiSqRedArrQ"],
                                     probqArr=pD["probArrQ"],
                                     uArr=pD["yArrU"],
                                     duArr=pD["dyArrU"],
                                     sigmaAdduArr=pD["sigmaAddArrU"],
                                     chiSqReduArr=pD["chiSqRedArrU"],
                                     probuArr=pD["probArrU"],
                                     mDict=mDict)
        tmpFig.show()

    # Save the  dirty FDF, RMSF and weight array to ASCII files
    print "Saving the dirty FDF, RMSF weight arrays to ASCII files."
    outFile = prefixOut + "_FDFdirty.dat"
    print "> %s" % outFile
    np.savetxt(outFile, zip(phiArr_radm2, dirtyFDF.real, dirtyFDF.imag))
    outFile = prefixOut + "_RMSF.dat"
    print "> %s" % outFile
    np.savetxt(outFile, zip(phi2Arr_radm2, RMSFArr.real, RMSFArr.imag))
    outFile = prefixOut + "_weight.dat"
    print "> %s" % outFile
    np.savetxt(outFile, zip(freqArr_Hz, weightArr))

    # Save the measurements to a "key=value" text file
    print "Saving the measurements on the FDF in 'key=val' and JSON formats."
    outFile = prefixOut + "_RMsynth.dat"
    print "> %s" % outFile
    FH = open(outFile, "w")
    for k, v in mDict.iteritems():
        FH.write("%s=%s\n" % (k, v))
    FH.close()
    outFile = prefixOut + "_RMsynth.json"
    print "> %s" % outFile
    json.dump(dict(mDict), open(outFile, "w"))

    # Print the results to the screen
    print
    print '-' * 80
    print 'RESULTS:\n'
    print 'FWHM RMSF = %.4g rad/m^2' % (mDict["fwhmRMSF"])

    print 'Pol Angle = %.4g (+/-%.4g) deg' % (mDict["polAngleFit_deg"],
                                              mDict["dPolAngleFit_deg"])
    print 'Pol Angle 0 = %.4g (+/-%.4g) deg' % (mDict["polAngle0Fit_deg"],
                                                mDict["dPolAngle0Fit_deg"])
    print 'Peak FD = %.4g (+/-%.4g) rad/m^2' % (mDict["phiPeakPIfit_rm2"],
                                                mDict["dPhiPeakPIfit_rm2"])
    print 'freq0_GHz = %.4g ' % (mDict["freq0_Hz"] / 1e9)
    print 'I freq0 = %.4g mJy/beam' % (mDict["Ifreq0_mJybm"])
    print 'Peak PI = %.4g (+/-%.4g) mJy/beam' % (
        mDict["ampPeakPIfit_Jybm"] * 1e3, mDict["dAmpPeakPIfit_Jybm"] * 1e3)
    print 'QU Noise = %.4g mJy/beam' % (mDict["dQU_Jybm"] * 1e3)
    print 'FDF Noise (theory)   = %.4g mJy/beam' % (mDict["dFDFth_Jybm"] * 1e3)
    print 'FDF SNR = %.4g ' % (mDict["snrPIfit"])
    print 'sigma_add(q) = %.4g (+%.4g, -%.4g)' % (
        mDict["sigmaAddQ"], mDict["dSigmaAddPlusQ"], mDict["dSigmaAddMinusQ"])
    print 'sigma_add(u) = %.4g (+%.4g, -%.4g)' % (
        mDict["sigmaAddU"], mDict["dSigmaAddPlusU"], mDict["dSigmaAddMinusU"])
    print
    print '-' * 80

    # Plot the RM Spread Function and dirty FDF
    if showPlots:
        fdfFig = plt.figure(figsize=(12.0, 8))
        plot_rmsf_fdf_fig(phiArr=phiArr_radm2,
                          FDF=dirtyFDF,
                          phi2Arr=phi2Arr_radm2,
                          RMSFArr=RMSFArr,
                          fwhmRMSF=fwhmRMSF,
                          vLine=mDict["phiPeakPIfit_rm2"],
                          fig=fdfFig)

        # Use the custom navigation toolbar
        try:
            fdfFig.canvas.toolbar.pack_forget()
            CustomNavbar(fdfFig.canvas, fdfFig.canvas.toolbar.window)
        except Exception:
            pass

        # Display the figure
        fdfFig.show()

    # Pause if plotting enabled
    if showPlots or debug:
        print "Press <RETURN> to exit ...",
        raw_input()
Exemplo n.º 2
0
def run_rmsynth(data,
                polyOrd=2,
                phiMax_radm2=None,
                dPhi_radm2=None,
                nSamples=10.0,
                weightType="variance",
                fitRMSF=False,
                noStokesI=False,
                modStokesI=None,
                phiNoise_radm2=1e6,
                nBits=32,
                showPlots=False,
                debug=False,
                verbose=False,
                log=print,
                units='Jy/beam',
                prefixOut="prefixOut",
                saveFigures=None,
                fit_function='log'):
    """Run RM synthesis on 1D data.

    Args:
        data (list): Contains frequency and polarization data as either:
            [freq_Hz, I, Q, U, dI, dQ, dU]
                freq_Hz (array_like): Frequency of each channel in Hz.
                I (array_like): Stokes I intensity in each channel.
                Q (array_like): Stokes Q intensity in each channel.
                U (array_like): Stokes U intensity in each channel.
                dI (array_like): Error in Stokes I intensity in each channel.
                dQ (array_like): Error in Stokes Q intensity in each channel.
                dU (array_like): Error in Stokes U intensity in each channel.
            or
            [freq_Hz, q, u,  dq, du]
                freq_Hz (array_like): Frequency of each channel in Hz.
                q (array_like): Fractional Stokes Q intensity (Q/I) in each channel.
                u (array_like): Fractional Stokes U intensity (U/I) in each channel.
                dq (array_like): Error in fractional Stokes Q intensity in each channel.
                du (array_like): Error in fractional Stokes U intensity in each channel.

    Kwargs:
        polyOrd (int): Order of polynomial to fit to Stokes I spectrum.
        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 uncertainty in Q and U.
            "uniform" -- Weight uniformly (i.e. with 1s)
        fitRMSF (bool): Fit a Gaussian to the RMSF?
        noStokesI (bool: Is Stokes I data provided?
        modStokesI (array_like): Stokes I model across for each channel (optional)
        phiNoise_radm2 (float): ????
        nBits (int): Precision of floating point numbers.
        showPlots (bool): Show plots?
        debug (bool): Turn on debugging messages & plots?
        verbose (bool): Verbosity.
        log (function): Which logging function to use.
        units (str): Units of data.

    Returns:
        mDict (dict): Summary of RM synthesis results.
        aDict (dict): Data output by RM synthesis.

    """

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

    # freq_Hz, I, Q, U, dI, dQ, dU
    try:
        if verbose: log("> Trying [freq_Hz, I, Q, U, dI, dQ, dU]", end=' ')
        (freqArr_Hz, IArr, QArr, UArr, dIArr, dQArr, dUArr) = data
        if verbose: log("... success.")
    except Exception:
        if verbose: log("...failed.")
        # freq_Hz, q, u, dq, du
        try:
            if verbose: log("> Trying [freq_Hz, q, u,  dq, du]", end=' ')
            (freqArr_Hz, QArr, UArr, dQArr, dUArr) = data
            if verbose: log("... success.")
            noStokesI = True
        except Exception:
            if verbose: log("...failed.")
            if debug:
                log(traceback.format_exc())
            sys.exit()
    if verbose: log("Successfully read in the Stokes spectra.")

    # If no Stokes I present, create a dummy spectrum = unity
    if noStokesI:
        if verbose: log("Warn: no Stokes I data in use.")
        IArr = np.ones_like(QArr)
        dIArr = np.zeros_like(QArr)

    # Convert to GHz for convenience
    freqArr_GHz = freqArr_Hz / 1e9
    dQUArr = (dQArr + dUArr) / 2.0

    # Fit the Stokes I spectrum and create the fractional spectra
    IModArr, qArr, uArr, dqArr, duArr, fitDict = \
             create_frac_spectra(freqArr  = freqArr_Hz,
                                 IArr     = IArr,
                                 QArr     = QArr,
                                 UArr     = UArr,
                                 dIArr    = dIArr,
                                 dQArr    = dQArr,
                                 dUArr    = dUArr,
                                 polyOrd  = polyOrd,
                                 verbose  = True,
                                 debug    = debug,
                                 fit_function = fit_function,
                                 modStokesI = modStokesI,
                                 )

    dquArr = (dqArr + duArr) / 2.0
    dquArr = np.where(np.isfinite(dquArr), dquArr, np.nan)

    # Plot the data and the Stokes I model fit
    if verbose: log("Plotting the input data and spectral index fit.")
    freqHirArr_Hz = np.linspace(freqArr_Hz[0], freqArr_Hz[-1], 10000)
    if modStokesI is None:
        IModHirArr = calculate_StokesI_model(fitDict, freqHirArr_Hz)
    elif modStokesI is not None:
        modStokesI_interp = interp1d(freqArr_Hz, modStokesI)
        IModHirArr = modStokesI_interp(freqHirArr_Hz)
    if showPlots or saveFigures:
        specFig = plt.figure(facecolor='w', figsize=(12.0, 8))
        plot_Ipqu_spectra_fig(freqArr_Hz=freqArr_Hz,
                              IArr=IArr,
                              qArr=qArr,
                              uArr=uArr,
                              dIArr=dIArr,
                              dqArr=dqArr,
                              duArr=duArr,
                              freqHirArr_Hz=freqHirArr_Hz,
                              IModArr=IModHirArr,
                              fig=specFig,
                              units=units)
    if saveFigures:
        outFilePlot = prefixOut + "_spectra-plots.pdf"
        specFig.savefig(outFilePlot, bbox_inches='tight')

    # Use the custom navigation toolbar (does not work on Mac OS X)
#        try:
#            specFig.canvas.toolbar.pack_forget()
#            CustomNavbar(specFig.canvas, specFig.canvas.toolbar.window)
#        except Exception:
#            pass

# Display the figure
#        if not plt.isinteractive():
#            specFig.show()

# DEBUG (plot the Q, U and average RMS spectrum)
    if debug:
        rmsFig = plt.figure(facecolor='w', figsize=(12.0, 8))
        ax = rmsFig.add_subplot(111)
        ax.plot(freqArr_Hz / 1e9,
                dQUArr,
                marker='o',
                color='k',
                lw=0.5,
                label='rms <QU>')
        ax.plot(freqArr_Hz / 1e9,
                dQArr,
                marker='o',
                color='b',
                lw=0.5,
                label='rms Q')
        ax.plot(freqArr_Hz / 1e9,
                dUArr,
                marker='o',
                color='r',
                lw=0.5,
                label='rms U')
        xRange = (np.nanmax(freqArr_Hz) - np.nanmin(freqArr_Hz)) / 1e9
        ax.set_xlim(
            np.min(freqArr_Hz) / 1e9 - xRange * 0.05,
            np.max(freqArr_Hz) / 1e9 + xRange * 0.05)
        ax.set_xlabel('$\\nu$ (GHz)')
        ax.set_ylabel('RMS ' + units)
        ax.set_title("RMS noise in Stokes Q, U and <Q,U> spectra")
#            rmsFig.show()

#-------------------------------------------------------------------------#

# Calculate some wavelength parameters
    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, fwhmRMSF_radm2 *
                           10.)  # Force the minimum phiMax to 10 FWHM

    # Faraday depth sampling. Zero always centred on middle channel
    nChanRM = int(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":
        weightArr = 1.0 / np.power(dquArr, 2.0)
    else:
        weightType = "uniform"
        weightArr = np.ones(freqArr_Hz.shape, dtype=dtFloat)
    if verbose: log("Weight type is '%s'." % weightType)

    startTime = time.time()

    # Perform RM-synthesis on the spectrum
    dirtyFDF, lam0Sq_m2 = do_rmsynth_planes(dataQ=qArr,
                                            dataU=uArr,
                                            lambdaSqArr_m2=lambdaSqArr_m2,
                                            phiArr_radm2=phiArr_radm2,
                                            weightArr=weightArr,
                                            nBits=nBits,
                                            verbose=verbose,
                                            log=log)

    # Calculate the Rotation Measure Spread Function
    RMSFArr, phi2Arr_radm2, fwhmRMSFArr, fitStatArr = \
        get_rmsf_planes(lambdaSqArr_m2  = lambdaSqArr_m2,
                        phiArr_radm2    = phiArr_radm2,
                        weightArr       = weightArr,
                        mskArr          = ~np.isfinite(qArr),
                        lam0Sq_m2       = lam0Sq_m2,
                        double          = True,
                        fitRMSF         = fitRMSF,
                        fitRMSFreal     = False,
                        nBits           = nBits,
                        verbose         = verbose,
                        log             = log)
    fwhmRMSF = float(fwhmRMSFArr)

    # ALTERNATE RM-SYNTHESIS CODE --------------------------------------------#

    #dirtyFDF, [phi2Arr_radm2, RMSFArr], lam0Sq_m2, fwhmRMSF = \
    #          do_rmsynth(qArr, uArr, lambdaSqArr_m2, phiArr_radm2, weightArr)

    #-------------------------------------------------------------------------#

    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
    # Multiply the dirty FDF by Ifreq0 to recover the PI
    freq0_Hz = C / m.sqrt(lam0Sq_m2)
    if modStokesI is None:
        Ifreq0 = calculate_StokesI_model(fitDict, freq0_Hz)
    elif modStokesI is not None:
        modStokesI_interp = interp1d(freqArr_Hz, modStokesI)
        Ifreq0 = modStokesI_interp(freq0_Hz)
    dirtyFDF *= (Ifreq0
                 )  # FDF is in fracpol units initially, convert back to flux

    if modStokesI is None:
        #Need to renormalize the Stokes I parameters here to the actual reference frequency.
        fitDict = renormalize_StokesI_model(fitDict, freq0_Hz)

    # Calculate the theoretical noise in the FDF !!Old formula only works for wariance weights!
    weightArr = np.where(np.isnan(weightArr), 0.0, weightArr)
    dFDFth = Ifreq0 * np.sqrt(
        np.nansum(weightArr**2 * np.nan_to_num(dquArr)**2) /
        (np.sum(weightArr))**2)

    # Measure the parameters of the dirty FDF
    # Use the theoretical noise to calculate uncertainties
    mDict = measure_FDF_parms(FDF=dirtyFDF,
                              phiArr=phiArr_radm2,
                              fwhmRMSF=fwhmRMSF,
                              dFDF=dFDFth,
                              lamSqArr_m2=lambdaSqArr_m2,
                              lam0Sq=lam0Sq_m2)
    mDict["Ifreq0"] = toscalar(Ifreq0)
    mDict["polyCoeffs"] = ",".join([str(x) for x in fitDict["p"]])
    mDict["IfitStat"] = fitDict["fitStatus"]
    mDict["IfitChiSqRed"] = fitDict["chiSqRed"]
    mDict["lam0Sq_m2"] = toscalar(lam0Sq_m2)
    mDict["freq0_Hz"] = toscalar(freq0_Hz)
    mDict["fwhmRMSF"] = toscalar(fwhmRMSF)
    mDict["dQU"] = toscalar(nanmedian(dQUArr))
    mDict["dFDFth"] = toscalar(dFDFth)
    mDict["units"] = units
    mDict['polyOrd'] = fitDict['polyOrd']

    if (fitDict["fitStatus"] >= 128) and verbose:
        log("WARNING: Stokes I model contains negative values!")
    elif (fitDict["fitStatus"] >= 64) and verbose:
        log("Caution: Stokes I model has low signal-to-noise.")

    #Add information on nature of channels:
    good_channels = np.where(np.logical_and(weightArr != 0,
                                            np.isfinite(qArr)))[0]
    mDict["min_freq"] = float(np.min(freqArr_Hz[good_channels]))
    mDict["max_freq"] = float(np.max(freqArr_Hz[good_channels]))
    mDict["N_channels"] = good_channels.size
    mDict["median_channel_width"] = float(np.median(np.diff(freqArr_Hz)))

    # Measure the complexity of the q and u spectra
    mDict["fracPol"] = mDict["ampPeakPIfit"] / (Ifreq0)
    mD, pD = measure_qu_complexity(freqArr_Hz=freqArr_Hz,
                                   qArr=qArr,
                                   uArr=uArr,
                                   dqArr=dqArr,
                                   duArr=duArr,
                                   fracPol=mDict["fracPol"],
                                   psi0_deg=mDict["polAngle0Fit_deg"],
                                   RM_radm2=mDict["phiPeakPIfit_rm2"])
    mDict.update(mD)

    # Debugging plots for spectral complexity measure
    if debug:
        tmpFig = plot_complexity_fig(xArr=pD["xArrQ"],
                                     qArr=pD["yArrQ"],
                                     dqArr=pD["dyArrQ"],
                                     sigmaAddqArr=pD["sigmaAddArrQ"],
                                     chiSqRedqArr=pD["chiSqRedArrQ"],
                                     probqArr=pD["probArrQ"],
                                     uArr=pD["yArrU"],
                                     duArr=pD["dyArrU"],
                                     sigmaAdduArr=pD["sigmaAddArrU"],
                                     chiSqReduArr=pD["chiSqRedArrU"],
                                     probuArr=pD["probArrU"],
                                     mDict=mDict)
        if saveFigures:
            if verbose: print("Saving debug plots:")
            outFilePlot = prefixOut + ".debug-plots.pdf"
            if verbose: print("> " + outFilePlot)
            tmpFig.savefig(outFilePlot, bbox_inches='tight')
        else:
            tmpFig.show()

    #add array dictionary
    aDict = dict()
    aDict["phiArr_radm2"] = phiArr_radm2
    aDict["phi2Arr_radm2"] = phi2Arr_radm2
    aDict["RMSFArr"] = RMSFArr
    aDict["freqArr_Hz"] = freqArr_Hz
    aDict["weightArr"] = weightArr
    aDict["dirtyFDF"] = dirtyFDF

    if verbose:
        # Print the results to the screen
        log()
        log('-' * 80)
        log('RESULTS:\n')
        log('FWHM RMSF = %.4g rad/m^2' % (mDict["fwhmRMSF"]))

        log('Pol Angle = %.4g (+/-%.4g) deg' %
            (mDict["polAngleFit_deg"], mDict["dPolAngleFit_deg"]))
        log('Pol Angle 0 = %.4g (+/-%.4g) deg' %
            (mDict["polAngle0Fit_deg"], mDict["dPolAngle0Fit_deg"]))
        log('Peak FD = %.4g (+/-%.4g) rad/m^2' %
            (mDict["phiPeakPIfit_rm2"], mDict["dPhiPeakPIfit_rm2"]))
        log('freq0_GHz = %.4g ' % (mDict["freq0_Hz"] / 1e9))
        log('I freq0 = %.4g %s' % (mDict["Ifreq0"], units))
        log('Peak PI = %.4g (+/-%.4g) %s' %
            (mDict["ampPeakPIfit"], mDict["dAmpPeakPIfit"], units))
        log('QU Noise = %.4g %s' % (mDict["dQU"], units))
        log('FDF Noise (theory)   = %.4g %s' % (mDict["dFDFth"], units))
        log('FDF Noise (Corrected MAD) = %.4g %s' %
            (mDict["dFDFcorMAD"], units))
        log('FDF Noise (rms)   = %.4g %s' % (mDict["dFDFrms"], units))
        log('FDF SNR = %.4g ' % (mDict["snrPIfit"]))
        log('sigma_add(q) = %.4g (+%.4g, -%.4g)' %
            (mDict["sigmaAddQ"], mDict["dSigmaAddPlusQ"],
             mDict["dSigmaAddMinusQ"]))
        log('sigma_add(u) = %.4g (+%.4g, -%.4g)' %
            (mDict["sigmaAddU"], mDict["dSigmaAddPlusU"],
             mDict["dSigmaAddMinusU"]))
        log('Fitted polynomial order = {} '.format(mDict['polyOrd']))
        log()
        log('-' * 80)

    # Plot the RM Spread Function and dirty FDF
    if showPlots or saveFigures:
        fdfFig = plt.figure(facecolor='w', figsize=(12.0, 8))
        plot_rmsf_fdf_fig(phiArr=phiArr_radm2,
                          FDF=dirtyFDF,
                          phi2Arr=phi2Arr_radm2,
                          RMSFArr=RMSFArr,
                          fwhmRMSF=fwhmRMSF,
                          vLine=mDict["phiPeakPIfit_rm2"],
                          fig=fdfFig,
                          units=units)

        # Use the custom navigation toolbar
#        try:
#            fdfFig.canvas.toolbar.pack_forget()
#            CustomNavbar(fdfFig.canvas, fdfFig.canvas.toolbar.window)
#        except Exception:
#            pass

# Display the figure
#        fdfFig.show()

# Pause if plotting enabled
    if showPlots:
        plt.show()
    elif saveFigures or debug:
        if verbose: print("Saving RMSF and dirty FDF plot:")
        outFilePlot = prefixOut + "_RMSF-dirtyFDF-plots.pdf"
        if verbose: print("> " + outFilePlot)
        fdfFig.savefig(outFilePlot, bbox_inches='tight')
        #        #if verbose: print "Press <RETURN> to exit ...",


#        input()

    return mDict, aDict
Exemplo n.º 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")
Exemplo n.º 4
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
Exemplo n.º 5
0
def run_rmsynth(data, polyOrd=3, phiMax_radm2=None, dPhi_radm2=None, 
                nSamples=10.0, weightType="variance", fitRMSF=False,
                noStokesI=False, phiNoise_radm2=1e6, nBits=32, showPlots=False,
                debug=False, verbose=False, log=print,units='Jy/beam'):
    """
    Read the I, Q & U data and run RM-synthesis.
    """

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

    # freq_Hz, I, Q, U, dI, dQ, dU
    try:
        if verbose: log("> Trying [freq_Hz, I, Q, U, dI, dQ, dU]", end=' ')
        (freqArr_Hz, IArr, QArr, UArr, dIArr, dQArr, dUArr) = data 
        if verbose: log("... success.")
    except Exception:
        if verbose: log("...failed.")
        # freq_Hz, q, u, dq, du
        try:
            if verbose: log("> Trying [freq_Hz, q, u,  dq, du]", end=' ')
            (freqArr_Hz, QArr, UArr, dQArr, dUArr) = data 
            if verbose: log("... success.")
            noStokesI = True
        except Exception:
            if verbose: log("...failed.")
            if debug:
                log(traceback.format_exc())
            sys.exit()
    if verbose: log("Successfully read in the Stokes spectra.")

    # If no Stokes I present, create a dummy spectrum = unity
    if noStokesI:
        log("Warn: no Stokes I data in use.")
        IArr = np.ones_like(QArr)
        dIArr = np.zeros_like(QArr)
        
    # Convert to GHz for convenience
    freqArr_GHz = freqArr_Hz / 1e9
    dQUArr = (dQArr + dUArr)/2.0
 
    # Fit the Stokes I spectrum and create the fractional spectra
    IModArr, qArr, uArr, dqArr, duArr, fitDict = \
             create_frac_spectra(freqArr  = freqArr_GHz,
                                 IArr     = IArr,
                                 QArr     = QArr,
                                 UArr     = UArr,
                                 dIArr    = dIArr,
                                 dQArr    = dQArr,
                                 dUArr    = dUArr,
                                 polyOrd  = polyOrd,
                                 verbose  = True,
                                 debug    = debug)

    # Plot the data and the Stokes I model fit
    if showPlots:
        if verbose: log("Plotting the input data and spectral index fit.")
        freqHirArr_Hz =  np.linspace(freqArr_Hz[0], freqArr_Hz[-1], 10000)     
        IModHirArr = poly5(fitDict["p"])(freqHirArr_Hz/1e9)    
        specFig = plt.figure(figsize=(12.0, 8))
        plot_Ipqu_spectra_fig(freqArr_Hz     = freqArr_Hz,
                              IArr           = IArr, 
                              qArr           = qArr, 
                              uArr           = uArr, 
                              dIArr          = dIArr,
                              dqArr          = dqArr,
                              duArr          = duArr,
                              freqHirArr_Hz  = freqHirArr_Hz,
                              IModArr        = IModHirArr,
                              fig            = specFig,
                              units          = units)

        # Use the custom navigation toolbar (does not work on Mac OS X)
#        try:
#            specFig.canvas.toolbar.pack_forget()
#            CustomNavbar(specFig.canvas, specFig.canvas.toolbar.window)
#        except Exception:
#            pass

        # Display the figure
#        if not plt.isinteractive():
#            specFig.show()

        # DEBUG (plot the Q, U and average RMS spectrum)
        if debug:
            rmsFig = plt.figure(figsize=(12.0, 8))
            ax = rmsFig.add_subplot(111)
            ax.plot(freqArr_Hz/1e9, dQUArr, marker='o', color='k', lw=0.5,
                    label='rms <QU>')
            ax.plot(freqArr_Hz/1e9, dQArr, marker='o', color='b', lw=0.5,
                    label='rms Q')
            ax.plot(freqArr_Hz/1e9, dUArr, marker='o', color='r', lw=0.5,
                    label='rms U')
            xRange = (np.nanmax(freqArr_Hz)-np.nanmin(freqArr_Hz))/1e9 
            ax.set_xlim( np.min(freqArr_Hz)/1e9 - xRange*0.05,
                         np.max(freqArr_Hz)/1e9 + xRange*0.05)
            ax.set_xlabel('$\\nu$ (GHz)')
            ax.set_ylabel('RMS '+units)
            ax.set_title("RMS noise in Stokes Q, U and <Q,U> spectra")
#            rmsFig.show()

    #-------------------------------------------------------------------------#

    # Calculate some wavelength parameters
    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 = int(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":
        weightArr = 1.0 / np.power(dQUArr, 2.0)
    else:
        weightType = "uniform"
        weightArr = np.ones(freqArr_Hz.shape, dtype=dtFloat)
    if verbose: log("Weight type is '%s'." % weightType)

    startTime = time.time()
    
    # Perform RM-synthesis on the spectrum
    dirtyFDF, lam0Sq_m2 = do_rmsynth_planes(dataQ           = qArr,
                                            dataU           = uArr,
                                            lambdaSqArr_m2  = lambdaSqArr_m2, 
                                            phiArr_radm2    = phiArr_radm2, 
                                            weightArr       = weightArr,
                                            nBits           = nBits,
                                            verbose         = verbose,
                                            log             = log)

    # Calculate the Rotation Measure Spread Function
    RMSFArr, phi2Arr_radm2, fwhmRMSFArr, fitStatArr = \
        get_rmsf_planes(lambdaSqArr_m2  = lambdaSqArr_m2,
                        phiArr_radm2    = phiArr_radm2, 
                        weightArr       = weightArr, 
                        mskArr          = ~np.isfinite(qArr),
                        lam0Sq_m2       = lam0Sq_m2, 
                        double          = True, 
                        fitRMSF         = fitRMSF, 
                        fitRMSFreal     = False, 
                        nBits           = nBits,
                        verbose         = verbose,
                        log             = log)
    fwhmRMSF = float(fwhmRMSFArr)
    
    # ALTERNATE RM-SYNTHESIS CODE --------------------------------------------#

    #dirtyFDF, [phi2Arr_radm2, RMSFArr], lam0Sq_m2, fwhmRMSF = \
    #          do_rmsynth(qArr, uArr, lambdaSqArr_m2, phiArr_radm2, weightArr)
    
    #-------------------------------------------------------------------------#
    
    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
    # Multiply the dirty FDF by Ifreq0 to recover the PI
    freq0_Hz = C / m.sqrt(lam0Sq_m2)
    Ifreq0 = poly5(fitDict["p"])(freq0_Hz/1e9)
    dirtyFDF *= (Ifreq0)    # FDF is in fracpol units initially, convert back to flux 

    # Calculate the theoretical noise in the FDF !!Old formula only works for wariance weights!
    dFDFth = np.sqrt( np.sum(weightArr**2 * dQUArr**2) / (np.sum(weightArr))**2 )
    
    
    # Measure the parameters of the dirty FDF
    # Use the theoretical noise to calculate uncertainties
    mDict = measure_FDF_parms(FDF         = dirtyFDF,
                              phiArr      = phiArr_radm2,
                              fwhmRMSF    = fwhmRMSF,
                              dFDF        = dFDFth,
                              lamSqArr_m2 = lambdaSqArr_m2,
                              lam0Sq      = lam0Sq_m2)
    mDict["Ifreq0"] = toscalar(Ifreq0)
    mDict["polyCoeffs"] =  ",".join([str(x) for x in fitDict["p"]])
    mDict["IfitStat"] = fitDict["fitStatus"]
    mDict["IfitChiSqRed"] = fitDict["chiSqRed"]
    mDict["lam0Sq_m2"] = toscalar(lam0Sq_m2)
    mDict["freq0_Hz"] = toscalar(freq0_Hz)
    mDict["fwhmRMSF"] = toscalar(fwhmRMSF)
    mDict["dQU"] = toscalar(nanmedian(dQUArr))
    mDict["dFDFth"] = toscalar(dFDFth)
    mDict["units"] = units

    #Add information on nature of channels:
    good_channels=np.where(np.logical_and(weightArr != 0,np.isfinite(qArr)))[0]
    mDict["min_freq"]=float(np.min(freqArr_Hz[good_channels]))
    mDict["max_freq"]=float(np.max(freqArr_Hz[good_channels]))
    mDict["N_channels"]=good_channels.size
    mDict["median_channel_width"]=float(np.median(np.diff(freqArr_Hz)))
        
    # Measure the complexity of the q and u spectra
    mDict["fracPol"] = mDict["ampPeakPIfit"]/(Ifreq0)
    mD, pD = measure_qu_complexity(freqArr_Hz = freqArr_Hz,
                                   qArr       = qArr,
                                   uArr       = uArr,
                                   dqArr      = dqArr,
                                   duArr      = duArr,
                                   fracPol    = mDict["fracPol"],
                                   psi0_deg   = mDict["polAngle0Fit_deg"],
                                   RM_radm2   = mDict["phiPeakPIfit_rm2"])
    mDict.update(mD)
    
    # Debugging plots for spectral complexity measure
    if debug:
        tmpFig = plot_complexity_fig(xArr=pD["xArrQ"],
                                     qArr=pD["yArrQ"],
                                     dqArr=pD["dyArrQ"],
                                     sigmaAddqArr=pD["sigmaAddArrQ"],
                                     chiSqRedqArr=pD["chiSqRedArrQ"],
                                     probqArr=pD["probArrQ"],
                                     uArr=pD["yArrU"],
                                     duArr=pD["dyArrU"],
                                     sigmaAdduArr=pD["sigmaAddArrU"],
                                     chiSqReduArr=pD["chiSqRedArrU"],
                                     probuArr=pD["probArrU"],
                                     mDict=mDict)
        tmpFig.show()
    
    #add array dictionary
    aDict = dict()
    aDict["phiArr_radm2"] = phiArr_radm2
    aDict["phi2Arr_radm2"] = phi2Arr_radm2
    aDict["RMSFArr"] = RMSFArr
    aDict["freqArr_Hz"] = freqArr_Hz
    aDict["weightArr"]=weightArr
    aDict["dirtyFDF"]=dirtyFDF
    
    if verbose: 
       # Print the results to the screen
       log()
       log('-'*80)
       log('RESULTS:\n')
       log('FWHM RMSF = %.4g rad/m^2' % (mDict["fwhmRMSF"]))
    
       log('Pol Angle = %.4g (+/-%.4g) deg' % (mDict["polAngleFit_deg"],
                                              mDict["dPolAngleFit_deg"]))
       log('Pol Angle 0 = %.4g (+/-%.4g) deg' % (mDict["polAngle0Fit_deg"],
                                                mDict["dPolAngle0Fit_deg"]))
       log('Peak FD = %.4g (+/-%.4g) rad/m^2' % (mDict["phiPeakPIfit_rm2"],
                                                mDict["dPhiPeakPIfit_rm2"]))
       log('freq0_GHz = %.4g ' % (mDict["freq0_Hz"]/1e9))
       log('I freq0 = %.4g %s' % (mDict["Ifreq0"],units))
       log('Peak PI = %.4g (+/-%.4g) %s' % (mDict["ampPeakPIfit"],
                                                mDict["dAmpPeakPIfit"],units))
       log('QU Noise = %.4g %s' % (mDict["dQU"],units))
       log('FDF Noise (theory)   = %.4g %s' % (mDict["dFDFth"],units))
       log('FDF Noise (Corrected MAD) = %.4g %s' % (mDict["dFDFcorMAD"],units))
       log('FDF Noise (rms)   = %.4g %s' % (mDict["dFDFrms"],units))
       log('FDF SNR = %.4g ' % (mDict["snrPIfit"]))
       log('sigma_add(q) = %.4g (+%.4g, -%.4g)' % (mDict["sigmaAddQ"],
                                            mDict["dSigmaAddPlusQ"],
                                            mDict["dSigmaAddMinusQ"]))
       log('sigma_add(u) = %.4g (+%.4g, -%.4g)' % (mDict["sigmaAddU"],
                                            mDict["dSigmaAddPlusU"],
                                            mDict["dSigmaAddMinusU"]))
       log()
       log('-'*80)



    # Plot the RM Spread Function and dirty FDF
    if showPlots:
        fdfFig = plt.figure(figsize=(12.0, 8))
        plot_rmsf_fdf_fig(phiArr     = phiArr_radm2,
                          FDF        = dirtyFDF,
                          phi2Arr    = phi2Arr_radm2,
                          RMSFArr    = RMSFArr,
                          fwhmRMSF   = fwhmRMSF,
                          vLine      = mDict["phiPeakPIfit_rm2"],
                          fig        = fdfFig,
                          units      = units)

        # Use the custom navigation toolbar
#        try:
#            fdfFig.canvas.toolbar.pack_forget()
#            CustomNavbar(fdfFig.canvas, fdfFig.canvas.toolbar.window)
#        except Exception:
#            pass
        
        # Display the figure
#        fdfFig.show()

    # Pause if plotting enabled
    if showPlots or debug:        
        plt.show()
        #        #if verbose: print "Press <RETURN> to exit ...",
#        input()

    return mDict, aDict
Exemplo n.º 6
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()
Exemplo n.º 7
0
def RM_synth(stokes,
             weight=True, # weighting needs more testing. Fails on certain events
             upchan=False, 
             RM_lim=None, 
             nSamples=None,
             normed=False,
             noise_type='theory',
             diagnostic_plots=False,
             cutoff=None):
    
    """ Performs rotation measure synthesis to extract Faraday Dispersion Function and RM measurement
    
    Parameters
    __________
    
    stokes : list (freq,I,Q,U,V,dI,dQ,dU,dV)
        Stokes params.
    weight : Bool. 
        Freq. channel std., used as to create array of weights for FDF
    band_lo, band_hi : float
        bottom and top freq. band limits of burst. 
    upchan: Bool
        If true, sets nSamples=3 
    RM_lim: float (optional)
        limits in Phi space to calculate the FDF
    nSamples: int (optional)
        sampling density in Phi space
    diagnostic_plots: Bool.
        outputs diagnostic plots of FDF
            
    Returns
    _______
    FDF params, (phi, FDF_arr) : list, array_like
        (RM,RM_err,PA,PA_err), FDF_arr
    """

    freqArr=stokes[0].copy()
    IArr=stokes[1].copy()
    QArr=stokes[2].copy()
    UArr=stokes[3].copy()
    VArr=stokes[4].copy()
    dIArr=stokes[5].copy()
    dQArr=stokes[6].copy()
    dUArr=stokes[7].copy()
    dVArr=stokes[8].copy()


    freqArr_Hz=freqArr*1e6
    lamArr_m=phys_const.speed_of_light/freqArr_Hz # convert to wavelength in m
    lambdaSqArr_m2=lamArr_m**2
    
    if normed is True:
        dQArr=IArr*np.sqrt((dQArr/QArr)**2+(dIArr/IArr)**2)
        dUArr=IArr*np.sqrt((dUArr/UArr)**2+(dIArr/IArr)**2)
        QArr/=IArr
        UArr/=IArr


    dQUArr = (dQArr + dUArr)/2.0
    if weight is True:
        weightArr = 1.0 / np.power(dQUArr, 2.0)
    else:
        weightArr = np.ones(freqArr_Hz.shape, dtype=float)
    dFDFth = np.sqrt( np.sum(weightArr**2 * dQUArr**2) / (np.sum(weightArr))**2 ) # check this equation!!!
    
    if nSamples is None:
        if upchan:
            nSamples=3 # sampling resolution of the FDF. 
        else:
            nSamples=10

    lambdaSqRange_m2 = (np.nanmax(lambdaSqArr_m2) - np.nanmin(lambdaSqArr_m2) )  
    fwhmRMSF_radm2 = 2.0 * np.sqrt(3.0) / lambdaSqRange_m2

#     dLambdaSqMin_m2 = np.nanmin(np.abs(np.diff(lambdaSqArr_m2)))
#     dLambdaSqMax_m2 = np.nanmax(np.abs(np.diff(lambdaSqArr_m2)))
    dLambdaSqMed_m2 = np.nanmedian(np.abs(np.diff(lambdaSqArr_m2)))

    dPhi_radm2 = fwhmRMSF_radm2 / nSamples

    #     phiMax_radm2 = np.sqrt(3.0) / dLambdaSqMax_m2
    phiMax_radm2 = np.sqrt(3.0) / dLambdaSqMed_m2 # sets the RM limit that can be probed based on intrachannel depolarization
    phiMax_radm2 = max(phiMax_radm2,600)    # Force the minimum phiMax

    if RM_lim  is None:
        # Faraday depth sampling. Zero always centred on middle channel
        nChanRM = int(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)

    else:
        startPhi_radm2 = RM_lim[0]
        stopPhi_radm2 = RM_lim[1]
        nChanRM = int(round(abs((((stopPhi_radm2-startPhi_radm2)//2) - 0.0) / dPhi_radm2)) * 2.0 + 1.0)
        phiArr_radm2 = np.linspace(startPhi_radm2, stopPhi_radm2, nChanRM)
    
    phiArr_radm2 = phiArr_radm2.astype(np.float)
  
    ### constructing FDF ###

    dirtyFDF, lam0Sq_m2 = do_rmsynth_planes(
    QArr, 
    UArr, 
    lambdaSqArr_m2, 
    phiArr_radm2)

    RMSFArr, phi2Arr_radm2, fwhmRMSFArr, fitStatArr = get_rmsf_planes(
    lambdaSqArr_m2 = lambdaSqArr_m2,
    phiArr_radm2 = phiArr_radm2,
    weightArr=weightArr, 
    mskArr=None,
    lam0Sq_m2=lam0Sq_m2, 
    double = True) # routine needed for RM-cleaning 

    FDF, lam0Sq_m2 = do_rmsynth_planes(
    QArr, 
    UArr, 
    lambdaSqArr_m2, 
    phiArr_radm2,                   
    weightArr=weightArr, 
    lam0Sq_m2=None,
    nBits=32, 
    verbose=False)

    FDF_max=np.argmax(abs(FDF))
    FDF_med=np.median(abs(FDF))

    dFDFobs=np.median(abs(abs(FDF)-FDF_med)) / np.sqrt(np.pi/2) #MADFM definition of noise
#   dFDF_obs=np.nanstd(abs(FDF)) #std. definition of noise

    if noise_type is 'observed':
        FDF_snr=abs((abs(FDF)-FDF_med)/dFDFobs)/2
    if noise_type is 'theory':
        FDF_snr=abs((abs(FDF)-FDF_med)/dFDFth)/2

    mDict = measure_FDF_parms(FDF         = dirtyFDF,
                              phiArr      = phiArr_radm2,
                              fwhmRMSF    = fwhmRMSF_radm2,
                              dFDF        = dFDFth, #FDF_noise
                              lamSqArr_m2 = lambdaSqArr_m2,
                              lam0Sq      = lam0Sq_m2)

    RM_radm2_fit=mDict["phiPeakPIfit_rm2"]
    dRM_radm2_fit=mDict["dPhiPeakPIfit_rm2"]

    RM_radm2=phiArr_radm2[FDF_max]
    dRM_radm2=fwhmRMSF_radm2/(2*FDF_snr.max())

    polAngle0Fit_deg=mDict["polAngle0Fit_deg"]
    dPolAngle0Fit_deg=mDict["dPolAngle0Fit_deg"] * np.sqrt(freqArr.size) # np.sqrt(freqArr.size) term corrects for band-average noise 

    
    if cutoff is None:
    
        if diagnostic_plots:
            fig, ax = plt.subplots(2,1, figsize=(20,10))
            plt.subplots_adjust(left=0.1, bottom=0.1, right=0.99, top=0.95, wspace=0)
            ax[0].set_title('Faraday Dispersion Function')    
            ax[0].plot(phiArr_radm2,FDF_snr)
            ax[0].set_xlim([phiArr_radm2.min(), phiArr_radm2.max()])
            ax[1].plot(phiArr_radm2,FDF_snr)
            #     ax[1].axvline(RM_radm2, color='k', ls=':', label=r'RM=%.2f $\pm$ %0.2f rad/m$^2$' %(RM_radm2,dRM_radm2))
            ax[1].set_xlim(phiArr_radm2[FDF_max]-300,phiArr_radm2[FDF_max]+300)
            ax[1].set_xlabel('$\phi$ [rad/m$^2$]')
            fig.text(0.03, 0.5, 'Polarized Intensity [S/N]', va='center', rotation='vertical')
            #     plt.legend(fontsize=20)
            #     plt.tight_layout()

            if isinstance(diagnostic_plots, bool):
                plt.show()
            else:
                plot_name = "FDF.png"
                plt.savefig(os.path.join(diagnostic_plots, plot_name))
                plt.close("all")
            
        return (RM_radm2_fit,RM_radm2,dRM_radm2_fit,dRM_radm2,polAngle0Fit_deg,dPolAngle0Fit_deg),(phiArr_radm2,FDF_snr)

    else:

        if noise_type is 'observed':
            cutoff_abs = dFDFobs*cutoff
        if noise_type is 'theory':
            cutoff_abs = dFDFth*cutoff

        cleanFDF, ccArr, iterCountArr, residFDF = do_rmclean_hogbom(dirtyFDF = FDF,
                                phiArr_radm2    = phiArr_radm2,
                                RMSFArr         = RMSFArr,
                                phi2Arr_radm2   = phi2Arr_radm2,
                                fwhmRMSFArr     = fwhmRMSF_radm2,
                                cutoff          = cutoff_abs,
    #                                 maxIter         = maxIter,
    #                                 gain            = gain,
    #                                 verbose         = verbose,
                                doPlots         = True)


        FDF_max=np.argmax(abs(cleanFDF))
        FDF_med=np.median(abs(cleanFDF))

        dFDFobs=np.median(abs(abs(cleanFDF)-FDF_med)) / np.sqrt(np.pi/2) #MADFM definition of noise                                                      #   dFDF_obs=np.nanstd(abs(FDF)) #std. definition of noise                                                                                      
        if noise_type is 'observed':
            FDF_snr_clean=abs((abs(cleanFDF)-FDF_med)/dFDFobs)/2
            ccArr_snr=(abs(ccArr)/dFDFobs)/2
        if noise_type is 'theory':
            FDF_snr_clean=abs((abs(cleanFDF)-FDF_med)/dFDFth)/2
            ccArr_snr=(abs(ccArr)/dFDFth)/2

        mDict = measure_FDF_parms(FDF     = cleanFDF,
                              phiArr      = phiArr_radm2,
                              fwhmRMSF    = fwhmRMSF_radm2,
                              dFDF        = dFDFth, #FDF_noise
                              lamSqArr_m2 = lambdaSqArr_m2,
                              lam0Sq      = lam0Sq_m2)

        RM_radm2_fit=mDict["phiPeakPIfit_rm2"]
        dRM_radm2_fit=mDict["dPhiPeakPIfit_rm2"]

        RM_radm2=phiArr_radm2[FDF_max]
        dRM_radm2=fwhmRMSF_radm2/(2*FDF_snr_clean.max())

        polAngle0Fit_deg=mDict["polAngle0Fit_deg"]
        dPolAngle0Fit_deg=mDict["dPolAngle0Fit_deg"] * np.sqrt(freqArr.size) # np.sqrt(freqArr.size) term corrects for band-average noise 


        if diagnostic_plots:
            fig, ax = plt.subplots(2,1, figsize=(20,10))
            plt.subplots_adjust(left=0.1, bottom=0.1, right=0.99, top=0.95, wspace=0)
            ax[0].set_title('Faraday Dispersion Function')    
            ax[0].plot(phiArr_radm2,FDF_snr_clean, label='clean FDF')
            ax[0].plot(phiArr_radm2,FDF_snr, label='dirty FDF')
            ax[0].axhline(cutoff, ls='--', color='k', label='clean cutoff')
            ax[0].legend()
            ax[0].set_xlim([phiArr_radm2.min(), phiArr_radm2.max()])
            ax[1].plot(phiArr_radm2,FDF_snr_clean, label='clean FDF')
            ax[1].plot(phiArr_radm2,FDF_snr, label='dirty FDF')
            ax[1].axhline(cutoff, ls='--', color='k',label='clean cutoff')
            ax[1].legend()
            ax[1].set_xlim(phiArr_radm2[FDF_max]-300,phiArr_radm2[FDF_max]+300)
            ax[1].set_xlabel('$\phi$ [rad/m$^2$]')
            fig.text(0.03, 0.5, 'Polarized Intensity [S/N]', va='center', rotation='vertical')

            if isinstance(diagnostic_plots, bool):
                plt.show()
            else:
                plot_name = "FDF.png"
                plt.savefig(os.path.join(diagnostic_plots, plot_name))
                plt.close("all")
                            
            fig, ax = plt.subplots(2,1,figsize=(20,10))
            plt.subplots_adjust(left=0.1, bottom=0.1, right=0.99, top=0.95, wspace=0, hspace=0.0)
            ax[0].set_title('Faraday Dispersion Function')    
            ax[0].plot(phi2Arr_radm2,RMSFArr, label='RMTF')
            ax[0].set_xlim([-300,300])
            ax[0].xaxis.set_ticklabels([])
            ax[0].legend()
            ax[1].plot(phiArr_radm2,FDF_snr_clean, label='clean FDF')
            ax[1].bar(phiArr_radm2,ccArr_snr, color='g', label='clean components')
            ax[1].legend()
            ax[1].set_xlim(phiArr_radm2[FDF_max]-300,phiArr_radm2[FDF_max]+300)
            ax[1].set_xlabel('$\phi$ [rad/m$^2$]')
            fig.text(0.03, 0.5, 'Polarized Intensity [S/N]', va='center', rotation='vertical')
            #     plt.legend(fontsize=20)
            #     plt.tight_layout()

            if isinstance(diagnostic_plots, bool): 
                plt.show()
            else:
                plot_name = "FDF_clean.png"
                plt.savefig(os.path.join(diagnostic_plots, plot_name))
                plt.close("all")

        return (RM_radm2_fit,RM_radm2,dRM_radm2_fit,dRM_radm2,polAngle0Fit_deg,dPolAngle0Fit_deg),(phiArr_radm2,FDF_snr_clean)