Loading csst_cpic_sim/camera.py +94 −0 Original line number Diff line number Diff line Loading @@ -278,7 +278,26 @@ class CosmicRayFrameMaker(object): class CpicVisEmccd(object): """ The class for Cpic EMCCD camera. Attributes ----------- switch : dict A dictionary to switch on/off the camera effects. """ def __init__(self, config_dict=None): """Initialize the camera. Parameters ---------- config_dict : dict, optional The configuration dictionary. Returns ------- None """ self._defaut_config() if config_dict is not None: old_switch = self.switch Loading @@ -288,6 +307,7 @@ class CpicVisEmccd(object): self.config_init() def _defaut_config(self): """set up defaut config for the camera.""" self.plszx = 1024 self.plszy = 1024 self.pscan1 = 8 Loading Loading @@ -359,6 +379,10 @@ class CpicVisEmccd(object): def config_init(self): """initialize the camera. If the config is set, call this function to update the config. """ self._img_index = 0 self._ccd_temp = self.cooler_temp self._vertical_i0 = np.random.randint(0, 2) Loading Loading @@ -409,6 +433,19 @@ class CpicVisEmccd(object): def em_fix_fuc_fit(self, emgain): """Calculate the emgain fix coeficient to fix the gamma distribution. The coeficient is from fixing of ideal emgain distribution. Parameters ---------- emgain : float The emgain. Returns ------- float The coeficient. """ emgain = np.array([emgain]).flatten() p = [0.01014486, -0.00712984, -0.17163414, 0.09523666, -0.53926089] def kernel(em): Loading @@ -430,6 +467,16 @@ class CpicVisEmccd(object): def bias_frame(self): """Generate bias frame The bias frame contains vertical, horizontal, peper-salt noise, bias drift effect. Can be configurable using self.switch. Returns ------- np.ndarray bias frame """ shape = self.bias_shape TPI = np.pi * 2 Loading Loading @@ -574,6 +621,20 @@ class CpicVisEmccd(object): return image def time_syn(self, t, readout=True, initial=False): """ time synchronization and update the system time and ccd temperature Parameters ---------- t : float relative time readout : bool, optional If the camera is readout before time synchronization, set readout to True if True, the ccd temperature will increase, otherwise, it will decrease initial : bool, optional If inital is True, the ccd will be intialized to the cooler temperature """ if initial: self.ccd_temp = self.cooler_temp self.system_time = t Loading @@ -599,6 +660,18 @@ class CpicVisEmccd(object): # pass def emgain_set(self, em_set, ccd_temp=None, self_update=True): """Set emgain from em set value. Parameters ---------- em_set : int em set value. 3FF is about 1.17×, 200 is about 1000×. ccd_temp : float, optional CCD temperature. If not given, use the current ccd temperature. self_update : bool, optional if True, update the emgain and emset. Default is True. if False, only return the emgain. """ if ccd_temp is None: ccd_temp = self.ccd_temp Loading Loading @@ -689,6 +762,27 @@ class CpicVisEmccd(object): # return img_line[:shape[0]*shape[1]].reshape(shape) def readout(self, image_focal, em_set, expt_set, image_cosmic_ray=False, emgain=None): """From focal planet image to ccd output. Interface function for emccd. Simulate the readout process. Parameters ---------- image_focal : np.ndarray focal planet image. Unit: photon-electron/s/pixel em_set : int emgain set value. expt_set: float exposure time set value. Unit: s (0 mains 1ms) image_cosmic_ray : np.ndarray, optional cosmic ray image. Unit: photon-electron/pixel emgain: float, optional if not None, use the given emgain. Else, calculate the emgain using em_set Returns ------- np.ndarray ccd output image. Unit: ADU """ expt = expt_set if expt_set == 0: Loading csst_cpic_sim/config.py +67 −2 Original line number Diff line number Diff line Loading @@ -30,6 +30,16 @@ config_aim = os.path.join(config_aim, 'data/refdata_path.yaml') def load_refdata_path(config_aim): """Load refdata path. The refdata path is stored in config_aim file. If not found, set it. Parameters ---------- config_aim : str config_aim file path """ with open(config_aim, 'r') as f: refdata_list = yaml.load(f, Loader=yaml.FullLoader) Loading Loading @@ -84,7 +94,22 @@ update_able_keys = [ 'apm_file', 'actuator_file', 'aberration', 'log_dir', 'log_level', 'catalog_folder', 'nsample', 'csst_format', 'output', 'check_fits_header' ] def replace_cpism_refdata(config, output='$'): def replace_cpism_refdata( config: dict, output: str = '$') -> None: """Replace the cpism_refdata in the config. In the config file, we use ${cpism_refdata} to indicate the cpism_refdata. This function is used to replace the cpism_refdata in the config, or replace back. Parameters ---------- config: dict config dict. output: str '$' or 'other'. If output is '$', then replace the cpism_refdata in the config with ${cpism_refdata}. If output is 'other', then replace the ${cpism_refdata} in the config file with the real path. '$' is used meanly to generate a demo config file. """ aim = cpism_refdata target = '${cpism_refdata}' if output != '$': Loading @@ -111,6 +136,18 @@ with warnings.catch_warnings(): # pragma: no cover def setup_config(new_config): """Set up config from a dict. Some of the configs need to calcuate. Parameters ---------- new_config: dict new config dict. Returns ------- None """ config.update(new_config) config['utc0_float'] = datetime.timestamp(datetime.fromisoformat(config['utc0'])) config['solar_spectrum'] = f"{os.environ['PYSYN_CDBS']}/grid/solsys/solar_spec.fits" Loading @@ -123,12 +160,13 @@ setup_config({}) def which_focalplane(band): """ Return the name of the focalplane which the band belongs to. right now only support vis Parameters ----------- band: str The name of the band. from ['f565', 'f661', 'f743', 'f883', 'f940', 'f1265', 'f1425', 'f1542', 'wfs'] Returns -------- Loading @@ -152,6 +190,20 @@ def which_focalplane(band): # raise ValueError(f"未知的波段{band}") def iso_time(time): """Transfer relative time to iso time format Parameters ---------- time: str or float The relative time in seconds. Returns ------- str The iso time format. """ if isinstance(time, str): _ = datetime.fromisoformat(time) return time Loading @@ -162,6 +214,19 @@ def iso_time(time): return time.isoformat() def relative_time(time): """Transfer iso time format to relative time in seconds Parameters ---------- time: str or float The iso time format. Returns ------- float The relative time in seconds. """ if isinstance(time, float): return time if isinstance(time, int): Loading csst_cpic_sim/main.py +99 −1 Original line number Diff line number Diff line Loading @@ -31,7 +31,48 @@ def vis_observation( emgain=None, prograss_bar=None, output='./' ): ) -> np.ndarray: """Observation simulation main process on visable band using EMCCD. Parameters ----------- target : dict target dictionary. See the input of `target.spectrum_generator` for more details. skybg : float sky background in unit of magnitude/arcsec^2 expt: float exposure time in unit of second nframe: int number of frames band: str band name emset: int EM gain setting value. 1023(0x3FF) for ~1.0× EM gain. obsid: int observation ID. Start from 4 for CPIC, 01 for science observation. See the input of io.obsid_parser for more details. rotation: float rotation of the telescope. in unit of degree. 0 means North is up. shift: list target shift in unit of arcsec gnc_info: dict gnc_info dictionary. See the input of io.primary_hdu for more details. csst_format: bool if True, the output fits file will be in the csst format. crmaker: CosmicRayFrameMaker CosmicRayFrameMaker object. See the input of camera.CosmicRayFrameMaker for more details. nsample: int number of samples for wide bandpass. emgain: float or None if None, emgain are set using emset parameter. Else, emgain are set using emgain parameter. prograss_bar: bool if True, a prograss_bar will be shown. output: str the output directory. Returns ------- image_cube: np.ndarray """ start_time = time.time() platescale = default_config['platescale'] iwa = default_config['mask_width'] / 2 Loading Loading @@ -148,6 +189,41 @@ def quick_run_v2( prograss_bar=False, output='./') -> np.ndarray: """A quick run function for CPIC. Parameters ---------- target_str: str target string. See the input of `target.target_file_load` for more details. band: str band name expt: float exposure time in unit of second emgain: float EM gain value. Note that emgain is not the same as emset, controled by emset_input keyword. nframe: int number of frames skybg: float or None sky background in unit of magnitude. If None, sky background will not be set. rotation: float rotation of the telescope. in unit of degree. 0 means North is up. shift: list target shift in unit of arcsec emset_input: bool if True, emgain paramter is emset. Else, emgain is set using emgain parameter. cr_frame: bool if True, cosmic ray frame will be added to the image. camera_effect: bool if True, camera effect will be added to the image. prograss_bar: bool if True, a prograss_bar will be shown. output: str the output directory. Returns ------- image_cube: np.ndarray """ print(f'Quick Run: {target_str}') Loading Loading @@ -209,12 +285,25 @@ def quick_run_v2( def deduplicate_names_add_count(names: list): """remove duplicate names and add count""" for i in range(len(names)-1,-1,-1): if names.count(names[i]) > 1: names[i] = names[i] + '_' + str(names.count(names[i])) def observation_simulation_from_config(obs_file, config_file): """Run observation simulation from config file Parameters ----------- obs_file: str observation file or folder. config_file: str config file. Examples: see examples in `example` folder. """ config = {} if config_file: with open(config_file) as fid: Loading Loading @@ -317,6 +406,15 @@ def observation_simulation_from_config(obs_file, config_file): log.error(f"{info_text} failed with {type(e).__name__}{e}.\n\n {traceback.format_exc()}") def main(argv=None): """ Command line interface of csst_cpic_sim Parameters ----------- argv: list input arguments. Default is None. If None, sys.argv is used. """ parser = argparse.ArgumentParser(description='Cpic obsevation image simulation') parser.set_defaults(func=lambda _: parser.print_usage()) Loading csst_cpic_sim/optics.py +47 −3 Original line number Diff line number Diff line Loading @@ -41,7 +41,7 @@ def filter_throughput(filter_name): return FILTERS[filter_name] def rotate_and_shift(shift, rotation, init_shifts): def _rotate_and_shift(shift, rotation, init_shifts): rotation_rad = rotation / 180 * np.pi return np.array([ shift[0] * np.cos(rotation_rad) + shift[1] * np.sin(rotation_rad), Loading @@ -55,7 +55,30 @@ def ideal_focus_image( platescale, platesize: list = [1024, 1024], init_shifts: list = [0, 0], rotation: float = 0,): rotation: float = 0) -> np.ndarray: """Ideal focus image of the targets. Each star is a little point of 1pixel. Parameters ----------- bandpass: synphot.SpectralElement The bandpass of the filter. targets: list The list of the targets. See the output of `spectrum_generator` for details. platescale: float The platescale of the camera. Unit: arcsec/pixel platesize: list The size of the image. Unit: pixel init_shifts: list The shifts of the targets to simulate the miss alignment. Unit: arcsec rotation: float The rotation of the image. Unit: degree Returns -------- np.ndarray The ideal focus image. """ focal_image = np.zeros(platesize) focal_shape = np.array(platesize)[::-1] # x, y Loading @@ -65,7 +88,7 @@ def ideal_focus_image( for target in targets: sub_x, sub_y, sub_spectrum, sub_image = target sub_shift = rotate_and_shift([sub_x, sub_y], rotation, init_shifts) / platescale sub_shift = _rotate_and_shift([sub_x, sub_y], rotation, init_shifts) / platescale sed = (sub_spectrum * bandpass).integrate() if sub_image is None: Loading Loading @@ -111,6 +134,27 @@ def focal_convolve( nsample: int = 5, error: float = 0, platesize: list = [1024, 1024]) -> np.ndarray : """PSF convolution of the ideal focus image. Parameters ---------- band: str The name of the band. target: list The list of thetargets. See the output of `spectrum_generator` for details. init_shifts: list The shifts of the targets to simulate the miss alignment. Unit: arcsec rotation: float The rotation of the image. Unit: degree error: float The error of the DM acceleration. Unit: nm platesize: list The size of the image. Unit: pixel Returns -------- np.ndarray """ # config = optics_config[which_focalplane(band)] platescale = config['platescale'] Loading csst_cpic_sim/psf_simulation.py +55 −7 Original line number Diff line number Diff line Loading @@ -73,7 +73,27 @@ aberration = SurfaceApodizer( aberration_distance = 80 * focal_length aberration = SurfaceAberrationAtDistance(aberration, aberration_distance) def single_band_masked_psf(wavelength, error=0, shift=[0, 0]): def single_band_masked_psf( wavelength: float, error: float = 0, shift: list = [0, 0]) -> np.ndarray: """CPIC PSF considering the focal plane mask. Parameters ----------- wavelength : float observation wavelength in meter error : float deformable mirror control error in nm shift : list angular shift of the target in arcsec. Returns ---------- psf : np.ndarray psf in the focal plane. Normalized as the input flux is 1. (Note that total flux of the psf is not 1, because it is masked) """ error = np.random.normal(0, error*1e-9, actuator.shape) deformable_mirror.actuators = actuator + error wf = Wavefront(aperture, wavelength) Loading @@ -89,15 +109,43 @@ def single_band_masked_psf(wavelength, error=0, shift=[0, 0]): psf = second_focal.intensity.shaped return psf / strength def single_band_psf(wavelength, error=0, aber_phase=None, intensity=True): def single_band_psf( wavelength: float, error: float = 0) -> np.ndarray: """CPIC PSF without considering the focal plane mask. Used to simulate the off-axis PSF. Parameters ----------- wavelength : float observation wavelength in meter error : float deformable mirror control error in nm Returns ---------- psf : np.ndarray psf in the focal plane. Normalized. The total flux is 1. """ error = np.random.normal(0, error*1e-9, actuator.shape) deformable_mirror.actuators = actuator + error wf = Wavefront(aperture, wavelength) wf = aberration(wf) if aber_phase is not None: other_error = SurfaceApodizer( surface_sag=aber_phase.flatten(), refractive_index=-1) wf = other_error(wf) first_focal = prop_full_frame(deformable_mirror(wf)) image = np.array(first_focal.intensity.shaped) return image / image.sum() # def single_band_psf(wavelength, error=0, aber_phase=None): # error = np.random.normal(0, error*1e-9, actuator.shape) # deformable_mirror.actuators = actuator + error # wf = Wavefront(aperture, wavelength) # wf = aberration(wf) # if aber_phase is not None: # other_error = SurfaceApodizer( # surface_sag=aber_phase.flatten(), refractive_index=-1) # wf = other_error(wf) # first_focal = prop_full_frame(deformable_mirror(wf)) # image = np.array(first_focal.intensity.shaped) # return image / image.sum() No newline at end of file Loading
csst_cpic_sim/camera.py +94 −0 Original line number Diff line number Diff line Loading @@ -278,7 +278,26 @@ class CosmicRayFrameMaker(object): class CpicVisEmccd(object): """ The class for Cpic EMCCD camera. Attributes ----------- switch : dict A dictionary to switch on/off the camera effects. """ def __init__(self, config_dict=None): """Initialize the camera. Parameters ---------- config_dict : dict, optional The configuration dictionary. Returns ------- None """ self._defaut_config() if config_dict is not None: old_switch = self.switch Loading @@ -288,6 +307,7 @@ class CpicVisEmccd(object): self.config_init() def _defaut_config(self): """set up defaut config for the camera.""" self.plszx = 1024 self.plszy = 1024 self.pscan1 = 8 Loading Loading @@ -359,6 +379,10 @@ class CpicVisEmccd(object): def config_init(self): """initialize the camera. If the config is set, call this function to update the config. """ self._img_index = 0 self._ccd_temp = self.cooler_temp self._vertical_i0 = np.random.randint(0, 2) Loading Loading @@ -409,6 +433,19 @@ class CpicVisEmccd(object): def em_fix_fuc_fit(self, emgain): """Calculate the emgain fix coeficient to fix the gamma distribution. The coeficient is from fixing of ideal emgain distribution. Parameters ---------- emgain : float The emgain. Returns ------- float The coeficient. """ emgain = np.array([emgain]).flatten() p = [0.01014486, -0.00712984, -0.17163414, 0.09523666, -0.53926089] def kernel(em): Loading @@ -430,6 +467,16 @@ class CpicVisEmccd(object): def bias_frame(self): """Generate bias frame The bias frame contains vertical, horizontal, peper-salt noise, bias drift effect. Can be configurable using self.switch. Returns ------- np.ndarray bias frame """ shape = self.bias_shape TPI = np.pi * 2 Loading Loading @@ -574,6 +621,20 @@ class CpicVisEmccd(object): return image def time_syn(self, t, readout=True, initial=False): """ time synchronization and update the system time and ccd temperature Parameters ---------- t : float relative time readout : bool, optional If the camera is readout before time synchronization, set readout to True if True, the ccd temperature will increase, otherwise, it will decrease initial : bool, optional If inital is True, the ccd will be intialized to the cooler temperature """ if initial: self.ccd_temp = self.cooler_temp self.system_time = t Loading @@ -599,6 +660,18 @@ class CpicVisEmccd(object): # pass def emgain_set(self, em_set, ccd_temp=None, self_update=True): """Set emgain from em set value. Parameters ---------- em_set : int em set value. 3FF is about 1.17×, 200 is about 1000×. ccd_temp : float, optional CCD temperature. If not given, use the current ccd temperature. self_update : bool, optional if True, update the emgain and emset. Default is True. if False, only return the emgain. """ if ccd_temp is None: ccd_temp = self.ccd_temp Loading Loading @@ -689,6 +762,27 @@ class CpicVisEmccd(object): # return img_line[:shape[0]*shape[1]].reshape(shape) def readout(self, image_focal, em_set, expt_set, image_cosmic_ray=False, emgain=None): """From focal planet image to ccd output. Interface function for emccd. Simulate the readout process. Parameters ---------- image_focal : np.ndarray focal planet image. Unit: photon-electron/s/pixel em_set : int emgain set value. expt_set: float exposure time set value. Unit: s (0 mains 1ms) image_cosmic_ray : np.ndarray, optional cosmic ray image. Unit: photon-electron/pixel emgain: float, optional if not None, use the given emgain. Else, calculate the emgain using em_set Returns ------- np.ndarray ccd output image. Unit: ADU """ expt = expt_set if expt_set == 0: Loading
csst_cpic_sim/config.py +67 −2 Original line number Diff line number Diff line Loading @@ -30,6 +30,16 @@ config_aim = os.path.join(config_aim, 'data/refdata_path.yaml') def load_refdata_path(config_aim): """Load refdata path. The refdata path is stored in config_aim file. If not found, set it. Parameters ---------- config_aim : str config_aim file path """ with open(config_aim, 'r') as f: refdata_list = yaml.load(f, Loader=yaml.FullLoader) Loading Loading @@ -84,7 +94,22 @@ update_able_keys = [ 'apm_file', 'actuator_file', 'aberration', 'log_dir', 'log_level', 'catalog_folder', 'nsample', 'csst_format', 'output', 'check_fits_header' ] def replace_cpism_refdata(config, output='$'): def replace_cpism_refdata( config: dict, output: str = '$') -> None: """Replace the cpism_refdata in the config. In the config file, we use ${cpism_refdata} to indicate the cpism_refdata. This function is used to replace the cpism_refdata in the config, or replace back. Parameters ---------- config: dict config dict. output: str '$' or 'other'. If output is '$', then replace the cpism_refdata in the config with ${cpism_refdata}. If output is 'other', then replace the ${cpism_refdata} in the config file with the real path. '$' is used meanly to generate a demo config file. """ aim = cpism_refdata target = '${cpism_refdata}' if output != '$': Loading @@ -111,6 +136,18 @@ with warnings.catch_warnings(): # pragma: no cover def setup_config(new_config): """Set up config from a dict. Some of the configs need to calcuate. Parameters ---------- new_config: dict new config dict. Returns ------- None """ config.update(new_config) config['utc0_float'] = datetime.timestamp(datetime.fromisoformat(config['utc0'])) config['solar_spectrum'] = f"{os.environ['PYSYN_CDBS']}/grid/solsys/solar_spec.fits" Loading @@ -123,12 +160,13 @@ setup_config({}) def which_focalplane(band): """ Return the name of the focalplane which the band belongs to. right now only support vis Parameters ----------- band: str The name of the band. from ['f565', 'f661', 'f743', 'f883', 'f940', 'f1265', 'f1425', 'f1542', 'wfs'] Returns -------- Loading @@ -152,6 +190,20 @@ def which_focalplane(band): # raise ValueError(f"未知的波段{band}") def iso_time(time): """Transfer relative time to iso time format Parameters ---------- time: str or float The relative time in seconds. Returns ------- str The iso time format. """ if isinstance(time, str): _ = datetime.fromisoformat(time) return time Loading @@ -162,6 +214,19 @@ def iso_time(time): return time.isoformat() def relative_time(time): """Transfer iso time format to relative time in seconds Parameters ---------- time: str or float The iso time format. Returns ------- float The relative time in seconds. """ if isinstance(time, float): return time if isinstance(time, int): Loading
csst_cpic_sim/main.py +99 −1 Original line number Diff line number Diff line Loading @@ -31,7 +31,48 @@ def vis_observation( emgain=None, prograss_bar=None, output='./' ): ) -> np.ndarray: """Observation simulation main process on visable band using EMCCD. Parameters ----------- target : dict target dictionary. See the input of `target.spectrum_generator` for more details. skybg : float sky background in unit of magnitude/arcsec^2 expt: float exposure time in unit of second nframe: int number of frames band: str band name emset: int EM gain setting value. 1023(0x3FF) for ~1.0× EM gain. obsid: int observation ID. Start from 4 for CPIC, 01 for science observation. See the input of io.obsid_parser for more details. rotation: float rotation of the telescope. in unit of degree. 0 means North is up. shift: list target shift in unit of arcsec gnc_info: dict gnc_info dictionary. See the input of io.primary_hdu for more details. csst_format: bool if True, the output fits file will be in the csst format. crmaker: CosmicRayFrameMaker CosmicRayFrameMaker object. See the input of camera.CosmicRayFrameMaker for more details. nsample: int number of samples for wide bandpass. emgain: float or None if None, emgain are set using emset parameter. Else, emgain are set using emgain parameter. prograss_bar: bool if True, a prograss_bar will be shown. output: str the output directory. Returns ------- image_cube: np.ndarray """ start_time = time.time() platescale = default_config['platescale'] iwa = default_config['mask_width'] / 2 Loading Loading @@ -148,6 +189,41 @@ def quick_run_v2( prograss_bar=False, output='./') -> np.ndarray: """A quick run function for CPIC. Parameters ---------- target_str: str target string. See the input of `target.target_file_load` for more details. band: str band name expt: float exposure time in unit of second emgain: float EM gain value. Note that emgain is not the same as emset, controled by emset_input keyword. nframe: int number of frames skybg: float or None sky background in unit of magnitude. If None, sky background will not be set. rotation: float rotation of the telescope. in unit of degree. 0 means North is up. shift: list target shift in unit of arcsec emset_input: bool if True, emgain paramter is emset. Else, emgain is set using emgain parameter. cr_frame: bool if True, cosmic ray frame will be added to the image. camera_effect: bool if True, camera effect will be added to the image. prograss_bar: bool if True, a prograss_bar will be shown. output: str the output directory. Returns ------- image_cube: np.ndarray """ print(f'Quick Run: {target_str}') Loading Loading @@ -209,12 +285,25 @@ def quick_run_v2( def deduplicate_names_add_count(names: list): """remove duplicate names and add count""" for i in range(len(names)-1,-1,-1): if names.count(names[i]) > 1: names[i] = names[i] + '_' + str(names.count(names[i])) def observation_simulation_from_config(obs_file, config_file): """Run observation simulation from config file Parameters ----------- obs_file: str observation file or folder. config_file: str config file. Examples: see examples in `example` folder. """ config = {} if config_file: with open(config_file) as fid: Loading Loading @@ -317,6 +406,15 @@ def observation_simulation_from_config(obs_file, config_file): log.error(f"{info_text} failed with {type(e).__name__}{e}.\n\n {traceback.format_exc()}") def main(argv=None): """ Command line interface of csst_cpic_sim Parameters ----------- argv: list input arguments. Default is None. If None, sys.argv is used. """ parser = argparse.ArgumentParser(description='Cpic obsevation image simulation') parser.set_defaults(func=lambda _: parser.print_usage()) Loading
csst_cpic_sim/optics.py +47 −3 Original line number Diff line number Diff line Loading @@ -41,7 +41,7 @@ def filter_throughput(filter_name): return FILTERS[filter_name] def rotate_and_shift(shift, rotation, init_shifts): def _rotate_and_shift(shift, rotation, init_shifts): rotation_rad = rotation / 180 * np.pi return np.array([ shift[0] * np.cos(rotation_rad) + shift[1] * np.sin(rotation_rad), Loading @@ -55,7 +55,30 @@ def ideal_focus_image( platescale, platesize: list = [1024, 1024], init_shifts: list = [0, 0], rotation: float = 0,): rotation: float = 0) -> np.ndarray: """Ideal focus image of the targets. Each star is a little point of 1pixel. Parameters ----------- bandpass: synphot.SpectralElement The bandpass of the filter. targets: list The list of the targets. See the output of `spectrum_generator` for details. platescale: float The platescale of the camera. Unit: arcsec/pixel platesize: list The size of the image. Unit: pixel init_shifts: list The shifts of the targets to simulate the miss alignment. Unit: arcsec rotation: float The rotation of the image. Unit: degree Returns -------- np.ndarray The ideal focus image. """ focal_image = np.zeros(platesize) focal_shape = np.array(platesize)[::-1] # x, y Loading @@ -65,7 +88,7 @@ def ideal_focus_image( for target in targets: sub_x, sub_y, sub_spectrum, sub_image = target sub_shift = rotate_and_shift([sub_x, sub_y], rotation, init_shifts) / platescale sub_shift = _rotate_and_shift([sub_x, sub_y], rotation, init_shifts) / platescale sed = (sub_spectrum * bandpass).integrate() if sub_image is None: Loading Loading @@ -111,6 +134,27 @@ def focal_convolve( nsample: int = 5, error: float = 0, platesize: list = [1024, 1024]) -> np.ndarray : """PSF convolution of the ideal focus image. Parameters ---------- band: str The name of the band. target: list The list of thetargets. See the output of `spectrum_generator` for details. init_shifts: list The shifts of the targets to simulate the miss alignment. Unit: arcsec rotation: float The rotation of the image. Unit: degree error: float The error of the DM acceleration. Unit: nm platesize: list The size of the image. Unit: pixel Returns -------- np.ndarray """ # config = optics_config[which_focalplane(band)] platescale = config['platescale'] Loading
csst_cpic_sim/psf_simulation.py +55 −7 Original line number Diff line number Diff line Loading @@ -73,7 +73,27 @@ aberration = SurfaceApodizer( aberration_distance = 80 * focal_length aberration = SurfaceAberrationAtDistance(aberration, aberration_distance) def single_band_masked_psf(wavelength, error=0, shift=[0, 0]): def single_band_masked_psf( wavelength: float, error: float = 0, shift: list = [0, 0]) -> np.ndarray: """CPIC PSF considering the focal plane mask. Parameters ----------- wavelength : float observation wavelength in meter error : float deformable mirror control error in nm shift : list angular shift of the target in arcsec. Returns ---------- psf : np.ndarray psf in the focal plane. Normalized as the input flux is 1. (Note that total flux of the psf is not 1, because it is masked) """ error = np.random.normal(0, error*1e-9, actuator.shape) deformable_mirror.actuators = actuator + error wf = Wavefront(aperture, wavelength) Loading @@ -89,15 +109,43 @@ def single_band_masked_psf(wavelength, error=0, shift=[0, 0]): psf = second_focal.intensity.shaped return psf / strength def single_band_psf(wavelength, error=0, aber_phase=None, intensity=True): def single_band_psf( wavelength: float, error: float = 0) -> np.ndarray: """CPIC PSF without considering the focal plane mask. Used to simulate the off-axis PSF. Parameters ----------- wavelength : float observation wavelength in meter error : float deformable mirror control error in nm Returns ---------- psf : np.ndarray psf in the focal plane. Normalized. The total flux is 1. """ error = np.random.normal(0, error*1e-9, actuator.shape) deformable_mirror.actuators = actuator + error wf = Wavefront(aperture, wavelength) wf = aberration(wf) if aber_phase is not None: other_error = SurfaceApodizer( surface_sag=aber_phase.flatten(), refractive_index=-1) wf = other_error(wf) first_focal = prop_full_frame(deformable_mirror(wf)) image = np.array(first_focal.intensity.shaped) return image / image.sum() # def single_band_psf(wavelength, error=0, aber_phase=None): # error = np.random.normal(0, error*1e-9, actuator.shape) # deformable_mirror.actuators = actuator + error # wf = Wavefront(aperture, wavelength) # wf = aberration(wf) # if aber_phase is not None: # other_error = SurfaceApodizer( # surface_sag=aber_phase.flatten(), refractive_index=-1) # wf = other_error(wf) # first_focal = prop_full_frame(deformable_mirror(wf)) # image = np.array(first_focal.intensity.shaped) # return image / image.sum() No newline at end of file