def mask_vertices(mask_im, vertices_file): """ Modify the input image to exclude regions outside of the polygon Parameters ---------- mask_im : pyrap.images image() object Mask image to modify vertices_file: str Filename of pickle file that contains direction dictionary with vertices Returns ------- new_im: pyrap.images image() object Modified mask image bool_mask : pyrap.images image() object Modified mask image """ import pyrap.images as pim ma = mask_im.coordinates() new_im = pim.image('',shape=mask_im.shape(), coordsys=ma) bool_mask = pim.image('',shape=mask_im.shape(), coordsys=ma) img_type = mask_im.imagetype() data = mask_im.getdata() bool_data = np.ones(data.shape) vertices = read_vertices(vertices_file) RAverts = vertices[0] Decverts = vertices[1] xvert = [] yvert = [] for RAvert, Decvert in zip(RAverts, Decverts): pixels = mask_im.topixel([1, 1, Decvert*np.pi/180.0, RAvert*np.pi/180.0]) xvert.append(pixels[2]) # x -> Dec yvert.append(pixels[3]) # y -> RA poly = Polygon(xvert, yvert) # Find distance to nearest poly edge and unmask those that # are outside the facet (dist < 0) masked_ind = np.indices(data[0, 0].shape) dist = poly.is_inside(masked_ind[0], masked_ind[1]) outside_ind = np.where(dist < 0.0) if len(outside_ind[0]) > 0: data[0, 0, masked_ind[0][outside_ind], [masked_ind[1][outside_ind]]] = 0 bool_data[0, 0, masked_ind[0][outside_ind], [masked_ind[1][outside_ind]]] = 0 new_im.putdata(data) bool_mask.putdata(bool_data) return new_im, bool_mask
def main(image_name, mask_name, atrous_do=False, threshisl=0.0, threshpix=0.0, rmsbox=None, rmsbox_bright=(35, 7), iterate_threshold=False, adaptive_rmsbox=False, img_format='fits', threshold_format='float', trim_by=0.0, vertices_file=None, atrous_jmax=6, pad_to_size=None, skip_source_detection=False, region_file=None, nsig=1.0, reference_ra_deg=None, reference_dec_deg=None, cellsize_deg=0.000417, use_adaptive_threshold=False): """ Make a clean mask and return clean threshold Parameters ---------- image_name : str Filename of input image from which mask will be made. If the image does not exist, a template image with center at (reference_ra_deg, reference_dec_deg) will be made internally mask_name : str Filename of output mask image atrous_do : bool, optional Use wavelet module of PyBDSM? threshisl : float, optional Value of thresh_isl PyBDSM parameter threshpix : float, optional Value of thresh_pix PyBDSM parameter rmsbox : tuple of floats, optional Value of rms_box PyBDSM parameter rmsbox_bright : tuple of floats, optional Value of rms_box_bright PyBDSM parameter iterate_threshold : bool, optional If True, threshold will be lower in 20% steps until at least one island is found adaptive_rmsbox : tuple of floats, optional Value of adaptive_rms_box PyBDSM parameter img_format : str, optional Format of output mask image (one of 'fits' or 'casa') threshold_format : str, optional Format of output threshold (one of 'float' or 'str_with_units') trim_by : float, optional Fraction by which the perimeter of the output mask will be trimmed (zeroed) vertices_file : str, optional Filename of file with vertices (must be a pickle file containing a dictionary with the vertices in the 'vertices' entry) atrous_jmax : int, optional Value of atrous_jmax PyBDSM parameter pad_to_size : int, optional Pad output mask image to a size of pad_to_size x pad_to_size skip_source_detection : bool, optional If True, source detection is not run on the input image region_file : str, optional Filename of region file in CASA format. If given, no mask image is made (the region file is used as the clean mask) nsig : float, optional Number of sigma of returned threshold value reference_ra_deg : float, optional RA for center of output mask image reference_dec_deg : float, optional Dec for center of output mask image cellsize_deg : float, optional Size of a pixel in degrees use_adaptive_threshold : bool, optional If True, use an adaptive threshold estimated from the negative values in the image Returns ------- result : dict Dict with nsig-sigma rms threshold """ if rmsbox is not None and type(rmsbox) is str: rmsbox = eval(rmsbox) if type(rmsbox_bright) is str: rmsbox_bright = eval(rmsbox_bright) if pad_to_size is not None and type(pad_to_size) is str: pad_to_size = int(pad_to_size) if type(atrous_do) is str: if atrous_do.lower() == 'true': atrous_do = True threshisl = 4.0 # override user setting to ensure proper source fitting else: atrous_do = False if type(iterate_threshold) is str: if iterate_threshold.lower() == 'true': iterate_threshold = True else: iterate_threshold = False if type(adaptive_rmsbox) is str: if adaptive_rmsbox.lower() == 'true': adaptive_rmsbox = True else: adaptive_rmsbox = False if type(skip_source_detection) is str: if skip_source_detection.lower() == 'true': skip_source_detection = True else: skip_source_detection = False if type(use_adaptive_threshold) is str: if use_adaptive_threshold.lower() == 'true': use_adaptive_threshold = True else: use_adaptive_threshold = False if reference_ra_deg is not None and reference_dec_deg is not None: reference_ra_deg = float(reference_ra_deg) reference_dec_deg = float(reference_dec_deg) if not os.path.exists(image_name): print('Input image not found. Making empty image...') if not skip_source_detection: print('ERROR: Source detection cannot be done on an empty image') sys.exit(1) if reference_ra_deg is not None and reference_dec_deg is not None: image_name = mask_name + '.tmp' make_template_image(image_name, reference_ra_deg, reference_dec_deg, cellsize_deg=float(cellsize_deg)) else: print('ERROR: if image not found, a refernce position must be given') sys.exit(1) trim_by = float(trim_by) atrous_jmax = int(atrous_jmax) threshpix = float(threshpix) threshisl = float(threshisl) nsig = float(nsig) threshold = 0.0 if not skip_source_detection: if vertices_file is not None: # Modify the input image to blank the regions outside of the polygon temp_img = pim.image(image_name) image_name += '.blanked' temp_img.saveas(image_name, overwrite=True) input_img = pim.image(image_name) data = input_img.getdata() vertices = read_vertices(vertices_file) RAverts = vertices[0] Decverts = vertices[1] xvert = [] yvert = [] for RAvert, Decvert in zip(RAverts, Decverts): pixels = input_img.topixel([1, 1, Decvert*np.pi/180.0, RAvert*np.pi/180.0]) xvert.append(pixels[2]) # x -> Dec yvert.append(pixels[3]) # y -> RA poly = Polygon(xvert, yvert) # Find masked regions masked_ind = np.where(data[0, 0]) # Find distance to nearest poly edge and set to NaN those that # are outside the facet (dist < 0) dist = poly.is_inside(masked_ind[0], masked_ind[1]) outside_ind = np.where(dist < 0.0) if len(outside_ind[0]) > 0: data[0, 0, masked_ind[0][outside_ind], masked_ind[1][outside_ind]] = np.nan # Save changes input_img.putdata(data) if use_adaptive_threshold: # Get an estimate of the rms img = bdsm.process_image(image_name, mean_map='zero', rms_box=rmsbox, thresh_pix=threshpix, thresh_isl=threshisl, atrous_do=atrous_do, ini_method='curvature', thresh='hard', adaptive_rms_box=adaptive_rmsbox, adaptive_thresh=150, rms_box_bright=rmsbox_bright, rms_map=True, quiet=True, atrous_jmax=atrous_jmax, stop_at='isl') # Find min and max pixels max_neg_val = abs(np.min(img.ch0_arr)) max_neg_pos = np.where(img.ch0_arr == np.min(img.ch0_arr)) max_pos_val = abs(np.max(img.ch0_arr)) max_pos_pos = np.where(img.ch0_arr == np.max(img.ch0_arr)) # Estimate new thresh_isl from min pixel value's sigma, but don't let # it get higher than 1/2 of the peak's sigma threshisl_neg = 2.0 * max_neg_val / img.rms_arr[max_neg_pos][0] max_sigma = max_pos_val / img.rms_arr[max_pos_pos][0] if threshisl_neg > max_sigma / 2.0: threshisl_neg = max_sigma / 2.0 # Use the new threshold only if it is larger than the user-specified one if threshisl_neg > threshisl: threshisl = threshisl_neg if iterate_threshold: # Start with given threshold and lower it until we get at least one island nisl = 0 while nisl == 0: img = bdsm.process_image(image_name, mean_map='zero', rms_box=rmsbox, thresh_pix=threshpix, thresh_isl=threshisl, atrous_do=atrous_do, ini_method='curvature', thresh='hard', adaptive_rms_box=adaptive_rmsbox, adaptive_thresh=150, rms_box_bright=rmsbox_bright, rms_map=True, quiet=True, atrous_jmax=atrous_jmax) nisl = img.nisl threshpix /= 1.2 threshisl /= 1.2 if threshpix < 5.0: break else: img = bdsm.process_image(image_name, mean_map='zero', rms_box=rmsbox, thresh_pix=threshpix, thresh_isl=threshisl, atrous_do=atrous_do, ini_method='curvature', thresh='hard', adaptive_rms_box=adaptive_rmsbox, adaptive_thresh=150, rms_box_bright=rmsbox_bright, rms_map=True, quiet=True, atrous_jmax=atrous_jmax) if img.nisl == 0: if region_file is None or region_file == '[]': print('No islands found. Clean mask cannot be made.') sys.exit(1) else: # Continue on and use user-supplied region file skip_source_detection = True threshold = nsig * img.clipped_rms # Check if there are large islands preset (indicating that multi-scale # clean is needed) has_large_isl = False for isl in img.islands: if isl.size_active > 100: # Assuming normal sampling, a size of 100 pixels would imply # a source of ~ 10 beams has_large_isl = True if (region_file is not None and region_file != '[]' and skip_source_detection): # Copy region file and return if source detection was not done os.system('cp {0} {1}'.format(region_file.strip('[]"'), mask_name)) if threshold_format == 'float': return {'threshold_5sig': threshold} elif threshold_format == 'str_with_units': # This is done to get around the need for quotes around strings in casapy scripts # 'casastr/' is removed by the generic pipeline return {'threshold_5sig': 'casastr/{0}Jy'.format(threshold)} elif not skip_source_detection: img.export_image(img_type='island_mask', mask_dilation=0, outfile=mask_name, img_format=img_format, clobber=True) if (vertices_file is not None or trim_by > 0 or pad_to_size is not None or (region_file is not None and region_file != '[]') or skip_source_detection): # Alter the mask in various ways if skip_source_detection: # Read the image mask_im = pim.image(image_name) else: # Read the PyBDSM mask mask_im = pim.image(mask_name) data = mask_im.getdata() coordsys = mask_im.coordinates() if reference_ra_deg is not None and reference_dec_deg is not None: values = coordsys.get_referencevalue() values[2][0] = reference_dec_deg/180.0*np.pi values[2][1] = reference_ra_deg/180.0*np.pi coordsys.set_referencevalue(values) imshape = mask_im.shape() del(mask_im) if pad_to_size is not None: imsize = pad_to_size coordsys['direction'].set_referencepixel([imsize/2, imsize/2]) pixmin = (imsize - imshape[2]) / 2 if pixmin < 0: print("The padded size must be larger than the original size.") sys.exit(1) pixmax = pixmin + imshape[2] data_pad = np.zeros((1, 1, imsize, imsize), dtype=np.float32) data_pad[0, 0, pixmin:pixmax, pixmin:pixmax] = data[0, 0] new_mask = pim.image('', shape=(1, 1, imsize, imsize), coordsys=coordsys) new_mask.putdata(data_pad) else: new_mask = pim.image('', shape=imshape, coordsys=coordsys) new_mask.putdata(data) data = new_mask.getdata() if skip_source_detection: # Mask all pixels data[:] = 1 if vertices_file is not None: # Modify the clean mask to exclude regions outside of the polygon vertices = read_vertices(vertices_file) RAverts = vertices[0] Decverts = vertices[1] xvert = [] yvert = [] for RAvert, Decvert in zip(RAverts, Decverts): try: pixels = new_mask.topixel([0, 1, Decvert*np.pi/180.0, RAvert*np.pi/180.0]) except: pixels = new_mask.topixel([1, 1, Decvert*np.pi/180.0, RAvert*np.pi/180.0]) xvert.append(pixels[2]) # x -> Dec yvert.append(pixels[3]) # y -> RA poly = Polygon(xvert, yvert) # Find masked regions masked_ind = np.where(data[0, 0]) # Find distance to nearest poly edge and unmask those that # are outside the facet (dist < 0) dist = poly.is_inside(masked_ind[0], masked_ind[1]) outside_ind = np.where(dist < 0.0) if len(outside_ind[0]) > 0: data[0, 0, masked_ind[0][outside_ind], masked_ind[1][outside_ind]] = 0 if trim_by > 0.0: sh = np.shape(data) margin = int(sh[2] * trim_by / 2.0 ) data[0, 0, 0:sh[2], 0:margin] = 0 data[0, 0, 0:margin, 0:sh[3]] = 0 data[0, 0, 0:sh[2], sh[3]-margin:sh[3]] = 0 data[0, 0, sh[2]-margin:sh[2], 0:sh[3]] = 0 if region_file is not None and region_file != '[]': # Merge the CASA regions with the mask casa_polys = read_casa_polys(region_file.strip('[]"'), new_mask) for poly in casa_polys: # Find unmasked regions unmasked_ind = np.where(data[0, 0] == 0) # Find distance to nearest poly edge and mask those that # are inside the casa region (dist > 0) dist = poly.is_inside(unmasked_ind[0], unmasked_ind[1]) inside_ind = np.where(dist > 0.0) if len(inside_ind[0]) > 0: data[0, 0, unmasked_ind[0][inside_ind], unmasked_ind[1][inside_ind]] = 1 # Save changes new_mask.putdata(data) if img_format == 'fits': new_mask.tofits(mask_name, overwrite=True) elif img_format == 'casa': new_mask.saveas(mask_name, overwrite=True) else: print('Output image format "{}" not understood.'.format(img_format)) sys.exit(1) if not skip_source_detection: if threshold_format == 'float': return {'threshold_5sig': nsig * img.clipped_rms, 'multiscale': has_large_isl} elif threshold_format == 'str_with_units': # This is done to get around the need for quotes around strings in casapy scripts # 'casastr/' is removed by the generic pipeline return {'threshold_5sig': 'casastr/{0}Jy'.format(nsig * img.clipped_rms), 'multiscale': has_large_isl} else: return {'threshold_5sig': '0.0'}
def main(input_image_file, vertices_file, output_image_file, blank_value='zero', img_format='fits', image_is_casa_model=False, nterms=1): """ Blank a region in an image Parameters ---------- input_image_file : str Filename of input image from which mask will be made. If the image does not exist, a template image with center at (reference_ra_deg, reference_dec_deg) will be made internally vertices_file : str, optional Filename of file with vertices (must be a pickle file containing a dictionary with the vertices in the 'vertices' entry) output_image_file : str Filename of output image blank_value : str, optional Value for blanks (one of 'zero' or 'nan') img_format : str, optional Format of output mask image (one of 'fits' or 'casa') image_is_casa_model : bool, optional If True, the input and output image files are treated as the root name of a casa model image (or images) nterms : int, optional If image_is_casa_model is True, this argument sets the number of nterms for the model """ if type(image_is_casa_model) is str: if image_is_casa_model.lower() == 'true': image_is_casa_model = True else: image_is_casa_model = False if type(nterms) is str: nterms = int(nterms) if image_is_casa_model: if nterms == 1: input_image_files = [input_image_file+'.model'] output_image_files = [output_image_file+'.model'] if nterms == 2: input_image_files = [input_image_file+'.model.tt0', input_image_file+'.model.tt1'] output_image_files = [output_image_file+'.model.tt0', output_image_file+'.model.tt1'] if nterms == 3: input_image_files = [input_image_file+'.model.tt0', input_image_file+'.model.tt1', input_image_file+'.model.tt2'] output_image_files = [output_image_file+'.model.tt0', output_image_file+'.model.tt1', output_image_file+'.model.tt2'] else: input_image_files = [input_image_file] output_image_files = [output_image_file] for input_image, output_image in zip(input_image_files, output_image_files): im = pim.image(input_image) data = im.getdata() coordsys = im.coordinates() imshape = im.shape() new_im = pim.image('', shape=imshape, coordsys=coordsys) # Construct polygon vertices = read_vertices(vertices_file) RAverts = vertices[0] Decverts = vertices[1] xvert = [] yvert = [] for RAvert, Decvert in zip(RAverts, Decverts): pixels = new_im.topixel([0, 1, Decvert*np.pi/180.0, RAvert*np.pi/180.0]) xvert.append(pixels[2]) # x -> Dec yvert.append(pixels[3]) # y -> RA poly = Polygon(xvert, yvert) # Find distance to nearest poly edge and blank those that # are outside the facet (dist < 0) pix_ind = np.indices(data[0, 0].shape) dist = poly.is_inside(pix_ind[0], pix_ind[1]) outside_ind = np.where(dist < 0.0) if len(outside_ind[0]) > 0: if blank_value == 'zero': blank_val = 0.0 elif blank_value == 'nan': blank_val = np.nan else: print('Blank value type "{}" not understood.'.format(blank_with)) sys.exit(1) data[0, 0, pix_ind[0][outside_ind], pix_ind[1][outside_ind]] = blank_val # Save changes new_im.putdata(data) if img_format == 'fits': new_im.tofits(output_image, overwrite=True) elif img_format == 'casa': new_im.saveas(output_image, overwrite=True) else: print('Output image format "{}" not understood.'.format(img_format)) sys.exit(1)
def main(image_name, mask_name, atrous_do=False, threshisl=0.0, threshpix=0.0, rmsbox=None, rmsbox_bright=(35, 7), iterate_threshold=False, adaptive_rmsbox=False, img_format='fits', threshold_format='float', trim_by=0.0, vertices_file=None, atrous_jmax=6, pad_to_size=None, skip_source_detection=False, region_file=None, nsig=5.0, reference_ra_deg=None, reference_dec_deg=None): """ Make a clean mask and return clean threshold Parameters ---------- image_name : str Filename of input image from which mask will be made. If the image does not exist, a template image with center at (reference_ra_deg, reference_dec_deg) will be made internally mask_name : str Filename of output mask image atrous_do : bool, optional Use wavelet module of PyBDSM? threshisl : float, optional Value of thresh_isl PyBDSM parameter threshpix : float, optional Value of thresh_pix PyBDSM parameter rmsbox : tuple of floats, optional Value of rms_box PyBDSM parameter rmsbox_bright : tuple of floats, optional Value of rms_box_bright PyBDSM parameter iterate_threshold : bool, optional If True, threshold will be lower in 20% steps until at least one island is found adaptive_rmsbox : tuple of floats, optional Value of adaptive_rms_box PyBDSM parameter img_format : str, optional Format of output mask image (one of 'fits' or 'casa') threshold_format : str, optional Format of output threshold (one of 'float' or 'str_with_units') trim_by : float, optional Fraction by which the perimeter of the output mask will be trimmed (zeroed) vertices_file : str, optional Filename of file with vertices (must be a pickle file containing a dictionary with the vertices in the 'vertices' entry) atrous_jmax : int, optional Value of atrous_jmax PyBDSM parameter pad_to_size : int, optional Pad output mask image to a size of pad_to_size x pad_to_size skip_source_detection : bool, optional If True, source detection is not run on the input image region_file : str, optional Filename of region file in CASA format. If given, no mask image is made (the region file is used as the clean mask) nsig : float, optional Number of sigma of returned threshold value reference_ra_deg : float, optional RA for center of output mask image reference_dec_deg : float, optional Dec for center of output mask image Returns ------- result : dict Dict with nsig-sigma rms threshold """ if image_name is not None: if image_name.lower() == 'none': image_name = None if rmsbox is not None and type(rmsbox) is str: rmsbox = eval(rmsbox) if type(rmsbox_bright) is str: rmsbox_bright = eval(rmsbox_bright) if pad_to_size is not None and type(pad_to_size) is str: pad_to_size = int(pad_to_size) if type(atrous_do) is str: if atrous_do.lower() == 'true': atrous_do = True threshisl = 4.0 # override user setting to ensure proper source fitting else: atrous_do = False if type(iterate_threshold) is str: if iterate_threshold.lower() == 'true': iterate_threshold = True else: iterate_threshold = False if type(adaptive_rmsbox) is str: if adaptive_rmsbox.lower() == 'true': adaptive_rmsbox = True else: adaptive_rmsbox = False if type(skip_source_detection) is str: if skip_source_detection.lower() == 'true': skip_source_detection = True else: skip_source_detection = False if reference_ra_deg is not None and reference_dec_deg is not None: reference_ra_deg = float(reference_ra_deg) reference_dec_deg = float(reference_dec_deg) if not os.path.exists(image_name): print('Input image not found. Making empty image...') if not skip_source_detection: print('ERROR: Source detection cannot be done on an empty image') sys.exit(1) if reference_ra_deg is not None and reference_dec_deg is not None: image_name = mask_name + '.tmp' make_template_image(image_name, reference_ra_deg, reference_dec_deg) else: print( 'ERROR: if image not found, a refernce position must be given') sys.exit(1) trim_by = float(trim_by) atrous_jmax = int(atrous_jmax) threshpix = float(threshpix) threshisl = float(threshisl) nsig = float(nsig) if not skip_source_detection: if vertices_file is not None: # Modify the input image to blank the regions outside of the polygon temp_img = pim.image(image_name) image_name += '.blanked' temp_img.saveas(image_name, overwrite=True) input_img = pim.image(image_name) data = input_img.getdata() vertices = read_vertices(vertices_file) RAverts = vertices[0] Decverts = vertices[1] xvert = [] yvert = [] for RAvert, Decvert in zip(RAverts, Decverts): pixels = input_img.topixel( [1, 1, Decvert * np.pi / 180.0, RAvert * np.pi / 180.0]) xvert.append(pixels[2]) # x -> Dec yvert.append(pixels[3]) # y -> RA poly = Polygon(xvert, yvert) # Find masked regions masked_ind = np.where(data[0, 0]) # Find distance to nearest poly edge and set to NaN those that # are outside the facet (dist < 0) dist = poly.is_inside(masked_ind[0], masked_ind[1]) outside_ind = np.where(dist < 0.0) if len(outside_ind[0]) > 0: data[0, 0, masked_ind[0][outside_ind], masked_ind[1][outside_ind]] = np.nan # Save changes input_img.putdata(data) if iterate_threshold: # Start with given threshold and lower it until we get at least one island nisl = 0 while nisl == 0: img = bdsm.process_image(image_name, mean_map='zero', rms_box=rmsbox, thresh_pix=threshpix, thresh_isl=threshisl, atrous_do=atrous_do, ini_method='curvature', thresh='hard', adaptive_rms_box=adaptive_rmsbox, adaptive_thresh=150, rms_box_bright=rmsbox_bright, rms_map=True, quiet=True, atrous_jmax=atrous_jmax) nisl = img.nisl threshpix /= 1.2 threshisl /= 1.2 if threshpix < 5.0: break else: img = bdsm.process_image(image_name, mean_map='zero', rms_box=rmsbox, thresh_pix=threshpix, thresh_isl=threshisl, atrous_do=atrous_do, ini_method='curvature', thresh='hard', adaptive_rms_box=adaptive_rmsbox, adaptive_thresh=150, rms_box_bright=rmsbox_bright, rms_map=True, quiet=True, atrous_jmax=atrous_jmax) if img.nisl == 0: print('No islands found. Clean mask cannot be made.') sys.exit(1) # Check if there are large islands preset (indicating that multi-scale # clean is needed) has_large_isl = False for isl in img.islands: if isl.size_active > 100: # Assuming normal sampling, a size of 100 pixels would imply # a source of ~ 10 beams has_large_isl = True if (region_file is not None) and (region_file != '[]'): # Copy the CASA region file (stripped of brackets, etc.) and return os.system('cp {0} {1}'.format(region_file.strip('[]"'), mask_name)) if not skip_source_detection: if threshold_format == 'float': return { 'threshold_5sig': nsig * img.clipped_rms, 'multiscale': has_large_isl } elif threshold_format == 'str_with_units': # This is done to get around the need for quotes around strings in casapy scripts # 'casastr/' is removed by the generic pipeline return { 'threshold_5sig': 'casastr/{0}Jy'.format(nsig * img.clipped_rms), 'multiscale': has_large_isl } else: return {'threshold_5sig': '0.0'} elif not skip_source_detection: img.export_image(img_type='island_mask', mask_dilation=0, outfile=mask_name, img_format=img_format, clobber=True) if vertices_file is not None or trim_by > 0 or pad_to_size is not None or skip_source_detection: # Alter the mask in various ways if skip_source_detection: # Read the image mask_im = pim.image(image_name) else: # Read the PyBDSM mask mask_im = pim.image(mask_name) data = mask_im.getdata() coordsys = mask_im.coordinates() if reference_ra_deg is not None and reference_dec_deg is not None: values = coordsys.get_referencevalue() values[2][0] = reference_dec_deg / 180.0 * np.pi values[2][1] = reference_ra_deg / 180.0 * np.pi coordsys.set_referencevalue(values) imshape = mask_im.shape() del (mask_im) if pad_to_size is not None: imsize = pad_to_size coordsys['direction'].set_referencepixel([imsize / 2, imsize / 2]) pixmin = (imsize - imshape[2]) / 2 if pixmin < 0: print("The padded size must be larger than the original size.") sys.exit(1) pixmax = pixmin + imshape[2] data_pad = np.zeros((1, 1, imsize, imsize), dtype=np.float32) data_pad[0, 0, pixmin:pixmax, pixmin:pixmax] = data[0, 0] new_mask = pim.image('', shape=(1, 1, imsize, imsize), coordsys=coordsys) new_mask.putdata(data_pad) else: new_mask = pim.image('', shape=imshape, coordsys=coordsys) new_mask.putdata(data) data = new_mask.getdata() if skip_source_detection: # Mask all pixels data[:] = 1 if vertices_file is not None: # Modify the clean mask to exclude regions outside of the polygon vertices = read_vertices(vertices_file) RAverts = vertices[0] Decverts = vertices[1] xvert = [] yvert = [] for RAvert, Decvert in zip(RAverts, Decverts): try: pixels = new_mask.topixel([ 0, 1, Decvert * np.pi / 180.0, RAvert * np.pi / 180.0 ]) except: pixels = new_mask.topixel([ 1, 1, Decvert * np.pi / 180.0, RAvert * np.pi / 180.0 ]) xvert.append(pixels[2]) # x -> Dec yvert.append(pixels[3]) # y -> RA poly = Polygon(xvert, yvert) # Find masked regions masked_ind = np.where(data[0, 0]) # Find distance to nearest poly edge and unmask those that # are outside the facet (dist < 0) dist = poly.is_inside(masked_ind[0], masked_ind[1]) outside_ind = np.where(dist < 0.0) if len(outside_ind[0]) > 0: data[0, 0, masked_ind[0][outside_ind], masked_ind[1][outside_ind]] = 0 if trim_by > 0.0: sh = np.shape(data) margin = int(sh[2] * trim_by / 2.0) data[0, 0, 0:sh[2], 0:margin] = 0 data[0, 0, 0:margin, 0:sh[3]] = 0 data[0, 0, 0:sh[2], sh[3] - margin:sh[3]] = 0 data[0, 0, sh[2] - margin:sh[2], 0:sh[3]] = 0 # Save changes new_mask.putdata(data) if img_format == 'fits': new_mask.tofits(mask_name, overwrite=True) elif img_format == 'casa': new_mask.saveas(mask_name, overwrite=True) else: print( 'Output image format "{}" not understood.'.format(img_format)) sys.exit(1) if not skip_source_detection: if threshold_format == 'float': return { 'threshold_5sig': nsig * img.clipped_rms, 'multiscale': has_large_isl } elif threshold_format == 'str_with_units': # This is done to get around the need for quotes around strings in casapy scripts # 'casastr/' is removed by the generic pipeline return { 'threshold_5sig': 'casastr/{0}Jy'.format(nsig * img.clipped_rms), 'multiscale': has_large_isl } else: return {'threshold_5sig': '0.0'}
def thiessen(directions_list, field_ra_deg, field_dec_deg, faceting_radius_deg, s=None, check_edges=False, target_ra=None, target_dec=None, target_radius_arcmin=None, beam_ratio=None): """ Generates and add thiessen polygons or patches to input directions Parameters ---------- directions_list : list of Direction objects List of input directions field_ra_deg : float RA in degrees of field center field_dec_deg : float Dec in degrees of field center faceting_radius_deg : float Maximum radius within which faceting will be done. Direction objects with postions outside this radius will get small rectangular patches instead of thiessen polygons s : LSMTool SkyModel object, optional Band to use to check for source near facet edges check_edges : bool, optional If True, check whether any know source falls on a facet edge. If sources are found that do, the facet is adjusted target_ra : str, optional RA of target source. E.g., '14h41m01.884' target_dec : str, optional Dec of target source. E.g., '+35d30m31.52' target_radius_arcmin : float, optional Radius in arcmin of target source beam_ratio : float, optional Ratio of semi-major (N-S) axis to semi-minor (E-W) axis for the primary beam """ import lsmtool import shapely.geometry from shapely.ops import cascaded_union from itertools import combinations from astropy.coordinates import Angle # Select directions inside FOV (here defined as ellipse given by # faceting_radius_deg and the mean elevation) faceting_radius_pix = faceting_radius_deg / 0.066667 # radius in pixels field_x, field_y = radec2xy([field_ra_deg], [field_dec_deg], refRA=field_ra_deg, refDec=field_dec_deg) fx = [] fy = [] for th in range(0, 360, 1): fx.append(faceting_radius_pix * np.cos(th * np.pi / 180.0) + field_x[0]) fy.append(faceting_radius_pix * beam_ratio * np.sin(th * np.pi / 180.0) + field_y[0]) fov_poly_tuple = tuple([(xp, yp) for xp, yp in zip(fx, fy)]) fov_poly = Polygon(fx, fy) points, _, _ = getxy(directions_list, field_ra_deg, field_dec_deg) for x, y, d in zip(points[0], points[1], directions_list): dist = fov_poly.is_inside(x, y) if dist < 0.0: # Source is outside of FOV, so use simple rectangular patches d.is_patch = True # Now do the faceting (excluding the patches) directions_list_thiessen = [d for d in directions_list if not d.is_patch] points, _, _ = getxy(directions_list_thiessen, field_ra_deg, field_dec_deg) points = points.T # Generate array of outer points used to constrain the facets nouter = 64 means = np.ones((nouter, 2)) * points.mean(axis=0) offsets = [] angles = [np.pi/(nouter/2.0)*i for i in range(0, nouter)] for ang in angles: offsets.append([np.cos(ang), np.sin(ang)]) # Generate initial facets radius = 5.0 * faceting_radius_deg / 0.066667 # radius in pixels scale_offsets = radius * np.array(offsets) outer_box = means + scale_offsets points_all = np.vstack([points, outer_box]) tri = Delaunay(points_all) circumcenters = np.array([_circumcenter(tri.points[t]) for t in tri.vertices]) thiessen_polys = [_thiessen_poly(tri, circumcenters, n) for n in range(len(points_all) - nouter)] # Check for vertices that are very close to each other, as this gives problems # to the edge adjustment below for thiessen_poly in thiessen_polys: dup_ind = 0 for i, (v1, v2) in enumerate(zip(thiessen_poly[:-1], thiessen_poly[1:])): if (approx_equal(v1[0], v2[0], rel=1e-6) and approx_equal(v1[1], v2[1], rel=1e-6)): thiessen_poly.pop(dup_ind) dup_ind -= 1 dup_ind += 1 # Clip the facets at FOV for i, thiessen_poly in enumerate(thiessen_polys): polyv = np.vstack(thiessen_poly) poly_tuple = tuple([(xp, yp) for xp, yp in zip(polyv[:, 0], polyv[:, 1])]) p1 = shapely.geometry.Polygon(poly_tuple) p2 = shapely.geometry.Polygon(fov_poly_tuple) if p1.intersects(p2): p1 = p1.intersection(p2) xyverts = [np.array([xp, yp]) for xp, yp in zip(p1.exterior.coords.xy[0].tolist(), p1.exterior.coords.xy[1].tolist())] thiessen_polys[i] = xyverts # Check for sources near / on facet edges and adjust regions accordingly if check_edges: log.info('Adjusting facets to avoid sources...') RA, Dec = s.getPatchPositions(asArray=True) sx, sy = radec2xy(RA, Dec, refRA=field_ra_deg, refDec=field_dec_deg) sizes = s.getPatchSizes(units='degree').tolist() if target_ra is not None and target_dec is not None and target_radius_arcmin is not None: log.info('Including target ({0}, {1}) in facet adjustment'.format( target_ra, target_dec)) tra = Angle(target_ra).to('deg').value tdec = Angle(target_dec).to('deg').value tx, ty = radec2xy([tra], [tdec], refRA=field_ra_deg, refDec=field_dec_deg) sx.extend(tx) sy.extend(ty) sizes.append(target_radius_arcmin*2.0/1.2/60.0) # Set minimum size to 2*FWHM of resolution of high-res image fwhm = 2.0 * 25.0 / 3600.0 # degrees sizes = [max(size, fwhm) for size in sizes] # Filter sources to get only those close to a boundary. We need to iterate # until no sources are found niter = 0 while niter < 3: niter += 1 ind_near_edge = [] for i, thiessen_poly in enumerate(thiessen_polys): polyv = np.vstack(thiessen_poly) poly_tuple = tuple([(x, y) for x, y in zip(polyv[:, 0], polyv[:, 1])]) poly = Polygon(polyv[:, 0], polyv[:, 1]) dists = poly.is_inside(sx, sy) for j, dist in enumerate(dists): pix_radius = sizes[j] * 1.2 / 2.0 / 0.066667 # radius of source in pixels if abs(dist) < pix_radius and j not in ind_near_edge: ind_near_edge.append(j) if len(ind_near_edge) == 0: break sx_filt = np.array(sx)[ind_near_edge] sy_filt = np.array(sy)[ind_near_edge] sizes_filt = np.array(sizes)[ind_near_edge] # Adjust all facets for each source near a boundary for x, y, size in zip(sx_filt, sy_filt, sizes_filt): for i, thiessen_poly in enumerate(thiessen_polys): polyv = np.vstack(thiessen_poly) poly_tuple = tuple([(xp, yp) for xp, yp in zip(polyv[:, 0], polyv[:, 1])]) poly = Polygon(polyv[:, 0], polyv[:, 1]) dist = poly.is_inside(x, y) p1 = shapely.geometry.Polygon(poly_tuple) pix_radius = size * 1.2 / 2.0 / 0.066667 # size of source in pixels if abs(dist) < pix_radius: p2 = shapely.geometry.Point((x, y)) p2buf = p2.buffer(pix_radius) if dist < 0.0: # If point is outside, difference the polys p1 = p1.difference(p2buf) else: # If point is inside, union the polys p1 = p1.union(p2buf) try: xyverts = [np.array([xp, yp]) for xp, yp in zip(p1.exterior.coords.xy[0].tolist(), p1.exterior.coords.xy[1].tolist())] except AttributeError: log.error('Source avoidance has caused a facet to be ' 'divided into multple parts. Please adjust the ' 'parameters (e.g., if a target source is specified, ' 'reduce its radius if possible)') sys.exit(1) thiessen_polys[i] = xyverts # Add the final facet and patch info to the directions patch_polys = [] for d in directions_list: # Make calibrator patch sx, sy = radec2xy([d.ra], [d.dec], refRA=field_ra_deg, refDec=field_dec_deg) # Compute size of patch in pixels, with a factor of 0.8 so that # sources are not added along the edges (the areas outside of this 80% # region are masked during imaging in the make_clean_mask() call with # trim_by = 0.2 patch_width = d.cal_imsize * 0.8 * d.cellsize_selfcal_deg / 0.066667 x0 = sx[0] - patch_width / 2.0 y0 = sy[0] - patch_width / 2.0 selfcal_poly = [np.array([x0, y0]), np.array([x0, y0+patch_width]), np.array([x0+patch_width, y0+patch_width]), np.array([x0+patch_width, y0])] if d.is_patch: # For sources beyond max radius, set facet poly to calibrator poly # and clip to facet polys and previous patch polys # # First, make a copy of the calibrator poly so that it is not # altered by the clipping patch_poly = [np.copy(vert) for vert in selfcal_poly] # Now loop over the facets and patches and clip for facet_poly in thiessen_polys + patch_polys: polyv = np.vstack(facet_poly) poly_tuple = tuple([(xp, yp) for xp, yp in zip(polyv[:, 0], polyv[:, 1])]) p1 = shapely.geometry.Polygon(poly_tuple) p2 = shapely.geometry.Polygon(patch_poly) if p2.intersects(p1): p2 = p2.difference(p1) try: xyverts = [np.array([xp, yp]) for xp, yp in zip(p2.exterior.coords.xy[0].tolist(), p2.exterior.coords.xy[1].tolist())] patch_poly = xyverts except AttributeError: pass add_facet_info(d, selfcal_poly, patch_poly, field_ra_deg, field_dec_deg) patch_polys.append(patch_poly) else: facet_poly = thiessen_polys[directions_list_thiessen.index(d)] add_facet_info(d, selfcal_poly, facet_poly, field_ra_deg, field_dec_deg)
def main(input_image_file, vertices_file, output_image_file, blank_value='zero', img_format='fits', image_is_casa_model=False, nterms=1): """ Blank a region in an image Parameters ---------- input_image_file : str Filename of input image from which mask will be made. If the image does not exist, a template image with center at (reference_ra_deg, reference_dec_deg) will be made internally vertices_file : str, optional Filename of file with vertices (must be a pickle file containing a dictionary with the vertices in the 'vertices' entry) output_image_file : str Filename of output image blank_value : str, optional Value for blanks (one of 'zero' or 'nan') img_format : str, optional Format of output mask image (one of 'fits' or 'casa') image_is_casa_model : bool, optional If True, the input and output image files are treated as the root name of a casa model image (or images) nterms : int, optional If image_is_casa_model is True, this argument sets the number of nterms for the model """ if type(image_is_casa_model) is str: if image_is_casa_model.lower() == 'true': image_is_casa_model = True else: image_is_casa_model = False if type(nterms) is str: nterms = int(nterms) if image_is_casa_model: if nterms == 1: input_image_files = [input_image_file + '.model'] output_image_files = [output_image_file + '.model'] if nterms == 2: input_image_files = [ input_image_file + '.model.tt0', input_image_file + '.model.tt1' ] output_image_files = [ output_image_file + '.model.tt0', output_image_file + '.model.tt1' ] if nterms == 3: input_image_files = [ input_image_file + '.model.tt0', input_image_file + '.model.tt1', input_image_file + '.model.tt2' ] output_image_files = [ output_image_file + '.model.tt0', output_image_file + '.model.tt1', output_image_file + '.model.tt2' ] else: input_image_files = [input_image_file] output_image_files = [output_image_file] for input_image, output_image in zip(input_image_files, output_image_files): im = pim.image(input_image) data = im.getdata() coordsys = im.coordinates() imshape = im.shape() new_im = pim.image('', shape=imshape, coordsys=coordsys) # Construct polygon vertices = read_vertices(vertices_file) RAverts = vertices[0] Decverts = vertices[1] xvert = [] yvert = [] for RAvert, Decvert in zip(RAverts, Decverts): pixels = new_im.topixel( [0, 1, Decvert * np.pi / 180.0, RAvert * np.pi / 180.0]) xvert.append(pixels[2]) # x -> Dec yvert.append(pixels[3]) # y -> RA poly = Polygon(xvert, yvert) # Find distance to nearest poly edge and blank those that # are outside the facet (dist < 0) pix_ind = np.indices(data[0, 0].shape) dist = poly.is_inside(pix_ind[0], pix_ind[1]) outside_ind = np.where(dist < 0.0) if len(outside_ind[0]) > 0: if blank_value == 'zero': blank_val = 0.0 elif blank_value == 'nan': blank_val = np.nan else: print( 'Blank value type "{}" not understood.'.format(blank_with)) sys.exit(1) data[0, 0, pix_ind[0][outside_ind], pix_ind[1][outside_ind]] = blank_val # Save changes new_im.putdata(data) if img_format == 'fits': new_im.tofits(output_image, overwrite=True) elif img_format == 'casa': new_im.saveas(output_image, overwrite=True) else: print( 'Output image format "{}" not understood.'.format(img_format)) sys.exit(1)
def main(input_image_file, vertices_file, output_image_file, blank_value='zero', image_is_wsclean_model=False): """ Blank a region in an image Parameters ---------- input_image_file : str Filename of input image to blank vertices_file : str, optional Filename of file with vertices (must be a pickle file containing a dictionary with the vertices in the 'vertices' entry) output_image_file : str Filename of output image blank_value : str, optional Value for blanks (one of 'zero' or 'nan') image_is_wsclean_model : bool, optional If True, the input and output image files are treated as the root name of a WSClean model image (or images) """ if type(image_is_wsclean_model) is str: if image_is_wsclean_model.lower() == 'true': image_is_wsclean_model = True else: image_is_wsclean_model = False if image_is_wsclean_model: input_image_files = glob.glob(input_image_file+'*-model.fits') output_image_files = [f.replace(input_image_file, output_image_file) for f in input_image_files] else: input_image_files = [input_image_file] output_image_files = [output_image_file] if blank_value == 'zero': blank_val = 0.0 elif blank_value == 'nan': blank_val = np.nan else: print('Blank value type "{}" not understood.'.format(blank_with)) sys.exit(1) # Construct polygon of facet region header = pyfits.getheader(input_image_files[0], 0) w = wcs.WCS(header) RAind = w.axis_type_names.index('RA') Decind = w.axis_type_names.index('DEC') vertices = read_vertices(vertices_file) RAverts = vertices[0] Decverts = vertices[1] xvert = [] yvert = [] for RAvert, Decvert in zip(RAverts, Decverts): ra_dec = np.array([[0.0, 0.0, 0.0, 0.0]]) ra_dec[0][RAind] = RAvert ra_dec[0][Decind] = Decvert xvert.append(w.wcs_world2pix(ra_dec, 0)[0][Decind]) yvert.append(w.wcs_world2pix(ra_dec, 0)[0][RAind]) poly = Polygon(xvert, yvert) for input_image, output_image in zip(input_image_files, output_image_files): hdu = pyfits.open(input_image, memmap=False) data = hdu[0].data # Find limits of facet poly and blank pixels outside them xmin = max(int(np.min(xvert)) - 2, 0) xmax = min(int(np.max(xvert)) + 2, data.shape[2]) ymin = max(int(np.min(yvert)) - 2, 0) ymax = min(int(np.max(yvert)) + 2, data.shape[3]) data[0, 0, :, :ymin] = blank_val data[0, 0, :, ymax:] = blank_val data[0, 0, :xmin, :] = blank_val data[0, 0, xmax:, :] = blank_val # Find distance to nearest poly edge and blank those that # are outside the facet (dist < 0) pix_ind = np.indices((xmax-xmin, ymax-ymin)) pix_ind[0] += xmin pix_ind[1] += ymin dist = poly.is_inside(pix_ind[0], pix_ind[1]) outside_ind = np.where(dist < 0.0) if len(outside_ind[0]) > 0: data[0, 0, pix_ind[0][outside_ind], pix_ind[1][outside_ind]] = blank_val hdu[0].data = data hdu.writeto(output_image, clobber=True)
def main(input_image_file, vertices_file, output_image_file, blank_value='zero', image_is_wsclean_model=False): """ Blank a region in an image Parameters ---------- input_image_file : str Filename of input image to blank vertices_file : str, optional Filename of file with vertices (must be a pickle file containing a dictionary with the vertices in the 'vertices' entry) output_image_file : str Filename of output image blank_value : str, optional Value for blanks (one of 'zero' or 'nan') image_is_wsclean_model : bool, optional If True, the input and output image files are treated as the root name of a WSClean model image (or images) """ if type(image_is_wsclean_model) is str: if image_is_wsclean_model.lower() == 'true': image_is_wsclean_model = True else: image_is_wsclean_model = False if image_is_wsclean_model: input_image_files = glob.glob(input_image_file + '*-model.fits') output_image_files = [ f.replace(input_image_file, output_image_file) for f in input_image_files ] else: input_image_files = [input_image_file] output_image_files = [output_image_file] if blank_value == 'zero': blank_val = 0.0 elif blank_value == 'nan': blank_val = np.nan else: print('Blank value type "{}" not understood.'.format(blank_with)) sys.exit(1) # Construct polygon of facet region header = pyfits.getheader(input_image_files[0], 0) w = wcs.WCS(header) RAind = w.axis_type_names.index('RA') Decind = w.axis_type_names.index('DEC') vertices = read_vertices(vertices_file) RAverts = vertices[0] Decverts = vertices[1] xvert = [] yvert = [] for RAvert, Decvert in zip(RAverts, Decverts): ra_dec = np.array([[0.0, 0.0, 0.0, 0.0]]) ra_dec[0][RAind] = RAvert ra_dec[0][Decind] = Decvert xvert.append(w.wcs_world2pix(ra_dec, 0)[0][Decind]) yvert.append(w.wcs_world2pix(ra_dec, 0)[0][RAind]) poly = Polygon(xvert, yvert) for input_image, output_image in zip(input_image_files, output_image_files): hdu = pyfits.open(input_image, memmap=False) data = hdu[0].data # Find limits of facet poly and blank pixels outside them xmin = max(int(np.min(xvert)) - 2, 0) xmax = min(int(np.max(xvert)) + 2, data.shape[2]) ymin = max(int(np.min(yvert)) - 2, 0) ymax = min(int(np.max(yvert)) + 2, data.shape[3]) data[0, 0, :, :ymin] = blank_val data[0, 0, :, ymax:] = blank_val data[0, 0, :xmin, :] = blank_val data[0, 0, xmax:, :] = blank_val # Find distance to nearest poly edge and blank those that # are outside the facet (dist < 0) pix_ind = np.indices((xmax - xmin, ymax - ymin)) pix_ind[0] += xmin pix_ind[1] += ymin dist = poly.is_inside(pix_ind[0], pix_ind[1]) outside_ind = np.where(dist < 0.0) if len(outside_ind[0]) > 0: data[0, 0, pix_ind[0][outside_ind], pix_ind[1][outside_ind]] = blank_val hdu[0].data = data hdu.writeto(output_image, clobber=True)