def multiply_cube_by_value(infile, value, brightness_unit, huge_cube_workaround=True): """ Multiply a cube by some value, and update the brightness unit accordingly. Includes a switch for large cubes, where getchunk/putchunk may fail. """ if huge_cube_workaround: casaStuff.exportfits(imagename=infile, fitsimage=infile + '.fits', overwrite=True) hdu = pyfits.open(infile + '.fits')[0] hdu.data *= value hdu.writeto(infile + '.fits', clobber=True) casaStuff.importfits(fitsimage=infile + '.fits', imagename=infile, overwrite=True) os.system('rm -rf ' + infile + '.fits') else: myia = au.createCasaTool(casaStuff.iatool) myia.open(infile) vals = myia.getchunk() vals *= value myia.putchunk(vals) myia.close() myia = au.createCasaTool(casaStuff.iatool) myia.open(infile) myia.setbrightnessunit(brightness_unit) myia.close() return True
def apply_additional_mask( old_mask_file=None, new_mask_file=None, new_thresh=0.0, operation='AND' ): """ Combine a mask with another mask on the same grid and some threshold. Can run AND/OR operations. Can be used to apply primary beam based masks by setting the PB file to new_mask_file and the pb_limit as new_thresh. """ if root_mask == None: logger.info("Specify a cube root file name.") return myia = au.createCasaTool(casaStuff.iatool) myia.open(new_mask_file) new_mask = myia.getchunk() myia.close() myia.open(old_mask_file) mask = myia.getchunk() if operation == "AND": mask *= (new_mask > new_thresh) else: mask = (mask + (new_mask > new_thresh)) >= 1.0 myia.putchunk(mask) myia.close() return
def get_mask(infile, huge_cube_workaround=True): """ Get a mask from a CASA image file. Includes a switch for large cubes, where getchunk can segfault. """ if huge_cube_workaround: os.system('rm -rf ' + infile + '.temp_deg_ordered') casaStuff.imtrans(imagename=infile + '.temp_deg', outfile=infile + '.temp_deg_ordered', order='0132') casaStuff.makemask(mode='copy', inpimage=infile + '.temp_deg_ordered', inpmask=infile + '.temp_deg_ordered:mask0', output=infile + '.temp_mask', overwrite=True) casaStuff.exportfits(imagename=infile + '.temp_mask', fitsimage=infile + '.temp.fits', stokeslast=False, overwrite=True) hdu = pyfits.open(infile + '.temp.fits')[0] mask = hdu.data.T[:, :, 0, :] os.system('rm -rf ' + infile + '.temp_deg_ordered') os.system('rm -rf ' + infile + '.temp_mask') os.system('rm -rf ' + infile + '.temp.fits') else: myia = au.createCasaTool(casaStuff.iatool) myia.open(infile+'.temp_deg') mask = myia.getchunk(getmask=True) myia.close() os.system('rm -rf ' + infile + '.temp_deg') return mask
def copy_mask(infile, outfile, huge_cube_workaround=True): """ Copy a mask from infile to outfile. Includes a switch for large cubes, where getchunk/putchunk can segfault """ if huge_cube_workaround: os.system('rm -rf ' + outfile + '/mask0') os.system('cp -r ' + infile + '/mask0' + ' ' + outfile + '/mask0') else: myia = au.createCasaTool(casaStuff.iatool) myia.open(infile) mask = myia.getchunk(getmask=True) myia.close() myia = au.createCasaTool(casaStuff.iatool) myia.open(outfile) mask = myia.putregion(pixelmask=mask) myia.close() return True
def calc_residual_statistics( resid_name=None, mask_name=None, ): """ """ if os.path.isdir(resid_name) == False: logger.error('Error! The input file "' + resid_name + '" was not found!') return if os.path.isdir(mask_name) == False: logger.error('Error! The input file "' + mask_name + '" was not found!') return myia = au.createCasaTool(casaStuff.iatool) myia.open(mask_name) mask = myia.getchunk() myia.close() myia.open(resid_name) resid = myia.getchunk() myia.close() vec = resid[((mask == 1) * np.isfinite(resid))] del mask del resid current_noise = cmr.noise_for_cube(infile=resid_name, method='chauvmad', niter=5) out_dict = { 'cubename': resid_name, 'maskname': mask_name, 'max': np.max(vec), 'p99': np.percentile(vec, 99), 'p95': np.percentile(vec, 95), 'p90': np.percentile(vec, 90), 'noise': current_noise, } return (out_dict)
def noise_for_cube( infile=None, maskfile=None, exclude_mask=True, method='mad', niter=None, ): """ Get a single noise estimate for an image cube. """ if infile is None: logger.error('No infile specified.') return (None) if not os.path.isdir(infile) and not os.path.isfile(infile): logger.error('infile specified but not found - ' + infile) return (None) if maskfile is not None: if not os.path.isdir(maskfile) and not os.path.isfile(maskfile): logger.error('maskfile specified but not found - ' + maskfile) return (None) myia = au.createCasaTool(casaStuff.iatool) myia.open(infile) data = myia.getchunk() mask = myia.getchunk(getmask=True) myia.close() if maskfile is not None: myia.open(maskfile) user_mask = myia.getchunk() user_mask_mask = myia.getchunk(getmask=True) myia.close() if exclude_mask: mask = mask * user_mask_mask * (user_mask < 0.5) else: mask = mask * user_mask_mask * (user_mask >= 0.5) this_noise = estimate_noise(data=data, mask=mask, method=method, niter=niter) return (this_noise)
def read_cube(infile, allow_huge=True): """ Read cube from CASA image file. Includes a switch for large cubes, where getchunk may fail. """ if allow_huge: casaStuff.exportfits(imagename=infile, fitsimage=infile + '.fits', stokeslast=False, overwrite=True) hdu = pyfits.open(infile + '.fits')[0] cube = hdu.data.T # Remove intermediate fits file os.system('rm -rf ' + infile + '.fits') else: myia = au.createCasaTool(casaStuff.iatool) myia.open(infile) cube = myia.getchunk() myia.close() return cube
def trim_rind( infile=None, outfile=None, overwrite=False, inplace=True, pixels=1 ): if infile is None or outfile is None: logger.error("Missing required input.") return(False) if os.path.isdir(infile) == False: logger.error("Input file not found: "+infile) return(False) if inplace == False: if os.path.isdir(outfile) or os.path.isfile(outfile): if overwrite: os.system('rm -rf '+outfile) else: logger.error("Output file already present: "+outfile) return(False) os.system('cp -r '+infile+' '+outfile) target_file = outfile else: target_file = infile # Figure out the extent of the image inside the cube myia = au.createCasaTool(casaStuff.iatool) myia.open(target_file) mask = myia.getchunk(getmask=True) elt = nd.generate_binary_structure(2,1) if pixels > 1: elt = nd.iterate_structure(elt, pixels-1) mask = nd.binary_erosion(mask, elt[:,:,np.newaxis, np.newaxis]) myia.putregion(pixelmask=mask) myia.close() return(True)
# CASA imports from taskinit import * from importfits import importfits from imregrid import imregrid from imsmooth import imsmooth from imval import imval from immoments import immoments from immath import immath from makemask import makemask from imhead import imhead from imstat import imstat from exportfits import exportfits import analysisUtils as aU mycl = aU.createCasaTool(cltool) mycs = aU.createCasaTool(cstool) myia = aU.createCasaTool(iatool) myrg = aU.createCasaTool(rgtool) myqa = aU.createCasaTool(qatool) ##################### ### Parameters ##################### dir_proj = "/Users/saito/data/proj_jwu/" image_co10 = dir_proj + "image_co10/co10.moment0" ##################### ### Functions #####################
def import_and_align_mask( in_file=None, out_file=None, template=None, blank_to_match=False, ): """ Align a mask to a target astrometry. This includes some klugy steps (especially related to axes and interpolation) to make this work, e.g., for clean masks, most of the time. """ # Import from FITS (could make optional) os.system('rm -rf ' + out_file + '.temp_copy' + ' 2>/dev/null') logger.debug('Importing mask file: "' + in_file + '"') casaStuff.importfits(fitsimage=in_file, imagename=out_file + '.temp_copy', overwrite=True) # Prepare analysis utility tool myia = au.createCasaTool(casaStuff.iatool) myim = au.createCasaTool(casaStuff.imtool) # Read mask data # myia.open(out_file+'.temp_copy') # mask = myia.getchunk(dropdeg=True) # myia.close() # print('**********************') # print('type(mask)', type(mask), 'mask.dtype', mask.dtype, 'mask.shape', mask.shape) # note that here the mask is in F dimension order, not Pythonic. # print(np.max(mask), np.min(mask)) # print('**********************') # Read template image header hdr = casaStuff.imhead(template) # print('hdr', hdr) maskhdr = casaStuff.imhead(out_file + '.temp_copy') # print('maskhdr', maskhdr) # Check if 2D or 3D logger.debug('Template data axis names: ' + str(hdr['axisnames']) + ', shape: ' + str(hdr['shape'])) logger.debug('Mask data axis names: ' + str(maskhdr['axisnames']) + ', shape: ' + str(maskhdr['shape'])) is_template_2D = (np.prod(list(hdr['shape'])) == np.prod(list(hdr['shape'])[0:2])) is_mask_2D = (np.prod(list(maskhdr['shape'])) == np.prod(list(maskhdr['shape'])[0:2])) if is_template_2D and not is_mask_2D: logger.debug('Template image is 2D but mask is 3D, collapsing the mask over channel axes: ' + str( np.arange(maskhdr['ndim'] - 1, 2 - 1, -1))) # read mask array myia.open(out_file + '.temp_copy') mask = myia.getchunk(dropdeg=True) myia.close() # print('**********************') # print('type(mask)', type(mask), 'mask.dtype', mask.dtype, 'mask.shape', mask.shape) # Note that here array shapes are in F dimension order, i.e., axis 0 is RA, axis 1 is Dec, axis 2 is Frequency, etc. # print('**********************') # # collapse channel and higher axes # mask = np.any(mask.astype(int).astype(bool), axis=np.arange(maskhdr['ndim']-1, 2-1, -1)) # Note that here array shapes are in F dimension order, i.e., axis 0 is RA, axis 1 is Dec, axis 2 is Frequency, etc. # mask = mask.astype(int) # while len(mask.shape) < len(hdr['shape']): # mask = np.expand_dims(mask, axis=len(mask.shape)) # Note that here array shapes are in F dimension order, i.e., axis 0 is RA, axis 1 is Dec, axis 2 is Frequency, etc. # print('**********************') # print('type(mask)', type(mask), 'mask.dtype', mask.dtype, 'mask.shape', mask.shape) # Note that here array shapes are in F dimension order, i.e., axis 0 is RA, axis 1 is Dec, axis 2 is Frequency, etc. # print('**********************') # os.system('rm -rf '+out_file+'.temp_collapsed'+' 2>/dev/null') ##myia.open(out_file+'.temp_copy') # newimage = myia.newimagefromarray(outfile=out_file+'.temp_collapsed', pixels=mask.astype(int), overwrite=True) # newimage.done() # myia.close() # # collapse channel and higher axes os.system('rm -rf ' + out_file + '.temp_collapsed' + ' 2>/dev/null') myia.open(out_file + '.temp_copy') collapsed = myia.collapse(outfile=out_file + '.temp_collapsed', function='max', axes=np.arange(maskhdr['ndim'] - 1, 2 - 1, -1)) collapsed.done() myia.close() # ia tools -- https://casa.nrao.edu/docs/CasaRef/image-Tool.html os.system('rm -rf ' + out_file + '.temp_copy' + ' 2>/dev/null') os.system('cp -r ' + out_file + '.temp_collapsed' + ' ' + out_file + '.temp_copy' + ' 2>/dev/null') os.system('rm -rf ' + out_file + '.temp_collapsed' + ' 2>/dev/null') # Align to the template grid os.system('rm -rf ' + out_file + '.temp_aligned' + ' 2>/dev/null') casaStuff.imregrid(imagename=out_file + '.temp_copy', template=template, output=out_file + '.temp_aligned', asvelocity=True, interpolation='nearest', replicate=False, overwrite=True) # Make an EXACT copy of the template, avoids various annoying edge cases os.system('rm -rf ' + out_file + ' 2>/dev/null') myim.mask(image=template, mask=out_file) # Pull the data out of the aligned mask and place it in the output file myia.open(out_file + '.temp_aligned') mask = myia.getchunk(dropdeg=True) myia.close() # If requested, blank the mask wherever the cube is non-finite. if blank_to_match: myia.open(template) nans = np.invert(myia.getchunk(dropdeg=True, getmask=True)) myia.close() mask[nans] = 0.0 # Shove the mask into the data set if is_template_2D and not is_mask_2D: while len(mask.shape) < len(hdr['shape']): mask = np.expand_dims(mask, axis=len(mask.shape)) # print('**********************') # print('type(mask)', type(mask), 'mask.dtype', mask.dtype, 'mask.shape', mask.shape) # Note that here array shapes are in F dimension order, i.e., axis 0 is RA, axis 1 is Dec, axis 2 is Frequency, etc. # print('**********************') myia.open(out_file) data = myia.getchunk(dropdeg=False) data = mask myia.putchunk(data) myia.close() else: # Need to make sure this works for two dimensional cases, too. if (hdr['axisnames'][3] == 'Frequency') and (hdr['ndim'] == 4): myia.open(out_file) data = myia.getchunk(dropdeg=False) data[:, :, 0, :] = mask myia.putchunk(data) myia.close() elif (hdr['axisnames'][2] == 'Frequency') and (hdr['ndim'] == 4): myia.open(out_file) data = myia.getchunk(dropdeg=False) data[:, :, :, 0] = mask myia.putchunk(data) myia.close() else: logger.info("ALERT! Did not find a case.") os.system('rm -rf ' + out_file + '.temp_copy' + ' 2>/dev/null') os.system('rm -rf ' + out_file + '.temp_aligned' + ' 2>/dev/null') return ()
def checksource(overwrite=True, verbose=False, subdir='', splitcal_vis=''): """ Images the phasecal and check source in a manually-calibrated dataset and reports statistics. Expects to find the *.split.cal measurement set and the corresponding .fluxscale file for it. Inputs: overwrite: if True, overwrite any existing image from a previous execution splitcal_vis: defaults to *.cal, but can be specified as list of strings, or a comma-delimited string Outputs: png image plots of each calibrator, and an ASCII file for each dataset The name of the ASCII file, and a list of pngs are returned. """ # Read the dataset(s) and get properties if (splitcal_vis == ''): vislist = glob.glob('*.cal') else: if (type(splitcal_vis) == str): vislist = splitcal_vis.split(',') else: vislist = splitcal_vis print("Checking datasets: ", vislist) mymsmd = au.createCasaTool(msmdtool) if (len(subdir) > 0): if (os.path.exists(subdir)): if (subdir[-1] != '/'): subdir += '/' else: os.mkdir(subdir) if (subdir[-1] != '/'): subdir += '/' pnglist = [] textfiles = [] for vis in vislist: mymsmd.open(vis) freq = mymsmd.meanfreq(0, unit='GHz') # Check Source check = mymsmd.fieldsforintent('OBSERVE_CHECK_SOURCE*', True)[0] checkid = mymsmd.fieldsforintent('OBSERVE_CHECK_SOURCE*', False)[0] checkpos = mymsmd.phasecenter(checkid) # Phase calibrator phase = mymsmd.fieldsforintent('CALIBRATE_PHASE*', True)[0] phaseid = mymsmd.fieldsforintent('CALIBRATE_PHASE*', False)[0] phasepos = mymsmd.phasecenter(phaseid) if ('OBSERVE_TARGET#ON_SOURCE' in mymsmd.intents()): nScienceFields = len( mymsmd.fieldsforintent('OBSERVE_TARGET*', False)) science = mymsmd.fieldsforintent('OBSERVE_TARGET*', True)[0] scienceid = mymsmd.fieldsforintent('OBSERVE_TARGET*', False)[0] else: nScienceFields = 0 mymsmd.done() floatcell = au.pickCellSize(vis, maxBaselinePercentile=99, verbose=verbose) cell = au.pickCellSize(vis, maxBaselinePercentile=99, cellstring=True, verbose=verbose) # imsize = int(au.nextValidImsize(int(5.0/floatcell))) # valid when we only had checksources for synthBeam < 0.25 imsize = int( au.nextValidImsize( int( np.max([5.0, 5.0 * au.estimateSynthesizedBeam(vis)]) / floatcell))) print("imsize = ", imsize) region = 'circle[[%dpix , %dpix], 15pix ]' % (int( imsize / 2), int(imsize / 2)) if False: # original method (for bands 3-6 only) cell = str(np.round(0.015 * (100 / freq), 3)) + 'arcsec' if freq < 116.0: imsize = [320, 320] region = 'circle[[160pix , 160pix] ,15pix ]' else: imsize = [680, 680] region = 'circle[[340pix , 340pix] ,15pix ]' ################################### # IMAGE ################################### weighting = 'briggs' robust = 0.5 niter = 50 threshold = '0.0mJy' spw = '' separation = au.angularSeparationOfTwoFields(vis, checkid, phaseid) if (nScienceFields > 0): separation_pcal_science = au.angularSeparationOfTwoFields( vis, scienceid, phaseid) separation_check_science = au.angularSeparationOfTwoFields( vis, scienceid, checkid) fieldtype = ['checksource', 'phasecal'] field = [check, phase] for i, cal in enumerate(field): if (not os.path.exists(cal + '_' + vis + '.image') or overwrite): os.system('rm -rf ' + cal + '_' + vis + '.*') if verbose: print( "Running tclean('%s', field='%s', cell=%s, imsize=%s, ...)" % (vis, cal, str(cell), str(imsize))) tclean(vis=vis, imagename=cal + '_' + vis, field=cal, spw=spw, specmode='mfs', deconvolver='hogbom', imsize=imsize, cell=cell, weighting=weighting, robust=robust, niter=niter, threshold=threshold, interactive=False, mask=region, gridder='standard') png = subdir + fieldtype[i] + '_' + cal + '_' + vis + '.image.png' pnglist.append(png) au.imviewField(cal + '_' + vis + '.image', radius=30 * floatcell, contourImage=cal + '_' + vis + '.mask', levels=[1], plotfile=png) ################################### # ANALYZE ################################### ########### # PHASE ########### imagename = phase + '_' + vis if verbose: print("Running imfit('%s', region='%s')" % (imagename + '.image', region)) # Fit the phase source to get position and flux imagefit = imfit(imagename=imagename + '.image', region=region) fitresults = au.imfitparse(imagefit) # Compare the Positions phasepos_obs = au.direction2radec(phasepos) if fitresults is not None: phasepos_fit = ','.join(fitresults.split()[:2]) phasepos_diff = au.angularSeparationOfStrings( phasepos_obs, phasepos_fit, verbose=False) * 3600. # Compare the Flux densities peakIntensity = au.imagePeak(imagename + '.image') selffluxfile = glob.glob('*.fluxscale')[0] fluxscaleResult = au.fluxscaleParseLog(selffluxfile, field=phase) if fluxscaleResult is not None: selfflux = fluxscaleResult[0][0] phaseflux_fit = float(fitresults.split()[2]) phaseCoherence = 100 * peakIntensity / phaseflux_fit phaseflux_diff = 100 * (selfflux - phaseflux_fit) / selfflux # Print the final results and save to file textfile = subdir + 'calimage_results_' + vis + '.txt' textfiles.append(textfile) f = open(textfile, 'w') f.write( '\n*************************************************************\n\n' ) line = 'CHECK_SOURCE IMAGE ANALYSIS REPORT (version %s)\n' % version( short=True) writeOut(f, line) info = au.getFitsBeam(imagename + '.image') synthBeam = (info[0] * info[1])**0.5 if fitresults is None: line = "Phasecal %s: imfit failed" % (phase) elif fluxscaleResult is not None: line = "Phasecal %s: Position difference = %s arcsec = %s synth.beam, Flux %% difference = %s" % ( phase, au.roundFiguresToString(phasepos_diff, 3), au.roundFiguresToString(phasepos_diff / synthBeam, 3), au.roundFiguresToString(phaseflux_diff, 3)) writeOut(f, line) line = " coherence = peakIntensity/fittedFluxDensity = %s%%" % ( au.roundFiguresToString(phaseCoherence, 3)) else: line = "Phasecal %s: Position difference = %s arcsec = %s synth.beam" % ( phase, au.roundFiguresToString(phasepos_diff, 3), au.roundFiguresToString(phasepos_diff / synthBeam, 3)) writeOut(f, line) f.close() if fluxscaleResult is None: print( "Full checksource analysis is not supported if there is no flux calibrator" ) return textfiles, pnglist ########### # CHECK ########### imagename = check + '_' + vis # Fit the check source to get position and flux if verbose: print("Running imfit('%s', region='%s')" % (imagename + '.image', region)) imagefit = imfit(imagename=imagename + '.image', region=region) fitresults = au.imfitparse(imagefit, deconvolved=True) info = au.getFitsBeam(imagename + '.image') synthMajor, synthMinor = info[0:2] synthBeam = (info[0] * info[1])**0.5 # Compare the Positions checkpos_obs = au.direction2radec(checkpos) if fitresults is not None: checkpos_fit = ','.join(fitresults.split()[:2]) checkpos_diff = au.angularSeparationOfStrings( checkpos_obs, checkpos_fit, verbose=False) * 3600. # Compare the Flux densities selffluxfile = glob.glob('*.fluxscale')[0] results = au.fluxscaleParseLog(selffluxfile, field=check) peakIntensity = au.imagePeak(imagename + '.image') if (results is not None and fitresults is not None): selfflux = results[0][0] checkflux_fit = float(fitresults.split()[2]) checkflux_diff = 100 * (selfflux - checkflux_fit) / selfflux checkCoherence = 100 * peakIntensity / checkflux_fit if fitresults is not None: if verbose: print("Checksource fitresults: ", fitresults) deconvolvedMajor = float(fitresults.split()[5]) deconvolvedMinor = float(fitresults.split()[7]) # Print the final results and save to file f = open(textfile, 'a') if fitresults is None: line = "Checksource %s: imfit failed" % (phase) else: if (results is not None): line = "\nChecksource %s: Position difference = %s arcsec = %s synth.beam, Flux %% difference = %s" % ( check, au.roundFiguresToString(checkpos_diff, 3), au.roundFiguresToString(checkpos_diff / synthBeam, 3), au.roundFiguresToString(checkflux_diff, 3)) writeOut(f, line) line = " coherence = peakIntensity/fittedFluxDensity = %s%%" % ( au.roundFiguresToString(checkCoherence, 3)) else: line = "\nChecksource %s: Position difference = %s arcsec = %s synth.beam" % ( check, au.roundFiguresToString(checkpos_diff, 3), au.roundFiguresToString(checkpos_diff / synthBeam, 3)) writeOut(f, line) line = " beam size = %s x %s arcsec" % (au.roundFiguresToString( synthMajor, 3), au.roundFiguresToString(synthMinor, 3)) writeOut(f, line) line = " apparent deconvolved size = %s x %s arcsec = %s synth.beam area" % ( au.roundFiguresToString(deconvolvedMajor, 2), au.roundFiguresToString(deconvolvedMinor, 2), au.roundFiguresToString( deconvolvedMajor * deconvolvedMinor / (synthBeam**2), 2)) writeOut(f, line) line = " angular separation of phasecal to checksource = %s degree" % ( au.roundFiguresToString(separation, 3)) writeOut(f, line) if (nScienceFields > 0): if (nScienceFields > 1): modifier = 'first' else: modifier = 'only' line = " angular separation of phasecal to %s science field (%d) = %s degree" % ( modifier, scienceid, au.roundFiguresToString(separation_pcal_science, 3)) writeOut(f, line) line = " angular separation of checksource to %s science field (%d) = %s degree" % ( modifier, scienceid, au.roundFiguresToString(separation_check_science, 3)) writeOut(f, line) f.close() # end 'for' loop over vislist return textfiles, pnglist
def generate_weight_file( image_file=None, input_file=None, input_value=None, input_type='pb', outfile=None, scale_by_noise=False, mask_for_noise=None, noise_value=None, scale_by_factor=None, overwrite=False, ): """ Generate a weight image for use in a linear mosaic. The weight image will be used in the linear mosaicking as a weight, multiplied by the image file and then divided out. The optimal S/N choice is often 1/noise^2. The program gives some options to calculate a weight image of this type from the data or using the primary beam response. image_file : the data cube or image associated with the weighting. Used for noise calculation and to supply a template astrometry. Not always necessary. input_file : an input image of some type (see below) used to form the basis for the weight. input_value : an input value of some type (see below) used to form the basis for the weight. This is a single value. Only one of input_file or input_value can be set. input_type : the type of input. This can be - 'pb' for primary beam response (weight goes at pb^2) - 'noise' for a noise estimate (weight goes as 1/noise^2) - 'weight' for a weight value outfile : the name of the output weight file to write scale_by_noise (default False) : if True, then scale the weight image by 1/noise^2 . Either the noise_value is supplied or it will be calculated from the image. mask_for_noise : if supplied, the "True" values in this mask image will be passed to the noise estimation routine and excluded from the noise calculation. noise_value : the noise in the image. If not set, this will be calculated from the image. scale_by_factor : a factor that will be applied directly to the weight image. overwrite (default False) : Delete existing files. You probably want to set this to True but it's a user decision. """ # Check input if image_file is None and input_file is None: logger.error("I need either an input or an image template file.") return (None) if input_file is None and input_value is None: logger.error("I need either an input value or an input file.") return (None) if input_file is not None and input_value is not None: logger.error( "I need ONE OF an input value or an input file. Got both.") return (None) if outfile is None: logger.error("Specify output file.") return (None) if input_file is not None: valid_types = ['pb', 'noise', 'weight'] if input_type not in valid_types: logger.error("Valid input types are :" + str(valid_types)) return (None) if input_file is None and input_value is None: logger.error("Need either an input value or an input file.") return (None) if input_file is not None: if not os.path.isdir(input_file): logger.error("Missing input file directory - " + input_file) return (None) if image_file is not None: if not os.path.isdir(image_file): logger.error("Missing image file directory - " + image_file) return (None) # If scaling by noise is requested and no estimate is provided, # generate an estimate if scale_by_noise: if noise_value is None and image_file is None: logger.error( "I can only scale by the noise if I have a noise value or an image." ) return (None) if noise_value is None: logger.info("Calculating noise for " + image_file) # Could use kwargs here to simplify parameter passing. Fine right now, too. noise_value = cma.noise_for_cube( infile=image_file, maskfile=mask_for_noise, exclude_mask=True, method='chauvmad', niter=5, ) logger.info("Noise " + str(noise_value)) # Define the template for the astrometry if input_file is None: template = image_file else: template = input_file logger.debug('Template for weight file is: ' + template) # Check the output file if os.path.isdir(outfile) or os.path.isfile(outfile): if not overwrite: logger.error("File exists and overwrite set to false - " + outfile) return (None) os.system('rm -rf ' + outfile) # Copy the template and read the data into memory os.system("cp -r " + template + " " + outfile) myia = au.createCasaTool(casaStuff.iatool) myia.open(outfile) data = myia.getchunk() # Case 1 : We just have an input value. if input_file is None and input_value is not None: if input_type is 'noise': weight_value = 1. / input_value**2 if input_type is 'pb': weight_value = input_value**2 if input_type is 'weight': weight_value = input_value weight_image = data * 0.0 + weight_value # Case 2 : We have an input image. Read in the data and manipulate # it into a weight array. if input_file is not None: if input_type is 'noise': weight_image = 1. / data**2 if input_type is 'pb': weight_image = data**2 if input_type is 'weight': weight_image = data # Now we have a weight image. If request, scale the data by a factor. if scale_by_factor is not None: weight_image = weight_image * scale_by_factor # If request, scale the data by the inverse square of the noise estimate. if scale_by_noise: weight_image = weight_image * 1. / noise_value**2 # Put the data back into the file and close it. myia.putchunk(weight_image) myia.close() return (None)
def trim_cube( infile=None, outfile=None, overwrite=False, inplace=False, min_pixperbeam=3, pad=1 ): """ Trim empty space from around the edge of a cube. Also rebin the cube to smaller size, while ensuring a minimum number of pixels across the beam. Used to reduce the volume of cubes. """ if infile is None or outfile is None: logger.error("Missing required input.") return(False) if os.path.isdir(infile) == False: logger.error("Input file not found: "+infile) return(False) # First, rebin if needed hdr = casaStuff.imhead(infile) if (hdr['axisunits'][0] != 'rad'): logger.error("ERROR: Based on CASA experience. I expected units of radians. I did not find this. Returning.") logger.error("Adjust code or investigate file "+infile) return(False) pixel_as = abs(hdr['incr'][0]/np.pi*180.0*3600.) if (hdr['restoringbeam']['major']['unit'] != 'arcsec'): logger.error("ERROR: Based on CASA experience. I expected units of arcseconds for the beam. I did not find this. Returning.") logger.error("Adjust code or investigate file "+infile) return(False) bmaj = hdr['restoringbeam']['major']['value'] pix_per_beam = bmaj*1.0 / pixel_as*1.0 if pix_per_beam > 6: casaStuff.imrebin( imagename=infile, outfile=outfile+'.temp', factor=[2,2,1], crop=True, dropdeg=True, overwrite=overwrite, ) else: os.system('cp -r '+infile+' '+outfile+'.temp') # Figure out the extent of the image inside the cube myia = au.createCasaTool(casaStuff.iatool) myia.open(outfile + '.temp') myia.adddegaxes(outfile=outfile + '.temp_deg', stokes='I', overwrite=True) myia.close() mask = get_mask(outfile, huge_cube_workaround=True) this_shape = mask.shape mask_spec_x = np.sum(np.sum(mask*1.0,axis=2),axis=1) > 0 xmin = np.max([0,np.min(np.where(mask_spec_x))-pad]) xmax = np.min([np.max(np.where(mask_spec_x))+pad,mask.shape[0]-1]) mask_spec_y = np.sum(np.sum(mask*1.0,axis=2),axis=0) > 0 ymin = np.max([0,np.min(np.where(mask_spec_y))-pad]) ymax = np.min([np.max(np.where(mask_spec_y))+pad,mask.shape[1]-1]) mask_spec_z = np.sum(np.sum(mask*1.0,axis=0),axis=0) > 0 zmin = np.max([0,np.min(np.where(mask_spec_z))-pad]) zmax = np.min([np.max(np.where(mask_spec_z))+pad,mask.shape[2]-1]) box_string = ''+str(xmin)+','+str(ymin)+','+str(xmax)+','+str(ymax) chan_string = ''+str(zmin)+'~'+str(zmax) logger.info("... box selection: "+box_string) logger.info("... channel selection: "+chan_string) if overwrite: os.system('rm -rf '+outfile) casaStuff.imsubimage( imagename=outfile+'.temp', outfile=outfile, box=box_string, chans=chan_string, ) os.system('rm -rf '+outfile+'.temp') return(True)
def feather_two_cubes( interf_file=None, sd_file=None, out_file=None, do_blank=False, do_apodize=False, apod_file=None, apod_cutoff=-1.0, overwrite=False, ): """ Feather together interferometric and total power data using CASA's default approach. Optionally, first apply some steps to homogenize the two data sets. Assumes that the data have been prepared e.g., using the prep_sd_for_feather routine in this module. interf_file : the interferometric cube to feather. sd_file : the single dish cube to feather. out_file : the output file name do_blank (default False) : if True then blank masked and not-a-number values in the cubes. The idea is to make sure missing data are zero for the FFT (probably not an issue) and to make sure some regions don't appear in one map but not the other. This is probably not necessary, but CASA's treatment in feather is a bit unclear and the case of, e.g., a much more extended single dish map compared to the interferometer map comes up. do_apodize (default False) : if True, then apodize BOTH data sets using the provided apodization map. apod_file : if do_apodize is True, this file is the map used to scale both the interferometer and single dish data. apod_cutoff : the cutoff in the apodization file below which data are blanked. overwrite (default False) : Delete existing files. You probably want to set this to True but it's a user decision. """ # Check inputs if (os.path.isdir(sd_file) == False): logger.error("Single dish file not found: " + sd_file) return (False) if (os.path.isdir(interf_file) == False): logger.error("Interferometric file not found: " + interf_file) return (False) if do_apodize: if (os.path.isdir(apod_file) == False): logger.error("Apodization requested, but file not found: " + apod_file) return (False) # Initialize file handling os.system('rm -rf ' + sd_file + '.temp') os.system('rm -rf ' + interf_file + '.temp') os.system('rm -rf ' + out_file + '.temp') os.system('rm -rf ' + sd_file + '.temp.temp') os.system('rm -rf ' + interf_file + '.temp.temp') os.system('rm -rf ' + out_file + '.temp.temp') # If requested, manipulate blanked (NaN and mask) values to make # sure they are zeros. This should probably not be necessary, but # some aspects of CASA's procedures are unclear. The main thing # here is that the mask used is the COMBINED single dish and # interferometer map, so that only regions in common should # survive. if do_blank: current_interf_file = interf_file + '.temp' current_sd_file = sd_file + '.temp' os.system('cp -rf ' + interf_file + ' ' + current_interf_file) os.system('cp -rf ' + sd_file + ' ' + current_sd_file) myia = au.createCasaTool(casaStuff.iatool) myia.open(interf_file) interf_mask = myia.getchunk(getmask=True) myia.close() myia.open(sd_file) sd_mask = myia.getchunk(getmask=True) myia.close() # CASA calls unmasked values True and masked values False. The # region with values in both cubes is the product. combined_mask = sd_mask * interf_mask # This isn't a great solution. Just zero out the masked # values. It will do what we want in the FFT but the CASA mask # bookkeeping is being left in the dust. The workaround is # complicated, though, because you can't directly manipulate # pixel masks for some reason. if np.sum(combined_mask == False) > 0: myia.open(current_interf_file) interf_data = myia.getchunk() interf_data[combined_mask == False] = 0.0 myia.putchunk(interf_data) myia.close() myia.open(current_sd_file) sd_data = myia.getchunk() sd_data[combined_mask == False] = 0.0 myia.putchunk(sd_data) myia.close() else: current_interf_file = interf_file current_sd_file = sd_file # If apodization is requested, multiply both data sets by the same # taper and create a new, temporary output data set. if do_apodize: casaStuff.impbcor(imagename=current_sd_file, pbimage=apod_file, outfile=current_sd_file + '.temp', mode='multiply') current_sd_file = current_sd_file + '.temp' casaStuff.impbcor(imagename=current_interf_file, pbimage=apod_file, outfile=current_interf_file + '.temp', mode='multiply') current_interf_file = current_interf_file + '.temp' # Call feather, followed by an imsubimage to deal with degenerate # axis stuff. if overwrite: os.system('rm -rf ' + out_file) os.system('rm -rf ' + out_file + '.temp') casaStuff.feather(imagename=out_file + '.temp', highres=current_interf_file, lowres=current_sd_file, sdfactor=1.0, lowpassfiltersd=False) casaStuff.imsubimage(imagename=out_file + '.temp', outfile=out_file, dropdeg=True) os.system('rm -rf ' + out_file + '.temp') # If we apodized, now divide out the common kernel. if do_apodize: os.system('rm -rf ' + out_file + '.temp') os.system('mv ' + out_file + ' ' + out_file + '.temp') casaStuff.impbcor(imagename=out_file + '.temp', pbimage=apod_file, outfile=out_file, mode='divide', cutoff=apod_cutoff) # Remove temporary files os.system('rm -rf ' + sd_file + '.temp') os.system('rm -rf ' + interf_file + '.temp') os.system('rm -rf ' + out_file + '.temp') os.system('rm -rf ' + sd_file + '.temp.temp') os.system('rm -rf ' + interf_file + '.temp.temp') os.system('rm -rf ' + out_file + '.temp.temp') return (True)
def editIntents(msName='', field='', scan='', newintents='', help=False, append=False): """ Change the observation intents for a specified field. Adapted from John Lightfoot's interactive function for the ALMA pipeline. For further help and examples, run editIntents(help=True) or see http://casaguides.nrao.edu/index.php?title=EditIntents - T. Hunter """ validIntents = ['AMPLITUDE', 'ATMOSPHERE', 'BANDPASS', 'DELAY', 'FLUX', 'PHASE', 'SIDEBAND_RATIO', 'TARGET', 'WVR', 'POL_ANGLE', 'POL_LEAKAGE', 'CALIBRATE_AMPLI', 'SYS_CONFIG', 'CALIBRATE_ATMOSPHERE', 'CALIBRATE_BANDPASS', 'CALIBRATE_DELAY', 'CALIBRATE_FLUX', 'CALIBRATE_PHASE', 'CALIBRATE_SIDEBAND_RATIO', 'OBSERVE_TARGET', 'CALIBRATE_WVR', 'CALIBRATE_POL_ANGLE', 'CALIBRATE_POL_LEAKAGE'] if help: print("Use editIntents?? to see the call sequence.") return if msName == '': raise TypeError('msName must be given.') if field == '': raise TypeError('field must be given') mytb = au.createCasaTool(tbtool) mytb.open(msName + '/FIELD') fieldnames = mytb.getcol('NAME') print("Found fieldnames = {}".format(fieldnames)) mytb.close() mytb.open(msName + '/STATE') intentcol = mytb.getcol('OBS_MODE') intentcol = intentcol mytb.close() mytb.open(msName, nomodify=False) naddedrows = 0 if isinstance(newintents, list): desiredintents = '' for n in newintents: desiredintents += n if (n != newintents[-1]): desiredintents += ',' else: desiredintents = newintents desiredintentsList = desiredintents.split(',') for intent in desiredintentsList: if intent not in validIntents: print "Invalid intent = %s. Valid intents = %s" % \ (intent, validIntents) mytb.close() return foundField = False if not isinstance(scan, list): scan = str(scan) for id, name in enumerate(fieldnames): if name == field or id == field: foundField = True print 'FIELD_ID %s has name %s' % (id, name) if scan == '': s = mytb.query('FIELD_ID==%s' % id) print "Got %d rows in the ms matching field=%s" \ % (s.nrows(), id) else: if isinstance(scan, str): scans = [int(x) for x in scan.split(',')] elif isinstance(scan, list): scans = [scan] else: scans = scan print "Querying: 'FIELD_ID==%s AND SCAN_NUMBER in %s'" \ % (id, str(scans)) s = mytb.query('FIELD_ID==%s AND SCAN_NUMBER in %s' % (id, str(scans))) print "Got %d rows in the ms matching field=%s and scan in %s" % \ (s.nrows(), id, str(scans)) if s.nrows() == 0: mytb.close() print "Found zero rows for this field (and/or scan). Stopping." return state_ids = s.getcol('STATE_ID') # original code from J. Lightfoot, can probably be replaced # by the np.unique() above states = [] for state_id in state_ids: if state_id not in states: states.append(state_id) # print "states = ", states for ij in range(len(states)): states[ij] = intentcol[states[ij]] print 'current intents are:' for state in states: print state if not append: states = [] for desiredintent in desiredintentsList: if desiredintent.find('TARGET') >= 0: states.append('OBSERVE_TARGET#UNSPECIFIED') elif desiredintent.find('BANDPASS') >= 0: states.append('CALIBRATE_BANDPASS#UNSPECIFIED') elif desiredintent.find('PHASE') >= 0: states.append('CALIBRATE_PHASE#UNSPECIFIED') elif desiredintent.find('AMPLI') >= 0: states.append('CALIBRATE_AMPLI#UNSPECIFIED') elif desiredintent.find('FLUX') >= 0: states.append('CALIBRATE_FLUX#UNSPECIFIED') elif desiredintent.find('ATMOSPHERE') >= 0: states.append('CALIBRATE_ATMOSPHERE#UNSPECIFIED') elif desiredintent.find('WVR') >= 0: states.append('CALIBRATE_WVR#UNSPECIFIED') elif desiredintent.find('SIDEBAND_RATIO') >= 0: states.append('CALIBRATE_SIDEBAND_RATIO#UNSPECIFIED') elif desiredintent.find('DELAY') >= 0: states.append('CALIBRATE_DELAY#UNSPECIFIED') elif desiredintent.find('POL_ANGLE') >= 0: states.append('CALIBRATE_POL_ANGLE#UNSPECIFIED') elif desiredintent.find('POL_LEAKAGE') >= 0: states.append('CALIBRATE_POL_LEAKAGE#UNSPECIFIED') elif desiredintent.find('SYS_CONFIG') >= 0: states.append('SYSTEM_CONFIGURATION#UNSPECIFIED') else: print "Unrecognized intent = %s" % desiredintent continue print 'setting %s' % (states[-1]) if states != []: state = reduce(lambda x, y: '%s,%s' % (x, y), states) if state not in intentcol: print 'adding intent to state table' intentcol = list(intentcol) intentcol.append(state) intentcol = np.array(intentcol) state_id = len(intentcol) - 1 naddedrows += 1 print 'state_id is', state_id state_ids[:] = state_id else: print 'intent already in state table' state_id = np.arange(len(intentcol))[intentcol == state] print 'state_id is', state_id if isinstance(state_id, list) or isinstance(state_id, np.ndarray): # ms can have identical combinations of INTENT, so just # pick the row for the first appearance - T. Hunter state_ids[:] = state_id[0] else: state_ids[:] = state_id s.putcol('STATE_ID', state_ids) if not foundField: print "Field not found" return mytb.close() print 'writing new STATE table' mytb.open(msName + '/STATE', nomodify=False) if naddedrows > 0: mytb.addrows(naddedrows) mytb.putcol('OBS_MODE', intentcol) mytb.close()
import matplotlib import matplotlib.pyplot as plt from matplotlib.colors import LogNorm import matplotlib.patches as patches from astropy.io import fits # CASA imports from taskinit import * from immoments import immoments from immath import immath from makemask import makemask from imhead import imhead from imstat import imstat from exportfits import exportfits import analysisUtils as aU mycl = aU.createCasaTool(cltool) myia = aU.createCasaTool(iatool) myrg = aU.createCasaTool(rgtool) ##################### ### Main Procedure ##################### def createmask(dir_data, imagename, thres, outmask="mask1.image", pixelmin=5.): #create mask with thres outfile = dir_data + outmask os.system("rm -rf " + outfile) immath(imagename=dir_data + imagename, mode="evalexpr", expr="iif(IM0 >= " + str(thres) + ", 1.0, 0.0)", outfile=outfile)
def calculate_mosaic_extent( infile_list=None, force_ra_ctr=None, force_dec_ctr=None, force_freq_ctr=None, ): """ Given a list of input files, calculate the center and extent of the mosaic needed to cover them all. Return the results as a dictionary. infile_list : list of input files to loop over. force_ra_ctr (default None) : if set then force the RA center of the mosaic to be this value, and the returned extent is the largest separation of any image corner from this value in RA. force_dec_ctr (default None) : as force_ra_ctr but for Declination. If the RA and Dec. centers are supplied, then they are assumed to be in decimal degrees. """ # Check inputs if infile_list is None: logger.error("Missing required infile_list.") return (None) for this_infile in infile_list: if not os.path.isdir(this_infile): logger.error("File not found " + this_infile + "Returning.") return (None) # Initialize the list of corner RA and Dec positions. ra_list = [] dec_list = [] # TBD - right now we assume matched frequency/velocity axis freq_list = [] # Loop over input files and calculate RA and Dec coordinates of # the corners. myia = au.createCasaTool(casaStuff.iatool) for this_infile in infile_list: this_hdr = casaStuff.imhead(this_infile) if this_hdr['axisnames'][0] != 'Right Ascension': logger.error("Expected axis 0 to be Right Ascension. Returning.") return (None) if this_hdr['axisunits'][0] != 'rad': logger.error("Expected axis units to be radians. Returning.") return (None) if this_hdr['axisnames'][1] != 'Declination': logger.error("Expected axis 1 to be Declination. Returning.") return (None) if this_hdr['axisunits'][1] != 'rad': logger.error("Expected axis units to be radians. Returning.") return (None) this_shape = this_hdr['shape'] xlo = 0 xhi = this_shape[0] - 1 ylo = 0 yhi = this_shape[1] - 1 pixbox = str(xlo) + ',' + str(ylo) + ',' + str(xlo) + ',' + str(ylo) blc = casaStuff.imval(this_infile, stokes='I', box=pixbox) pixbox = str(xlo) + ',' + str(yhi) + ',' + str(xlo) + ',' + str(yhi) tlc = casaStuff.imval(this_infile, stokes='I', box=pixbox) pixbox = str(xhi) + ',' + str(yhi) + ',' + str(xhi) + ',' + str(yhi) trc = casaStuff.imval(this_infile, stokes='I', box=pixbox) pixbox = str(xhi) + ',' + str(ylo) + ',' + str(xhi) + ',' + str(ylo) brc = casaStuff.imval(this_infile, stokes='I', box=pixbox) ra_list.append(blc['coords'][:, 0]) ra_list.append(tlc['coords'][:, 0]) ra_list.append(trc['coords'][:, 0]) ra_list.append(brc['coords'][:, 0]) dec_list.append(blc['coords'][:, 1]) dec_list.append(tlc['coords'][:, 1]) dec_list.append(trc['coords'][:, 1]) dec_list.append(brc['coords'][:, 1]) freq_list.append(blc['coords'][:, 2]) freq_list.append(tlc['coords'][:, 2]) freq_list.append(trc['coords'][:, 2]) freq_list.append(brc['coords'][:, 2]) # Get the minimum and maximum RA and Declination. # TBD - this breaks straddling the meridian (RA = 0) or the poles # (Dec = 90). Add catch cases or at least error calls for # this. Meridian seems more likely to come up, so just that is # probably fine. min_ra = np.min(np.concatenate(ra_list)) max_ra = np.max(np.concatenate(ra_list)) min_dec = np.min(np.concatenate(dec_list)) max_dec = np.max(np.concatenate(dec_list)) # TBD - right now we assume matched frequency/velocity axis min_freq = np.min(np.concatenate(freq_list)) max_freq = np.max(np.concatenate(freq_list)) # If we do not force the center of the mosaic, then take it to be # the average of the min and max, so that the image will be a # square. if force_ra_ctr == None: ra_ctr = (max_ra + min_ra) * 0.5 else: ra_ctr = force_ra_ctr * np.pi / 180. if force_dec_ctr == None: dec_ctr = (max_dec + min_dec) * 0.5 else: dec_ctr = force_dec_ctr * np.pi / 180. if force_freq_ctr == None: freq_ctr = (max_freq + min_freq) * 0.5 else: freq_ctr = force_freq_ctr # Now calculate the total extent of the mosaic given the center. delta_ra = 2.0 * np.max([np.abs(max_ra - ra_ctr), np.abs(min_ra - ra_ctr)]) delta_ra *= np.cos(dec_ctr) delta_dec = 2.0 * np.max( [np.abs(max_dec - dec_ctr), np.abs(min_dec - dec_ctr)]) delta_freq = 2.0 * np.max( [np.abs(max_freq - freq_ctr), np.abs(min_freq - freq_ctr)]) # Put the output into a dictionary. output = { 'ra_ctr': [ra_ctr * 180. / np.pi, 'degrees'], 'dec_ctr': [dec_ctr * 180. / np.pi, 'degrees'], 'delta_ra': [delta_ra * 180. / np.pi * 3600., 'arcsec'], 'delta_dec': [delta_dec * 180. / np.pi * 3600., 'arcsec'], 'freq_ctr': [freq_ctr, 'Hz'], 'delta_freq': [delta_freq, 'Hz'], } return (output)
def offset(workingdir, vis='', plotfile='', imfitlog=False, spw='', verbose=False): """ Takes a pipeline working directory and find all images of the checksource and produces a plot showing the relative directions of the first two science targets, the phase calibrator, and the checksource, and a vector showing the offset of the checksource from its catalog position (computed using the results of the CASA task imfit), and a text label showing the RAO and DECO offsets. workingdir: path to pipeline working directory vis: alternate location for a measurement set to consult (ignores *_target.ms) Looks first for *chk*iter2.image; if not found, then *chk*iter1.image plotfile: default = img+'_offset.png' imfitlog: if True, then request imfit to generate log files (*.imfit) spw: int or comma-delimited string, if specified, limit to this or these spws verbose: print more messages explaining what images it is operating on """ mymsmd = au.createCasaTool(msmdtool) if verbose: print("workingdir: ", workingdir) imglist = sorted(glob.glob(os.path.join(workingdir, '*_chk.spw*image'))) if len(imglist) == 0: print("No check source images found in this directory.") return # If iter2.image is found, then drop the iter1 version from the list for i in imglist: if i.find('iter2') > 0: imglist.remove(i.replace('iter2', 'iter1')) if verbose: print("Processing %d images:" % (len(imglist))) for i in imglist: print(i) if vis == '': searchpath = os.path.join(workingdir, '*.ms') if verbose: print("searchpath: ", searchpath) allvislist = sorted(glob.glob(searchpath)) if verbose: print("all vis found: ", allvislist) vislist = [] for vis in allvislist: if vis.find('_target') < 0: vislist.append(vis) else: vislist = [vis] raos = [] decos = [] totals = [] sourcenames = [] spws = au.parseSpw(vis, spw) scienceSpws = au.getScienceSpws(vis, returnString=False) spws = np.intersect1d(scienceSpws, spws) if verbose: print("using spws: ", spws) newimglist = [] for img in imglist: # there will be an image for each spw if img.find('spw') > 0 and spw != '': myspw = int(img.split('spw')[1].split('.')[0]) if myspw in spws: sourcenames.append(au.imageSource(img)) newimglist.append(img) if verbose: print("Using %s" % (img)) elif verbose: print("Skipping %s" % (img)) else: sourcenames.append(au.imageSource(img)) newimglist.append(img) sourcenames = np.unique(sourcenames) pngs = [] print("vislist = ", vislist) imglist = newimglist for sourcename in sourcenames: for ispw, img in enumerate( imglist): # there will be an image for each spw if 'spw' not in img: print("No spw in the image name: ", img) continue spw = int(img.split('spw')[1].split('.')[0]) # find the first vis that observed this target as check source checkid = -1 for vis in vislist: # print "Checking ", vis mymsmd.open(vis) if spw >= mymsmd.nspw(): print("Guessing that spw %d is spw %d in the split ms." % (spw, ispw)) spw = ispw if 'OBSERVE_CHECK_SOURCE#ON_SOURCE' in mymsmd.intents(): checksources = mymsmd.fieldsforintent( 'OBSERVE_CHECK_SOURCE*', True) else: checksources = mymsmd.fieldsforintent( 'CALIBRATE_DELAY*', True) if sourcename in checksources: check = checksources[0] checkid = mymsmd.fieldsforname(sourcename)[0] checkpos = mymsmd.phasecenter(checkid) # Phase calibrator phase = mymsmd.fieldsforintent('CALIBRATE_PHASE*', True)[0] phaseid = mymsmd.fieldsforintent('CALIBRATE_PHASE*', False)[0] phasepos = mymsmd.phasecenter(phaseid) if ('OBSERVE_TARGET#ON_SOURCE' in mymsmd.intents()): nScienceFields = len( mymsmd.fieldsforintent('OBSERVE_TARGET*', False)) science = mymsmd.fieldsforintent( 'OBSERVE_TARGET*', True)[0] scienceid = mymsmd.fieldsforintent( 'OBSERVE_TARGET*', False)[0] sciencepos = mymsmd.phasecenter(scienceid) if nScienceFields > 1: science2 = mymsmd.fieldsforintent( 'OBSERVE_TARGET*', True)[1] science2id = mymsmd.fieldsforintent( 'OBSERVE_TARGET*', False)[1] science2pos = mymsmd.phasecenter(science2id) else: nScienceFields = 0 rxBand = mymsmd.namesforspws(spw)[0].split('#')[1].split( '_')[-1].lstrip('0') # string break else: mymsmd.close() if checkid < 0: print( "Could not find an ms that observed this check source: %s. Try including the vis parameter." % (sourcename)) continue info = au.getFitsBeam(img) imsize = info[5] # size in RA direction region = 'circle[[%dpix , %dpix], 15pix ]' % (int( imsize / 2), int(imsize / 2)) freq = mymsmd.meanfreq(spw, unit='GHz') if imfitlog: logfile = img + '.imfit' else: logfile = '' imagefit = imfit(imagename=img, region=region, logfile=logfile) fitresults = au.imfitparse(imagefit, deconvolved=True) synthMajor, synthMinor = info[0:2] synthBeam = (info[0] * info[1])**0.5 # Compare the Positions checkpos_obs = au.direction2radec(checkpos) if fitresults is not None: checkpos_fit = ','.join(fitresults.split()[:2]) print("spw %d: checksource fitted position: " % (spw), checkpos_fit) result = au.angularSeparationOfStrings(checkpos_fit, checkpos_obs, True, verbose=False) checkpos_diff, deltaLong, deltaLat, deltaLongCosDec, pa = result total = checkpos_diff * 3600. rao = deltaLongCosDec * 3600. deco = deltaLat * 3600. print( "spw %d: %s offset=%.4f arcsec, RAO=%+.4f, DECO=%+.4f, PA=%.1fdeg" % (spw, sourcename, total, rao, deco, pa)) totals.append(total) raos.append(rao) decos.append(deco) mymsmd.close() if nScienceFields > 1: scienceDeg = np.degrees( au.angularSeparationOfDirections(science2pos, sciencepos, True)) phaseDeg = np.degrees( au.angularSeparationOfDirections(phasepos, sciencepos, True)) checkDeg = np.degrees( au.angularSeparationOfDirections(checkpos, sciencepos, True)) if len(raos) == 1: pl.clf() desc = pl.subplot(111) if nScienceFields > 1: pl.plot([0, scienceDeg[3], phaseDeg[3], checkDeg[3]], [0, scienceDeg[2], phaseDeg[2], checkDeg[2]], 'b+', ms=10, mew=2) else: pl.plot([0, phaseDeg[3], checkDeg[3]], [0, phaseDeg[2], checkDeg[2]], 'b+', ms=10, mew=2) pl.hold(True) pl.axis('equal') yrange = np.diff(pl.ylim())[0] # reverse RA axis x0, x1 = pl.xlim() xoffset = 0.15 * (x1 - x0) # Keep a fixed scale among the spws/images xscale = 0.5 * xoffset / np.max(np.abs([rao, deco])) # draw the arrow for each spw's image pl.arrow(checkDeg[3], checkDeg[2], rao * xscale, deco * xscale, lw=1, shape='full', head_width=0.15 * xoffset, head_length=0.2 * xoffset, fc='b', ec='b') if len(raos) == 1: pl.xlim([x1 + xoffset, x0 - xoffset]) yoffset = yrange * 0.025 pl.text(0, 0 + yoffset, 'science', ha='center', va='bottom') if nScienceFields > 1: pl.text(scienceDeg[3], scienceDeg[2] + yoffset, 'science (%.1fdeg)' % scienceDeg[0], ha='center', va='bottom') pl.text(scienceDeg[3], scienceDeg[2] - yoffset, science2, ha='center', va='top') pl.text(phaseDeg[3], phaseDeg[2] + yoffset, 'phase (%.1fdeg)' % phaseDeg[0], ha='center', va='bottom') pl.text(checkDeg[3], checkDeg[2] + yoffset, 'check (%.1fdeg)' % checkDeg[0], ha='center', va='bottom') pl.text(0, 0 - yoffset, science, ha='center', va='top') pl.text(phaseDeg[3], phaseDeg[2] - yoffset, phase, ha='center', va='top') pl.text(checkDeg[3], checkDeg[2] - yoffset, check, ha='center', va='top') pl.xlabel('RA offset (deg)') pl.ylabel('Dec offset (deg)') projCode = au.projectCodeFromDataset(vis) if type(projCode) == str: if verbose: print("Did not find project code") projCode = '' else: projCode = projCode[0] + ', Band %s, ' % (rxBand) pl.title(projCode + os.path.basename(img).split('.spw')[0] + ', spws=%s' % spws, size=12) pl.ylim( [pl.ylim()[0] - yoffset * 8, pl.ylim()[1] + yoffset * 8]) minorLocator = MultipleLocator(0.5) # degrees desc.xaxis.set_minor_locator(minorLocator) desc.yaxis.set_minor_locator(minorLocator) # end 'for' loop over spws/images if len(raos) < 1: return pl.ylim([pl.ylim()[0] - yoffset * 7, pl.ylim()[1] + yoffset * 15]) rao = np.median(raos) raostd = np.std(raos) deco = np.median(decos) decostd = np.std(decos) total = np.median(totals) totalstd = np.std(totals) raoBeams = rao / synthBeam raostdBeams = raostd / synthBeam decoBeams = deco / synthBeam decostdBeams = decostd / synthBeam # draw the median arrow in thick black line pl.arrow(checkDeg[3], checkDeg[2], rao * xscale, deco * xscale, lw=2, shape='full', head_width=0.12 * xoffset, head_length=0.18 * xoffset, ec='k', fc='k') print( "median +- std: offset=%.4f+-%.4f, RAO=%.4f+-%.4f, DECO=%.4f+-%.4f" % (total, totalstd, rao, raostd, deco, decostd)) # pl.text(checkDeg[3], checkDeg[2]-0.6*xoffset, '$\Delta\\alpha$: %+.4f"+-%.4f"' % (rao,raostd), ha='center') # pl.text(checkDeg[3], checkDeg[2]-0.85*xoffset, '$\Delta\\delta$: %+.4f"+-%.4f"' % (deco,decostd), ha='center') pl.text(0.05, 0.95, '$\Delta\\alpha$: %+.4f"+-%.4f" = %+.2f+-%.2f beams' % (rao, raostd, raoBeams, raostdBeams), ha='left', transform=desc.transAxes) pl.text(0.05, 0.91, '$\Delta\\delta$: %+.4f"+-%.4f" = %+.2f+-%.2f beams' % (deco, decostd, decoBeams, decostdBeams), ha='left', transform=desc.transAxes) if plotfile == '': png = img + '_offset.png' else: png = plotfile pl.savefig(png, bbox_inches='tight') pl.draw() pngs.append(png) print("Wrote ", png)
def mosaic_aligned_data( infile_list=None, weightfile_list=None, outfile=None, overwrite=False, ): """ Combine a list of previously aligned data into a single image using linear mosaicking. Weight each file using a corresponding weight file and also create sum and integrated weight files. infile_list : list of input files. Required. weightfile_list : a list of weight files that correspond to the input files. Can be a dictionary or a list. If it's a list then matching is by order, so that the first infile goes to first weight file, etc. If it's a dictionary, it looks for the infile name as a key. outfile : the name of the output mosaic image. Will create associated files with ".sum" and ".weight" appended to this file name. overwrite (default False) : Delete existing files. You probably want to set this to True but it's a user decision. """ # Check inputs if infile_list is None: logger.error("Input file list required.") return (None) if outfile is None: logger.error("Output file is required.") return (None) # Define some extra outputs and then check file existence sum_file = outfile + '.sum' weight_file = outfile + '.weight' mask_file = outfile + '.mask' temp_file = outfile + '.temp' for this_file in [outfile, sum_file, weight_file, temp_file, mask_file]: if os.path.isdir(this_file): if not overwrite: logger.error("Output file present and overwrite off - " + this_file) return (None) os.system('rm -rf ' + this_file) # Check the weightfile dictionary/list and get it set. if weightfile_list is None: logger.error("Missing weightfile_list required for mosaicking.") return (None) if (type(weightfile_list) != type([])) and (type(weightfile_list) != type( {})): logger.error("Weightfile_list must be dictionary or list.") return (None) if type(weightfile_list) == type([]): if len(infile_list) != len(weightfile_list): logger.error("Mismatch in input and output list lengths.") return (None) weightfile_dict = {} for ii in range(len(infile_list)): weightfile_dict[infile_list[ii]] = weightfile_list[ii] if type(weightfile_list) == type({}): weightfile_dict = weightfile_list # Check file existence for this_infile in infile_list: if not os.path.isdir(this_infile): logger.error("Missing file - " + this_infile) return (None) this_weightfile = weightfile_dict[this_infile] if not os.path.isdir(this_weightfile): logger.error("Missing file - " + this_weightfile) return (None) # Define LEL expressions to be fed to immath. These just sum up # weight*image and weight. Those produce the .sum and .weight # output file. full_imlist = [] lel_exp_sum = '' lel_exp_weight = '' first = True counter = 0 for this_infile in infile_list: # Build out to a list that goes infile1, infile2, ... infilen, # weightfile1, weightfile2, ... weightfilen. full_imlist.append(this_infile) full_imlist.append(weightfile_dict[this_infile]) # Make LEL string expressions that refer to these two images # and then increment the counter by 2. So IM0 is the first # image, IM1 the first weight, IM2 the second image, IM3 the # second weight, and so on. this_im = 'IM' + str(counter) this_wt = 'IM' + str(counter + 1) counter += 2 # LEL expressions that refer to the weighted sum and the # weight for this image pair. this_lel_sum = '(' + this_im + '*' + this_wt + ')' this_lel_weight = '(' + this_wt + ')' # Chain these together into a full string that adds all of the # images together. if first: lel_exp_sum += this_lel_sum lel_exp_weight += this_lel_weight first = False else: lel_exp_sum += '+' + this_lel_sum lel_exp_weight += '+' + this_lel_weight # Feed our two LEL strings into immath to make the sum and weight # images. myia = au.createCasaTool(casaStuff.iatool) for thisfile in full_imlist: myia.open(thisfile) if not np.all(myia.getchunk(getmask=True)): myia.replacemaskedpixels(0.0) myia.set(pixelmask=1) myia.close() cwd = os.getcwd() ppdir = os.chdir(os.path.dirname(full_imlist[0])) local_imlist = [os.path.basename(ll) for ll in full_imlist] sum_file = os.path.basename(sum_file) weight_file = os.path.basename(weight_file) temp_file = os.path.basename(temp_file) local_outfile = os.path.basename(outfile) local_maskfile = os.path.basename(mask_file) casaStuff.immath(imagename=local_imlist, mode='evalexpr', expr=lel_exp_sum, outfile=sum_file, imagemd=local_imlist[0]) casaStuff.immath(imagename=local_imlist, mode='evalexpr', expr=lel_exp_weight, outfile=weight_file, imagemd=local_imlist[0]) # Just to be safe, reset the masks on the two images. myia = au.createCasaTool(casaStuff.iatool) myia.open(sum_file) myia.set(pixelmask=1) myia.close() myia.open(weight_file) myia.set(pixelmask=1) myia.close() # Now divide the sum*weight image by the weight image. casaStuff.immath(imagename=[sum_file, weight_file], mode='evalexpr', expr='iif(IM1 > 0.0, IM0/IM1, 0.0)', outfile=temp_file, imagemd=sum_file) # The mask for the final output is where we have any weight. This # may not be exactly what's desired in all cases, but it's not # clear to me what else to do except for some weight threshold # (does not have to be zero, though, I guess). casaStuff.immath(imagename=weight_file, mode='evalexpr', expr='iif(IM0 > 0.0, 1.0, 0.0)', outfile=local_maskfile) # Strip out any degenerate axes and create the final output file. casaStuff.imsubimage(imagename=temp_file, outfile=local_outfile, mask='"' + local_maskfile + '"', dropdeg=True) os.chdir(cwd) return (None)
def get_mosaic_info(vis, spw, sourceid=None, intent='TARGET', pblevel=0.1): ''' Return image size based on mosaic fields Parameters ---------- vis : str MS Name spw : str or int If str, searches for an exact match with the names in the MS. If int, is the index of SPW in the MS. sourceid : str, optional The field names used will contain sourceid. intent : str, optional Use every field with the given intent (wildcards used by default). pblevel : float between 0 and 1 PB level that defines the edges of the mosaic. ''' mytb = au.createCasaTool(au.tbtool) # Check SPWs to make sure given choice is valid mytb.open(vis + '/SPECTRAL_WINDOW') spwNames = mytb.getcol('NAME') if isinstance(spw, str): match = False for spw_name in spwNames: if spw == spw_name: match = True if not match: raise ValueError("The given SPW ({0}) is not in the MS SPW" " names ({1})".format(spw, spwNames)) elif isinstance(spw, (int, np.integer)): try: spwNames[spw] except IndexError: raise IndexError("The given SPW index {0} is not in the range" " of SPWs in the MS ({1})." .format(spw, len(spwNames))) else: raise TypeError("spw must be a str or int.") refFreq = mytb.getcol("REF_FREQUENCY")[spw] lambdaMeters = au.c_mks / refFreq mytb.close() # Get field info mytb.open(vis + '/FIELD') delayDir = mytb.getcol('DELAY_DIR') ra = delayDir[0, :][0] * 12 / np.pi for i in range(len(ra)): if ra[i] < 0: ra[i] += 24 ra *= 15 dec = np.degrees(delayDir[1, :][0]) # First choose fields by given sourceid if sourceid is not None: names = mytb.getcol('NAME') fields = mytb.getcol("SOURCE_ID") good_names = [] good_fields = [] for name, field in zip(names, fields): # Check if it has sourceid if name.find(sourceid) != -1: good_names.append(name) good_fields.append(field) names = good_names fields = good_fields # Then try choosing all fields based on the given intent. elif intent is not None: # Ensure a string intent = str(intent) mymsmd = au.createCasaTool(au.msmdtool) mymsmd.open(vis) intentsToSearch = '*' + intent + '*' fields = mymsmd.fieldsforintent(intentsToSearch) names = mymsmd.namesforfields(fields) mymsmd.close() # On or the other must be given else: raise ValueError("Either sourceid or intent must be given.") mytb.close() ra = ra[fields] dec = dec[fields] raAverageDegrees = np.mean(ra) decAverageDegrees = np.mean(dec) raRelativeArcsec = 3600 * (ra - raAverageDegrees) * \ np.cos(np.deg2rad(decAverageDegrees)) decRelativeArcsec = 3600 * (dec - decAverageDegrees) centralField = au.findNearestField(ra, dec, raAverageDegrees, decAverageDegrees)[0] # This next step is crucial, as it converts from the field number # determined from a subset list back to the full list. centralFieldName = names[centralField] centralField = fields[centralField] # Find which antenna have data mytb.open(vis) antennasWithData = np.sort(np.unique(mytb.getcol('ANTENNA1'))) mytb.close() if antennasWithData.size == 0: raise Warning("No antennas with data found.") # Now we need the dish diameters mytb.open(vis + "/ANTENNA") # These are in m dish_diameters = \ np.unique(mytb.getcol("DISH_DIAMETER")[antennasWithData]) mytb.close() # Find maxradius maxradius = 0 for diam in dish_diameters: arcsec = 0.5 * \ au.primaryBeamArcsec(wavelength=lambdaMeters * 1000, diameter=diam, showEquation=False) radius = arcsec / 3600.0 if radius > maxradius: maxradius = radius # Border about each point, down to the given pblevel border = 2 * maxradius * au.gaussianBeamOffset(pblevel) * 3600. size_ra = np.ptp(raRelativeArcsec) + 2 * border size_dec = np.ptp(decRelativeArcsec) + 2 * border mosaicInfo = {"Central_Field_ID": centralField, "Central_Field_Name": centralFieldName, "Center_RA": ra, "Center_Dec": dec, "Size_RA": size_ra, "Size_Dec": size_dec} return mosaicInfo