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', e_num=1): """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? 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_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, mylist = do_rmsynth_planes( dataQ=qArr, dataU=uArr, lambdaSqArr_m2=lambdaSqArr_m2, phiArr_radm2=phiArr_radm2, weightArr=weightArr, nBits=nBits, verbose=verbose, log=log, e_num=e_num) # 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! weightArr = np.where(np.isnan(weightArr), 0.0, weightArr) dFDFth = np.sqrt( np.sum(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['dQUArr'] = dQUArr if fitDict["fitStatus"] >= 128: log("WARNING: Stokes I model contains negative values!") elif fitDict["fitStatus"] >= 64: 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) 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) myfig = plotmylist(mylist) plt.show() myfig.show() # 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, mylist
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")
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()
def determine_RMSF_parameters(freq_array, weights_array, phi_max, dphi, plotfile=None, plotname=None): """ Characterizes an RMSF given the supplied frequency and weight arrays. Prints the results to terminal. Inputs: freq_array: array of frequency values (in Hz) weights_array: array of channel weights (arbitrary units) phi_max (float): maximum Faraday depth to compute RMSF out to. dphi (float): step size in Faraday depth plotfile (str): file name and path to save RMSF plot. plotname (str): title of plot """ lambda2_array = C**2 / freq_array**2 l2_min = np.min(lambda2_array) l2_max = np.max(lambda2_array) dl2 = np.median(np.abs(np.diff(lambda2_array))) if phi_max == None: phi_max = 10 * 2 * np.sqrt(3.0) / (l2_max - l2_min) #~10*FWHM if dphi == None: dphi = 0.1 * 2 * np.sqrt(3.0) / (l2_max - l2_min) #~10*FWHM phi_array = np.arange(-1 * phi_max, phi_max + 1e-6, dphi) RMSFcube, phi2Arr, fwhmRMSFArr, statArr = get_rmsf_planes( lambda2_array, phi_array, weightArr=weights_array, fitRMSF=True) #Output key results to terminal: print('RMSF PROPERTIES:') print('Theoretical (unweighted) FWHM: {:.4g} rad m^-2'.format( 2 * np.sqrt(3.0) / (l2_max - l2_min))) print('Measured FWHM: {:.4g} rad m^-2'.format( fwhmRMSFArr)) print('Theoretical largest FD scale probed: {:.4g} rad m^-2'.format( np.pi / l2_min)) print('Theoretical maximum FD*: {:.4g} rad m^-2'.format( np.sqrt(3.0) / dl2)) print( '*50% bandwdith depolarization threshold, for median channel width in Delta-lambda^2' ) print( '* may not be reliable over very large fractional bandwidths or in data with ' ) print('differing channel widths or many frequency gaps.') #Explanation for below: This code find the local maxima in the positive half of the RMSF, #finds the highest amplitude one, and calls that the first sidelobe. x = np.diff(np.sign(np.diff(np.abs( RMSFcube[RMSFcube.size // 2:])))) #-2=local max, +2=local min y = 1 + np.where(x == -2)[ 0] #indices of peaks, +1 is because of offset from double differencing peaks = np.abs(RMSFcube[RMSFcube.size // 2:])[y] print('First sidelobe FD and amplitude: {:.4g} rad m^-2'.format( phi2Arr[phi2Arr.size // 2:][y[np.argmax(peaks)]])) print(' {:.4g} % of peak'.format( np.max(peaks) * 100)) #Plotting: plt.figure(figsize=(7, 7)) plt.subplot(211) plt.axhline(0, color='k') if plotname == None: plt.title('Simulated RMSF') else: plt.title(plotname) plt.plot(phi2Arr, np.real(RMSFcube), 'b-', label='Stokes Q') plt.plot(phi2Arr, np.imag(RMSFcube), 'r--', label='Stokes U') plt.plot(phi2Arr, np.abs(RMSFcube), 'k-', label='Amplitude') plt.legend() plt.xlabel('Faraday depth (rad m$^{-2}$)') plt.ylabel('RMSF (unitless)') plt.subplot(212) ax = plt.gca() ax.axis([0, 1, 0, 1]) ax.axis('off') ax.text(0.1, 0.8, ('Theoretical (unweighted) FWHM: {:.4g} rad m^-2\n' + 'Measured FWHM: {:.4g} rad m^-2\n' + 'Theoretical largest FD scale probed: {:.4g} rad m^-2\n' + 'Theoretical maximum FD: {:.4g} rad m^-2\n' + 'First sidelobe FD and amplitude: {:.4g} rad m^-2\n' + ' {:.4g} % of peak\n\n' + 'Lowest frequency/wavelength [GHz/cm]: {:>7.4g}/{:.4g}\n' + 'Highest frequency/wavelength [GHz/cm]: {:>7.4g}/{:.4g}\n' + '# of channels: {:.4g}\n').format( 2 * np.sqrt(3.0) / (l2_max - l2_min), fwhmRMSFArr, np.pi / l2_min, np.sqrt(3.0) / dl2, phi2Arr[phi2Arr.size // 2:][y[np.argmax(peaks)]], np.max(peaks) * 100, np.min(freq_array) / 1e9, C / np.min(freq_array) * 100., np.max(freq_array) / 1e9, C / np.max(freq_array) * 100., freq_array.size), family='monospace', horizontalalignment='left', verticalalignment='top') # ax.text(0.,0.7,('Theoretical (unweighted) FWHM: {:.4g} rad m^-2'.format(2*np.sqrt(3.0) / (l2_max-l2_min))) # ax.text(0.,0.58,'Measured FWHM: {:.4g} rad m^-2'.format(fwhmRMSFArr)) # ax.text(0.,0.46,'Theoretical largest FD scale probed: {:.4g} rad m^-2'.format(np.pi/l2_min)) # ax.text(0.,0.34,'Theoretical maximum FD: {:.4g} rad m^-2'.format(np.sqrt(3.0)/dl2)) # ax.text(0.,0.22,'First sidelobe FD and amplitude: {:.4g} rad m^-2'.format(phi2Arr[phi2Arr.size//2:][y[np.argmax(peaks)]])) # ax.text(0.,0.1,' {:.4g} % of peak'.format(np.max(peaks)*100)) if plotfile != None: plt.savefig(plotfile, bbox_inches='tight') else: plt.show()
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
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()
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): """ 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_Jy, Q_Jy, U_Jy, dI_Jy, dQ_Jy, dU_Jy try: if verbose: log("> Trying [freq_Hz, I_Jy, Q_Jy, U_Jy, dI_Jy, dQ_Jy, dU_Jy]", end=' ') (freqArr_Hz, IArr_Jy, QArr_Jy, UArr_Jy, dIArr_Jy, dQArr_Jy, dUArr_Jy) = data if verbose: log("... success.") except Exception: if verbose: log("...failed.") # freq_Hz, q_Jy, u_Jy, dq_Jy, du_Jy try: if verbose: log("> Trying [freq_Hz, q_Jy, u_Jy, dq_Jy, du_Jy]", end=' ') (freqArr_Hz, QArr_Jy, UArr_Jy, dQArr_Jy, dUArr_Jy) = 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_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: if verbose: log("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 # 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_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 = 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_mJy, 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=True, 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 = True, 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 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 !!Old formula only works for wariance weights! #dFDFth_Jybm = np.sqrt(1./np.sum(1./dQUArr_Jy**2.)) dFDFth_Jybm = np.sqrt( np.sum(weightArr**2 * dQUArr_Jy**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_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) if mDict['phiPeakPIfit_rm2'] == None: log('Peak is at edge of RM spectrum! Peak fitting failed!\n') log('Rerunning with Phi_max twice as large.') #The following code re-runs everything with higher phiMax, #Then overwrite the appropriate variables so as to continue on without #interuption. mDict, aDict = run_rmsynth(data=data, polyOrd=polyOrd, phiMax_radm2=phiMax_radm2 * 2, dPhi_radm2=dPhi_radm2, nSamples=nSamples, weightType=weightType, fitRMSF=fitRMSF, noStokesI=noStokesI, nBits=nBits, showPlots=False, debug=debug, verbose=verbose) phiArr_radm2 = aDict["phiArr_radm2"] phi2Arr_radm2 = aDict["phi2Arr_radm2"] RMSFArr = aDict["RMSFArr"] freqArr_Hz = aDict["freqArr_Hz"] weightArr = aDict["weightArr"] dirtyFDF = aDict["dirtyFDF"] # 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() #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 mJy/beam' % (mDict["Ifreq0_mJybm"])) log('Peak PI = %.4g (+/-%.4g) mJy/beam' % (mDict["ampPeakPIfit_Jybm"] * 1e3, mDict["dAmpPeakPIfit_Jybm"] * 1e3)) log('QU Noise = %.4g mJy/beam' % (mDict["dQU_Jybm"] * 1e3)) log('FDF Noise (theory) = %.4g mJy/beam' % (mDict["dFDFth_Jybm"] * 1e3)) log('FDF Noise (Corrected MAD) = %.4g mJy/beam' % (mDict["dFDFcorMAD_Jybm"] * 1e3)) log('FDF Noise (rms) = %.4g mJy/beam' % (mDict["dFDFrms_Jybm"] * 1e3)) 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) # 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
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)