import os import yaml import numpy as np from .config import cpism_refdata, which_focalplane, S # S is synphot from .config import optics_config from .utils import region_replace from .io import log FILTERS = { "f565": S.FileBandpass(f"{cpism_refdata}/throughtput/f565_total.fits"), "f661": S.FileBandpass(f"{cpism_refdata}/throughtput/f661_total.fits"), "f743": S.FileBandpass(f"{cpism_refdata}/throughtput/f743_total.fits"), "f883": S.FileBandpass(f"{cpism_refdata}/throughtput/f883_total.fits"), "f940": S.FileBandpass(f"{cpism_refdata}/throughtput/f940_total.fits"), "f1265": S.FileBandpass(f"{cpism_refdata}/throughtput/f1265_total.fits"), "f1425": S.FileBandpass(f"{cpism_refdata}/throughtput/f1425_total.fits"), "f1542": S.FileBandpass(f"{cpism_refdata}/throughtput/f1542_total.fits"), "f860": S.FileBandpass(f"{cpism_refdata}/throughtput/f860.fits"), "f850": S.FileBandpass(f"{cpism_refdata}/throughtput/f850.fits"), "f725": S.FileBandpass(f"{cpism_refdata}/throughtput/f725.fits"), "f720": S.FileBandpass(f"{cpism_refdata}/throughtput/f725.fits"), "f520": S.FileBandpass(f"{cpism_refdata}/throughtput/f520.fits"), "f662": S.FileBandpass(f"{cpism_refdata}/throughtput/f662.fits"), "f729": S.FileBandpass(f"{cpism_refdata}/throughtput/f729.fits"), "f825": S.FileBandpass(f"{cpism_refdata}/throughtput/f825.fits"), } def filter_throughput(filter_name): """ Totally throughput of each CPIC band. Including the throughput of the filter, telescope, cpic, and camera QE. If the filter_name is not supported, return the throughput of the default filter(f661). Parameters ----------- filter_name: str The name of the filter. One of ['f565', 'f661'(default), 'f743', 'f883', 'f940', 'f1265', 'f1425', 'f1542'] Returns -------- synphot.Bandpass The throughput of the filter. """ filter_name = filter_name.lower() filter_name = "f661" if filter_name == "default" else filter_name if filter_name not in FILTERS.keys(): log.warning(f"滤光片名称错误({filter_name}),返回默认滤光片(f661)透过率") filter_name = "f661" return FILTERS[filter_name] def example_psf_func(band, spectrum, frame_size, error=0.1): """ Example psf generating function. Parameters ------------- band: str The name of the band. spectrum: synphot.Spectrum or synphot.SourceSpectrum The spectrum of the target. frame_size: int The size of the frame. error: float Phase RMS error. Returns --------------- 2D array psf image with shape of `frame_size` """ pass def make_focus_image( band: str, targets: list, psf_function: callable, init_shifts: list = [0, 0], rotation: float = 0, platesize: list = [1024, 1024], ) -> np.ndarray: """ Make the focus image of the targets. Parameters ----------- band: str The name of the band. targets: list The list of the targets. Each element of the list is a tuple of (x, y, spectrum). - x, y: float - The position of the target in the focal plane. - spectrum: synphot.Spectrum or synphot.SourceSpectrum - The spectrum of the target. psf_function: callable The function to generate the PSF, with same parameters and return as `example_psf_func`. init_shifts: list The initial shifts of the center targets. Unit: arcsec. The default is [0, 0]. rotation: float The rotation of the focal plane. Unit: degree. The default is 0 degree. platesize: list The size of the focal plane. Unit: pixel. The default is [1024, 1024]. Returns -------- np.ndarray The focus image of the targets. 2D array with the shape of platesize. """ config = optics_config[which_focalplane(band)] platescale = config["platescale"] focal_image = np.zeros(platesize) if not targets: return focal_image def rotate_and_shift(shift): rotation_rad = rotation / 180 * np.pi return np.array( [ shift[0] * np.cos(rotation_rad) + shift[1] * np.sin(rotation_rad), -shift[0] * np.sin(rotation_rad) + shift[1] * np.cos(rotation_rad), ] ) + np.array(init_shifts) cstar_x, cstar_y, cstar_spectrum = targets[0] cstar_shift = rotate_and_shift([cstar_x, cstar_y]) / platescale error_value = 0 # nm cstar_psf = psf_function( band, cstar_spectrum, config["cstar_frame_size"], error=error_value ) platesize = np.array(platesize)[::-1] psf_shape = np.array(cstar_psf.shape)[::-1] cstar_shift += (platesize - 1) / 2 - (psf_shape - 1) / 2 focal_image = region_replace( focal_image, cstar_psf, cstar_shift, padded_in=False, padded_out=False, subpix=True, ) for i_target in range(1, len(targets)): sub_x, sub_y, sub_spectrum = targets[i_target] pdout = False if i_target == len(targets) - 1 else True pdin = False if i_target == 1 else True log.debug(f"input target {sub_x=:}, {sub_y=:}") sub_shift = rotate_and_shift([sub_x, sub_y]) / platescale log.debug(f"after rotate and shift {sub_shift=:}") sub_psf = psf_function( band, sub_spectrum, config["substellar_frame_size"], error=error_value ) psf_shape = np.array(sub_psf.shape)[::-1] sub_shift += (platesize - 1) / 2 - (psf_shape - 1) / 2 log.debug(f"input shift of region_replace: {sub_shift=:}") focal_image = region_replace( focal_image, sub_psf, sub_shift, padded_in=pdin, padded_out=pdout, subpix=True, ) return focal_image def focal_mask(image, iwa, platescale, throughtput=1e-6): """ Mask the image outside the inner working angle. Parameters ----------- image: np.ndarray The image to be masked. iwa: float The inner working angle. Unit: arcsec. platescale: float The platescale of the image. Unit: arcsec/pixel. throughtput: float The throughtput of the mask. The default is 1e-6. Returns -------- np.ndarray The masked image. """ xx, yy = np.mgrid[0 : image.shape[0], 0 : image.shape[1]] center = np.array([(image.shape[0] - 1) / 2, (image.shape[1] - 1) / 2]) mask = (abs(xx - center[0]) < iwa / platescale) | ( abs(yy - center[1]) < iwa / platescale ) image_out = image.copy() image_out[mask] *= throughtput return image_out