def __init__(self, env, x_init, min_dist=.3, nfourier=20, t_horizon=2, t_history=0, mode='mi', gui=True): self.x = x_init self.bounds = env.bounds self.u_max = env.get_u_max() self.min_dist = min_dist self.nfourier = nfourier self.t_horizon = t_horizon self.nagents = x_init.shape[0] self.tp_rate = env.tp_rate self.fp_rate = env.fp_rate self.u = [np.zeros((2, t_horizon)) for i in range(self.nagents)] K1, K2 = np.meshgrid(np.arange(nfourier), np.arange(nfourier), indexing='ij') self.k1 = K1.flatten() * np.pi self.k2 = K2.flatten() * np.pi self.hk = np.ones(self.k1.shape[0]) self.hk[self.k1 != 0] *= np.sqrt(.5) self.hk[self.k2 != 0] *= np.sqrt(.5) s = (2 + 1) / 2 self.Lambdak = (1 + np.square(self.k1) + np.square(self.k2))**(-s) self.t_history = t_history self.ck_history_cum = [] r = env.target_r + env.view_r npix = int(np.ceil(r / env.grid_dx) * 2 + 1) reg = CirclePixelRegion(center=PixCoord(npix, npix), radius=r / env.grid_dx) self.footprint_mask = reg.to_mask(mode='exact').data self.image1 = None self.image2 = None self.lines = None # self.change_paramt0 = 5 # self.change_param = .01 self.change_paramt0 = .1 self.change_param = .01 self.mode = mode self.gui = gui
def makeMask(xs, ys, radius, cenx, ceny): shape = (xs, ys) fin_mask = np.zeros(shape) print(radius) for i in np.arange(len(cenx)): if radius[i] > 5: radius[i] = 6 center = PixCoord(cenx[i], ceny[i]) circle = CirclePixelRegion(center, radius[i] - 1) mask = circle.to_mask() newmask = mask.to_image(shape) fin_mask += newmask fin_mask[fin_mask > 1] = 1 return fin_mask
def find_regions(self): """Find reflected regions.""" curr_angle = self._angle + self._min_ang + self.min_distance_input reflected_regions = [] while curr_angle < self._max_angle: test_pos = self._compute_xy(self._pix_center, self._offset, curr_angle) test_reg = CirclePixelRegion(test_pos, self._pix_region.radius) if not self._is_inside_exclusion(test_reg, self._distance_image): refl_region = test_reg.to_sky(self.exclusion_mask.wcs) log.debug('Placing reflected region\n{}'.format(refl_region)) reflected_regions.append(refl_region) curr_angle = curr_angle + self._min_ang else: curr_angle = curr_angle + self.angle_increment log.info('Found {} reflected regions'.format(len(reflected_regions))) self.reflected_regions = reflected_regions
def test_regions_pixel(self): from regions import PixCoord, CirclePixelRegion # Out-of-bounds should still overlay the overlapped part. my_reg = CirclePixelRegion(center=PixCoord(x=6, y=2), radius=5) self.imviz.load_static_regions({'my_reg': my_reg}) self.verify_region_loaded('my_reg') assert self.imviz.get_interactive_regions() == {}
def test_regions_fully_out_of_bounds(self): my_reg = CirclePixelRegion(center=PixCoord(x=100, y=100), radius=5) with pytest.warns(UserWarning, match='failed to load, skipping'): bad_regions = self.imviz.load_static_regions( {'my_oob_reg': my_reg}) assert len(bad_regions) == 1 # BUG: https://github.com/glue-viz/glue/issues/2275 self.verify_region_loaded('my_oob_reg', count=1) # Should be: count=0
def find_regions(self): """Find reflected regions.""" curr_angle = self._angle + self._min_ang + self.min_distance_input reflected_regions = [] while curr_angle < self._max_angle: test_pos = self._compute_xy(self._pix_center, self._offset, curr_angle) test_reg = CirclePixelRegion(test_pos, self._pix_region.radius) if not self._is_inside_exclusion(test_reg, self._distance_image): refl_region = test_reg.to_sky(self.reference_map.geom.wcs) log.debug("Placing reflected region\n{}".format(refl_region)) reflected_regions.append(refl_region) curr_angle = curr_angle + self._min_ang if self.max_region_number <= len(reflected_regions): break else: curr_angle = curr_angle + self.angle_increment log.debug("Found {} reflected regions".format(len(reflected_regions))) self.reflected_regions = reflected_regions
def test_region_mask(): mask = SkyImage.empty(nxpix=5, nypix=4) # Test with a pixel region region = CirclePixelRegion(center=PixCoord(x=2, y=1), radius=1.1) actual = mask.region_mask(region) expected = [ [0, 0, 1, 0, 0], [0, 1, 1, 1, 0], [0, 0, 1, 0, 0], [0, 0, 0, 0, 0], ] assert_equal(actual.data, expected) # Test with a sky region # Output is the same in this case, because the sky region # represents roughly the same region as the pixel region sky_region = region.to_sky(wcs=mask.wcs) actual = mask.region_mask(sky_region) assert_equal(actual.data, expected)
def test_region_mask(): mask = SkyImage.empty(nxpix=5, nypix=4) # Test with a pixel region region = CirclePixelRegion(center=PixCoord(x=2, y=1), radius=1.1) actual = mask.region_mask(region) expected = [ [0, 0, 1, 0, 0], [0, 1, 1, 1, 0], [0, 0, 1, 0, 0], [0, 0, 0, 0, 0], ] assert_equal(actual.data, expected) # Test with a sky region # Output is the same in this case, because the sky region # represents roughly the same region as the pixel region sky_region = region.to_sky(wcs=mask.wcs) actual = mask.region_mask(sky_region) assert_equal(actual.data, expected)
def test_wcslink_affine_with_extras(self): from regions import PixCoord, CirclePixelRegion self.imviz.link_data(link_type='wcs', wcs_fallback_scheme=None, error_on_fail=True) links = self.imviz.app.data_collection.external_links assert len(links) == 1 assert isinstance(links[0], OffsetLink) # Customize display on second image (last loaded). self.imviz.set_colormap('viridis') self.imviz.stretch = 'sqrt' self.imviz.cuts = (0, 100) # Add subsets, both interactive and static. self.imviz._apply_interactive_region('bqplot:circle', (1.5, 2.5), (3.6, 4.6)) self.imviz.load_static_regions({ 'my_reg': CirclePixelRegion(center=PixCoord(x=6, y=2), radius=5)}) # Add markers. tbl = Table({'x': (0, 0), 'y': (0, 1)}) self.imviz.add_markers(tbl, marker_name='xy_markers') assert 'xy_markers' in self.imviz.app.data_collection.labels # Run linking again, does not matter what kind. self.imviz.link_data(link_type='wcs', wcs_fallback_scheme=None, error_on_fail=True) # Ensure display is still customized. assert self.viewer.state.layers[1].cmap.name == 'viridis' assert self.viewer.state.layers[1].stretch == 'sqrt' assert_allclose((self.viewer.state.layers[1].v_min, self.viewer.state.layers[1].v_max), (0, 100)) # Ensure subsets are still there. assert 'Subset 1' in self.imviz.get_interactive_regions() assert 'my_reg' in [layer.layer.label for layer in self.viewer.state.layers] # Ensure markers are deleted. # Zoom and pan will reset in this case, so we do not check those. assert 'xy_markers' not in self.imviz.app.data_collection.labels assert len(self.imviz._marktags) == 0 # Pan/zoom. self.imviz.center_on((5, 5)) self.imviz.zoom_level = 0.789 ans = (self.viewer.state.x_min, self.viewer.state.y_min, self.viewer.state.x_max, self.viewer.state.y_max) # Run linking again, does not matter what kind. self.imviz.link_data(link_type='wcs', wcs_fallback_scheme=None, error_on_fail=True) # Ensure pan/zoom does not change when markers are not present. assert_allclose((self.viewer.state.x_min, self.viewer.state.y_min, self.viewer.state.x_max, self.viewer.state.y_max), ans)
def angle_in_circle(image_filename, position_of_star, rht_data, radius): """ Gets a star, draws a circle around it and returns the average rht angle and standard deviation in this circle. """ ipoints, jpoints, hthets, naxis1, naxis2, wlen, smr, thresh, thets_deg = rht_data c = CirclePixelRegion(PixCoord( np.round(position_of_star)[0], np.round(position_of_star)[1]), radius=radius) # Check which RHT data is in this region, these are the indices: indices = c.contains(PixCoord(ipoints, jpoints)) # hthets[indices] are all the pixel angle distributions found in the circle # Loop through pixels: # We multiply by thets_deg to get angle distribution, then normalise by dividing by the sum of the distribution, and finally take the sum of all values to get the average angle. average_on_pixel = [] for i, v in enumerate(hthets[indices]): average_on_pixel.append( (hthets[indices][i] * thets_deg / hthets[indices][i].sum()).sum()) return np.mean(average_on_pixel), np.std(average_on_pixel)
def test_regions_pixel(self): # A little out-of-bounds should still overlay the overlapped part. my_reg = CirclePixelRegion(center=PixCoord(x=6, y=2), radius=5) bad_regions = self.imviz.load_static_regions({'my_reg': my_reg}) assert len(bad_regions) == 0 self.verify_region_loaded('my_reg') # Unsupported shape but a valid region my_pt_reg = PointPixelRegion(center=PixCoord(x=1, y=1)) with pytest.warns(UserWarning, match='failed to load, skipping'): bad_regions = self.imviz.load_static_regions( {'my_pt_reg': my_pt_reg}) assert len(bad_regions) == 1 self.verify_region_loaded('my_pt_reg', count=0) assert self.imviz.get_interactive_regions() == {}
def pix_region(center=([49,49]), radius=5): ''' Creat a region file, with pixel units Parameter -------- center: The center of the region, with ([reg_x, reg_y]); radius: The radius of the region. Return -------- A region which is ds9-like. ''' center= PixCoord(x=center[0],y=center[1]) region = CirclePixelRegion(center, radius) return region
def cutout(filename, position, size, circ): """ Cut out a square of size size at center position position of the filename filename. Returns: the cut out filename, so it can be used in other scripts. """ # Load the image and the WCS hdu = fits.open(filename)[0] naxis = hdu.header['NAXIS'] wcs = WCS(hdu.header, naxis=naxis) # Make the cutout, including the WCS cutout = Cutout2D(hdu.data, position=position, size=size, wcs=wcs) # Put the cutout image in the FITS HDU hdu.data = cutout.data # Update the FITS header with the cutout WCS hdu.header.update(cutout.wcs.to_header()) # Choose whether you want a circle or a square if (circ): # Define a circle region and keep only data that is in that region circle_pix = CirclePixelRegion( PixCoord(len(cutout.data[:, 1]) // 2, len(cutout.data[1, :]) // 2), radius=np.min(size) / 2) # region object for i in range(len(cutout.data[:, 1])): for j in range(len(cutout.data[1, :])): if not PixCoord(i, j) in circle_pix: cutout.data[i, j] = 'nan' # Write the cutout to a new FITS file cutout_filename = filename.split( '.')[0] + '_cutout_circ.' + filename.split('.')[1] hdu.writeto(cutout_filename, overwrite=True) print("Made circle cutout, output: " + cutout_filename) else: # Write the cutout to a new FITS file cutout_filename = filename.split('.')[0] + '_cutout.' + filename.split( '.')[1] hdu.writeto(cutout_filename, overwrite=True) print("Made cutout, output: " + cutout_filename) # Return the output filename so we can use it in other scripts return cutout_filename
def pix_region(center=[49.0, 49.0], radius=5): """ Creat a region file, in pixel units. Parameter -------- center: The center of the region, with [reg_x, reg_y]. radius: The radius of the region. Return -------- A region which is ds9-like. """ center = PixCoord(x=center[0], y=center[1]) region = CirclePixelRegion(center, radius) return region
def _detail_check(eph, polygon_pix, observation_coords, start_date, end_date, radius=0.0083, aggressive_check=False): # A more detailed check for polygon footprint matching. # This checks each location in the original ephemerides and confirms if an observation intersects it flag = False for row in eph: if aggressive_check: if ((row['datetime_jd'] - 2400000.5) > end_date) or ((row['datetime_jd'] - 2400000.5) < start_date): flag = False continue target_coords = PixCoord(row['RA'], row['DEC']) if radius is None or radius < 0 and observation_coords is not None: flag = target_coords in polygon_pix else: target_circle = CirclePixelRegion(center=target_coords, radius=radius) flag = (target_coords in polygon_pix) or (observation_coords in target_circle) if flag: break return flag
def write_ds9_region_file(self, region_file): """Write a ds9-format region file (image coordinates) of the stars within the photometry table. """ # TODO pick color based on Filter reg_color = 'red' pix_fmt = '.2f' # 0.01 pixels (for degrees use .6f) num_stars = len(self._phot_table) ap_radius = math.ceil(self._ap_fwhm_mult * self._search_fwhm) regions = [] for x,y,id in zip(self._phot_table['xcenter'], self._phot_table['ycenter'], self._phot_table['id']): # There is no need to convert from python 0-based to FITS # 1-based as the region class(es) do this for us. xpos = x ypos = y meta_dict = {'name': id} vis_dict = {'color': reg_color} cpr = CirclePixelRegion(center=PixCoord(xpos, ypos), radius=ap_radius, meta=meta_dict, visual=vis_dict) regions.append( cpr ) reg_str = ds9_objects_to_string(regions, coordsys='image', fmt=pix_fmt, radunit='pix') with open(region_file, 'w', encoding='utf-8') as f_out: f_out.write(reg_str) self._logger.debug(f'Wrote ds9-format region file to {region_file}') return
def read_sextractor(infile=None): ''' This function will read in a sectractor output and return a list of regions. Working notes: - how do the SexTractor coordinates work? the test files ones don't make sense for MIRI. Input ----- - infile: the sextractor output file ''' # sextractor files can be read in with astropy.io.ascii hurrah t = ascii.read(infile) # now format each line into a CirclePixelRegion object coord_list = [] for i in range(len(t)): reg = CirclePixelRegion(center=PixCoord(x=t['X_IMAGE'][i], y=t['Y_IMAGE'][i]), radius=t['FWHM_IMAGE'][i] / 2.) coord_list.append(reg) return coord_list
def __call__(self, abs_sci_name): ''' PCA-based background subtraction, for a single frame so as to parallelize job INPUTS: abs_sci_name: science array filename ''' # start the timer start_time = time.time() # read in the science frame from raw data directory sciImg, header_sci = fits.getdata(abs_sci_name, 0, header=True) # apply mask over weird detector regions to science image sciImg = np.multiply(sciImg, make_first_pass_mask(self.quad_choice)) ## mask the PSF # define region psf_loc = find_airy_psf(sciImg) # center of science PSF print("PSF location in " + os.path.basename(abs_sci_name) + ": [" + str(psf_loc[0]) + ", " + str(psf_loc[1]) + "]") radius = 30. # radius around PSF that will be masked center = PixCoord(x=psf_loc[1], y=psf_loc[0]) region = CirclePixelRegion(center, radius) mask_psf_region = region.to_mask() # apply the mask to science array psf_mask = np.ones(np.shape( sciImg)) # initialize arrays of same size as science image mask_psf_region.data[ mask_psf_region.data == 1] = np.nan # make zeros within mask cutout (but not in the mask itself) nans mask_psf_region.data[mask_psf_region.data == 0] = 1 ##mask_psf_region.data[mask_psf_region.data == -99999] = 0 # have to avoid nans in the linear algebra psf_mask[ mask_psf_region.bbox. slices] = mask_psf_region.data # place the mask cutout (consisting only of 1s) onto the array of nans # I don't know why, but the sciImg nans in weird detector regions become zeros by this point, and I want them to stay nans # so, re-apply the mask over the bad regions of the detector sciImg = np.multiply(sciImg, make_first_pass_mask(self.quad_choice)) sciImg_masked = np.multiply( sciImg, psf_mask) # this is now the masked science frame # subtract the median (just a constant) from the remaining science image sciImg_psf_masked = np.subtract( sciImg_masked, np.nanmedian(sciImg_masked)) # where PSF is masked sciImg_psf_not_masked = np.subtract( sciImg, np.nanmedian(sciImg_masked)) # where PSF is not masked # apply the PSF mask to PCA slices, with which we will do the fitting pca_cube_masked = np.multiply(self.pca_cube, psf_mask) ## PCA-decompose # flatten the science array and PCA cube pca_not_masked_1ds = np.reshape( self.pca_cube, (np.shape(self.pca_cube)[0], np.shape(self.pca_cube)[1] * np.shape(self.pca_cube)[2])) sci_masked_1d = np.reshape( sciImg_psf_masked, (np.shape(sciImg_masked)[0] * np.shape(sciImg_masked)[1])) pca_masked_1ds = np.reshape( pca_cube_masked, (np.shape(pca_cube_masked)[0], np.shape(pca_cube_masked)[1] * np.shape(pca_cube_masked)[2])) ## remove nans from the linear algebra # indices of finite elements over a single flattened frame idx = np.logical_and(np.isfinite(pca_masked_1ds[0, :]), np.isfinite(sci_masked_1d)) # reconstitute only the finite elements together in another PCA cube and a science image pca_masked_1ds_noNaN = np.nan * np.ones( (len(pca_masked_1ds[:, 0]), np.sum(idx)) ) # initialize array with slices the length of number of finite elements for t in range( 0, len(pca_masked_1ds[:, 0]) ): # for each PCA component, populate the arrays without nans with the finite elements pca_masked_1ds_noNaN[t, :] = pca_masked_1ds[t, idx] sci_masked_1d_noNaN = np.array(1, np.sum(idx)) # science frame sci_masked_1d_noNaN = sci_masked_1d[idx] # the vector of component amplitudes soln_vector = np.linalg.lstsq(pca_masked_1ds_noNaN[0:self.n_PCA, :].T, sci_masked_1d_noNaN) # reconstruct the background based on that vector # note that the PCA components WITHOUT masking of the PSF location is being # used to reconstruct the background recon_backgrnd_2d = np.dot(self.pca_cube[0:self.n_PCA, :, :].T, soln_vector[0]).T # now do the same, but for the channel bias variation contributions only (assumes 32 elements only) recon_backgrnd_2d_channels_only_no_psf_masking = np.dot( self.pca_cube[0:32, :, :].T, soln_vector[0][0:32]).T # without PSF masking recon_backgrnd_2d_channels_only_psf_masked = np.dot( self.pca_cube[0:32, :, :].T, soln_vector[0][0:32]).T # with PSF masking # do the actual subtraction: # all-background subtraction sciImg_subtracted = np.subtract(sciImg_psf_not_masked, recon_backgrnd_2d) # background subtraction of channels only: # without PSF masking #sciImg_subtracted_channels_only_no_psf_masking = np.subtract(sciImg_psf_not_masked,recon_backgrnd_2d_channels_only) # with PSF masking sciImg_subtracted_channels_only_psf_masked = np.subtract( sciImg_psf_masked, np.multiply(recon_backgrnd_2d_channels_only_psf_masked, np.multiply(self.pca_cube, psf_mask))) # add last reduction step to header header_sci["RED_STEP"] = "pca_background_subtracted" # save reconstructed background for checking abs_recon_bkgd = str(self.config_data["data_dirs"]["DIR_OTHER_FITS"] + 'recon_bkgd_quad_' + str("{:0>2d}".format(self.quad_choice)) + '_PCAseqStart_' + str("{:0>6d}".format(self.cube_start_framenum)) + '_PCAseqStop_' + str("{:0>6d}".format(self.cube_stop_framenum)) + '_' + os.path.basename(abs_sci_name)) fits.writeto(filename=abs_recon_bkgd, data=recon_backgrnd_2d, overwrite=True) # save masked science frame BEFORE background-subtraction abs_masked_sci_before_bkd_subt = str( self.config_data["data_dirs"]["DIR_OTHER_FITS"] + 'masked_sci_before_bkd_subt_quad_' + str("{:0>2d}".format(self.quad_choice)) + '_PCAseqStart_' + str("{:0>6d}".format(self.cube_start_framenum)) + '_PCAseqStop_' + str("{:0>6d}".format(self.cube_stop_framenum)) + os.path.basename(abs_sci_name)) fits.writeto(filename=abs_masked_sci_before_bkd_subt, data=sciImg_psf_masked, overwrite=True) # save masked, background-subtracted science frame background_subtracted_masked = np.multiply( sciImg_subtracted, make_first_pass_mask(self.quad_choice)) background_subtracted_masked = np.multiply( background_subtracted_masked, psf_mask) abs_masked_sci_after_bkd_subt = str( self.config_data["data_dirs"]["DIR_OTHER_FITS"] + 'masked_sci_after_bkd_subt_quad_' + str("{:0>2d}".format(self.quad_choice)) + '_PCAseqStart_' + str("{:0>6d}".format(self.cube_start_framenum)) + '_PCAseqStop_' + str("{:0>6d}".format(self.cube_stop_framenum)) + os.path.basename(abs_sci_name)) fits.writeto(filename=abs_masked_sci_after_bkd_subt, data=background_subtracted_masked, overwrite=True) # save background-subtracted science frame sciImg_subtracted_name = str( self.config_data["data_dirs"]["DIR_PCAB_SUBTED"] + os.path.basename(abs_sci_name)) fits.writeto(filename=sciImg_subtracted_name, data=sciImg_subtracted, header=header_sci, overwrite=True) print('Background-subtracted frame ' + os.path.basename(abs_sci_name) + ' written out. PCA = ' + str(self.n_PCA)) ## make FYI plots of the effectiveness of the background subtraction # (N.b. the PSF and weird detector regions are masked here) self.vital_stats(file_base_name=str(os.path.basename(abs_sci_name)), sci_img_pre=sciImg_masked, sci_img_post_channel_subt= sciImg_subtracted_channels_only_psf_masked, sci_img_post_all_subt=background_subtracted_masked, pca_spec=soln_vector[0]) print('Elapsed time:') elapsed_time = time.time() - start_time print('--------------------------------------------------------------') print(elapsed_time)
def clean_up_results(t_init, obj_name, id_type='smallbody', location=None, radius=0.0083): """ Function to clean up results. Will check if the target is inside the observation footprint. If a radius is provided, will also construct a circle and check if the observation center is in the target circle. Parameters ---------- t_init: atropy Table Initial astropy Table obj_name: str Object name. May require specific formatting (i.e. selecting between the codes for Jupiter and Jupiter barycenter). See JPL Horizons documentation id_type: str Object ID type for JPL Horizons. Defaults to smallbody (an asteroid or comet). Best to be as specific as possible to find the correct body. majorbody: planets and satellites smallbody: asteroids and comets asteroid_name: name of asteroid comet_name: name of comet name: any target name designation: any asteroid or comet designation location: str Default of None uses a geocentric location for queries. For specific spacecrafts, insert location Examples: TESS: @TESS Hubble: @hst Kepler: 500@-227 radius : float Size of target for intersection calculations Returns ------- t: astropy Table Astropy Table with only those where the moving target was in the footprint """ t = t_init.copy() # Add mid-point time and sort by that first t['t_mid'] = (t['t_max'] + t['t_min'])/2 + 2400000.5 t.sort('t_mid') # Ephemerides results are sorted by time, hence the initial sort print('Verifying footprints...') eph = Horizons(id=obj_name, location=location, id_type=id_type, epochs=t['t_mid']).ephemerides() # For each row in table, check s_region versus target position at mid-time check_list = [] for i, row in enumerate(t): # print(row['t_mid'], eph['datetime_jd'][i], eph['RA'][i], eph['DEC'][i]) # Create a polygon for the footprint and check if target is inside polygon try: stcs = parse_s_region(row['s_region']) xs = stcs['ra'] ys = stcs['dec'] polygon_pix = PolygonPixelRegion(vertices=PixCoord(x=xs, y=ys)) target_coords = PixCoord(eph['RA'][i], eph['DEC'][i]) if radius is None or radius <= 0: flag = target_coords in polygon_pix else: target_circle = CirclePixelRegion(center=target_coords, radius=radius) observation_coords = PixCoord(row['s_ra'], row['s_dec']) flag = (target_coords in polygon_pix) or (observation_coords in target_circle) # print(stcs, flag) except Exception as e: print(f"ERROR checking footprint for {row['obs_id']} with: {e}" f"\nAssuming False") flag = False check_list.append(flag) # Set the flags t['in_footprint'] = check_list return t[t['in_footprint']]
def clean_up_results(t_init, obj_name, orig_eph=None, id_type='smallbody', location=None, radius=0.0083, aggressive_check=False): """ Function to clean up results. Will check if the target is inside the observation footprint. If a radius is provided, will also construct a circle and check if the observation center is in the target circle. TODO: This is a buggy for several reasons: 1- regions doesn't have intersection of polygons yet so valid observations are missed 2- the mid point for observations like TESS can be days away from the target location 3- the _detail_check function is effectively re-adding most of what it clears Parameters ---------- t_init: atropy Table Initial astropy Table obj_name: str Object name. May require specific formatting (i.e. selecting between the codes for Jupiter and Jupiter barycenter). See JPL Horizons documentation orig_eph: astropy Table Original ephemerides table id_type: str Object ID type for JPL Horizons. Defaults to smallbody (an asteroid or comet). Best to be as specific as possible to find the correct body. majorbody: planets and satellites smallbody: asteroids and comets asteroid_name: name of asteroid comet_name: name of comet name: any target name designation: any asteroid or comet designation location: str Default of None uses a geocentric location for queries. For specific spacecrafts, insert location Examples: TESS: @TESS Hubble: @hst Kepler: 500@-227 radius: float Size of target for intersection calculations aggressive_check: bool Perform additional time checks; can remove valid observations (Default: False) Returns ------- t: astropy Table Astropy Table with only those where the moving target was in the footprint """ if len(t_init) == 0: return None if radius is not None and not isinstance(radius, float): radius = float(radius) t = t_init.copy() # Sort by mid point time t['t_mid'] = (t['t_max'] + t['t_min']) / 2 + 2400000.5 t.sort('t_mid') # Ephemerides results are sorted by time, hence the initial sort print('Verifying footprints...') # Fix for TESS if location is not None and location.upper() == '@TESS': print('Restriction for TESS observations') threshold = 2456778.50000 # 2018-05-01 ind = t['t_mid'] > threshold t = t[ind] eph = Horizons(id=obj_name, location=location, id_type=id_type, epochs=t['t_mid']).ephemerides() # For each row in table, check s_region versus target position at mid-time check_list = [] for i, row in enumerate(t): # print(row['t_mid'], eph['datetime_jd'][i], eph['RA'][i], eph['DEC'][i]) # Create a polygon for the footprint and check if target is inside polygon try: stcs = parse_s_region(row['s_region']) xs = stcs['ra'] ys = stcs['dec'] polygon_pix = PolygonPixelRegion(vertices=PixCoord(x=xs, y=ys)) target_coords = PixCoord(eph['RA'][i], eph['DEC'][i]) observation_coords = PixCoord(row['s_ra'], row['s_dec']) if radius is None or radius < 0: flag = target_coords in polygon_pix else: target_circle = CirclePixelRegion(center=target_coords, radius=radius) flag = (target_coords in polygon_pix) or (observation_coords in target_circle) if orig_eph is not None and not flag: flag = _detail_check(orig_eph, polygon_pix, observation_coords, start_date=row['t_min'], end_date=row['t_max'], radius=radius, aggressive_check=aggressive_check) # print(row['obs_id'], flag) except Exception as e: print(f"ERROR checking footprint for {row['obs_id']} with: {e}" f"\nAssuming False") flag = False check_list.append(flag) # Set the flags t['in_footprint'] = check_list return t[t['in_footprint']]
obj_name=obj_name, id_type=id_type, location=location) print(filtered_results) # Using regions import matplotlib.pyplot as plt from astropy.coordinates import SkyCoord from regions import PixCoord, PolygonSkyRegion, PolygonPixelRegion, CirclePixelRegion patch_xs = parse_s_region(stcs)['ra'] patch_ys = parse_s_region(stcs)['dec'] polygon_sky = PolygonSkyRegion( vertices=SkyCoord(patch_xs, patch_ys, unit='deg', frame='icrs')) # Treating as pixels for simplicity (and since I have no WCS) polygon_pix = PolygonPixelRegion(vertices=PixCoord(x=patch_xs, y=patch_ys)) PixCoord(eph['RA'][1], eph['DEC'][1]) in polygon_pix radius = 0.0083 target_coords = PixCoord(eph['RA'][0], eph['DEC'][0]) target_circle = CirclePixelRegion(center=target_coords, radius=radius) intersection = target_circle & polygon_pix # intersection.area # not implemented yet fig, ax = plt.subplots(figsize=(8, 4)) patch = polygon_pix.as_artist(facecolor='none', edgecolor='red', lw=2) ax.add_patch(patch) plt.plot([eph['RA'][1]], [eph['DEC'][1]], 'ko') plt.xlim(84.2, 81.4) plt.ylim(41.2, 41.5)
def fftMask(sciImg, wavel_lambda, plateScale): # sciImg: this is actually the FFT image, not the science detector image # wavel_lambda: wavelenth of the observation # plateScale: plate scale of the detector (asec/pixel) # make division lines separating different parts of the PSF line_M1diam_pixOnFFT = findFFTloc(8.25, np.shape(sciImg)[0], wavel_lambda, plateScale) line_center2center_pixOnFFT = findFFTloc(14.4, np.shape(sciImg)[0], wavel_lambda, plateScale) line_edge2edge_pixOnFFT = findFFTloc(22.65, np.shape(sciImg)[0], wavel_lambda, plateScale) # define circles circRad = 60 # pixels in FFT space circle_highFreqPerfect_L = CirclePixelRegion(center=PixCoord( x=line_center2center_pixOnFFT[0], y=0.5 * np.shape(sciImg)[0]), radius=circRad) circle_highFreqPerfect_R = CirclePixelRegion(center=PixCoord( x=line_center2center_pixOnFFT[1], y=0.5 * np.shape(sciImg)[0]), radius=circRad) circle_lowFreqPerfect = CirclePixelRegion(center=PixCoord( x=0.5 * np.shape(sciImg)[1], y=0.5 * np.shape(sciImg)[0]), radius=circRad) # define central rectangular region that includes all three nodes rect_pix = PolygonPixelRegion( vertices=PixCoord(x=[ line_edge2edge_pixOnFFT[0], line_edge2edge_pixOnFFT[1], line_edge2edge_pixOnFFT[1], line_edge2edge_pixOnFFT[0] ], y=[ line_M1diam_pixOnFFT[1], line_M1diam_pixOnFFT[1], line_M1diam_pixOnFFT[0], line_M1diam_pixOnFFT[0] ])) # make the masks mask_circHighFreq_L = circle_highFreqPerfect_L.to_mask() mask_circHighFreq_R = circle_highFreqPerfect_R.to_mask() mask_circLowFreq = circle_lowFreqPerfect.to_mask() mask_rect = rect_pix.to_mask() # apply the masks sciImg1 = np.copy( sciImg) # initialize arrays of same size as science image sciImg2 = np.copy(sciImg) sciImg3 = np.copy(sciImg) sciImg4 = np.copy(sciImg) # region 1 sciImg1.fill(np.nan) # initialize arrays of nans mask_circHighFreq_L.data[ mask_circHighFreq_L.data == 0] = np.nan # make zeros within mask cutout (but not in the mask itself) nans sciImg1[ mask_circHighFreq_L.bbox. slices] = mask_circHighFreq_L.data # place the mask cutout (consisting only of 1s) onto the array of nans sciImg1 = np.multiply( sciImg1, sciImg) # 'transmit' the original science image through the mask # region 2 sciImg2.fill(np.nan) # initialize arrays of nans mask_circHighFreq_R.data[ mask_circHighFreq_R.data == 0] = np.nan # make zeros within mask cutout (but not in the mask itself) nans sciImg2[ mask_circHighFreq_R.bbox. slices] = mask_circHighFreq_R.data # place the mask cutout (consisting only of 1s) onto the array of nans sciImg2 = np.multiply( sciImg2, sciImg) # 'transmit' the original science image through the mask # region 3 sciImg3.fill(np.nan) # initialize arrays of nans mask_circLowFreq.data[ mask_circLowFreq.data == 0] = np.nan # make zeros within mask cutout (but not in the mask itself) nans sciImg3[ mask_circLowFreq.bbox. slices] = mask_circLowFreq.data # place the mask cutout (consisting only of 1s) onto the array of nans sciImg3 = np.multiply( sciImg3, sciImg) # 'transmit' the original science image through the mask # region 4 sciImg4.fill(np.nan) # initialize arrays of nans mask_rect.data[ mask_rect.data == 0] = np.nan # make zeros within mask cutout (but not in the mask itself) nans sciImg4[ mask_rect.bbox. slices] = mask_rect.data # place the mask cutout (consisting only of 1s) onto the array of nans sciImg4 = np.multiply( sciImg4, sciImg) # 'transmit' the original science image through the mask # return medians of regions under masks med_highFreqPerfect_L = np.nanmedian(sciImg1) med_highFreqPerfect_R = np.nanmedian(sciImg2) med_lowFreqPerfect = np.nanmedian(sciImg3) med_rect = np.nanmedian(sciImg4) # return normal vectors corresponding to [x,y,z] to surfaces (x- and y- components are of interest) normVec_highFreqPerfect_L = normalVector(sciImg1) normVec_highFreqPerfect_R = normalVector(sciImg2) normVec_lowFreqPerfect = normalVector(sciImg3) normVec_rect = normalVector(sciImg4) # generate images showing footprints of regions of interest # (comment this bit in/out as desired) ''' plt.imshow(sciImg1, origin='lower') plt.show() plt.imshow(sciImg2, origin='lower') plt.show() plt.imshow(sciImg3, origin='lower') plt.show() plt.imshow(sciImg4, origin='lower') plt.show() plt.clf() fig = plt.figure() ax = fig.add_subplot(1, 1, 1) cax = ax.imshow(sciImg, origin="lower") ax.axhline(line_M1diam_pixOnFFT[0]) ax.axhline(line_M1diam_pixOnFFT[1]) ax.axvline(line_M1diam_pixOnFFT[0]) ax.axvline(line_M1diam_pixOnFFT[1]) ax.axvline(line_center2center_pixOnFFT[0]) ax.axvline(line_center2center_pixOnFFT[1]) ax.axvline(line_edge2edge_pixOnFFT[0]) ax.axvline(line_edge2edge_pixOnFFT[1]) ax.add_patch(circle_highFreqPerfect_L.as_patch(facecolor='none', edgecolor='blue')) ax.add_patch(circle_highFreqPerfect_R.as_patch(facecolor='none', edgecolor='blue')) ax.add_patch(circle_lowFreqPerfect.as_patch(facecolor='none', edgecolor='blue')) ax.add_patch(rect_pix.as_patch(facecolor='none', edgecolor='red')) cbar = fig.colorbar(cax) plt.savefig("junk.pdf") ''' dictFFTstuff = {} dictFFTstuff["med_highFreqPerfect_L"] = med_highFreqPerfect_L dictFFTstuff["med_highFreqPerfect_R"] = med_highFreqPerfect_R dictFFTstuff["med_lowFreqPerfect"] = med_lowFreqPerfect dictFFTstuff["med_rect"] = med_rect # note vectors are [a,b,c] corresponding to the eqn Z = a*X + b*Y + c dictFFTstuff["normVec_highFreqPerfect_L"] = normVec_highFreqPerfect_L dictFFTstuff["normVec_highFreqPerfect_R"] = normVec_highFreqPerfect_R dictFFTstuff["normVec_lowFreqPerfect"] = normVec_lowFreqPerfect dictFFTstuff["normVec_rect"] = normVec_rect return dictFFTstuff
if not only_fulldisk_images: if(PRINTER.input_text("Automatically find most-intense region? [y/n]") == "y"): PRINTER.info_text("Identifying region") px = np.argwhere(MAPCUBE[0].data == MAPCUBE[0].data.max()) * u.pixel if len(px) > 1: temp = ndimage.measurements.center_of_mass(np.array(px)) px = [px[int(temp[0] + 0.5)]] ######################### center = PixCoord(x = 2048, y = 2048) radius = 1600 region = CirclePixelRegion(center, radius) point = PixCoord(px[0][1], px[0][0]) if not region.contains(point): PRINTER.info_text("Identified region is outside the solar disk") PRINTER.info_text("Defaulting to user selection...") INIT_COORD = cutout_selection(MAPCUBE) else: INIT_COORD = MAPCUBE[0].pixel_to_world(px[0][1], px[0][0]) auto_sel = True else: INIT_COORD = cutout_selection(MAPCUBE) auto_sel = False
def fftMask(sciImg,wavel_lambda,plateScale,fyi_string=''): ''' Take a FFT image, generate masks to select interesting areas of the FFT, and return data about those areas (amplitudes, normal vectors, etc.) INPUTS: sciImg: this is actually the FFT image, not the science detector image wavel_lambda: wavelength of the observation plateScale: plate scale of the detector (asec/pixel) fyi_string: an FYI string that could be used for plots OUTPUTS: dictFFTstuff: dictionary with keys corresponding to different parts of the FFT ''' # make division lines separating different parts of the PSF line_M1diam_pixOnFFT = findFFTloc(D,np.shape(sciImg)[0],wavel_lambda,plateScale) line_center2center_pixOnFFT = findFFTloc(B_c2c,np.shape(sciImg)[0],wavel_lambda,plateScale) line_edge2edge_pixOnFFT = findFFTloc(B_e2e,np.shape(sciImg)[0],wavel_lambda,plateScale) # define circles circRad = 60 # pixels in FFT space circle_highFreqPerfect_L = CirclePixelRegion(center=PixCoord(x=line_center2center_pixOnFFT[0], y=0.5*np.shape(sciImg)[0]), radius=circRad) circle_highFreqPerfect_R = CirclePixelRegion(center=PixCoord(x=line_center2center_pixOnFFT[1], y=0.5*np.shape(sciImg)[0]), radius=circRad) circle_lowFreqPerfect = CirclePixelRegion(center=PixCoord(x=0.5*np.shape(sciImg)[1], y=0.5*np.shape(sciImg)[0]), radius=circRad) # define central rectangular region that includes all three nodes rect_pix = PolygonPixelRegion(vertices=PixCoord(x=[line_edge2edge_pixOnFFT[0],line_edge2edge_pixOnFFT[1],line_edge2edge_pixOnFFT[1],line_edge2edge_pixOnFFT[0]], y=[line_M1diam_pixOnFFT[1],line_M1diam_pixOnFFT[1],line_M1diam_pixOnFFT[0],line_M1diam_pixOnFFT[0]])) # make the masks mask_circHighFreq_L = circle_highFreqPerfect_L.to_mask() mask_circHighFreq_R = circle_highFreqPerfect_R.to_mask() mask_circLowFreq = circle_lowFreqPerfect.to_mask() mask_rect = rect_pix.to_mask() ## apply the masks # initialize arrays of same size as science image sciImg1 = np.copy(sciImg) sciImg2 = np.copy(sciImg) sciImg3 = np.copy(sciImg) sciImg4 = np.copy(sciImg) # region 1: high-freq lobe, left sciImg1.fill(np.nan) # initialize arrays of nans mask_circHighFreq_L.data[mask_circHighFreq_L.data == 0] = np.nan # make zeros within mask cutout (but not in the mask itself) nans sciImg1[mask_circHighFreq_L.bbox.slices] = mask_circHighFreq_L.data # place the mask cutout (consisting only of 1s) onto the array of nans sciImg1 = np.multiply(sciImg1,sciImg) # 'transmit' the original science image through the mask sciImg1 = sciImg1.filled(fill_value=np.nan) # turn all masked '--' elements to nans # region 2: high-freq lobe, right sciImg2.fill(np.nan) # initialize arrays of nans mask_circHighFreq_R.data[mask_circHighFreq_R.data == 0] = np.nan # make zeros within mask cutout (but not in the mask itself) nans sciImg2[mask_circHighFreq_R.bbox.slices] = mask_circHighFreq_R.data # place the mask cutout (consisting only of 1s) onto the array of nans sciImg2 = np.multiply(sciImg2,sciImg) # 'transmit' the original science image through the mask sciImg2 = sciImg2.filled(fill_value=np.nan) # turn all masked '--' elements to nans # region 3: low-freq lobe sciImg3.fill(np.nan) # initialize arrays of nans mask_circLowFreq.data[mask_circLowFreq.data == 0] = np.nan # make zeros within mask cutout (but not in the mask itself) nans sciImg3[mask_circLowFreq.bbox.slices] = mask_circLowFreq.data # place the mask cutout (consisting only of 1s) onto the array of nans sciImg3 = np.multiply(sciImg3,sciImg) # 'transmit' the original science image through the mask sciImg3 = sciImg3.filled(fill_value=np.nan) # turn all masked '--' elements to nans # region 4: rectangular region containing parts of all lobes sciImg4.fill(np.nan) # initialize arrays of nans mask_rect.data[mask_rect.data == 0] = np.nan # make zeros within mask cutout (but not in the mask itself) nans sciImg4[mask_rect.bbox.slices] = mask_rect.data # place the mask cutout (consisting only of 1s) onto the array of nans sciImg4 = np.multiply(sciImg4,sciImg) # 'transmit' the original science image through the mask sciImg4 = sciImg4.filled(fill_value=np.nan) # turn all masked '--' elements to nans # return medians of regions under masks med_highFreqPerfect_L = np.nanmedian(sciImg1) med_highFreqPerfect_R = np.nanmedian(sciImg2) med_lowFreqPerfect = np.nanmedian(sciImg3) med_rect = np.nanmedian(sciImg4) # return normal vectors corresponding to [x,y,z] to surfaces (x- and y- components are of interest) normVec_highFreqPerfect_L = normalVector(sciImg1) normVec_highFreqPerfect_R = normalVector(sciImg2) normVec_lowFreqPerfect = normalVector(sciImg3) normVec_rect = normalVector(sciImg4) # return stdev in each region std_highFreqPerfect_L = np.nanstd(sciImg1) std_highFreqPerfect_R = np.nanstd(sciImg2) std_lowFreqPerfect = np.nanstd(sciImg3) std_rect = np.nanstd(sciImg4) # generate images showing footprints of regions of interest # (comment this bit in/out as desired) # initialize dictionary to contain FFT data # N.b. all the info in this dictionary is EITHER for # the FFT amplitude OR the FFT phase, depending on what # the 'sciImg' is dictFFTstuff = {} # median of high-freq lobe on left side, within circular region centered around # where a perfect high-freq lobe would be dictFFTstuff["med_highFreqPerfect_L"] = med_highFreqPerfect_L # median of right-side high-freq lobe dictFFTstuff["med_highFreqPerfect_R"] = med_highFreqPerfect_R # median of low-frequency lobe dictFFTstuff["med_lowFreqPerfect"] = med_lowFreqPerfect # median of rectangle that is drawn to contain both high- and low-freq lobes dictFFTstuff["med_rect"] = med_rect # stdev of the same regions dictFFTstuff["std_highFreqPerfect_L"] = std_highFreqPerfect_L # median of right-side high-freq lobe dictFFTstuff["std_highFreqPerfect_R"] = std_highFreqPerfect_R # median of low-frequency lobe dictFFTstuff["std_lowFreqPerfect"] = std_lowFreqPerfect # median of rectangle that is drawn to contain both high- and low-freq lobes dictFFTstuff["std_rect"] = std_rect # normal vectors to the high- and low- frequency # note vectors are [a,b,c] corresponding to the eqn Z = a*X + b*Y + c dictFFTstuff["normVec_highFreqPerfect_L"] = normVec_highFreqPerfect_L dictFFTstuff["normVec_highFreqPerfect_R"] = normVec_highFreqPerfect_R dictFFTstuff["normVec_lowFreqPerfect"] = normVec_lowFreqPerfect dictFFTstuff["normVec_rect"] = normVec_rect return dictFFTstuff
"""Example how to plot pixel regions on an image without WCS. """ import numpy as np import matplotlib.pyplot as plt from regions import PixCoord, CirclePixelRegion fig, ax = plt.subplots() region = CirclePixelRegion(center=PixCoord(x=3, y=5), radius=3) data = np.arange(10 * 15).reshape((10, 15)) ax.imshow(data, cmap='gray', interpolation='nearest', origin='lower') region.plot(ax=ax, color='red') plt.show()
def to_object(self, subset): """ Convert a glue Subset object to a astropy regions Region object. Parameters ---------- subset : `glue.core.subset.Subset` The subset to convert to a Region object """ data = subset.data if data.pixel_component_ids[0].axis == 0: x_pix_att = data.pixel_component_ids[1] y_pix_att = data.pixel_component_ids[0] else: x_pix_att = data.pixel_component_ids[0] y_pix_att = data.pixel_component_ids[1] subset_state = subset.subset_state if isinstance(subset_state, RoiSubsetState): roi = subset_state.roi if isinstance(roi, RectangularROI): xcen = 0.5 * (roi.xmin + roi.xmax) ycen = 0.5 * (roi.ymin + roi.ymax) width = roi.xmax - roi.xmin height = roi.ymax - roi.ymin return RectanglePixelRegion(PixCoord(xcen, ycen), width, height) elif isinstance(roi, PolygonalROI): return PolygonPixelRegion(PixCoord(roi.vx, roi.vy)) elif isinstance(roi, CircularROI): return CirclePixelRegion(PixCoord(*roi.get_center()), roi.get_radius()) elif isinstance(roi, EllipticalROI): return EllipsePixelRegion(PixCoord(roi.xc, roi.yc), roi.radius_x, roi.radius_y) elif isinstance(roi, PointROI): return PointPixelRegion(PixCoord(*roi.center())) elif isinstance(roi, RangeROI): return range_to_rect(data, roi.ori, roi.min, roi.max) elif isinstance(roi, AbstractMplRoi): temp_sub = Subset(data) temp_sub.subset_state = RoiSubsetState(x_pix_att, y_pix_att, roi.roi()) try: return self.to_object(temp_sub) except NotImplementedError: raise NotImplementedError( "ROIs of type {0} are not yet supported".format( roi.__class__.__name__)) else: raise NotImplementedError( "ROIs of type {0} are not yet supported".format( roi.__class__.__name__)) elif isinstance(subset_state, RangeSubsetState): if subset_state.att == x_pix_att: return range_to_rect(data, 'x', subset_state.lo, subset_state.hi) elif subset_state.att == y_pix_att: return range_to_rect(data, 'y', subset_state.lo, subset_state.hi) else: raise ValueError( 'Range subset state att should be either x or y pixel coordinate' ) elif isinstance(subset_state, MultiRangeSubsetState): if subset_state.att == x_pix_att: ori = 'x' elif subset_state.att == y_pix_att: ori = 'y' else: message = 'Multirange subset state att should be either x or y pixel coordinate' raise ValueError(message) if len(subset_state.pairs) == 0: message = 'Multirange subset state should contain at least one range' raise ValueError(message) region = range_to_rect(data, ori, subset_state.pairs[0][0], subset_state.pairs[0][1]) for pair in subset_state.pairs[1:]: region = region | range_to_rect(data, ori, pair[0], pair[1]) return region elif isinstance(subset_state, PixelSubsetState): return PointPixelRegion(PixCoord(*subset_state.get_xy(data, 1, 0))) elif isinstance(subset_state, AndState): temp_sub1 = Subset(data=data) temp_sub1.subset_state = subset_state.state1 temp_sub2 = Subset(data=data) temp_sub2.subset_state = subset_state.state2 return self.to_object(temp_sub1) & self.to_object(temp_sub2) elif isinstance(subset_state, OrState): temp_sub1 = Subset(data=data) temp_sub1.subset_state = subset_state.state1 temp_sub2 = Subset(data=data) temp_sub2.subset_state = subset_state.state2 return self.to_object(temp_sub1) | self.to_object(temp_sub2) elif isinstance(subset_state, XorState): temp_sub1 = Subset(data=data) temp_sub1.subset_state = subset_state.state1 temp_sub2 = Subset(data=data) temp_sub2.subset_state = subset_state.state2 return self.to_object(temp_sub1) ^ self.to_object(temp_sub2) elif isinstance(subset_state, MultiOrState): temp_sub = Subset(data=data) temp_sub.subset_state = subset_state.states[0] region = self.to_object(temp_sub) for state in subset_state.states[1:]: temp_sub.subset_state = state region = region | self.to_object(temp_sub) return region else: raise NotImplementedError( "Subset states of type {0} are not supported".format( subset_state.__class__.__name__))
def find_reflected_regions(region, center, exclusion_mask=None, angle_increment='0.1 rad', min_distance='0 rad', min_distance_input='0.1 rad'): """Find reflected regions. Converts to pixel coordinates internally. Parameters ---------- region : `~regions.CircleSkyRegion` Region center : `~astropy.coordinates.SkyCoord` Rotation point exclusion_mask : `~gammapy.image.SkyImage`, optional Exclusion mask angle_increment : `~astropy.coordinates.Angle` Rotation angle for each step min_distance : `~astropy.coordinates.Angle` Minimal distance between to reflected regions min_distance_input : `~astropy.coordinates.Angle` Minimal distance from input region Returns ------- regions : list of `~regions.SkyRegion` Reflected regions list """ angle_increment = Angle(angle_increment) min_distance = Angle(min_distance) min_distance_input = Angle(min_distance_input) # Create empty exclusion mask if None is provided if exclusion_mask is None: exclusion_mask = make_default_exclusion_mask(center, exclusion_mask, region) distance_image = exclusion_mask.distance_image reflected_regions_pix = list() wcs = exclusion_mask.wcs pix_region = region.to_pixel(wcs) pix_center = PixCoord(*center.to_pixel(wcs)) # Compute angle of the ON regions dx = pix_region.center.x - pix_center.x dy = pix_region.center.y - pix_center.y offset = np.hypot(dx, dy) angle = Angle(np.arctan2(dx, dy), 'rad') # Get the minimum angle a Circle has to be moved in order to not overlap # with the previous one min_ang = Angle(2 * np.arcsin(pix_region.radius / offset), 'rad') # Add required minimal distance between two off regions min_ang += min_distance # Maximum allowed angle before the an overlap with the ON regions happens max_angle = angle + Angle('360 deg') - min_ang - min_distance_input # Starting angle curr_angle = angle + min_ang + min_distance_input while curr_angle < max_angle: test_pos = _compute_xy(pix_center, offset, curr_angle) test_reg = CirclePixelRegion(test_pos, pix_region.radius) if distance_image.lookup_pix(test_reg.center) > pix_region.radius: reflected_regions_pix.append(test_reg) curr_angle = curr_angle + min_ang else: curr_angle = curr_angle + angle_increment reflected_regions = [_.to_sky(wcs) for _ in reflected_regions_pix] return reflected_regions
def find_reflected_regions(region, center, exclusion_mask=None, angle_increment=None, min_distance=None, min_distance_input=None): """Find reflected regions. Converts to pixel coordinates internally. Parameters ---------- region : `~regions.CircleSkyRegion` Region center : `~astropy.coordinates.SkyCoord` Rotation point exclusion_mask : `~gammapy.image.SkyMask`, optional Exclusion mask angle_increment : `~astropy.coordinates.Angle` Rotation angle for each step, default: 0.1 rad min_distance : `~astropy.coordinates.Angle` Minimal distance between to reflected regions, default: 0 rad min_distance_input : `~astropy.coordinates.Angle` Minimal distance from input region, default: 0.1 rad Returns ------- regions : list of `~regions.SkyRegion` Reflected regions list """ angle_increment = Angle('0.1 rad') if angle_increment is None else Angle( angle_increment) min_distance = Angle('0 rad') if min_distance is None else Angle( min_distance) min_distance_input = Angle( '0.1 rad') if min_distance_input is None else Angle(min_distance_input) # Create empty exclusion mask if None is provided if exclusion_mask is None: min_size = region.center.separation(center) binsz = 0.02 npix = int((3 * min_size / binsz).value) exclusion_mask = SkyMask.empty(name='empty exclusion mask', xref=center.galactic.l.value, yref=center.galactic.b.value, binsz=binsz, nxpix=npix, nypix=npix, fill=1) reflected_regions_pix = list() wcs = exclusion_mask.wcs pix_region = region.to_pixel(wcs) pix_center = PixCoord(*center.to_pixel(wcs)) # Compute angle of the ON regions dx = pix_region.center.x - pix_center.x dy = pix_region.center.y - pix_center.y offset = np.hypot(dx, dy) angle = Angle(np.arctan2(dx, dy), 'rad') # Get the minimum angle a Circle has to be moved in order to not overlap # with the previous one min_ang = Angle(2 * np.arcsin(pix_region.radius / offset), 'rad') # Add required minimal distance between two off regions min_ang += min_distance # Maximum allowd angle before the an overlap with the ON regions happens max_angle = angle + Angle('360deg') - min_ang - min_distance_input # Starting angle curr_angle = angle + min_ang + min_distance_input while curr_angle < max_angle: test_pos = _compute_xy(pix_center, offset, curr_angle) test_reg = CirclePixelRegion(test_pos, pix_region.radius) if not _is_inside_exclusion(test_reg, exclusion_mask): reflected_regions_pix.append(test_reg) curr_angle = curr_angle + min_ang else: curr_angle = curr_angle + angle_increment reflected_regions = [_.to_sky(wcs) for _ in reflected_regions_pix] log.debug('Found {} reflected regions:\n {}'.format( len(reflected_regions), reflected_regions)) return reflected_regions