def pixelwise_peak_fitting(FDF, phiArr, fwhmRMSF, lamSqArr_m2, lam0Sq, product_list, noiseArr=None, stokesIcube=None): """ Performs the 1D FDF peak fitting used in RMsynth/RMclean_1D, pixelwise on all pixels in a 3D FDF cube. Inputs: FDF: FDF cube (3D array). This is assumed to be in astropy axis ordering (Phi, dec, ra) phiArr: (1D) array of phi values fwhmRMSF: 2D array of RMSF FWHM values lamSqArr_m2: 1D array of channel lambda^2 values. lam0Sq: scalar value for lambda^2_0, the reference wavelength squared. product_list: list containing the names of the fitting products to save. dFDF: 2D array of theoretical noise values. If not supplied, the peak fitting will default to using the measured noise. Outputs: dictionary of 2D maps, 1 per fit output """ #FDF: output by synth3d or clean3d #phiArr: can be generated from FDF cube #fwhm: 2D map produced synth3D #dFDFth: not currently produced (default mode not to input noise!) # If not present, measure_FDF_parms uses the corMAD noise. # #lamSqArr is only needed for computing errors in derotated angles # This could be compressed to a map or single value from RMsynth? #lam0Sq is necessary for de-rotation map_size = FDF.shape[1:] #Create pixel location arrays: xarr, yarr = np.meshgrid(range(map_size[0]), range(map_size[1])) xarr = xarr.ravel() yarr = yarr.ravel() #Create empty maps: map_dict = {} for parameter in product_list: map_dict[parameter] = np.zeros(map_size) freqArr_Hz = C / np.sqrt(lamSqArr_m2) freq0_Hz = C / np.sqrt(lam0Sq) if stokesIcube is not None: idx = np.abs(freqArr_Hz - freq0_Hz).argmin() if freqArr_Hz[idx] < freq0_Hz: Ifreq0Arr = interp_images(stokesIcube[idx, :, :], stokesIcube[idx + 1, :, :], f=0.5) elif freqArr_Hz[idx] > freq0_Hz: Ifreq0Arr = interp_images(stokesIcube[idx - 1, :, :], stokesIcube[idx, :, :], f=0.5) else: Ifreq0Arr = stokesIcube[idx, :, :] else: Ifreq0Arr = np.ones(map_size) stokesIcube = np.ones((freqArr_Hz.size, map_size[0], map_size[1])) #compute weights if needed: if noiseArr is not None: weightArr = 1.0 / np.power(noiseArr, 2.0) weightArr = np.where(np.isnan(weightArr), 0.0, weightArr) dFDF = Ifreq0Arr * np.sqrt( np.sum(weightArr**2 * np.nan_to_num(noiseArr)**2) / (np.sum(weightArr))**2) else: weightArr = np.ones(lamSqArr_m2.shape, dtype=np.float32) dFDF = None #Run fitting pixel-wise: progress(40, 0) for i in range(xarr.size): FDF_pix = FDF[:, xarr[i], yarr[i]] fwhmRMSF_pix = fwhmRMSF[xarr[i], yarr[i]] if type(dFDF) == type(None): dFDF_pix = None else: dFDF_pix = dFDF[xarr[i], yarr[i]] try: mDict = measure_FDF_parms(FDF_pix, phiArr, fwhmRMSF_pix, dFDF=dFDF_pix, lamSqArr_m2=lamSqArr_m2, lam0Sq=lam0Sq, snrDoBiasCorrect=5.0) #Add keywords not included by the above function: mDict['lam0Sq_m2'] = lam0Sq mDict['freq0_Hz'] = freq0_Hz mDict['fwhmRMSF'] = fwhmRMSF_pix mDict['Ifreq0'] = Ifreq0Arr[xarr[i], yarr[i]] mDict['fracPol'] = mDict["ampPeakPIfit"] / mDict['Ifreq0'] mDict["min_freq"] = float(np.min(freqArr_Hz)) mDict["max_freq"] = float(np.max(freqArr_Hz)) mDict["N_channels"] = lamSqArr_m2.size mDict["median_channel_width"] = float( np.median(np.diff(freqArr_Hz))) if dFDF_pix is not None: mDict['dFDFth'] = dFDF_pix else: mDict['dFDFth'] = np.nan for parameter in product_list: map_dict[parameter][xarr[i], yarr[i]] = mDict[parameter] except: for parameter in product_list: map_dict[parameter][xarr[i], yarr[i]] = np.nan if i % 100 == 0: progress(40, i / xarr.size * 100) return map_dict
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(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(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()