#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu Apr 11 15:18:57 2024

@author: yan
"""
from astropy.utils.iers import conf
import galsim
from scipy.integrate import simps
import pandas as pd
from datetime import datetime, timedelta
from astropy.coordinates import get_sun
from astropy.time import Time
from astropy import units as u
from astropy.coordinates import SkyCoord, EarthLocation
from scipy import interpolate
import numpy as np
import scipy.io as sio
from astropy.io import fits
from scipy.signal import fftconvolve
from scipy import ndimage
import cmath
import configparser as ConfigParser
import os
from scipy.interpolate import interp1d
import astropy.coordinates as coord
import ctypes
import sys
from .ifs_so import cdm03bidir

##sys.path.append('./csst_ifs_sim')
conf.auto_max_age = None

"""
"""
"""
Contact Information: zhaojunyan@shao.ac.cn

The CSST IFS Image Simulator
=============================================

This file contains an image simulator for the CSST IFS.

The approximate sequence of events in the simulator is as follows:

#. Read in a configuration file, which defines for example,
   detector characteristics (bias, dark and readout noise, gain,
   pixel scale,exposure time etc.).
#. Read in another file containing charge trap definitions (for CTI modelling).
#. Read in a file defining the cosmic rays (trail lengths and
   cumulative distributions).
#. Load the wavefront aberration data used to calculate PSF .
#. Apply a multiplicative flat-field map to emulate pixel-to-pixel
non-uniformity.
#. Add cosmic ray tracks onto the CCD with random positions but known
distribution.
#. Apply detector charge bleeding in column direction.
#. Add constant dark current and background light from Zodiacal
light.
#. Add photon (Poisson) noise
#. Add cosmetic defects from an input file.
#. Add pre- and overscan regions in the serial direction.
#. Apply the CDM03 radiation damage model.
#. Apply non-linearity model to the pixel data.
#. Add readout noise selected from a Gaussian distribution.
#. Convert from electrons to ADUs using a given gain factor.
#. Add a given bias level and discretise the counts 
#. Finally the simulated image is converted to a FITS file.

Warning:: The code is still work in progress and new features are being added.
The code has been tested, but nevertheless bugs may be lurking in corners, so
please report any weird or inconsistent simulations to the author.

Note:: This class is Python 3 compatible.
2023.06.13 , add new lamp hole simulation
2023.12.26   add date frame transfer effect and software version
2024.1.19    add multiple exposure mode function;
2024.3.20  --- update the fits header as defined order 0 data format
2024.5.10  ---updata and correct the bug of frame transfer effect simulation

"""
######################## functions definition ################################


class CDM03bidir():
    """
    Charge Transfer Inefficiency
    ============================

    This file contains a simple class to run a CDM03 CTI model developed by Alex Short (ESA).

    This now contains both the official CDM03 and a new version that allows different trap
    parameters in parallel and serial direction.

    :requires: NumPy
    :requires: CDM03 (FORTRAN code, f2py -c -m cdm03bidir cdm03bidir.f90)

    Class to run CDM03 CTI model, class Fortran routine to perform the actual CDM03 calculations.

     :param settings: input parameters
     :type settings: dict
     :param data: input data to be radiated
     :type data: ndarray
     :param log: instance to Python logging
     :type log: logging instance
    """
    def __init__(self, settings, data, log=None):
        """
        Class constructor.

        :param settings: input parameters
        :type settings: dict
        :param data: input data to be radiated
        :type data: ndarray
        :param log: instance to Python logging
        :type log: logging instance
        """
        self.data = data
        self.values = dict(quads=(0,1,2,3), xsize=2048, ysize=2066, dob=0.0, rdose=8.0e9)
        self.values.update(settings)
        self.log = log
        self._setupLogger()

        # #default CDM03 settings
        # self.params = dict(beta_p=0.6, beta_s=0.6, fwc=200000., vth=1.168e7, vg=6.e-11, t=20.48e-3,
        #                    sfwc=730000., svg=1.0e-10, st=5.0e-6, parallel=1., serial=1.)
        
        self.params = dict(beta_p=0.6, beta_s=0.6, fwc=100000., vth=1.168e7, vg=6.e-11, t=1.0e-3,
                           sfwc=700000., svg=1.0e-10, st=1.0e-6, parallel=1., serial=0.)
        
        #update with inputs
        self.params.update(self.values)

        #read in trap information
        trapdata = np.loadtxt(self.values['dir_path']+self.values['paralleltrapfile'])
        if trapdata.ndim > 1:
            self.nt_p = trapdata[:, 0]
            self.sigma_p = trapdata[:, 1]
            self.taur_p = trapdata[:, 2]
        else:
            #only one trap species
            self.nt_p = [trapdata[0],]
            self.sigma_p = [trapdata[1],]
            self.taur_p = [trapdata[2],]

        trapdata = np.loadtxt(self.values['dir_path']+self.values['serialtrapfile'])
        if trapdata.ndim > 1:
            self.nt_s = trapdata[:, 0]
            self.sigma_s = trapdata[:, 1]
            self.taur_s = trapdata[:, 2]
        else:
            #only one trap species
            self.nt_s = [trapdata[0],]
            self.sigma_s = [trapdata[1],]
            self.taur_s = [trapdata[2],]

        #scale thibaut's values
        if 'thibaut' in self.values['parallelTrapfile']:
            self.nt_p /= 0.576  #thibaut's values traps / pixel
            self.sigma_p *= 1.e4 #thibaut's values in m**2
        if 'thibaut' in self.values['serialTrapfile']:
            self.nt_s *= 0.576 #thibaut's values traps / pixel  #should be division?
            self.sigma_s *= 1.e4 #thibaut's values in m**2


    def _setupLogger(self):
        """
        Set up the logger.
        """
        self.logger = True


    def applyRadiationDamage(self, data, iquadrant=0):
        """
        Apply radian damage based on FORTRAN CDM03 model. The method assumes that
        input data covers only a single quadrant defined by the iquadrant integer.

        :param data: imaging data to which the CDM03 model will be applied to.
        :type data: ndarray

        :param iquandrant: number of the quadrant to process
        :type iquandrant: int

        cdm03 - Function signature::

              sout = cdm03(sinp,iflip,jflip,dob,rdose,in_nt,in_sigma,in_tr,[xdim,ydim,zdim])
            Required arguments:
              sinp : input rank-2 array('d') with bounds (xdim,ydim)
              iflip : input int
              jflip : input int
              dob : input float
              rdose : input float
              in_nt : input rank-1 array('d') with bounds (zdim)
              in_sigma : input rank-1 array('d') with bounds (zdim)
              in_tr : input rank-1 array('d') with bounds (zdim)
            Optional arguments:
              xdim := shape(sinp,0) input int
              ydim := shape(sinp,1) input int
              zdim := len(in_nt) input int
            Return objects:
              sout : rank-2 array('d') with bounds (xdim,ydim)

        .. Note:: Because Python/NumPy arrays are different row/column based, one needs
                  to be extra careful here. NumPy.asfortranarray will be called to get
                  an array laid out in Fortran order in memory. Before returning the
                  array will be laid out in memory in C-style (row-major order).

        :return: image that has been run through the CDM03 model
        :rtype: ndarray   
        """""
        #return data
    
        iflip = iquadrant / 2
        jflip = iquadrant % 2

        params = [self.params['beta_p'], self.params['beta_s'], self.params['fwc'], self.params['vth'],
                  self.params['vg'], self.params['t'], self.params['sfwc'], self.params['svg'], self.params['st'],
                  self.params['parallel'], self.params['serial']]

        if self.logger:
            self.log.info('nt_p=' + str(self.nt_p))
            self.log.info('nt_s=' + str(self.nt_s))
            self.log.info('sigma_p= ' + str(self.sigma_p))
            self.log.info('sigma_s= ' + str(self.sigma_s))
            self.log.info('taur_p= ' + str(self.taur_p))
            self.log.info('taur_s= ' + str(self.taur_s))
            self.log.info('dob=%f' % self.values['dob'])
            self.log.info('rdose=%e' % self.values['rdose'])
            self.log.info('xsize=%i' % data.shape[1])
            self.log.info('ysize=%i' % data.shape[0])
            self.log.info('quadrant=%i' % iquadrant)
            self.log.info('iflip=%i' % iflip)
            self.log.info('jflip=%i' % jflip)

#################################################################################
###modify
        #sys.path.append('../so') 
        
        # from ifs_so.cdm03.cpython-38-x86_64-linux-gnu import cdm03bidir
        # import cdm03bidir
        CTIed = cdm03bidir.cdm03(np.asfortranarray(data),
                                  jflip, iflip,
                                  self.values['dob'], self.values['rdose'],
                                  self.nt_p, self.sigma_p, self.taur_p,
                                  self.nt_s, self.sigma_s, self.taur_s,
                                  params,
                                  [data.shape[0], data.shape[1], len(self.nt_p), len(self.nt_s), len(self.params)])
       
        
        return np.asanyarray(CTIed)
       
#################################################################################################################
        
#################################################################################################################
      
"""
These functions can be used for logging information.

.. Warning:: logger is not multiprocessing safe.

:version: 0.3
"""
import logging
import logging.handlers


def lg(log_filename, loggername='logger'):
    """
    Sets up a logger.

    :param: log_filename: name of the file to save the log.
    :param: loggername: name of the logger

    :return: logger instance
    """
    # create logger
    logger = logging.getLogger(loggername)
    logger.setLevel(logging.DEBUG)
    # Add the log message handler to the logger
    handler = logging.handlers.RotatingFileHandler(log_filename)
    #maxBytes=20, backupCount=5)
    # create formatter
    formatter = logging.Formatter('%(asctime)s - %(module)s - %(funcName)s - %(levelname)s - %(message)s')
    # add formatter to ch
    handler.setFormatter(formatter)
    # add handler to logger 
    
    if (logger.hasHandlers()):
        logger.handlers.clear()
        
    logger.addHandler(handler)

    return logger


##############################################################################

def IFSinformation():
    """
    IFS Instrument Model
    ====================

    The file provides a function that returns IFS related information such as pixel
    size, dark current, gain...
    
    Returns a dictionary describing VIS. The following information is provided 
    (id: value - reference)::

    :return: instrument model parameters
    :rtype: dict
    """

    #########################################################################################################
    #out = dict(readnoise=4, pixel_size=0.1, dark=0.0008333, fullwellcapacity=90000, bluesize=4000, redsize=6000,  readtime=300.)
    out=dict()
    out.update({'dob' : 0, 'rdose' : 8.0e9,
                'parallelTrapfile' : 'cdm_euclid_parallel.dat', 'serialTrapfile' : 'cdm_euclid_serial.dat',
                'beta_s' : 0.6, 'beta_p': 0.6, 'fwc' : 90000, 'vth' : 1.168e7, 't' : 20.48e-3, 'vg' : 6.e-11,
                'st' : 5.0e-6, 'sfwc' : 730000., 'svg' : 1.0e-10})
    return out


def CCDnonLinearityModel(data, beta=6e-7):
    """   

    The non-linearity is modelled based on the results presented. 
    :param data: data to which the non-linearity model is being applied to
    :type data: ndarray

    :return: input data after conversion with the non-linearity model
    :rtype: float or ndarray
    """
    out = data-beta*data**2
    
    return out

#############################################################################

from scipy.interpolate import InterpolatedUnivariateSpline

class cosmicrays():
    """
    
    Cosmic Rays
    ===========

    This simple class can be used to include cosmic ray events to an image.
    By default the cosmic ray events are drawn from distributions describing
    the length and energy of the events. Such distributions can be generated
    for example using Stardust code (http://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=04636917).
    The energy of the cosmic ray events can also be set to constant for
    testing purposes. The class can be used to draw a single cosmic ray
    event or up to a covering fraction.


    Cosmic ray generation class. Can either draw events from distributions or
    set the energy of the events to a constant.

    :param log: logger instance
    :param image: image to which cosmic rays are added to (a copy is made not to change the original numpy array)
    :param crInfo: column information (cosmic ray file)
    :param information: cosmic ray track information (file containing track length and energy information) and
                        exposure time.
    """
    def __init__(self, log, image, crInfo=None, information=None):
        """
        Cosmic ray generation class. Can either draw events from distributions or
        set the energy of the events to a constant.

        :param log: logger instance
        :param image: image to which cosmic rays are added to (a copy is made not to change the original numpy array)
        :param crInfo: column information (cosmic ray file)
        :param information: cosmic ray track information (file containing track length and energy information) and
                            exposure time.
        """
        #setup logger
        self.log = log

        #image and size
        self.image = image.copy()
        self.ysize, self.xsize = self.image.shape

        #set up the information dictionary, first with defaults and then overwrite with inputs if given
        self.information = (dict(cosmicraylengths='/home/yan/csst-master/data/cdf_cr_length.dat',
                                 cosmicraydistance='/home/yan/csst-master/data/cdf_cr_total.dat',
                                 exptime=565))
        if information is not None:
            self.information.update(information)

        if crInfo is not None:
            self.cr = crInfo
        else:
            self._readCosmicrayInformation()
##############################################################################

    def _cosmicRayIntercepts(self, lum, x0, y0, l, phi):
        """
        Derive cosmic ray streak intercept points.

        :param lum: luminosities of the cosmic ray tracks
        :param x0: central positions of the cosmic ray tracks in x-direction
        :param y0: central positions of the cosmic ray tracks in y-direction
        :param l: lengths of the cosmic ray tracks
        :param phi: orientation angles of the cosmic ray tracks

        :return: cosmic ray map (image)
        :rtype: nd-array
        """
        #create empty array
        crImage = np.zeros((self.ysize, self.xsize), dtype=np.float64)

        #x and y shifts
        dx = l * np.cos(phi) / 2.
        dy = l * np.sin(phi) / 2.
        mskdx = np.abs(dx) < 1e-8
        mskdy = np.abs(dy) < 1e-8
        dx[mskdx] = 0.
        dy[mskdy] = 0.

        #pixels in x-direction
        ilo = np.round(x0.copy() - dx)
        msk = ilo < 0.
        ilo[msk] = 0
        ilo = ilo.astype(int)

        ihi = 1 + np.round(x0.copy() + dx)
        msk = ihi > self.xsize
        ihi[msk] = self.xsize
        ihi = ihi.astype(int)

        #pixels in y-directions
        jlo = np.round(y0.copy() - dy)
        msk = jlo < 0.
        jlo[msk] = 0
        jlo = jlo.astype(int)

        jhi = 1 + np.round(y0.copy() + dy)
        msk = jhi > self.ysize
        jhi[msk] = self.ysize
        jhi = jhi.astype(int)

        #loop over the individual events
        for i, luminosity in enumerate(lum):
            n = 0  # count the intercepts

            u = []
            x = []
            y = []

            #Compute X intercepts on the pixel grid
            if ilo[i] < ihi[i]:
                for xcoord in range(ilo[i], ihi[i]):
                    ok = (xcoord - x0[i]) / dx[i]
                    if np.abs(ok) <= 0.5:
                        n += 1
                        u.append(ok)
                        x.append(xcoord)
                        y.append(y0[i] + ok * dy[i])
            else:
                for xcoord in range(ihi[i], ilo[i]):
                    ok = (xcoord - x0[i]) / dx[i]
                    if np.abs(ok) <= 0.5:
                        n += 1
                        u.append(ok)
                        x.append(xcoord)
                        y.append(y0[i] + ok * dy[i])

            #Compute Y intercepts on the pixel grid
            if jlo[i] < jhi[i]:
                for ycoord in range(jlo[i], jhi[i]):
                    ok = (ycoord - y0[i]) / dy[i]
                    if np.abs(ok) <= 0.5:
                        n += 1
                        u.append(ok)
                        x.append(x0[i] + ok * dx[i])
                        y.append(ycoord)
            else:
                for ycoord in range(jhi[i], jlo[i]):
                    ok = (ycoord - y0[i]) / dy[i]
                    if np.abs(ok) <= 0.5:
                        n += 1
                        u.append(ok)
                        x.append(x0[i] + ok * dx[i])
                        y.append(ycoord)

            #check if no intercepts were found
            if n < 1:
                xc = int(np.floor(x0[i]))
                yc = int(np.floor(y0[i]))
                crImage[yc, xc] += luminosity

            #Find the arguments that sort the intersections along the track
            u = np.asarray(u)
            x = np.asarray(x)
            y = np.asarray(y)

            args = np.argsort(u)

            u = u[args]
            x = x[args]
            y = y[args]

            #Decide which cell each interval traverses, and the path length
            for i in range(1, n - 1):
                w = u[i + 1] - u[i]
                cx = int(1 + np.floor((x[i + 1] + x[i]) / 2.))
                cy = int(1 + np.floor((y[i + 1] + y[i]) / 2.))

                if 0 <= cx < self.xsize and 0 <= cy < self.ysize:
                    crImage[cy, cx] += (w * luminosity)

        return crImage



    def _drawEventsToCoveringFactor(self, coveringFraction=3.0, limit=1000, verbose=False):
        """
        Generate cosmic ray events up to a covering fraction and include it to a cosmic ray map (self.cosmicrayMap).

        :param coveringFraction: covering fraction of cosmic rya events in per cent of total number of pixels
        :type coveringFraction: float
        :param limit: limiting energy for the cosmic ray event [None = draw from distribution]
        :type limit: None or float
        :param verbose: print out information to stdout
        :type verbose: bool


        :return: None
        """
        self.cosmicrayMap = np.zeros((self.ysize, self.xsize))

        #how many events to draw at once, too large number leads to exceeding the covering fraction
        cr_n = int(295 * self.information['exptime'] / 565. * coveringFraction / 1.4)

        covering = 0.0

        while covering < coveringFraction:
            #pseudo-random numbers taken from a uniform distribution between 0 and 1
            np.random.seed()
            luck = np.random.rand(cr_n)

            #draw the length of the tracks
            ius = InterpolatedUnivariateSpline(self.cr['cr_cdf'], self.cr['cr_u'])
            self.cr['cr_l'] = ius(luck)

            if limit is None:
                ius = InterpolatedUnivariateSpline(self.cr['cr_cde'], self.cr['cr_v'])
                self.cr['cr_e'] = ius(luck)
            else:
                #set the energy directly to the limit
                self.cr['cr_e'] = np.asarray([limit,])

            #Choose the properties such as positions and an angle from a random Uniform dist
            np.random.seed()
            cr_x = self.xsize * np.random.rand(int(np.floor(cr_n)))
            
            np.random.seed()
            cr_y = self.ysize * np.random.rand(int(np.floor(cr_n)))
            
            np.random.seed()
            cr_phi = np.pi * np.random.rand(int(np.floor(cr_n)))

            #find the intercepts
            self.cosmicrayMap += self._cosmicRayIntercepts(self.cr['cr_e'], cr_x, cr_y, self.cr['cr_l'], cr_phi)

            #count the covering factor
            area_cr = np.count_nonzero(self.cosmicrayMap)
            covering = 100.*area_cr / (self.xsize*self.ysize)


    def addUpToFraction(self, coveringFraction, limit=None, verbose=False):
        """
        Add cosmic ray events up to the covering Fraction.

        :param coveringFraction: covering fraction of cosmic rya events in per cent of total number of pixels
        :type coveringFraction: float
        :param limit: limiting energy for the cosmic ray event [None = draw from distribution]
        :type limit: None or float
        :param verbose: print out information to stdout
        :type verbose: bool

        :return: image with cosmic rays
        :rtype: ndarray
        """
        self._drawEventsToCoveringFactor(coveringFraction, limit=limit, verbose=verbose)

        #paste cosmic rays
        self.image += self.cosmicrayMap

        return self.image


###############################################################################
def transRaDec2D(ra, dec):
    """

    ra,dec转为竞天程序里的ob, 赤道坐标系下的笛卡尔三维坐标xyz.
    Parameters
    ----------
    ra : float
        ra in deg.
    dec : float
        dec in deg.

    Returns
    -------
    TYPE
        DESCRIPTION.

    """
    x1 = np.cos(dec / 57.2957795) * np.cos(ra / 57.2957795)
    y1 = np.cos(dec / 57.2957795) * np.sin(ra / 57.2957795)
    z1 = np.sin(dec / 57.2957795)
    return np.array([x1, y1, z1])
###############################################################################

################################################################


def ill2flux(path, E):
    """


    Parameters
    ----------
    path : TYPE
        DESCRIPTION.
    E : TYPE
        DESCRIPTION.

    Returns
    -------
    wave0 : TYPE
        DESCRIPTION.
    spec_scaled : TYPE
        DESCRIPTION.

    """

    # use template from sky_bkg (background_spec_hst.dat)
    filename = path+'IFS_inputdata/refs/background_spec_hst.dat'
    cat_spec = pd.read_csv(filename, sep='\s+', header=None, comment='#')
    wave0 = cat_spec[0].values       # A
    spec0 = cat_spec[2].values      # erg/s/cm^2/A/arcsec^2

    # convert erg/s/cm^2/A/arcsec^2 to erg/s/cm^2/A/sr
    flux1 = spec0 / (1/4.25452e10)
    # convert erg/s/cm^2/A/sr to W/m^2/sr/um
    flux2 = flux1 * 10

    # 对接收面积积分，输出单位 W/m^2/nm
    D = 2   # meter
    f = 28  # meter, 焦距，转换关系来源于王维notes.
    flux3 = flux2 * np.pi * D**2 / 4 / f**2 / 10**3

    f = interp1d(wave0, flux3)
    wave_range = np.arange(3800, 7800)
    flux3_mean = f(wave_range)
    delta_lamba = 0.1   # nm
    E0 = np.sum(flux3_mean * delta_lamba)

    factor = E / E0
    spec_scaled = factor * spec0

    return wave0, spec_scaled

##############################################################

##############################################################################


def earth_angle(time_jd, x_sat, y_sat, z_sat, ra_obj, dec_obj):
    """
    # 

    Parameters
    ----------
    time_jd : TYPE
        DESCRIPTION.
    x_sat : TYPE
        DESCRIPTION.
    y_sat : TYPE
        DESCRIPTION.
    z_sat : TYPE
        DESCRIPTION.
    ra_obj : TYPE
        DESCRIPTION.
    dec_obj : TYPE
        DESCRIPTION.

    Returns
    -------
    angle : TYPE
        DESCRIPTION.

    """

    ra_sat = np.arctan2(y_sat, x_sat) / np.pi * 180
    dec_sat = np.arctan2(z_sat, np.sqrt(x_sat**2+y_sat**2)) / np.pi * 180
    radec_sat = SkyCoord(ra=ra_sat*u.degree,
                         dec=dec_sat*u.degree, frame='gcrs')
    lb_sat = radec_sat.transform_to('geocentrictrueecliptic')

    # get the obj location
    radec_obj = SkyCoord(ra=ra_obj*u.degree,
                         dec=dec_obj*u.degree, frame='gcrs')
    lb_obj = radec_obj.transform_to('geocentrictrueecliptic')

    # calculate the angle between sub-satellite point and the earth side
    earth_radius = 6371     # km
    sat_height = np.sqrt(x_sat**2 + y_sat**2 + z_sat**2)
    angle_a = np.arcsin(earth_radius/sat_height) / np.pi * 180

    # calculate the angle between satellite position and the target position
    angle_b = lb_sat.separation(lb_obj)

    # calculat the earth angle
    angle = 180 - angle_a - angle_b.degree

    return angle

#####################################################################


class StrayLight(object):

    def __init__(self, path, jtime=2460843., sat=np.array([0, 0, 0]), radec=np.array([0, 0])):
        self.path = path
        self.jtime = jtime
        self.sat = sat
        self.equator = coord.SkyCoord(
            radec[0]*u.degree, radec[1]*u.degree, frame='icrs')
        self.ecliptic = self.equator.transform_to('barycentrictrueecliptic')
        self.pointing = transRaDec2D(radec[0], radec[1])
        self.slcdll = ctypes.CDLL(
            self.path+'IFS_inputdata/refs/libstraylight.so')  # dylib

        self.slcdll.Calculate.argtypes = [ctypes.c_double, ctypes.POINTER(ctypes.c_double),
                                          ctypes.POINTER(ctypes.c_double), ctypes.POINTER(
                                              ctypes.c_double),
                                          ctypes.POINTER(ctypes.c_double), ctypes.c_char_p]

        self.slcdll.PointSource.argtypes = [ctypes.c_double, ctypes.POINTER(ctypes.c_double),
                                            ctypes.POINTER(ctypes.c_double), ctypes.POINTER(
                                                ctypes.c_double),
                                            ctypes.POINTER(ctypes.c_double), ctypes.c_char_p]

        self.slcdll.EarthShine.argtypes = [ctypes.c_double, ctypes.POINTER(ctypes.c_double),
                                           ctypes.POINTER(ctypes.c_double), ctypes.POINTER(
                                               ctypes.c_double),
                                           ctypes.POINTER(ctypes.c_double)]

        self.slcdll.Zodiacal.argtypes = [ctypes.c_double, ctypes.POINTER(ctypes.c_double),
                                         ctypes.POINTER(ctypes.c_double)]
        self.slcdll.ComposeY.argtypes = [ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_double),
                                         ctypes.POINTER(ctypes.c_double)]
        self.slcdll.Init.argtypes = [
            ctypes.c_char_p, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_char_p]
        self.deFn = self.path+"IFS_inputdata/refs/DE405"
        self.PSTFn = self.path+"IFS_inputdata/refs/PST"
        self.RFn = self.path + "IFS_inputdata/refs/R"
        self.ZolFn = self.path+"IFS_inputdata/refs/Zodiacal"
        self.brightStarTabFn = self.path+"IFS_inputdata/refs/BrightGaia_with_csst_mag"

        self.slcdll.Init(str.encode(self.deFn), str.encode(
            self.PSTFn), str.encode(self.RFn), str.encode(self.ZolFn))
############################################################################

    def caculateStarLightFilter(self, filter='i'):
        """


        Parameters
        ----------
        filter : TYPE, optional
            DESCRIPTION. The default is 'i'.

        Returns
        -------
        TYPE
            DESCRIPTION.

        """
        filterIndex = {'nuv': 0, 'u': 1, 'g': 2,
                       'r': 3, 'i': 4, 'z': 5, 'y': 6}

        sat = (ctypes.c_double*3)()
        sat[:] = self.sat
        ob = (ctypes.c_double*3)()
        ob[:] = self.pointing

        py1 = (ctypes.c_double*3)()
        py2 = (ctypes.c_double*3)()
        self.slcdll.ComposeY(ob, py1, py2)

        star_e1 = (ctypes.c_double*7)()
        self.slcdll.PointSource(self.jtime, sat, ob, py1,
                                star_e1, str.encode(self.brightStarTabFn))
        star_e2 = (ctypes.c_double*7)()
        self.slcdll.PointSource(self.jtime, sat, ob, py2,
                                star_e2, str.encode(self.brightStarTabFn))

        band_star_e1 = star_e1[:][filterIndex[filter]]
        band_star_e2 = star_e2[:][filterIndex[filter]]

        return max(band_star_e1, band_star_e2)
###############################################################################

    def caculateEarthShineFilter(self, filter='i'):
        """
        # 

        Parameters
        ----------
        filter : TYPE, optional
            DESCRIPTION. The default is 'i'.

        Returns
        -------
        TYPE
            DESCRIPTION.

        """
        filterIndex = {'nuv': 0, 'u': 1, 'g': 2,
                       'r': 3, 'i': 4, 'z': 5, 'y': 6}

        sat = (ctypes.c_double*3)()
        sat[:] = self.sat
        ob = (ctypes.c_double*3)()
        ob[:] = self.pointing

        py1 = (ctypes.c_double*3)()
        py2 = (ctypes.c_double*3)()
        self.slcdll.ComposeY(ob, py1, py2)

        earth_e1 = (ctypes.c_double*7)()
        self.slcdll.EarthShine(self.jtime, sat, ob, py1,  earth_e1)
        # e[7]代表7个波段的照度

        earth_e2 = (ctypes.c_double*7)()
        self.slcdll.EarthShine(self.jtime, sat, ob, py2, earth_e2)

        band_earth_e1 = earth_e1[:][filterIndex[filter]]
        band_earth_e2 = earth_e2[:][filterIndex[filter]]

        return max(band_earth_e1, band_earth_e2)

###############################################################################
###############################################################################


def time2mjd(dateT):
    """


    Parameters
    ----------
    dateT : TYPE
        DESCRIPTION.

    Returns
    -------
    TYPE
        DESCRIPTION.

    """
    t0 = datetime(1858, 11, 17, 0, 0, 0, 0)  # 简化儒略日起始日
    mjd = (dateT-t0).days
    mjd_s = dateT.hour*3600.0+dateT.minute*60.0 + \
        dateT.second+dateT.microsecond/1000000.0
    return mjd+mjd_s/86400.0

###########################################################################


def time2jd(dateT):
    """


    Parameters
    ----------
    dateT : TYPE
        DESCRIPTION.

    Returns
    -------
    TYPE
        DESCRIPTION.

    """
    t0 = datetime(1858, 11, 17, 0, 0, 0, 0)  # 简化儒略日起始日
    mjd = (dateT-t0).days
    mjd_s = dateT.hour*3600.0+dateT.minute*60.0 + \
        dateT.second+dateT.microsecond/1000000.0
    return mjd+mjd_s/86400.0++2400000.5

# mjd转datetime类
#############################################################################


def mjd2time(mjd):
    """


    Parameters
    ----------
    mjd : TYPE
        DESCRIPTION.

    Returns
    -------
    TYPE
        DESCRIPTION.

    """
    t0 = datetime(1858, 11, 17, 0, 0, 0, 0)  # 简化儒略日起始日
    return t0+timedelta(days=mjd)

#############################################################################


def jd2time(jd):
    """


    Parameters
    ----------
    jd : TYPE
        DESCRIPTION.

    Returns
    -------
    TYPE
        DESCRIPTION.

    """
    mjd = jd2mjd(jd)
    return mjd2time(mjd)

# mjd和jd互转

############################################################################


def mjd2jd(mjd):
    """


    Parameters
    ----------
    mjd : TYPE
        DESCRIPTION.

    Returns
    -------
    TYPE
        DESCRIPTION.

    """
    return mjd+2400000.5

###########################################################################


def jd2mjd(jd):
    """


    Parameters
    ----------
    jd : TYPE
        DESCRIPTION.

    Returns
    -------
    TYPE
        DESCRIPTION.

    """
    return jd-2400000.5

##########################################################################


def dt2hmd(dt):
    """


    Parameters
    ----------
    dt : TYPE
        DESCRIPTION.

    Returns
    -------
    TYPE
        DESCRIPTION.

    """
    ## dt is datetime
    hour = dt.hour
    minute = dt.minute
    second = dt.second
    if hour < 10:
        str_h = '0'+str(hour)
    else:
        str_h = str(hour)

    if minute < 10:
        str_m = '0'+str(minute)
    else:
        str_m = str(minute)

    if second < 10:
        str_d = '0'+str(second+dt.microsecond*1e-6)
    else:
        str_d = str(second+dt.microsecond*1e-6)

    return str_h+':'+str_m+':'+str_d

###############################################################################
###############################################################################


def deg2HMS(ra0, dec0):
    """


    Parameters
    ----------
    ra0 : TYPE
        DESCRIPTION.
    dec0 : TYPE
        DESCRIPTION.

    Returns
    -------
    TYPE
        DESCRIPTION.

    """
    '''convert deg to ra's HMS and  dec's DMS'''
    c = SkyCoord(ra=ra0*u.degree, dec=dec0*u.degree)
    ss = c.to_string('hmsdms')
    print(ss)
    for k in range(5, len(ss)-1):

        if ss[k] == 's':
            s_idx = k

        if ss[k] == 'd':
            d_idx = k

        if ss[k] == 'm':
            m_idx = k

    if c.ra.hms.s < 10 and c.ra.hms.s > 0.1:
        temp = str(c.ra.hms.s)
        ra_ss = '0'+temp[:3]
    if c.ra.hms.s > 10:
        temp = str(c.ra.hms.s)
        ra_ss = temp[:4]

    if c.ra.hms.s < 0.1:
        temp = str(c.ra.hms.s)
        ra_ss = '00.0'

    dms_d = ss[s_idx+2:d_idx]
    dms_m = ss[d_idx+1:m_idx]
    dms_s = ss[m_idx+1:m_idx+3]

    hhmmss = ss[0:2]+ss[3:5]+ra_ss
    return hhmmss+dms_d+dms_m+dms_s


############################################################################

def beta_angle(x_sat, y_sat, z_sat, vx_sat, vy_sat, vz_sat, ra_obj, dec_obj):
    """


    Parameters
    ----------
    x_sat : TYPE
        DESCRIPTION.
    y_sat : TYPE
        DESCRIPTION.
    z_sat : TYPE
        DESCRIPTION.
    vx_sat : TYPE
        DESCRIPTION.
    vy_sat : TYPE
        DESCRIPTION.
    vz_sat : TYPE
        DESCRIPTION.
    ra_obj : TYPE
        DESCRIPTION.
    dec_obj : TYPE
        DESCRIPTION.

    Returns
    -------
    angle : TYPE
        DESCRIPTION.

    """

    # get the vector for next motion
    x_sat2 = x_sat + vx_sat
    y_sat2 = y_sat + vy_sat
    z_sat2 = z_sat + vz_sat

    vector1 = np.stack((x_sat, y_sat, z_sat),    axis=0)
    vector2 = np.stack((x_sat2, y_sat2, z_sat2), axis=0)
    vector_normal = np.cross(vector1, vector2)

    location_normal = EarthLocation.from_geocentric(vector_normal[0],
                                                    vector_normal[1],
                                                    vector_normal[2],
                                                    unit=u.km)
    radec_normal = SkyCoord(ra=location_normal.lon,
                            dec=location_normal.lat,
                            frame='gcrs')
    lb_normal = radec_normal.transform_to('geocentrictrueecliptic')

    radec_sun = SkyCoord(ra=ra_obj*u.degree,
                         dec=dec_obj*u.degree, frame='gcrs')
    lb_sun = radec_sun.transform_to('geocentrictrueecliptic')

    # get the angle between normal and solar
    angle = 90 - lb_normal.separation(lb_sun).degree

    return angle

###############################################################################
##########################################################


def LSR_velocity(ra, dec, velocity, Obstime):
    """


    Parameters
    ----------
    ra : TYPE
        DESCRIPTION.
    dec : TYPE
        DESCRIPTION.
    velocity : TYPE
        DESCRIPTION.
    Obstime : TYPE
        DESCRIPTION.

    Returns
    -------
    TYPE
        DESCRIPTION.

    """
    # 	'''
    # 	输入参数为ra，dec 原始视向速度in km/s,观测时间
    # 	local为观测地位置，c为利用输入坐标建立的坐标系对象。
    # 	barycorr为程序输出的地球坐标系与BSR间的修正，单位为m/s
    # 	修正后再将BSR中的速度转化为LSR中的速度。
    # 	'''
    local = EarthLocation.from_geodetic(
        lat=22.349368*u.deg, lon=113.584068*u.deg, height=10*u.m)
    # convert ra and dec to
    source = SkyCoord(ra*u.deg, dec*u.deg, frame='icrs',
                      unit=(u.hourangle, u.deg))
    l = source.galactic.l.deg
    b = source.galactic.b.deg
    c = SkyCoord(l=l*u.degree, b=b*u.degree, frame='galactic')
    c_icrs = c.transform_to('icrs')
    barycorr = c_icrs.radial_velocity_correction(
        obstime=Time(Obstime), location=local)
    velocity = velocity + barycorr.value/1000
    # print(barycorr.value/1000)
    l = l * np.pi / 180
    b = b * np.pi / 180
    return velocity + 9 * np.cos(l) * np.cos(b) + 12 * np.sin(l) * np.cos(b) + 7 * np.sin(b)

###############################################################################


def rotation_yan(x0, y0, x1, y1, angle):
    """


    Parameters
    ----------
    x0 : TYPE
        DESCRIPTION.
    y0 : TYPE
        DESCRIPTION.
    x1 : TYPE
        DESCRIPTION.
    y1 : TYPE
        DESCRIPTION.
    angle : TYPE
        DESCRIPTION.

    Returns
    -------
    x2 : TYPE
        DESCRIPTION.
    y2 : TYPE
        DESCRIPTION.

    """
    # % point A (x0,y0)
    # % point B(x1,y1)
    # % roate angle ,point B rotate with point A
    alpha = angle/180*3.1415926  # % in radians
    x2 = (x1-x0)*np.cos(alpha)-(y1-y0)*np.sin(alpha)+x0
    y2 = (x1-x0)*np.sin(alpha)+(y1-y0)*np.cos(alpha)+y0
    return x2, y2
######################################################################


def centroid(data):
    """


    Parameters
    ----------
    data : TYPE
        DESCRIPTION.

    Returns
    -------
    TYPE
        DESCRIPTION.

    """
    h, w = np.shape(data)
    x = np.arange(0, w)
    y = np.arange(0, h)
    x1 = np.ones((1, h))
    y1 = np.ones((w, 1))
    cx = (np.dot(np.dot(x1, data), y)) / (np.dot(np.dot(x1, data), y1))
    cy = (np.dot(np.dot(x, data), y1)) / (np.dot(np.dot(x1, data), y1))
    return float(cx), float(cy)

###############################################################################


def SpecCube2photon(data, wave):
    """


    Parameters
    ----------
    data : TYPE
        DESCRIPTION.
    wave : TYPE
        DESCRIPTION.

    Returns
    -------
    Nphoton : TYPE
        DESCRIPTION.

    """
    # calcutle photons from original spec cube data,
    # data: input data, two-dimentional, unit :  1e-17 erg/s/A/cm^2
    # wave: the relative wavefront, unit in nm;
    planckh = 6.62620*10**-27  # % erg s;
    cc = 2.99792458*10**17  # in nm/s
    # fov2=0.01                       # 单个像素的面积 arcsec^2
    # telarea=3.1415926*100*100       # in cm^2，望远镜聚光面积；
    fluxlam = 1e-17*data     # convert original unit to unit of erg/s/A/cm^2
    lam = wave                  # wave in nm   ;;
    ephoton = planckh*cc/lam         # in erg/photon,  cc与lambda单位需要一致；
    Nphoton = fluxlam/ephoton           # in unit of  photons/cm2/s/A
    return Nphoton

######################################################################
######################################################################


def mag2flux(starmag, lam):
    """


    Parameters
    ----------
    starmag : TYPE
        DESCRIPTION.
    lam : TYPE
        DESCRIPTION.

    Returns
    -------
    fluxlam : TYPE
        DESCRIPTION.
    photonu : TYPE
        DESCRIPTION.

    """
    # starmag: input AB mag
    # lambda: wavelength in nm

    planckh = 6.62620*10**-27  # % erg s;
    cc = 2.99792458*10**17  # % in nm/s

    iskyv0 = starmag         # mag/arcsec^2 unit
    fluxnu = 10.0**((iskyv0+48.6)/-2.5)        # erg/s/cm2/Hz/arcsec^2
    fluxlam = fluxnu*cc/lam**2/10                   # erg/s/cm2/A/arcsec^2

    Ee = planckh*cc/lam  # single photon energy in erg/photon;
    photonu = fluxlam/Ee  # photons/cm2/s/A/arcsec^2

    return fluxlam, photonu

###############################################################################


def flux2photons(flux, lam):
    """


    Parameters
    ----------
    flux : TYPE
        DESCRIPTION.
    lam : TYPE
        DESCRIPTION.

    Returns
    -------
    photonu : TYPE
        DESCRIPTION.

    """
    # flux: # erg/s/cm2/A/
    # lam:  #  wavelength in nm

    planckh = 6.62620*10**-27  # % erg s;
    cc = 2.99792458*10**17  # % in nm/s
    Ee = planckh*cc/lam  # singe photon energy in erg/photon;
    photonu = flux/Ee        # photons/cm2/s/A
    return photonu

###############################################################################


def fftrange(n, dtype=float):
    """


    Parameters
    ----------
    n : TYPE
        DESCRIPTION.
    dtype : TYPE, optional
        DESCRIPTION. The default is float.

    Returns
    -------
    TYPE
        DESCRIPTION.

    """
    """FFT-aligned coordinate grid for n samples."""
    return np.arange(-n//2, -n//2+n, dtype=dtype)

###############################################################################


def dft2(ary, Q, samples, shift=None):
    """


    Parameters
    ----------
    ary : TYPE
        DESCRIPTION.
    Q : TYPE
        DESCRIPTION.
    samples : TYPE
        DESCRIPTION.
    shift : TYPE, optional
        DESCRIPTION. The default is None.

    Returns
    -------
    out : TYPE
        DESCRIPTION.

    """
    """Compute the two dimensional Discrete Fourier Transform of a matrix.

    Parameters
    ----------
    ary : `numpy.ndarray`
        an array, 2D, real or complex.  Not fftshifted.
    Q : `float`
        oversampling / padding factor to mimic an FFT.  If Q=2, Nyquist sampled
    samples : `int` or `Iterable`
        number of samples in the output plane.
        If an int, used for both dimensions.  If an iterable, used for each dim
    shift : `float`, optional
        shift of the output domain, as a frequency.  Same broadcast
        rules apply as with samples.

    Returns
    -------
    `numpy.ndarray`
        2D array containing the shifted transform.
        Equivalent to ifftshift(fft2(fftshift(ary))) modulo output
        sampling/grid differences

    """

    # this is for dtype stabilization
    Q = float(Q)  # Q=lambda*f/D/pixelsize

    n, m = ary.shape  # ary maybe is the pupil function
    N, M = samples, samples

    X, Y, U, V = (fftrange(n) for n in (m, n, M, N))

    a = 1 / Q

    ###################################################################
    Eout_fwd = np.exp(-1j * 2 * np.pi * a / n * np.outer(Y, V).T)
    Ein_fwd = np.exp(-1j * 2 * np.pi * a / m * np.outer(X, U))

    ###################################################################

    out = Eout_fwd @ ary @ Ein_fwd  # ary is the input pupil function

    return out
##############################################################################
###############################################################################


def anySampledPSFnew(wavefront, pupil, Q, sizeout):
    """


    Parameters
    ----------
    wavefront : TYPE
        DESCRIPTION.
    pupil : TYPE
        DESCRIPTION.
    Q : TYPE
        DESCRIPTION.
    sizeout : TYPE
        DESCRIPTION.

    Returns
    -------
    psf : TYPE
        DESCRIPTION.

    """
    '''
    written on 2020.12.04 by Yan @Shao
    % wavefront sampled in the united circle;
    % pupil function samped as wavefront;
    % sample ratio Q=lambda/D/pixel;
    % lambda is wavelength;
    % D is diameter;
    % pixel is in arcsec;
    % or sample ratio Q=lambda*f/D/pixelsize
    % f is focal length;
    % pixelsize is the actural size of the detector;
    % make sure all the varia have the same unit;
    % the returned PSF has sum value of 1 
    '''

    m, n = np.shape(wavefront)

    phase = 2.0*np.pi*wavefront  # % wavefront phase in radians

    # %generalized pupil function of channel1;
    pk = pupil*np.exp(cmath.sqrt(-1)*(phase))

    pf = dft2(pk, Q, sizeout)

    pf2 = abs(pf)**2
    psfout = pf2/pf2.sum()

    cx, cy = centroid(psfout)
    Nt = sizeout

    # for convolve method
    psf = ndimage.shift(
        psfout, [Nt/2-cy-1, Nt/2-cx-1], order=1, mode='nearest')
    psf = psf/psf.sum()

    return psf

##############################################################################

##############################################################################


def get_dx_dy_blue(wave):
    """


    Parameters
    ----------
    wave : TYPE
        DESCRIPTION.

    Returns
    -------
    dx : TYPE
        DESCRIPTION.
    dy : TYPE
        DESCRIPTION.

    """
    # wave is the wavelength in nm;
    # dx is in dispersion direction, dy is in vertical direction;
    dydl = np.array([-423.256, 0.001,           0.00075,
                     0.0000078,       -0.0000000000007,    0.0,        0.0])
    # 色散方向
    dxdl = 0.2*np.array([-9.1519, -1.00000000e-06,  3.50000000e-08, -5.00000000e-09,
                         -1.70000000e-11,     4.00949787e-12, -6.16873452e-15])
    # 垂直方向
    dx = 0.0
    dy = 0.0
    for i in range(len(dxdl)):
        dx = dx+dxdl[i]*wave**(i)
        dy = dy+dydl[i]*wave**(i)
    return dx, dy
##############################################################################


def get_dx_dy_red(wave):
    """


    Parameters
    ----------
    wave : TYPE
        DESCRIPTION.

    Returns
    -------
    dx : TYPE
        DESCRIPTION.
    dy : TYPE
        DESCRIPTION.

    """
    # wave is the wavelength in nm;
    dydl = np.array([-640.0239901372472,        0.0018,          0.00048,
                     0.0000028,         -0.0000000000007,   0.0,      0.0])  # 色散方向
    dxdl = 0.00325*np.array([-1638.8,                      4.0e-2,         5.500e-3,       -
                             5.2e-10,           1.7000e-10,        7.1e-13,   -5.16e-15])  # 垂直方向

    dx = 0.0
    dy = 0.0
    for i in range(len(dxdl)):
        dx = dx+dxdl[i]*wave**(i)
        dy = dy+dydl[i]*wave**(i)
    return dx, dy

##############################################################################################


def getSpectrum(Spectrum0, lam, sigma):
    """


    Parameters
    ----------
    Spectrum0 : TYPE
        DESCRIPTION.
    lam : TYPE
        DESCRIPTION.
    sigma : TYPE
        DESCRIPTION.

    Returns
    -------
    SpmOut : TYPE
        DESCRIPTION.

    """

    # %获得输入波长lambda的光谱强度
    wave = Spectrum0[:, 0]  # %原始光谱数据给定的波长；
    Qt = Spectrum0[:, 1]  # %原始光谱数据给定的光谱强度；
    d = abs(lam-wave)  # %计算波长距离；
    if min(d) > 3*sigma:

        SpmOut = 1.0/20000
    else:

        column = np.where(d == min(d))
        SpmOut = Qt[column[0]]*np.exp(-(lam-wave[column[0]])**2/2/sigma**2)

    return SpmOut


##############################################################################
##############################################################################

class IFSsimulator():
    """
    CSST IFS Simulator

    The image that is being build is in::

        self.image
        self.image_b   blue channel
        self.image_r   red channel

    :param opts: OptionParser instance
    :type opts: OptionParser instance
    """

    def __init__(self, configfile):
        """
        Class Constructor.

        :param opts: OptionParser instance
        :type opts: OptionParser instance

        """

        ####################################

        self.configfile = configfile
        self.section = 'TEST'  # simulation section;

        # load instrument model
        self.information = IFSinformation()

        # update settings with defaults
        self.information.update(dict(quadrant=int(0),
                                     ccdx=int(0),
                                     ccdy=int(0),

                                     ccdxgap=1.643,
                                     ccdygap=8.116,
                                     bluesize=4000,
                                     redsize=6000,
                                     # prescanx=50,
                                     # ovrscanx=20,
                                     fullwellcapacity=90000,
                                     dark=0.001,
                                     readout=4,
                                     bias=500.0,
                                     e_adu=1.5,
                                     readouttime=0.13824,
                                     rdose=8.0e9,
                                     ra=0.0,
                                     dec=0.0,
                                     coveringfraction=0.1,
                                     # CR: 3.0% is for 565s exposure
                                     #########################################

                                     mode='same'))

        #####################################

    def readConfigs(self, simnumber):
        """


        Parameters
        ----------
        simnumber : TYPE
            DESCRIPTION.

        Returns
        -------
        None.

        """
        """
        Reads the config file information using configParser and sets up a logger.
        """
        self.config = ConfigParser.RawConfigParser()

        self.config.read_file(open(self.configfile))

###############################################################################

    def processConfigs(self):
        """
        Processes configuration information and save the information to a dictionary self.information.

        The configuration file may look as follows::

            [TEST]


        For explanation of each field, see /data/test.config. Note that if an input field does not exist,
        then the values are taken from the default instrument model as described in
        support.IFSinstrumentModel.VISinformation(). Any of the defaults can be overwritten by providing
        a config file with a correct field name.
        """
        # parse options and update the information dictionary
        options = self.config.options(self.section)
        settings = {}
        for option in options:
            try:
                settings[option] = self.config.getint(self.section, option)
            except ValueError:
                try:
                    settings[option] = self.config.getfloat(
                        self.section, option)
                except ValueError:
                    settings[option] = self.config.get(self.section, option)

        self.information.update(settings)

        # force gain to be float
        self.information['e_adu'] = float(self.information['e_adu'])

        self.cosmicRays = self.config.getboolean(self.section, 'cosmicRays')
        self.darknoise = self.config.getboolean(self.section, 'darknoise')
        self.cosmetics = self.config.getboolean(self.section, 'cosmetics')
        self.radiationDamage = self.config.getboolean(
            self.section, 'radiationDamage')

        self.bleeding = self.config.getboolean(self.section, 'bleeding')
        self.save_cosmicrays = self.config.getboolean(
            self.section, 'save_cosmicrays')
        self.sky_noise = self.config.getboolean(self.section, 'sky_noise')

        self.nonlinearity = self.config.getboolean(
            self.section, 'nonlinearity')

        self.flatfieldM = self.config.getboolean(
            self.section, 'flatfieldM')

        self.readoutNoise = self.config.getboolean(
            self.section, 'readoutnoise')

        self.intscale = True
        ######################################################################

        self.information['variablePSF'] = False

        self.booleans = dict(nonlinearity=self.nonlinearity,
                             flatfieldM=self.flatfieldM,

                             cosmicRays=self.cosmicRays,
                             darknoise=self.darknoise,
                             cosmetics=self.cosmetics,
                             radiationDamage=self.radiationDamage,
                             bleeding=self.bleeding,
                             sky_noise=self.sky_noise,
                             intscale=self.intscale,
                             save_cosmicrays=self.save_cosmicrays)

 ############################################################################

    def _createEmpty(self):
        """
        Creates and empty array of a given x and y size full of zeros.
        add g r i channel images;

        Creates lensing parameters;    


        """

        self.imgtemp = dict()

        self.pixel = 0.1  # arcsec, pixel scale size;


##############################################################################
##############################################################################

    def zodiacal(self, ra, dec, time):
        """


        Parameters
        ----------
        ra : TYPE
            DESCRIPTION.
        dec : TYPE
            DESCRIPTION.
        time : TYPE
            DESCRIPTION.

        Returns
        -------
        wave_A : TYPE
            DESCRIPTION.
        spec_erg2 : TYPE
            DESCRIPTION.

        """
        """
            For given RA, DEC and TIME, return the interpolated zodical
            spectrum in Leinert-1998.

        :param ra: RA in unit of degree, ICRS frame
        :param dec: DEC in unit of degree, ICRS frame
        :param time: the specified string that in ISO format i.e., yyyy-mm-dd.
        :return:
            wave_A: wavelength of the zodical spectrum
            spec_mjy: flux of the zodical spectrum, in unit of MJy/sr
            spec_erg: flux of the zodical spectrum, in unit of erg/s/cm^2/A/sr

        """

        # get solar position
        dt = datetime.fromisoformat(time)
        jd = time2jd(dt)
        ## jd = julian.to_jd(dt, fmt='jd')

        t = Time(jd, format='jd', scale='utc')

        astro_sun = get_sun(t)
        ra_sun, dec_sun = astro_sun.gcrs.ra.deg, astro_sun.gcrs.dec.deg

        radec_sun = SkyCoord(ra=ra_sun*u.degree,
                             dec=dec_sun*u.degree, frame='gcrs')
        lb_sun = radec_sun.transform_to('geocentrictrueecliptic')

        # get offsets between the target and sun.
        radec_obj = SkyCoord(ra=ra*u.degree, dec=dec*u.degree, frame='icrs')
        lb_obj = radec_obj.transform_to('geocentrictrueecliptic')

        beta = abs(lb_obj.lat.degree)
        lamda = abs(lb_obj.lon.degree - lb_sun.lon.degree)

        # interpolated zodical surface brightness at 0.5 um
        zodi = pd.read_csv(
            self.information['dir_path']+'IFS_inputdata/refs/zodi_map.dat', sep='\s+', header=None, comment='#')
        beta_angle = np.array([0, 5, 10, 15, 20, 25, 30, 45, 60, 75])
        lamda_angle = np.array([0, 5, 10, 15, 20, 25, 30, 35, 40, 45,
                                60, 75, 90, 105, 120, 135, 150, 165, 180])
        xx, yy = np.meshgrid(beta_angle, lamda_angle)
        #xx, yy = np.meshgrid(beta_angle, lamda_angle,indexing='ij', sparse=True)

        f = interpolate.interp2d(xx, yy, zodi, kind='linear')
        #f = interpolate.RegularGridInterpolator((xx, yy), zodi, method='linear')

        zodi_obj = f(beta, lamda)       #

        # read the zodical spectrum in the ecliptic
        cat_spec = pd.read_csv(
            self.information['dir_path']+'IFS_inputdata/refs/solar_spec.dat', sep='\s+', header=None, comment='#')
        wave = cat_spec[0].values       # A
        spec0 = cat_spec[1].values      #
        zodi_norm = 252                 #

        spec = spec0 * (zodi_obj / zodi_norm) * 1e-8  #

        # convert to the commonly used unit of MJy/sr, erg/s/cm^2/A/sr
        wave_A = wave                                               # A
        # spec_mjy = spec * 0.1 * wave_A**2 / 3e18 * 1e23 * 1e-6      # MJy/sr
        spec_erg = spec * 0.1                                       # erg/s/cm^2/A/sr
        # erg/s/cm^2/A/arcsec^2
        spec_erg2 = spec_erg / 4.25452e10

        # self.zodiacal_wave=wave_A    # in A

        # self.zodiacal_flux=spec_erg2

        return wave_A, spec_erg2
    ##########################################################################

    # def smoothingWithChargeDiffusion(self, image, sigma=(0.32, 0.32)):
    #     """

    #     Parameters
    #     ----------
    #     image : TYPE
    #         DESCRIPTION.
    #     sigma : TYPE, optional
    #         DESCRIPTION. The default is (0.32, 0.32).

    #     Returns
    #     -------
    #     TYPE
    #         DESCRIPTION.

    #     """
    #     """
    #     Smooths a given image with a gaussian kernel with widths given as sigmas.
    #     This smoothing can be used to mimic charge diffusion within the CCD.

    #     The default values are from Table 8-2 of CCD_273_Euclid_secification_1.0.130812.pdf converted
    #     to sigmas (FWHM / (2sqrt(2ln2)) and rounded up to the second decimal.

    #     .. Note:: This method should not be called for the full image if the charge spreading
    #               has already been taken into account in the system PSF to avoid double counting.

    #     :param image: image array which is smoothed with the kernel
    #     :type image: ndarray
    #     :param sigma: widths of the gaussian kernel that approximates the charge diffusion [0.32, 0.32].
    #     :param sigma: tuple

    #     :return: smoothed image array
    #     :rtype: ndarray
    #     """
    #     return ndimage.filters.gaussian_filter(image, sigma)
###############################################################################

    def readCosmicRayInformation(self):
        """


        Returns
        -------
        None.

        """
        """
        Reads in the cosmic ray track information from two input files.

        Stores the information to a dictionary called cr.
        """
        self.log.info('Reading in cosmic ray information from %s and %s' % (self.information['cosmicraylengths'],
                                                                            self.information['cosmicraydistance']))
        # print(self.information)

        crLengths = np.loadtxt(
            self.information['dir_path']+self.information['cosmicraylengths'])
        crDists = np.loadtxt(
            self.information['dir_path']+self.information['cosmicraydistance'])

        self.cr = dict(cr_u=crLengths[:, 0], cr_cdf=crLengths[:, 1], cr_cdfn=np.shape(crLengths)[0],
                       cr_v=crDists[:, 0], cr_cde=crDists[:, 1], cr_cden=np.shape(crDists)[0])

###############################################################################
###############################################################################
    def configure(self, sourcein,  dir_path, result_path, simnumber, debug, applyhole):
        """


        Parameters
        ----------
        simnumber : TYPE
            DESCRIPTION.

        Returns
        -------
        None.

        """
        """
        Configures the simulator with input information and creates and
        empty array to which the final image will
        be build on.
        """
        self.readConfigs(simnumber)

        self.processConfigs()

        self._createEmpty()
        
        self.information['dir_path']=dir_path
        
        self.information['result_path']=result_path
        
        self.information['debug'] = debug
        
        self.source=sourcein
        
        self.simnumber=simnumber

        ############################################################

        now = datetime.utcnow()

        result_day = now.strftime("%Y-%m-%d")

        # self.information['holemask']

        if self.source == 'LAMP':
            if applyhole == 'yes':
                ss = '_with_hole_'
            else:
                ss = '_no_hole_'

        else:
            ss = '_'

        # if self.information['dir_path'] == '/nfsdata/share/simulation-unittest/ifs_sim/':
        #     self.result_path = self.information['dir_path'] + \
        #         self.information['result_path']+'/'+self.source+ss+result_day
        # else:

        #     home_path = os.environ['HOME']

        #     if home_path == '/home/yan':

        #         self.result_path = '../IFS_simData/'+self.source+ss+result_day
        #     else:
        #         self.result_path = '/data/ifspip/CCD_ima/'+self.source+ss+result_day
        
        
                
        self.result_path= self.information['result_path']+'/'+self.source+ss+result_day 
        print(self.information['result_path'])

        if os.path.isdir(self.result_path) == False:
            os.mkdir(self.result_path)
            os.mkdir(self.result_path+'/calibration_Data')
            os.mkdir(self.result_path+'/log_file')
            os.mkdir(self.result_path+'/original_Calibration')
            os.mkdir(self.result_path+'/original_sky')
            os.mkdir(self.result_path+'/shift_rot_sky')
            os.mkdir(self.result_path+'/sky_Data')

        ##############################################################################
        now = datetime.utcnow()

        data_time = now.strftime("%Y-%m-%d-%H-%M-%S")
        ############################################################

        self.log = lg(self.result_path+'/log_file/IFS_' +
                                  self.source+'_'+data_time+'_Num_'+str(simnumber)+'.log')

        self.log.info('STARTING A NEW SIMULATION')

        self.log.info('The exposure order is  %i ' % self.simnumber)

        ################################################################################
        # public system information
        self.pixelscale = 0.1  # the pixel size is 0.1 arcsec
        self.pixelsize = 15   # the pixel physical size is 15 micron
        self.Fnum = 15.469875  # the f number= focal_length/D;
        self.telD = 2   # tht telescope size is 2 meter, diamter size;
        self.telarea = 3.1415926*(self.telD/2)**2 * \
            100*100  # telescope square in cm^2
        self.fov2 = 0.01  # the pixel square
        self.planckh = 6.62620*1e-27  # % erg s;
        self.cc = 2.99792458*1e17  # in nm/s
        self.light_FWHM = 0.175
        self.HgArsigma = self.light_FWHM/2.35  # sigma value of the light source

        # load system optical and CCD efficiency data
        matfn0 = self.information['dir_path']+'IFS_inputdata/TotalQ200923.mat'
        self.log.info('Optical optical efficiency file path is: %s' % (matfn0))
        da0 = sio.loadmat(matfn0)
        # optical efficiency of blue channel
        self.optical_blue_Q = da0['opticalQ_1']
        # optical efficiency of red channel
        self.optical_red_Q = da0['opticalQ_2']
        self.CCD_Qe = da0['ccdQE']  # red channel

        # load all useful data;
        # load wavefront data;
        matfn2 = self.information['dir_path']+'IFS_inputdata/opd/opd_638nm.mat'
        self.log.info('OPD0 file path is: %s' % (matfn2))
        da2 = sio.loadmat(matfn2)
        opd0 = da2['opd']  # opd unit is in meter
        self.opd0 = opd0*1e9  # convert opd  to nm
        self.pupil = abs(opd0) > 0.0

        ####################
        matfn2 = self.information['dir_path']+'IFS_inputdata/opd/opd1.fits'
        da = fits.open(matfn2)
        self.opd1 = da[0].data
        self.log.info('OPD1 file path is: %s' % (matfn2))

        matfn2 = self.information['dir_path']+'IFS_inputdata/opd/opd2.fits'
        self.log.info('OPD2 file path is: %s' % (matfn2))
        da = fits.open(matfn2)
        self.opd2 = da[0].data

        matfn2 = self.information['dir_path']+'IFS_inputdata/opd/opd3.fits'
        self.log.info('OPD3 file path is: %s' % (matfn2))
        da = fits.open(matfn2)
        self.opd3 = da[0].data

        ################################################################################
        # slice information; the slice is put in vertial direction;
        # the silce long size is 64 pixels
        slice_blue = dict()
        slice_red = dict()

        randRedpos = np.array([0.52835362,  1.1105926, -0.81667794,  0.88860884, -2.78092636,
                               -0.15810022, -1.56726852, -0.71601855, -1.31768647,  1.73107581,
                               0.4933876,  2.83673377,  0.22226286, -0.02634998,  0.35539383,
                               -0.91989574, -2.44856212,  0.91020484, -3.03097852, -1.11638071,
                               1.28360669, -0.12521128, -0.3907698,  0.70183575,  1.00578099,
                               1.67339662,  0.18067182, -0.56303075,  0.40959616,  1.45518379,
                               -0.93194744,  0.41492972])

        randBluepos = np.array([0.97449008, -0.21371406, -1.62513338, -3.06938604,  1.72615283,
                                0.73333374,  0.80923919, -0.9418576, -0.16806578, -1.04416631,
                                2.20155068, -0.0900156,  0.07597706,  0.76208373,  0.29426592,
                                -0.89434703,  0.34017826,  1.16936499,  0.10977829, -1.31179539,
                                -0.50859787, -1.01891651, -0.95791581, -1.53018041,  0.88358827,
                                0.07837641, -0.86157157, -1.18070438,  0.53970599,  1.4913733,
                                2.10938775,  1.23213412])

        #####################
        for i in range(32):
            if i == 0:
                slice_blue['py'] = np.zeros(32)
                slice_blue['px'] = np.zeros(32)

                slice_red['py'] = np.zeros(32)
                slice_red['px'] = np.zeros(32)

            if i < 16:
                slice_blue['py'][i] = 50+randBluepos[i]*4
                slice_blue['px'][i] = 3.55/0.015*i+166.0

                slice_red['py'][i] = 50+randRedpos[i]*4
                slice_red['px'][i] = 3.55/0.015*i+1190.0

            else:
                slice_blue['py'][i] = 50+250+randBluepos[i]*4
                slice_blue['px'][i] = 3.55/0.015*(i-16)+166.0+118

                slice_red['py'][i] = 50+250+randRedpos[i]*4
                slice_red['px'][i] = 3.55/0.015*(i-16)+1190.0+118
        #######

        self.slice_blue = slice_blue
        self.slice_red = slice_red

        #######################################################################
        maskSlice = dict()
        maskSlit = dict()
        sizeout = 100
        for k in range(32):
            maskSlice[str(k)] = np.zeros((sizeout, sizeout))
            maskSlice[str(k)][2*k+18:2*k+20, int(sizeout/2) -
                              32:int(sizeout/2)+32] = 1

            maskSlit[str(k)] = np.zeros((sizeout, sizeout))
            maskSlit[str(k)][2*k+18:2*k+20, int(sizeout/2) -
                             37:int(sizeout/2)+37] = 1

        self.maskSlice = maskSlice
        self.maskSlit = maskSlit
######################################################################

#############################################################################

    def MakeFlatMatrix(self, img, seed):
        """


        Parameters
        ----------
        img : TYPE
            DESCRIPTION.
        seed : TYPE
            DESCRIPTION.

        Returns
        -------
        FlatMat : TYPE
            DESCRIPTION.

        """
        ####
        ysize, xsize = img.shape
        np.random.seed(seed)
        r1, r2, r3, r4 = np.random.random(4)
        a1 = -0.5 + 0.2*r1
        a2 = -0.5 + 0.2*r2
        a3 = r3+5
        a4 = r4+5
        xmin, xmax, ymin, ymax = 0, xsize, 0, ysize
        Flty, Fltx = np.mgrid[ymin:ymax, xmin:xmax]
        np.random.seed(seed)
        p1, p2, bg = np.random.poisson(1000, 3)
        Fltz = 1e-6*(a1 * (Fltx-p1) ** 2 + a2 * (Flty-p2)
                     ** 2 - a3*Fltx - a4*Flty) + bg*20
        FlatMat = Fltz/np.mean(Fltz)

        return FlatMat
    ####################################################################

    def getFrameTransferImg(self):
        """


        Returns
        -------
        None.

        """
        """
        get frame transfer image from original image of self.image_b and self.image_r
        """
        self.frame_b_4 = np.zeros((1024+320, 2048), dtype=float)
        self.frame_b_3 = np.zeros((1024+320, 2048), dtype=float)
        self.frame_b_2 = np.zeros((1024+320, 2048), dtype=float)
        self.frame_b_1 = np.zeros((1024+320, 2048), dtype=float)

        self.frame_r_4 = np.zeros((1536+320, 3072), dtype=float)
        self.frame_r_3 = np.zeros((1536+320, 3072), dtype=float)
        self.frame_r_2 = np.zeros((1536+320, 3072), dtype=float)
        self.frame_r_1 = np.zeros((1536+320, 3072), dtype=float)

        # blue channle
        b_4 = np.sum(self.image_b[0:1024, 0:2048], axis=0)

        # flip right to  left of part3 and part2
        temp_b_3 = np.fliplr(self.image_b[0:1024, 2048:4096])  # left to right
        b_3 = np.sum(temp_b_3, axis=0)

        temp_b_2 = np.fliplr(
            self.image_b[1024:1024+1024, 2048:2048+2048])  # left to right
        b_2 = np.sum(temp_b_2, axis=0)

        b_1 = np.sum(self.image_b[1024:1024+1024, 0:2048], axis=0)
        ####

        for k in range(1024+320):
            # part
            self.frame_b_4[k, :] += b_4*0.09216/self.information['exptime']
            self.frame_b_3[k, :] += b_3*0.09216/self.information['exptime']
            self.frame_b_2[k, :] += b_2*0.09216/self.information['exptime']
            self.frame_b_1[k, :] += b_1*0.09216/self.information['exptime']

        ###################
        # red channle
        r_4 = np.sum(self.image_r[0:1536, 0:3072], axis=0)

        # flip right to  left of part3 and part2
        temp_r_3 = np.fliplr(self.image_r[0:1536, 3072:6144])  # left to right
        r_3 = np.sum(temp_r_3, axis=0)

        temp_r_2 = np.fliplr(
            self.image_r[1536:1536+1536, 3072:6144])  # left to right
        r_2 = np.sum(temp_r_2, axis=0)

        #r_3 = np.sum(self.image_r[0:1536, 3072:6144], axis=0)

        #r_2 = np.sum(self.image_r[1536:1536+1536, 3072:6144], axis=0)

        r_1 = np.sum(self.image_r[1536:1536+1536, 0:3072], axis=0)

        for k in range(1536+320):
            # part 4, OSH zone;
            self.frame_r_4[k, :] += r_4*0.13824/self.information['exptime']
            self.frame_r_3[k, :] += r_3*0.13824/self.information['exptime']
            self.frame_r_2[k, :] += r_2*0.13824/self.information['exptime']
            self.frame_r_1[k, :] += r_1*0.13824/self.information['exptime']
            # end

###############################################################################
    def applyflatfield(self, simnumber):
        """


        Parameters
        ----------
        simnumber : TYPE
            DESCRIPTION.

        Returns
        -------
        None.

        """
        """
        Applies multiplicative flat field to emulate pixel-to-pixel non-uniformity.

        Because the pixel-to-pixel non-uniformity effect (i.e. multiplicative) flat fielding takes place
        before CTI and other effects, the flat field file must be the same size as the pixels that see
        the sky.
        """
        # apply Flat field to image
        ###
        flat_b = self.MakeFlatMatrix(self.image_b, 100)

        flat_r = self.MakeFlatMatrix(self.image_r, 200)

        self.image_b *= flat_b

        self.image_r *= flat_r

        self.log.info('Applied flatfield to images.')

        return

###############################################################################

###############################################################################
    def addCosmicRays(self, idk):
        """


        Parameters
        ----------
        idk : TYPE
            DESCRIPTION.

        Returns
        -------
        None.

        """
        """
        Add cosmic rays to the arrays based on a power-law intensity
        distribution for tracks.
        Cosmic ray properties (such as location and angle) are chosen from
        random Uniform distribution.
        For details, see the documentation for the cosmicrays class in the
        support package.
        """
        self.readCosmicRayInformation()
        # to scale the number of cosmics with exposure time
        self.cr['exptime'] = self.information['exptime']

        # cosmic ray image
        crImage_b = np.zeros((2048, 4096), dtype=float)

        crImage_r = np.zeros((3072, 6144), dtype=float)

        # cosmic ray instance
        cosmics_b = cosmicrays(self.log, crImage_b, crInfo=self.cr)
        cosmics_r = cosmicrays(self.log, crImage_r, crInfo=self.cr)

        CCD_cr_b = cosmics_b.addUpToFraction(
            self.information['coveringfraction']*self.information['exptime']/300.0, limit=None)
        CCD_cr_r = cosmics_r.addUpToFraction(
            self.information['coveringfraction']*self.information['exptime']/300.0, limit=None)

        # paste the information
        #self.image += CCD_cr
        self.image_b += CCD_cr_b
        self.image_r += CCD_cr_r
        # count the covering factor
        area_cr_b = np.count_nonzero(CCD_cr_b)
        area_cr_r = np.count_nonzero(CCD_cr_r)

        #self.log.info('The cosmic ray covering factor is %i pixels ' % area_cr)
        self.log.info(
            'The cosmic ray in blue channel covering factor is %i pixels ' % area_cr_b)
        self.log.info(
            'The cosmic ray in red channel covering factor is %i pixels ' % area_cr_r)

        # if self.save_cosmicrays:

        #     self.log.info('Saved the cosmicRays fits...')
        #     fits.writeto(self.result_path+'/calibration_Data/cosmicMap_B_SN_'+str(
        #         self.simnumber)+'_exp_'+str(idk)+'.fits', np.int32(CCD_cr_b), overwrite=True)
        #     fits.writeto(self.result_path+'/calibration_Data/cosmicMap_R_SN_'+str(
        #         self.simnumber)+'_exp_'+str(idk)+'.fits', np.int32(CCD_cr_r), overwrite=True)

        ##########################################################
#########################################################################


##############################################################################

    def applyDarkCurrent(self):
        """


        Returns
        -------
        None.

        """
        """
        Apply dark current. Scales the dark with the exposure time.

        Additionally saves the image without noise to a FITS file.
        """

        self.log.info('Added dark current to bule and red channel')

        # blue zone 4
        self.image_b[0:1024, 0:2048] += self.information['exptime'] * \
            self.information['dark4_b']

        ########## zone 1   #################
        self.image_b[1024:2048, 0:2048] += self.information['exptime'] * \
            self.information['dark1_b']

        ########## zone 3  ###################
        self.image_b[0:1024, 2048:4096] += self.information['exptime'] * \
            self.information['dark3_b']

        # zone 2
        self.image_b[1024:2048, 2048:4096] += self.information['exptime'] * \
            self.information['dark2_b']

        # red  zone 4
        self.image_r[0:1536, 0:3072] += self.information['exptime'] * \
            self.information['dark4_r']
        ########## zone 1   #################

        self.image_r[1536:3712, 0:3072] += self.information['exptime'] * \
            self.information['dark1_r']

        ########## zone 3  ###################
        self.image_r[0:1536, 3072:6144] += self.information['exptime'] * \
            self.information['dark3_r']

        # zone 2
        self.image_r[1536:3072, 3072:6144] += self.information['exptime'] * \
            self.information['dark2_r']


##############################################################################
##############################################################################

    def applyPoissonNoise(self):
        """


        Returns
        -------
        None.

        """
        """
        Add Poisson noise to the image.
        """

        rounded = np.rint(self.image_b)  # round to
        # ugly workaround for multiple rounding operations...
        residual = self.image_b.copy() - rounded
        rounded[rounded < 0.0] = 0.0

        np.random.seed()
        self.image_b = np.random.poisson(rounded).astype(float)
        self.log.info('Added Poisson noise on channel blue')
        self.image_b += residual

        rounded = np.rint(self.image_r)  # round to
        # ugly workaround for multiple rounding operations...
        residual = self.image_r.copy() - rounded
        rounded[rounded < 0.0] = 0.0
        np.random.seed()
        self.image_r = np.random.poisson(rounded).astype(float)
        self.log.info('Added Poisson noise on channel red')
        self.image_r += residual


##############################################################################

    def applyCosmetics(self):
        """


        Returns
        -------
        None.

        """
        """
        Apply cosmetic defects described in the input file.

        #Number of hot and dead pixels from MSSL/Euclid/TR/12003 Issue 2 Draft b

        .. Warning:: This method does not work if the input file has exactly one line.
        """
        cosmetics = np.loadtxt(
            self.information['dir_path']+self.information['cosmeticsfile_b'])
        self.log.info('cosmeticsfile_b path is: %s' %
                      (self.information['cosmeticsfile_b']))

        x = np.round(cosmetics[:, 0]).astype(int)
        y = np.round(cosmetics[:, 1]).astype(int)
        value = cosmetics[:, 2]

        # cosmetics_b=np.zeros((1344,9642))

        # cosmetics_r=np.zeros((1856,13768))

        self.log.info('Adding cosmetic defects to blue channel:')
        for xc, yc, val in zip(x, y, value):
            if 0 <= xc <= 1024 and 50 < yc % 2418 <= 2048+50:
                #self.image[yc, xc] = val
                self.image_b[xc, yc] = val
                # cosmetics_b[xc,yc]=val
                self.log.info('x=%i, y=%i, value=%f' % (xc, yc, val))
#############################################################################

        cosmetics = np.loadtxt(
            self.information['dir_path']+self.information['cosmeticsfile_r'])

        self.log.info('cosmeticsfile_r path is: %s' %
                      (self.information['cosmeticsfile_r']))

        x = np.round(cosmetics[:, 0]).astype(int)
        y = np.round(cosmetics[:, 1]).astype(int)
        value = cosmetics[:, 2]

        self.log.info('Adding cosmetic defects to red channel:')

        for xc, yc, val in zip(x, y, value):
            if 0 <= xc <= 1536 and 50 < yc % 3442 <= 3072+50:
                #self.image[yc, xc] = val
                self.image_r[xc, yc] = val
                # cosmetics_r[yc,xc]=val
                self.log.info('x=%i, y=%i, value=%f' % (xc, yc, val))

        #######  save cosmetics image  code ####
        # if self.simnumber<2:
        #     #save cosmic ray image map

        #     obsid=self.simnumber
        #     bluefile=self.result_path+'/calibration_Data/IFS_cosmetics'+'_'+str(obsid)+'_B.fits'
        #     redfile =self.result_path+'/calibration_Data/IFS_cosmetics'+'_'+str(obsid)+'_R.fits'

        #     fits.writeto(bluefile, cosmetics_b, overwrite=True)
        #     fits.writeto(redfile,  cosmetics_r, overwrite=True)

        #     #output information to a FITS file

        #     self.log.info('Save cosmetics images fits file to  calibration_Data file ')
################################################################################

    def applyRadiationDamage(self):
        """


        Returns
        -------
        None.

        """
        """
        Applies CDM03 radiation model to the image being constructed.

        .. seealso:: Class :`CDM03`
        """

        self.log.debug('Starting to apply radiation damage model...')
        # at this point we can give fake data...
        cti = CDM03bidir(self.information, [], log=self.log)

        # here we need the right input data
        self.image_b = cti.applyRadiationDamage(self.image_b.copy(
        ).transpose(), iquadrant=self.information['quadrant']).transpose()
        self.log.info('Radiation damage added.')

        self.log.debug('Starting to apply radiation damage model...')
        # at this point we can give fake data...
        cti = CDM03bidir(self.information, [], log=self.log)
        # here we need the right input data
        self.image_r = cti.applyRadiationDamage(self.image_r.copy(
        ).transpose(), iquadrant=self.information['quadrant']).transpose()
        self.log.info('Radiation damage added.')
##############################################################################

    def applyNonlinearity(self):
        """


        Returns
        -------
        None.

        """
        """
        Applies a CCD273 non-linearity model to the image being constructed.
        """

        self.log.debug('Starting to apply non-linearity model...')
        self.image_b = CCDnonLinearityModel(
            self.image_b.copy())

        self.log.info('Non-linearity effects included.')

        self.log.debug('Starting to apply non-linearity model...')
        self.image_r = CCDnonLinearityModel(
            self.image_r.copy())

        self.log.info('Non-linearity effects included.')
        ######################################################################

    def applyReadoutNoise(self):
        """


        Returns
        -------
        None.

        """
        """
        Applies readout noise to the image being constructed.

        The noise is drawn from a Normal (Gaussian) distribution with 
        average=0.0 and std=readout noise.
        """
        self.log.info('readnoise added in blue channel')
        # blue zone 1
        np.random.seed()
        prescan = 50
        overscan = 320
        # zone 4 ,  OSH ####  1024,  2048
        self.image_b[0:1024+overscan, 0:2048+prescan+overscan] += np.random.normal(
            loc=0.0, scale=self.information['rn4_b'], size=(1344, 2418))

        ########## zone 3, OSG    #################
        np.random.seed()
        self.image_b[0:1024+overscan, 2418:2418+2048+prescan+overscan] += np.random.normal(
            loc=0.0, scale=self.information['rn3_b'], size=(1344, 2418))

        ########## zone 1, OSE  ###################
        np.random.seed()
        self.image_b[0:1024+overscan, 2418*2:2418*2+2048+prescan+overscan] += np.random.normal(
            loc=0.0, scale=self.information['rn1_b'], size=(1344, 2418))

        ########## zone 2, OSF ###############
        np.random.seed()
        self.image_b[0:1024+overscan, 2418*3:2418*3+2048+prescan+overscan] += np.random.normal(
            loc=0.0, scale=self.information['rn2_b'], size=(1344, 2418))

        self.log.info('readnoise added in blue channel')

        # red zone 4 , OSH### 1536* 3072
        np.random.seed()
        self.image_r[0:1536+overscan, 0:3072+prescan+overscan] += np.random.normal(
            loc=0.0, scale=self.information['rn4_r'], size=(1856, 3442))

        ########## zone 3 ,OSG #################
        np.random.seed()
        self.image_r[0:1536+overscan, 3442:3442+3072+prescan+overscan] += np.random.normal(
            loc=0.0, scale=self.information['rn3_r'], size=(1856, 3442))

        ########## zone 1 ,OSE ###################
        np.random.seed()
        self.image_r[0:1536+overscan, 3442*2:3442*2+3072+prescan+overscan] += np.random.normal(
            loc=0.0, scale=self.information['rn1_r'], size=(1856, 3442))

        ########## zone 2,OSF ###########
        np.random.seed()
        self.image_r[0:1536+overscan, 3442*3:3442*3+3072+prescan+overscan] += np.random.normal(
            loc=0.0, scale=self.information['rn2_r'], size=(1856, 3442))

##########################################################################################

    def appFrameTransferEffect(self):
        """


        Returns
        -------
        None.

        """
        """
        apply frame transfer effect to the four part images
        """
        prescan = 50
        overscan = 8

        # zone 4 ,  OSH ####  1024,  2048
        self.image_b[0:1024+overscan, prescan:2048 +
                     prescan] += self.frame_b_4[0:1024+overscan, 0:2048]

        ########## zone 3, OSG    #################

        self.image_b[0:1024+overscan, prescan+2418:2418+2048 +
                     prescan] += self.frame_b_3[0:1024+overscan, 0:2048]

        ########## zone 1, OSE  ###################

        self.image_b[0:1024+overscan, prescan+2418*2:2418*2 +
                     2048+prescan] += self.frame_b_1[0:1024+overscan, 0:2048]

        ########## zone 2, OSF ###############

        self.image_b[0:1024+overscan, prescan+2418*3:2418*3 +
                     2048+prescan] += self.frame_b_2[0:1024+overscan, 0:2048]

        # 3

        self.log.info('readnoise added in blue channel')

        # red zone 4 , OSH### 1536* 3072

        self.image_r[0:1536+overscan, prescan+3442*0:3442*0 +
                     3072+prescan] += self.frame_r_4[0:1536+overscan, 0:3072]

        ########## zone 3 ,OSG #################

        self.image_r[0:1536+overscan, prescan+3442*1:3442*1 +
                     3072+prescan] += self.frame_r_3[0:1536+overscan, 0:3072]

        ########## zone 1 ,OSE ###################
        self.image_r[0:1536+overscan, prescan+3442*2:3442*2 +
                     3072+prescan] += self.frame_r_1[0:1536+overscan, 0:3072]

        ########## zone 2,OSF ###########

        self.image_r[0:1536+overscan, prescan+3442*3:3442*3 +
                     3072+prescan] += self.frame_r_2[0:1536+overscan, 0:3072]

    ##########################################################################

    def electrons2ADU(self):
        """


        Returns
        -------
        None.

        """
        """
        Convert from electrons to ADUs using the value read from the configuration file.
        """
        self.log.info(
            'Converting from electrons to ADUs using a factor of gain')

        # blue zone 4
        self.image_b[0:1344, 0:2418] /= self.information['gain4_b']

        ########## zone 3  #################
        self.image_b[0:1344, 2418:2418*2] /= self.information['gain3_b']

        ########## zone 1  ###################
        self.image_b[0:1344, 2418*2:2418*3] /= self.information['gain1_b']

        # zone 2
        self.image_b[0:1344, 2418*3:2418*4] /= self.information['gain2_b']

        ############################################################################

        # red zone 4
        self.image_r[0:1856, 0:3442] /= self.information['gain4_r']

        ########## zone 3   #################
        self.image_r[0:1856, 3442:3442*2] /= self.information['gain3_r']

        ########## zone 1  ###################
        self.image_r[0:1856, 3442*2:3442*3] /= self.information['gain1_r']

        # zone 2
        self.image_r[0:1856, 3442*3:3442*4] /= self.information['gain2_r']

 # 3

 ####################################################################################

    def applyBias(self):
        """


        Returns
        -------
        None.

        """
        """
        Adds a bias level to the image being constructed.

        The value of bias is read from the configure file and stored
        in the information dictionary (key bias).
        """

        # blue zone 4
        self.image_b[0:1344, 0:2418] += self.information['bias4_b']

        ########## zone 3  #################
        self.image_b[0:1344, 2418:2418*2] += self.information['bias3_b']

        ########## zone 1  ###################
        self.image_b[0:1344, 2418*2:2418*3] += self.information['bias1_b']

        # zone 2
        self.image_b[0:1344, 2418*3:2418*4] += self.information['bias2_b']

        ############################################################################

        self.log.info('Bias counts were added to the blue image')

        #######################################################################

        # red zone 4
        self.image_r[0:1856, 0:3442] += self.information['bias4_r']

        ########## zone 3   #################
        self.image_r[0:1856, 3442:3442*2] += self.information['bias3_r']

        ########## zone 1  ###################
        self.image_r[0:1856, 3442*2:3442*3] += self.information['bias1_r']

        # zone 2
        self.image_r[0:1856, 3442*3:3442*4] += self.information['bias2_r']

        #######################################################################

        self.log.info('Bias counts were added to the red image')

###############################################################################
    def addPreOverScans(self):
        """


        Returns
        -------
        None.

        """
        """
        Add pre- and overscan regions to the self.image. These areas are added only in the serial direction.
        Because the 1st and 3rd quadrant are read out in to a different serial direction than the nominal
        orientation, in these images the regions are mirrored.

        The size of prescan and overscan regions are defined by the prescanx and overscanx keywords, respectively.
        """
        self.log.info('Adding pre- and overscan regions')

        canvas = np.zeros((self.information['bluesize'],
                           (self.information['bluesize'] + self.information['prescanx'] + self.information['ovrscanx'])))

        # because the pre- and overscans are in x-direction this needs to be taken into account for the
        # 1st and 3rd quadrant
        if self.information['quadrant'] in (0, 2):
            canvas[:, self.information['prescanx']: self.information['prescanx'] +
                   self.information['bluesize']] = self.image_b
            self.image_b = canvas
        elif self.information['quadrant'] in (1, 3):
            canvas[:, self.information['ovrscanx']: self.information['ovrscanx'] +
                   self.information['bluesize']] = self.image_b
            self.image_b = canvas
        else:
            self.log.error(
                'Cannot include pre- and overscan because of an unknown quadrant!')

         #####################################################################

        canvas = np.zeros((self.information['redsize'],
                           (self.information['redsize'] + self.information['prescanx'] + self.information['ovrscanx'])))

        # because the pre- and overscans are in x-direction this needs to be taken into account for the
        # 1st and 3rd quadrant
        if self.information['quadrant'] in (0, 2):
            canvas[:, self.information['prescanx']: self.information['prescanx'] +
                   self.information['redsize']] = self.image_r
            self.image_r = canvas
        elif self.information['quadrant'] in (1, 3):
            canvas[:, self.information['ovrscanx']: self.information['ovrscanx'] +
                   self.information['redsize']] = self.image_r
            self.image_r = canvas
        else:
            self.log.error(
                'Cannot include pre- and overscan because of an unknown quadrant!')

###############################################################################

    def applyBleeding_yan(self):
        """


        Returns
        -------
        None.

        """
        """
        Apply bleeding along the CCD columns if the number of electrons in
        a pixel exceeds the full-well capacity.

        Bleeding is modelled in the parallel direction only, because the
        CCD273s are assumed not to bleed in
        serial direction.

        :return: None
        """
        self.log.info('Applying column bleeding...')

        # loop over each column, as bleeding is modelled column-wise
        for i, column in enumerate(self.image_b.T):
            sum = 0.
            for j, value in enumerate(column):
                # first round - from bottom to top (need to half the bleeding)
                overload = value - self.information['fullwellcapacity']
                if overload > 0.:
                    overload /= 2.
                    #self.image[j, i] -= overload
                    self.image_b[j, i] -= overload

                    sum += overload

                elif sum > 0.:
                    if -overload > sum:
                        overload = -sum

                    self.image_b[j, i] -= overload
                    sum += overload

        for i, column in enumerate(self.image_b.T):
            sum = 0.
            for j, value in enumerate(column[::-1]):
                # second round - from top to bottom (bleeding was half'd already, so now full)
                overload = value - self.information['fullwellcapacity']
                if overload > 0.:
                    #self.image[-j-1, i] -= overload
                    self.image_b[-j-1, i] -= overload

                    sum += overload
                elif sum > 0.:
                    if -overload > sum:
                        overload = -sum
                    #self.image[-j-1, i] -= overload
                    self.image_b[-j-1, i] -= overload

                    sum += overload
        print('Applying column bleeding to blue image finished.......')
        # 3333
        for i, column in enumerate(self.image_r.T):
            sum = 0.
            for j, value in enumerate(column):
                # first round - from bottom to top (need to half the bleeding)
                overload = value - self.information['fullwellcapacity']
                if overload > 0.:
                    overload /= 2.
                    #self.image[j, i] -= overload
                    self.image_r[j, i] -= overload

                    sum += overload
                elif sum > 0.:
                    if -overload > sum:
                        overload = -sum
                    #self.image[j, i] -= overload
                    self.image_r[j, i] -= overload

                    sum += overload

        for i, column in enumerate(self.image_r.T):
            sum = 0.
            for j, value in enumerate(column[::-1]):
                # second round - from top to bottom (bleeding was half'd already, so now full)
                overload = value - self.information['fullwellcapacity']
                if overload > 0.:
                    #self.image[-j-1, i] -= overload
                    self.image_r[-j-1, i] -= overload

                    sum += overload
                elif sum > 0.:
                    if -overload > sum:
                        overload = -sum
                    #self.image[-j-1, i] -= overload
                    self.image_r[-j-1, i] -= overload

                    sum += overload
        print('Applying column bleeding to red image finished.......')

    ###########################################################################

    ############################################################################

    def discretise(self, max=2**16-1):
        """


        Parameters
        ----------
        max : TYPE, optional
            DESCRIPTION. The default is 2**16-1.

        Returns
        -------
        None.

        """
        """
        Converts a floating point image array (self.image) to an integer array with max values
        defined by the argument max.

        :param max: maximum value the the integer array may contain [default 65k]
        :type max: float

        :return: None
        """
        # avoid negative numbers in case bias level was not added
        self.image_b[self.image_b < 0.0] = 0.
        # cut of the values larger than max
        self.image_b[self.image_b > max] = max

        self.image_b = np.rint(self.image_b).astype(int)
        self.log.info('Maximum and total values of the image are %i and %i, respectively' % (np.max(self.image_b),
                                                                                             np.sum(self.image_b)))

        # avoid negative numbers in case bias level was not added
        self.image_r[self.image_r < 0.0] = 0.
        # cut of the values larger than max
        self.image_r[self.image_r > max] = max

        self.image_r = np.rint(self.image_r).astype(int)
        self.log.info('Maximum and total values of the image are %i and %i, respectively' % (np.max(self.image_r),
                                                                                             np.sum(self.image_r)))

  ##################################################################################################

    # def applyImageShift(self):
    #     """


    #     Returns
    #     -------
    #     None.

    #     """

    #     np.random.seed(9*self.simnumber)
    #     ud = np.random.random()   # Choose a random rotation
    #     dx = 2 * (ud-0.5) * self.information['shiftmax']

    #     np.random.seed(99*self.simnumber)
    #     ud = np.random.random()   # Choose a random rotation
    #     dy = 2 * (ud-0.5) * self.information['shiftmax']

    #     self.image_b = ndimage.shift(self.image_b.copy(), [
    #                                  dy+self.information['shift_b_y'], dx+self.information['shift_b_x']], order=0, mode='nearest')
    #     self.image_r = ndimage.shift(self.image_r.copy(), [
    #                                  dy+self.information['shift_r_y'], dx+self.information['shift_r_x']], order=0, mode='nearest')

    #     self.log.info('Applied image shifting to g r i channels.')
    #     self.information['ra'] = dx*self.information['pixel_size']
    #     self.information['dec'] = dy*self.information['pixel_size']


##############################################################################

    # def applyImageRotate(self):
    #     """

    #     Returns
    #     -------
    #     None.

    #     """
    #     np.random.seed(10*self.simnumber)
    #     ud = np.random.random()   # Choose a random rotation
    #     angle = 2 * (ud-0.5) * self.information['tel_rotmax']

    #     inputimg = self.image_b.copy()
    #     # here we choose reshape=False, the rotated image will
    #     rotimg = ndimage.rotate(
    #         inputimg, angle+self.information['rotate_b'], order=1, reshape=False)
    #     self.image_b = rotimg

    #     inputimg = self.image_r.copy()
    #     # here we choose reshape=False, the rotated image will
    #     rotimg = ndimage.rotate(
    #         inputimg, angle+self.information['rotate_r'], order=1, reshape=False)
    #     self.image_r = rotimg

    #     self.information['Tel_rot'] = angle

    #     self.log.info(
    #         'Applied telescope rotation with angle (in degree)= %f.', angle)
###############################################################################


    def CCDreadout(self):
        """


        Returns
        -------
        None.

        """

        imgb = self.image_b.copy()
        prescan = int(self.information['prescan'])
        overscan = int(self.information['overscan'])

        temp = np.zeros((1344, 9672))
        # zone 4, OSH ######0:1024, 50:2048+50
        x1 = 0
        x2 = x1+1024

        y1 = 0+prescan
        y2 = y1+2048
        temp[x1:x2, y1:y2] = imgb[0:1024, 0:2048]
        # zone 3, OSG  , left to right  #################
        # np.fliplr(b2)      ## left to right
        # np.flipud(b3)      ## down to up
        x1 = 0
        x2 = x1+1024

        y1 = 2418+prescan
        y2 = y1+2048
        temp[x1:x2, y1:y2] = np.fliplr(imgb[0:1024, 2048:4096])
        #  zone 1, OSE,down to up   ###################
        x1 = 0
        x2 = x1+1024

        y1 = 2418*2+prescan
        y2 = y1+2048
        temp[x1:x2, y1:y2] = np.flipud(imgb[1024:2048, 0:2048])
        ########## zone 2, OSF down to yp ,left to right #######
        x1 = 0
        x2 = x1+1024

        y1 = 2418*3+prescan
        y2 = y1+2048
        temp[x1:x2, y1:y2] = np.flipud(np.fliplr(imgb[1024:2048, 2048:4096]))

        self.image_b = temp

        #######################################################################
        imgr = self.image_r.copy()
        temp = np.zeros((1856, 13768))

        # zone 4, OSH ######0:1024, 50:2048+50
        x1 = 0
        x2 = x1+1536

        y1 = 0+prescan
        y2 = y1+3072
        temp[x1:x2, y1:y2] = imgr[0:1536, 0:3072]
        ########## zone 3, OSG  , left to right  #################
        # np.fliplr(b2)      ## left to right
        # np.flipud(b3)      ## down to up
        x1 = 0
        x2 = x1+1536

        y1 = 3442+prescan
        y2 = y1+3072
        temp[x1:x2, y1:y2] = np.fliplr(imgr[0:1536, 3072:6144])
        ########## zone 1, OSE,down to up   ############################
        x1 = 0
        x2 = x1+1536

        y1 = 3442*2+prescan
        y2 = y1+3072
        temp[x1:x2, y1:y2] = np.flipud(imgr[1536:3072, 0:3072])
        ########## zone 2, OSF down to yp ,left to right ################
        x1 = 0
        x2 = x1+1536

        y1 = 3442*3+prescan
        y2 = y1+3072
        temp[x1:x2, y1:y2] = np.flipud(np.fliplr(imgr[1536:3072, 3072:6144]))

        self.image_r = temp

        return
##############################################################################

    def writeOutputs(self, obnum):
        """


        Parameters
        ----------
        obnum : TYPE
            DESCRIPTION.

        Returns
        -------
        None.

        """
        """
        Writes out a FITS file using PyFITS and converts the image array to 16bit unsigned integer as
        appropriate for VIS.

        Updates header with the input values and flags used during simulation.
        """
        HeaderTest = 'no'
        sim_ver = str(self.information['sim_ver'])

        # Readout information
        now = self.dt.utcnow()

        # exposre start time
        exp_start_utc = now
        exp_start_str = now.strftime("%Y%m%d%H%M%S")

        # exsposure end time,including mulipe exposure times and readout times;
        # but minus one readout_time
        tt = (self.information['exptime']+self.information['readouttime']) * \
            self.information['exposuretimes']-self.information['readouttime']
        end = self.dt.utcnow()+timedelta(seconds=tt)

        exp_end_str = end.strftime("%Y%m%d%H%M%S")
        exp_end_utc = end

        # write the actual file
        tt = (self.information['exptime']+self.information['readouttime']
              )*self.information['exposuretimes']
        write_end = self.dt.utcnow()+timedelta(seconds=tt)

        # write_time_utc = write_end.strftime("%Y-%m-%dT%H:%M:%S")
        # write_time_str = end.strftime("%Y-%m-%dT%H:%M:%S")

        if self.source == 'SCI' or self.source == 'COMP':
            if self.source == 'SCI':
                obsid = 30100000000+obnum  # 3+OBSTYPE+8位曝光编号；
            else:
                obsid = 30200000000+obnum  # 3+OBSTYPE+8位曝光编号；

            filename_b = 'CSST_IFS_B_'+self.source+'_'+exp_start_str + \
                '_'+exp_end_str+'_'+str(obsid)+'_B_L0_V'+sim_ver
            self.file_b = self.result_path+'/sky_Data/'+filename_b+'.fits'

            filename_r = 'CSST_IFS_R_'+self.source+'_'+exp_start_str + \
                '_'+exp_end_str+'_'+str(obsid)+'_R_L0_V'+sim_ver
            self.file_r = self.result_path+'/sky_Data/'+filename_r + '.fits'

        else:

            if self.source == 'BIAS':
                obsid = 30300000000+obnum  # 3+OBSTYPE+8位曝光编号；
            if self.source == 'DARK':
                obsid = 30400000000+obnum  # 3+OBSTYPE+8位曝光编号；
            if self.source == 'FLAT':
                obsid = 30500000000+obnum  # 3+OBSTYPE+8位曝光编号；
            if self.source == 'LAMP':
                obsid = 30600000000+obnum  # 3+OBSTYPE+8位曝光编号；

            filename_b = 'CSST_IFS_B_'+self.source+'_'+exp_start_str + \
                '_'+exp_end_str+'_'+str(obsid)+'_B_L0_V'+sim_ver
            self.file_b = self.result_path+'/calibration_Data/'+filename_b+'.fits'

            filename_r = 'CSST_IFS_R_'+self.source+'_'+exp_start_str + \
                '_'+exp_end_str+'_'+str(obsid)+'_R_L0_V'+sim_ver
            self.file_r = self.result_path+'/calibration_Data/'+filename_r+'.fits'

        # create a new FITS file, using HDUList instance
        ##### layer 0  ####

        ofd_b = fits.PrimaryHDU()

        ofd_b.header['SIMPLE'] = (True,   'conforms to FITS standard')
        ofd_b.header['BITPIX'] = (0,   'array data type')
        ofd_b.header['NAXIS'] = (0,   'number of array dimensions')
        ofd_b.header['NEXTEND'] = (np.int16(1),   'number of array dimensions')

        ############################################################################################
        temp = write_end.utcnow()
        data_time = temp.strftime("%Y-%m-%dT%H:%M:%S.%f")
        ofd_b.header['DATE'] = (
            data_time[:21], 'written date (yyyy-mm-ddThh:mm:ss.s)')
        ofd_b.header['FILENAME'] = (filename_b[:68], '')
        ofd_b.header['FILETYPE'] = (self.source[:12],   'observation type')
        ofd_b.header['TELESCOP'] = ('CSST',   'telescope name')
        ofd_b.header['INSTRUME'] = ('IFS',   'instrument name')
        ofd_b.header['RADECSYS'] = (
            'ICRS',   'coordinate system of the object')
        ofd_b.header['EQUINOX'] = (float(2000.0),   '')
        ofd_b.header['FITSSWV'] = (
            'ifs_sim_0.8.03',   'FITS creating software version')
        #########   Object information  #############
        if self.source == 'SCI' or self.source == 'COMP':
            ofd_b.header['OBJECT'] = (
                self.information['name_obj'][:30],   'object name')
            ofd_b.header['TARGET'] = (
                (self.information['target']),     'target name (hhmmss.s+ddmmss)')

            ofd_b.header['OBSID'] = (str(obsid),   'observation ID')

            ofd_b.header['RA_OBJ'] = (np.float64(
                self.information['ra_obj']),   'object RA (deg)')
            ofd_b.header['DEC_OBJ'] = (np.float64(
                self.information['dec_obj']),   'object Dec (deg)')

        else:
            ofd_b.header['OBJECT'] = (self.source,   ' object name')
            ofd_b.header['TARGET'] = (0,   'target name (hhmmss.s+ddmmss)')

            ofd_b.header['OBSID'] = (str(obsid),   'observation ID')

            ofd_b.header['RA_OBJ'] = (np.float64(0),   'object RA (deg)')
            ofd_b.header['DEC_OBJ'] = (np.float64(0),   'object Dec (deg)')

            # ,   'the orbital position of CSST in X direction at EXPSTART')
            self.information['POSI0_X'] = 0.0
            # ,   'the orbital position of CSST in Y direction at EXPSTART')
            self.information['POSI0_Y'] = 0.0
            # ,   'the orbital position of CSST in Z direction at EXPSTART')
            self.information['POSI0_Z'] = 0.0
            # ,   'the orbital velocity of CSST in X direction at EXPSTART')
            self.information['VELO0_X'] = 0.0
            # ,   'the orbital velocity of CSST in Y direction at EXPSTART')
            self.information['VELO0_Y'] = 0.0
            # ,   'the orbital velocity of CSST in Z direction at EXPSTART')
            self.information['VELO0_Z'] = 0.0

            # ,   'the orbital position of CSST in X direction at EXPSTART')
            self.information['POSI1_X'] = 0.0
            # ,   'the orbital position of CSST in Y direction at EXPSTART')
            self.information['POSI1_Y'] = 0.0
            # ,   'the orbital position of CSST in Z direction at EXPSTART')
            self.information['POSI1_Z'] = 0.0
            # ,   'the orbital velocity of CSST in X direction at EXPSTART')
            self.information['VELO1_X'] = 0.0
            # ,   'the orbital velocity of CSST in Y direction at EXPSTART')
            self.information['VELO1_Y'] = 0.0
            # ,   'the orbital velocity of CSST in Z direction at EXPSTART')
            self.information['VELO1_Z'] = 0.0

        #######################################################################

        ########  Telescope information ###############

        ofd_b.header['REFFRAME'] = ('CSSTGSC-1.0',   'guiding catalog version')
        ofd_b.header['DATE-OBS'] = (data_time[:21],
                                    'observation date (yyyy-mm-ddThh:mm:ss.s)')
        ofd_b.header['SATESWV'] = (
            'softwave-1.0',   'satellite software version')
        ofd_b.header['EXPSTART'] = (np.float64(
            time2mjd(exp_start_utc)),   'exposure start time (MJD)')
        ofd_b.header['CABSTART'] = (np.float64(
            time2jd(exp_start_utc)),   'first cabin time after exposure start (MJD)')
        ofd_b.header['SUNANGL0'] = (np.float32(
            0.0),   'angle between the Sun and opt axis at CABSTART')
        ofd_b.header['MOONANG0'] = (np.float32(
            0.0),   'angle between the Moon and opt axis at CABSTART')
        ofd_b.header['TEL_ALT0'] = (np.float64(
            0.0),   'angle between opt axis and Elimb at CABSTART')
        ofd_b.header['POS_ANG0'] = (np.float64(
            0.0),   'angle between y axis and North Pole at CABSTART')
        ofd_b.header['POSI0_X'] = (np.float64(
            self.information['POSI0_X']),   'orbital position in X at CABSTART (km)')
        ofd_b.header['POSI0_Y'] = (np.float64(
            self.information['POSI0_Y']),   'orbital position in Y at CABSTART (km)')
        ofd_b.header['POSI0_Z'] = (np.float64(
            self.information['POSI0_Z']),   'orbital position in Z at CABSTART (km)')
        ofd_b.header['VELO0_X'] = (np.float64(
            self.information['VELO0_X']),   'orbital velocity in X at CABSTART (km/s)')
        ofd_b.header['VELO0_Y'] = (np.float64(
            self.information['VELO0_Y']),   'orbital velocity in Y at CABSTART (km/s)')
        ofd_b.header['VELO0_Z'] = (np.float64(
            self.information['VELO0_Z']),   'orbital velocity in Z at CABSTART (km/s)')
        ofd_b.header['Euler0_1'] = (np.float64(
            0.0),   'Euler angle 1 at CABSTART (deg)')
        ofd_b.header['Euler0_2'] = (np.float64(
            0.0),   'Euler angle 2 at CABSTART (deg)')
        ofd_b.header['Euler0_3'] = (np.float64(
            0.0),   'Euler angle 3 at CABSTART (deg)')

        #####
        if self.source == 'SCI' or self.source == 'COMP':

            ofd_b.header['RA_PNT0'] = (np.float64(
                self.information['ra_pnt0']),     'pointing RA at CABSTART (deg)')
            ofd_b.header['DEC_PNT0'] = (np.float64(
                self.information['dec_pnt0']),    'pointing Dec at CABSTART (deg)')

        else:

            ofd_b.header['RA_PNT0'] = (np.float64(
                0.0),     'pointing RA at CABSTART (deg)')
            ofd_b.header['DEC_PNT0'] = (np.float64(
                0.0),     'pointing Dec at CABSTART (deg)')

        ofd_b.header['EXPEND'] = (np.float64(
            time2mjd(exp_end_utc)),       'exposure end time (MJD)')

        ofd_b.header['CABEND'] = (np.float64(
            time2jd(exp_end_utc)),       'first cabin time after exposure end (MJD)')

        ofd_b.header['SUNANGL1'] = (np.float32(
            0.0),   'angle between the Sun and opt axis at CABEND')
        ofd_b.header['MOONANG1'] = (np.float32(
            0.0),   'angle between the Moon and opt axis at CABEND')

        ofd_b.header['TEL_ALT1'] = (np.float64(
            0.0),   'angle between opt axis and Elimb at CABEND')
        ofd_b.header['POS_ANG1'] = (np.float64(
            self.information['sky_rot']),   'angle between y axis and North Pole at CABEND')

        ofd_b.header['POSI1_X'] = (np.float64(
            self.information['POSI1_X']),   'orbital position in X at CABEND (km)')
        ofd_b.header['POSI1_Y'] = (np.float64(
            self.information['POSI1_Y']),   'orbital position in Y at CABEND (km)')
        ofd_b.header['POSI1_Z'] = (np.float64(
            self.information['POSI1_Z']),   'orbital position in Z at CABEND (km)')
        ofd_b.header['VELO1_X'] = (np.float64(
            self.information['VELO1_X']),   'orbital velocity in X at CABEND (km/s)')
        ofd_b.header['VELO1_Y'] = (np.float64(
            self.information['VELO1_Y']),   'orbital velocity in Y at CABEND (km/s)')
        ofd_b.header['VELO1_Z'] = (np.float64(
            self.information['VELO1_Z']),   'orbital velocity in Z at CABEND (km/s)')
        ofd_b.header['Euler1_1'] = (np.float64(
            0.0),   'Euler angle 1 at CABEND (deg)')
        ofd_b.header['Euler1_2'] = (np.float64(
            0.0),   'Euler angle 2 at CABEND (deg)')
        ofd_b.header['Euler1_3'] = (np.float64(
            0.0),   'Euler angle 3 at CABEND (deg)')
        ofd_b.header['RA_PNT1'] = (np.float64(
            ofd_b.header['RA_PNT0']),    'pointing RA at CABEND (deg)')
        ofd_b.header['DEC_PNT1'] = (np.float64(
            ofd_b.header['DEC_PNT0']),   'pointing Dec at CABEND (deg)')
        ofd_b.header['EPOCH'] = (np.float32(
            time2jd(end.utcnow())),   'equinox of pointing RA and Dec')
        ofd_b.header['EXPTIME'] = (np.float32(
            self.information['exptime']*self.information['exposuretimes']),   'exposure time (s)')
        ss1 = 'HDU checksum'
        ss2 = 'data unit checksum'
        ofd_b.header['CHECKSUM'] = (data_time[:19], ss1)
        ofd_b.header['DATASUM'] = (data_time[:19], ss2)

        ##########
        ########## finish header for 0 layer   ######################
        #############################################################
        # header
        # new image HDU, blue channel, layer 1
        if HeaderTest == 'yes':
            hdu_b = fits.ImageHDU(data=np.zeros((10, 10, 3)))
        else:
            hdu_b = fits.ImageHDU(data=np.uint16(self.image_b_N))

        #####

        hdu_b.header['XTENSION'] = ('IMAGE', 'extension type')
        hdu_b.header['BITPIX'] = (np.int16(16), 'bits per data value')
        hdu_b.header['NAXIS'] = (np.int16(2), 'number of data axies')

        hdu_b.header['NAXIS1'] = (np.int32(1344), 'length of first data axis')

        hdu_b.header['NAXIS2'] = (np.int32(9672), 'length of second data axis')

        if self.information['exposuretimes'] > 1:
            hdu_b.header['NAXIS3'] = (
                np.int32(self.information['exposuretimes']), 'length of third data axis')

        if self.source == 'SCI' or self.source == 'COMP':
            hdu_b.header['EXTNAME'] = ('SCI', '')

        else:

            hdu_b.header['EXTNAME'] = ('CAL', '')

        hdu_b.header['EXTVER'] = (np.int16(1), '')
        hdu_b.header['BSCALE'] = (np.float64(1), '')
        hdu_b.header['BZERO'] = (np.float64(32768), '')
        hdu_b.header['BUNIT'] = ('ADU', 'physical unit of array values')

        #######################################################################
        #########  instrument information  ###################################
        if self.source == 'SCI' or self.source == 'COMP':

            hdu_b.header['CMIRRPOS'] = (
                bool(False), 'position of calibration switch mirror')
        else:

            hdu_b.header['CMIRRPOS'] = (
                bool(True), 'position of calibration switch mirror')

        if self.source == 'FLAT':
            hdu_b.header['FLAMP'] = (np.int16(1),     'status of flat lamp')
        else:
            hdu_b.header['FLAMP'] = (np.int16(0),     'status of flat lamp')

        if self.source == 'LAMP':
            hdu_b.header['ALAMP'] = (
                np.int16(1), 'status of flat atomic emission line lamp')
        else:
            hdu_b.header['ALAMP'] = (
                np.int16(0), 'status of flat atomic emission line lamp')

        #############
        hdu_b.header['IFSMODE'] = (np.int32(0),     'IFS working mode')
        hdu_b.header['IFSTEMP'] = (np.float32(
            0), 'IFS components temperature in degC')
        hdu_b.header['IFSSTAT'] = (
            np.int32(0),     'IFS components status parameter')

        ######################################################################

        ####################   detector information##########################

        hdu_b.header['CAMERA'] = ('Blue', 'camera of IFS')
        hdu_b.header['DETSN'] = ('CCD231-c4-00', 'detector serial number')
        hdu_b.header['DETNAME'] = ('blue', 'detector name')
        hdu_b.header['DETSIZE'] = ('2048*4096', 'detector size')
        hdu_b.header['DATASECT'] = ('data-section', 'data section')
        hdu_b.header['PIXSCAL1'] = (np.float32(1344), 'pixel scale for axis 1')
        hdu_b.header['PIXSCAL2'] = (np.float32(9672), 'pixel scale for axis 2')
        hdu_b.header['PIXSIZE1'] = (np.float32(
            15), 'pixel size for axis 1 (micron)')
        hdu_b.header['PIXSIZE2'] = (np.float32(
            15), 'pixel size for axis 2 (micron)')

        hdu_b.header['NCHAN'] = (np.int16(4), 'number of readout channels')
        hdu_b.header['PSCAN1'] = (
            np.int32(50),  'horizontal prescan width, per readout channel')
        hdu_b.header['PSCAN2'] = (
            np.int32(0),   'vertical prescan height, per readout channel')
        hdu_b.header['OSCAN1'] = (
            np.int32(320), 'horizontal overscan width, per readout channel')
        hdu_b.header['OSCAN2'] = (
            np.int32(320), 'vertical overscan height, per readout channel')

        #######################################################################
        # Readout information, revised on 2024.2.27

        frame_time_b = 0.09216  # data frame transfer time in Bule camera
        exptimes = self.information['exposuretimes']
        for k in range(exptimes):

            tt = (self.information['exptime']+frame_time_b)*(k)
            temp = self.dt+timedelta(seconds=tt)
            read_start = temp.strftime("%Y-%m-%dT%H:%M:%S.%f")

            temp2 = temp+timedelta(seconds=self.information['exptime'])
            read_end = temp2.strftime("%Y-%m-%dT%H:%M:%S.%f")

            str1 = 'EXPT0_'+str(k+1)
            str2 = 'EXPT1_'+str(k+1)

            if k == 0:
                com1 = 'exposure start time of the 1st frame (UTC)'
                com2 = 'exposure end time of the 1st frame (UTC)'
            if k == 1:
                com1 = 'exposure start time of the 2nd frame (UTC)'
                com2 = 'exposure end time of the 2nd frame (UTC)'
            if k == 2:
                com1 = 'exposure start time of the 3rd frame (UTC)'
                com2 = 'exposure end time of the 3rd frame (UTC)'
            if k > 2:
                com1 = 'exposure start time of the '+str(k+1)+'th frame'
                com2 = 'exposure end time of the '+str(k+1)+'th frame'

            hdu_b.header[str1] = (read_start[:21], com1)
            hdu_b.header[str2] = (read_end[:21],  com2)

        hdu_b.header['DETTEMP0'] = (np.float32(
            0.0), 'detector temperature at EXPT0_1 (K)')
        hdu_b.header['DETTEMP1'] = (np.float32(
            0.0), 'detector temperature at EXPT1_'+str(k+1)+' (K)')

        #######################end revised on 2024.2.27 ###

        hdu_b.header['BIN_X'] = (
            np.int16(1),    'bin number in X (wavelength)')
        hdu_b.header['BIN_Y'] = (np.int16(1),    'bin number in Y (spatial)')

        hdu_b.header['GAIN1'] = (np.float32(
            self.information['gain1_b']), 'CCD gain (e-/ADU, channel 1)')
        hdu_b.header['GAIN2'] = (np.float32(
            self.information['gain2_b']), 'CCD gain (e-/ADU, channel 2)')
        hdu_b.header['GAIN3'] = (np.float32(
            self.information['gain3_b']), 'CCD gain (e-/ADU, channel 3)')
        hdu_b.header['GAIN4'] = (np.float32(
            self.information['gain4_b']), 'CCD gain (e-/ADU, channel 4)')

        hdu_b.header['DARK1'] = (np.float32(
            self.information['dark1_b']), 'CCD dark (e-/s, channel 1)')
        hdu_b.header['DARK2'] = (np.float32(
            self.information['dark2_b']), 'CCD dark (e-/s, channel 2)')
        hdu_b.header['DARK3'] = (np.float32(
            self.information['dark3_b']), 'CCD dark (e-/s, channel 3)')
        hdu_b.header['DARK4'] = (np.float32(
            self.information['dark4_b']), 'CCD dark (e-/s, channel 4)')

        hdu_b.header['RON1'] = (np.float32(
            self.information['rn1_b']), 'read noise (e-, channel 1)')
        hdu_b.header['RON2'] = (np.float32(
            self.information['rn2_b']), 'read noise (e-, channel 2)')
        hdu_b.header['RON3'] = (np.float32(
            self.information['rn3_b']), 'read noise (e-, channel 3)')
        hdu_b.header['RON4'] = (np.float32(
            self.information['rn4_b']), 'read noise (e-, channel 4)')

        hdu_b.header['DETBIA1'] = (np.float32(
            self.information['bias1_b']), 'amplifier bias voltage (ADU, channel1)')
        hdu_b.header['DETBIA2'] = (np.float32(
            self.information['bias2_b']), 'amplifier bias voltage (ADU, channel2)')
        hdu_b.header['DETBIA3'] = (np.float32(
            self.information['bias3_b']), 'amplifier bias voltage (ADU, channel3)')
        hdu_b.header['DETBIA4'] = (np.float32(
            self.information['bias4_b']), 'amplifier bias voltage (ADU, channel4)')

        hdu_b.header['ROSPEED'] = (np.float32(100), 'readout speed (MHz)')

        hdu_b.header['TOML_TAG'] = ('csst-ifs-l0-c09', 'the tag of toml file')

        tt = write_end.utcnow()
        date_time = tt.strftime("%Y-%m-%dT%H:%M:%S")

        hdu_b.header['CHECKSUM'] = (date_time, 'HDU checksum')

        hdu_b.header['DATASUM'] = (data_time, 'data unit checksum')

        if self.source == 'LAMP' and self.information['holemask'] == 'yes':
            hdu_b.header['Hole'] = ('yes', 'apply hole to LAMP')

        #####################################################################
        ####################    red camera  ######################

        # create a new FITS file, using HDUList instance
        ofd_r = fits.PrimaryHDU()

        ofd_r.header['SIMPLE'] = (True,   'conforms to FITS standard')
        ofd_r.header['BITPIX'] = (0,   'array data type')
        ofd_r.header['NAXIS'] = (0,   'number of array dimensions')
        ofd_r.header['NEXTEND'] = (np.int16(1),   'number of array dimensions')

        temp = write_end.utcnow()
        data_time = temp.strftime("%Y-%m-%dT%H:%M:%S.%f")

        ofd_r.header['DATE'] = (
            data_time[:21], 'written date (yyyy-mm-ddThh:mm:ss.s)')
        ofd_r.header['FILENAME'] = (filename_r[:68], '')

        ofd_r.header['FILETYPE'] = (self.source[:12],   'observation type')

        ofd_r.header['TELESCOP'] = ('CSST',   'telescope name')

        ofd_r.header['INSTRUME'] = ('IFS',   'instrument name')

        ofd_r.header['RADECSYS'] = (
            'ICRS',   'coordinate system of the object')

        ofd_r.header['EQUINOX'] = (float(2000.0),   '')
        ofd_r.header['FITSSWV'] = (
            'ifs_sim_0.8.03',   'FITS creating software version')

        #########   Object information  #############
        if self.source == 'SCI' or self.source == 'COMP':
            ofd_r.header['OBJECT'] = (
                self.information['name_obj'][:30],   'object name')
            ofd_r.header['TARGET'] = (
                (self.information['target']), 'target name (hhmmss.s+ddmmss)')

            ofd_r.header['OBSID'] = (str(obsid),   'observation ID')

            ofd_r.header['RA_OBJ'] = (np.float64(
                self.information['ra_obj']),   'object RA (deg)')
            ofd_r.header['DEC_OBJ'] = (np.float64(
                self.information['dec_obj']),   'object Dec (deg)')

        else:
            ofd_r.header['OBJECT'] = (self.source,   ' object name')
            ofd_r.header['TARGET'] = (0,   'target name (hhmmss.s+ddmmss)')

            ofd_r.header['OBSID'] = (str(obsid),   'observation ID')

            ofd_r.header['RA_OBJ'] = (np.float64(0),   'object RA (deg)')
            ofd_r.header['DEC_OBJ'] = (np.float64(0),   'object Dec (deg)')

            # ,   'the orbital position of CSST in X direction at EXPSTART')
            self.information['POSI0_X'] = 0.0
            # ,   'the orbital position of CSST in Y direction at EXPSTART')
            self.information['POSI0_Y'] = 0.0
            # ,   'the orbital position of CSST in Z direction at EXPSTART')
            self.information['POSI0_Z'] = 0.0
            # ,   'the orbital velocity of CSST in X direction at EXPSTART')
            self.information['VELO0_X'] = 0.0
            # ,   'the orbital velocity of CSST in Y direction at EXPSTART')
            self.information['VELO0_Y'] = 0.0
            # ,   'the orbital velocity of CSST in Z direction at EXPSTART')
            self.information['VELO0_Z'] = 0.0

            # ,   'the orbital position of CSST in X direction at EXPSTART')
            self.information['POSI1_X'] = 0.0
            # ,   'the orbital position of CSST in Y direction at EXPSTART')
            self.information['POSI1_Y'] = 0.0
            # ,   'the orbital position of CSST in Z direction at EXPSTART')
            self.information['POSI1_Z'] = 0.0
            # ,   'the orbital velocity of CSST in X direction at EXPSTART')
            self.information['VELO1_X'] = 0.0
            # ,   'the orbital velocity of CSST in Y direction at EXPSTART')
            self.information['VELO1_Y'] = 0.0
            # ,   'the orbital velocity of CSST in Z direction at EXPSTART')
            self.information['VELO1_Z'] = 0.0

        #######################################################################

        ########  Telescope information ###############

        ofd_r.header['REFFRAME'] = ('CSSTGSC-1.0',   'guiding catalog version')
        ofd_r.header['DATE-OBS'] = (data_time[:21],
                                    'observation date (yyyy-mm-ddThh:mm:ss.s) ')

        ofd_r.header['SATESWV'] = (
            'softwave-1.0',   'satellite software version')

        ofd_r.header['EXPSTART'] = (np.float64(
            time2mjd(exp_start_utc)),   'exposure start time (MJD)')

        ofd_r.header['CABSTART'] = (np.float64(
            time2jd(exp_start_utc)),   'first cabin time after exposure start (MJD)')

        ofd_r.header['SUNANGL0'] = (np.float32(
            0.0),   'angle between the Sun and opt axis at CABSTART')
        ofd_r.header['MOONANG0'] = (np.float32(
            0.0),   'angle between the Moon and opt axis at CABSTART')

        ofd_r.header['TEL_ALT0'] = (np.float64(
            0.0),   'angle between opt axis and Elimb at CABSTART')
        ofd_r.header['POS_ANG0'] = (np.float64(
            0.0),   'angle between y axis and North Pole at CABSTART')

        ofd_r.header['POSI0_X'] = (np.float64(
            self.information['POSI0_X']),   'orbital position in X at CABSTART (km)')
        ofd_r.header['POSI0_Y'] = (np.float64(
            self.information['POSI0_Y']),   'orbital position in Y at CABSTART (km)')
        ofd_r.header['POSI0_Z'] = (np.float64(
            self.information['POSI0_Z']),   'orbital position in Z at CABSTART (km)')
        ofd_r.header['VELO0_X'] = (np.float64(
            self.information['VELO0_X']),   'orbital velocity in X at CABSTART (km/s)')
        ofd_r.header['VELO0_Y'] = (np.float64(
            self.information['VELO0_Y']),   'orbital velocity in Y at CABSTART (km/s)')
        ofd_r.header['VELO0_Z'] = (np.float64(
            self.information['VELO0_Z']),   'orbital velocity in Z at CABSTART (km/s)')

        ofd_r.header['Euler0_1'] = (np.float64(
            0.0),   'Euler angle 1 at CABSTART (deg)')
        ofd_r.header['Euler0_2'] = (np.float64(
            0.0),   'Euler angle 2 at CABSTART (deg)')
        ofd_r.header['Euler0_3'] = (np.float64(
            0.0),   'Euler angle 3 at CABSTART (deg)')

        #####
        if self.source == 'SCI' or self.source == 'COMP':

            ofd_r.header['RA_PNT0'] = (np.float64(
                self.information['ra_pnt0']),     'pointing RA at CABSTART (deg)')
            ofd_r.header['DEC_PNT0'] = (np.float64(
                self.information['dec_pnt0']),    'pointing Dec at CABSTART (deg)')

        else:

            ofd_r.header['RA_PNT0'] = (np.float64(
                0.0),     'pointing RA at CABSTART (deg)')
            ofd_r.header['DEC_PNT0'] = (np.float64(
                0.0),     'pointing Dec at CABSTART (deg)')

        ofd_r.header['EXPEND'] = (np.float64(
            time2mjd(exp_end_utc)),       'exposure end time (MJD)')

        ofd_r.header['CABEND'] = (np.float64(
            time2jd(exp_end_utc)),       'first cabin time after exposure end (MJD)')

        ofd_r.header['SUNANGL1'] = (np.float32(
            0.0),   'angle between the Sun and opt axis at CABEND')
        ofd_r.header['MOONANG1'] = (np.float32(
            0.0),   'angle between the Moon and opt axis at CABEND')

        ofd_r.header['TEL_ALT1'] = (np.float64(
            0.0),   'angle between opt axis and Elimb at CABEND')
        ofd_r.header['POS_ANG1'] = (np.float64(
            self.information['sky_rot']),   'angle between y axis and North Pole at CABEND')

        ofd_r.header['POSI1_X'] = (np.float64(
            self.information['POSI1_X']),   'orbital position in X at CABEND (km)')
        ofd_r.header['POSI1_Y'] = (np.float64(
            self.information['POSI1_Y']),   'orbital position in Y at CABEND (km)')
        ofd_r.header['POSI1_Z'] = (np.float64(
            self.information['POSI1_Z']),   'orbital position in Z at CABEND (km)')

        ofd_r.header['VELO1_X'] = (np.float64(
            self.information['VELO1_X']),   'orbital velocity in X at CABEND (km/s)')
        ofd_r.header['VELO1_Y'] = (np.float64(
            self.information['VELO1_Y']),   'orbital velocity in Y at CABEND (km/s)')
        ofd_r.header['VELO1_Z'] = (np.float64(
            self.information['VELO1_Z']),   'orbital velocity in Z at CABEND (km/s)')

        ofd_r.header['Euler1_1'] = (np.float64(
            0.0),   'Euler angle 1 at CABEND (deg)')
        ofd_r.header['Euler1_2'] = (np.float64(
            0.0),   'Euler angle 2 at CABEND (deg)')
        ofd_r.header['Euler1_3'] = (np.float64(
            0.0),   'Euler angle 3 at CABEND (deg)')

        ofd_r.header['RA_PNT1'] = (np.float64(
            ofd_r.header['RA_PNT0']),    'pointing RA at CABEND (deg)')
        ofd_r.header['DEC_PNT1'] = (np.float64(
            ofd_r.header['DEC_PNT0']),   'pointing Dec at CABEND (deg)')

        ofd_r.header['EPOCH'] = (np.float32(
            time2jd(end.utcnow())),   'equinox of pointing RA and Dec')
        ofd_r.header['EXPTIME'] = (np.float32(
            self.information['exptime']*self.information['exposuretimes']),   'exposure time (s)')

        ss1 = 'HDU checksum'
        ss2 = 'data unit checksum'
        ofd_r.header['CHECKSUM'] = (data_time[:19], ss1)
        ofd_r.header['DATASUM'] = (data_time[:19], ss2)

        ##########
        ########## finish header for 0 layer   ######################
        #############################################################

        # header
        #    new image HDU,    red  channel,   layer 1
        if HeaderTest == 'yes':
            hdu_r = fits.ImageHDU(data=np.zeros((10, 10, 3)))
        else:
            hdu_r = fits.ImageHDU(data=np.uint16(self.image_r_N))
        #
        hdu_r.header['XTENSION'] = ('IMAGE', 'extension type')
        hdu_r.header['BITPIX'] = (np.int16(16), 'bits per data value')
        hdu_r.header['NAXIS'] = (np.int16(2), 'number of data axies')

        hdu_r.header['NAXIS1'] = (np.int32(1856), 'length of first data axis')

        hdu_r.header['NAXIS2'] = (
            np.int32(13768), 'length of second data axis')

        if self.information['exposuretimes'] > 1:
            hdu_r.header['NAXIS3'] = (
                np.int32(self.information['exposuretimes']), 'length of third data axis')

        if self.source == 'SCI' or self.source == 'COMP':
            hdu_r.header['EXTNAME'] = ('SCI', '')

        else:

            hdu_r.header['EXTNAME'] = ('CAL', '')

        hdu_r.header['EXTVER'] = (np.int16(1), '')
        hdu_r.header['BSCALE'] = (np.float64(1), '')
        hdu_r.header['BZERO'] = (np.float64(32768), '')
        hdu_r.header['BUNIT'] = ('ADU', 'physical unit of array values')

        #########  instrument information  ######

        if self.source == 'SCI' or self.source == 'COMP':

            hdu_r.header['CMIRRPOS'] = (
                bool(False), 'position of calibration switch mirror')
        else:

            hdu_r.header['CMIRRPOS'] = (
                bool(True), 'position of calibration switch mirror')

        if self.source == 'FLAT':
            hdu_r.header['FLAMP'] = (np.int16(1),     'status of flat lamp')
        else:
            hdu_r.header['FLAMP'] = (np.int16(0),     'status of flat lamp')

        if self.source == 'LAMP':
            hdu_r.header['ALAMP'] = (
                np.int16(1), 'status of flat atomic emission line lamp')
        else:
            hdu_r.header['ALAMP'] = (
                np.int16(0), 'status of flat atomic emission line lamp')

        #############
        hdu_r.header['IFSMODE'] = (np.int32(0),     'IFS working mode')
        hdu_r.header['IFSTEMP'] = (np.float32(
            0), 'IFS components temperature in degC')
        hdu_r.header['IFSSTAT'] = (
            np.int32(0),     'IFS components status parameter')

        ###################   detector information############################

        hdu_r.header['CAMERA'] = ('Red', 'camera of IFS')
        hdu_r.header['DETSN'] = ('CCD231-c6-00', 'detector serial number')
        hdu_r.header['DETNAME'] = ('red', 'detector name')

        hdu_r.header['DETSIZE'] = ('3072*6144', 'detector size')
        hdu_r.header['DATASECT'] = ('data-section', 'data section')
        hdu_r.header['PIXSCAL1'] = (np.float32(1856), 'pixel scale for axis 1')
        hdu_r.header['PIXSCAL2'] = (
            np.float32(13768), 'pixel scale for axis 2')
        hdu_r.header['PIXSIZE1'] = (np.float32(
            15), 'pixel size for axis 1 (micron)')
        hdu_r.header['PIXSIZE2'] = (np.float32(
            15), 'pixel size for axis 2 (micron)')

        hdu_r.header['NCHAN'] = (np.int16(4), 'number of readout channels')

        hdu_r.header['PSCAN1'] = (
            np.int32(50),  'horizontal prescan width, per readout channel')
        hdu_r.header['PSCAN2'] = (
            np.int32(0),   'vertical prescan height, per readout channel')
        hdu_r.header['OSCAN1'] = (
            np.int32(320), 'horizontal overscan width, per readout channel')
        hdu_r.header['OSCAN2'] = (
            np.int32(320), 'vertical overscan height, per readout channel')

##############################################################################
        # Readout information
        frame_time_r = 0.13824  # data frame transfer time in red camera

        for k in range(exptimes):
            tt = (self.information['exptime']+frame_time_r)*(k)

            temp = self.dt+timedelta(seconds=tt)
            read_start = temp.strftime("%Y-%m-%dT%H:%M:%S.%f")

            temp2 = temp+timedelta(seconds=self.information['exptime'])
            read_end = temp2.strftime("%Y-%m-%dT%H:%M:%S.%f")

            str1 = 'EXPT0_'+str(k+1)
            str2 = 'EXPT1_'+str(k+1)

            if k == 0:
                com1 = 'exposure start time of the 1st frame (UTC)'
                com2 = 'exposure end time of the 1st frame (UTC)'
            if k == 1:
                com1 = 'exposure start time of the 2nd frame (UTC)'
                com2 = 'exposure end time of the 2nd frame (UTC)'
            if k == 2:
                com1 = 'exposure start time of the 3rd frame (UTC)'
                com2 = 'exposure end time of the 3rd frame (UTC)'
            if k > 2:
                com1 = 'exposure start time of the '+str(k+1)+'th frame'
                com2 = 'exposure end time of the '+str(k+1)+'th frame'

            hdu_r.header[str1] = (read_start[:21], com1)
            hdu_r.header[str2] = (read_end[:21], com2)
        ####################################################################################
        hdu_r.header['DETTEMP0'] = (np.float32(
            0.0), 'detector temperature at EXPT0_1 (K)')
        hdu_r.header['DETTEMP1'] = (np.float32(
            0.0), 'detector temperature at EXPT1_'+str(k+1)+' (K)')

        hdu_r.header['BIN_X'] = (
            np.int16(1),    'bin number in X (wavelength)')
        hdu_r.header['BIN_Y'] = (np.int16(1),    'bin number in Y (spatial)')

        hdu_r.header['GAIN1'] = (np.float32(
            self.information['gain1_r']), 'CCD gain (e-/ADU, channel 1)')
        hdu_r.header['GAIN2'] = (np.float32(
            self.information['gain2_r']), 'CCD gain (e-/ADU, channel 2)')
        hdu_r.header['GAIN3'] = (np.float32(
            self.information['gain3_r']), 'CCD gain (e-/ADU, channel 3)')
        hdu_r.header['GAIN4'] = (np.float32(
            self.information['gain4_r']), 'CCD gain (e-/ADU, channel 4)')

        hdu_r.header['DARK1'] = (np.float32(
            self.information['dark1_r']), 'CCD dark (e-/s, channel 1)')
        hdu_r.header['DARK2'] = (np.float32(
            self.information['dark2_r']), 'CCD dark (e-/s, channel 2)')
        hdu_r.header['DARK3'] = (np.float32(
            self.information['dark3_r']), 'CCD dark (e-/s, channel 3)')
        hdu_r.header['DARK4'] = (np.float32(
            self.information['dark4_r']), 'CCD dark (e-/s, channel 4)')

        hdu_r.header['RON1'] = (np.float32(
            self.information['rn1_r']), 'read noise (e-, channel 1)')
        hdu_r.header['RON2'] = (np.float32(
            self.information['rn2_r']), 'read noise (e-, channel 2)')
        hdu_r.header['RON3'] = (np.float32(
            self.information['rn3_r']), 'read noise (e-, channel 3)')
        hdu_r.header['RON4'] = (np.float32(
            self.information['rn4_r']), 'read noise (e-, channel 4)')

        hdu_r.header['DETBIA1'] = (np.float32(
            self.information['bias1_r']), 'amplifier bias voltage (ADU, channel1)')
        hdu_r.header['DETBIA2'] = (np.float32(
            self.information['bias2_r']), 'amplifier bias voltage (ADU, channel2)')
        hdu_r.header['DETBIA3'] = (np.float32(
            self.information['bias3_r']), 'amplifier bias voltage (ADU, channel3)')
        hdu_r.header['DETBIA4'] = (np.float32(
            self.information['bias4_r']), 'amplifier bias voltage (ADU, channel4)')

        hdu_r.header['ROSPEED'] = (np.float32(100), 'readout speed (MHz)')
        hdu_r.header['TOML_TAG'] = ('csst-ifs-l0-c09', 'the tag of toml file')

        date_timer = write_end.strftime("%Y-%m-%dT%H:%M:%S")

        hdu_r.header['CHECKSUM'] = (date_timer, 'HDU checksum')

        hdu_r.header['DATASUM'] = (date_timer, 'data unit checksum')

        if self.source == 'LAMP' and self.information['holemask'] == 'yes':
            hdu_r.header['Hole'] = ('yes', 'apply hole to LAMP')

       #########################

        hdu1 = fits.PrimaryHDU(header=ofd_b.header)
        hdu2 = fits.PrimaryHDU(header=ofd_r.header)
        #####################################################################
        hdu1.header.add_comment(
            '========================================================================', after='FITSSWV')
        hdu1.header.add_comment('OBJECT INFORMATION', after='FITSSWV')
        hdu1.header.add_comment(
            '========================================================================', after='FITSSWV')

        hdu1.header.add_comment(
            '========================================================================', after='DEC_OBJ')
        hdu1.header.add_comment('TELESCOPE INFORMATION', after='DEC_OBJ')
        hdu1.header.add_comment(
            '========================================================================', after='DEC_OBJ')

        hdu1.header.add_comment(
            '========================================================================', after='EPOCH')
        hdu1.header.add_comment('VERIFICATION INFORMATION', after='EPOCH')
        hdu1.header.add_comment(
            '========================================================================', after='EPOCH')

        ######################################################################
        hdu2.header.add_comment(
            '========================================================================', after='FITSSWV')

        hdu2.header.add_comment(
            '========================================================================', after='DEC_OBJ')
        hdu2.header.add_comment('TELESCOPE INFORMATION', after='DEC_OBJ')
        hdu2.header.add_comment(
            '========================================================================', after='DEC_OBJ')

        hdu2.header.add_comment(
            '========================================================================', after='EPOCH')
        hdu2.header.add_comment('VERIFICATION INFORMATION', after='EPOCH')
        hdu2.header.add_comment(
            '========================================================================', after='EPOCH')

        hdulist_b = fits.HDUList([hdu1, hdu_b])

        hdu_b.header.add_comment(
            '========================================================================', before='CMIRRPOS')
        hdu_b.header.add_comment('INSTRUMENT INFORMATION', before='CMIRRPOS')
        hdu_b.header.add_comment(
            '========================================================================', before='CMIRRPOS')

        hdu_b.header.add_comment(
            '========================================================================', before='CAMERA')
        hdu_b.header.add_comment('DETECTOR INFORMATION', before='CAMERA')
        hdu_b.header.add_comment(
            '========================================================================', before='CAMERA')

        hdu_b.header.add_comment(
            '========================================================================', before='EXPT0_1')
        hdu_b.header.add_comment('READOUT INFORMATION', before='EXPT0_1')
        hdu_b.header.add_comment(
            '========================================================================', before='EXPT0_1')

        hdulist_b.writeto(self.file_b, output_verify='ignore', checksum=True)
     #########################################################################
        hdulist_r = fits.HDUList([hdu2, hdu_r])

        hdu_r.header.add_comment(
            '========================================================================', before='CMIRRPOS')
        hdu_r.header.add_comment('INSTRUMENT INFORMATION', before='CMIRRPOS')
        hdu_r.header.add_comment(
            '========================================================================', before='CMIRRPOS')

        hdu_r.header.add_comment(
            '========================================================================', before='CAMERA')
        hdu_r.header.add_comment('DETECTOR INFORMATION', before='CAMERA')
        hdu_r.header.add_comment(
            '========================================================================', before='CAMERA')

        hdu_r.header.add_comment(
            '========================================================================', before='EXPT0_1')
        hdu_r.header.add_comment('READOUT INFORMATION', before='EXPT0_1')
        hdu_r.header.add_comment(
            '========================================================================', before='EXPT0_1')

        hdulist_r.writeto(self.file_r, output_verify='ignore', checksum=True)

        ######################################################################

    def earthshine(self, theta):
        """
            For given theta angle, return the earth-shine spectrum.

        :param theta: angle (in degree) from the target to earth limb.
        :return: the scaled solar spectrum
            template_wave: unit in A
            template_flux: unit in erg/s/cm^2/A/arcsec^2

        """

        # read solar template
        solar_template = pd.read_csv(self.information['dir_path']+'IFS_inputdata/refs/solar_spec.dat', sep='\s+',
                                     header=None, comment='#')
        template_wave = solar_template[0].values
        template_flux = solar_template[1].values

        # read earth shine surface brightness
        earthshine_curve = pd.read_csv(self.information['dir_path']+'IFS_inputdata/refs/earthshine.dat',
                                       header=None, comment='#')
        angle = earthshine_curve[0].values
        surface_brightness = earthshine_curve[1].values

        # read V-band throughtput
        cat_filter_V = pd.read_csv(self.information['dir_path']+'IFS_inputdata/refs/filter_Bessell_V.dat', sep='\s+',
                                   header=None, comment='#')
        filter_wave = cat_filter_V[0].values
        filter_response = cat_filter_V[1].values

        # interplate to the target wavelength in V-band
        ind_filter = (template_wave >= np.min(filter_wave)) & (
            template_wave <= np.max(filter_wave))
        filter_wave_interp = template_wave[ind_filter]
        filter_response_interp = np.interp(
            filter_wave_interp, filter_wave, filter_response)

        filter_constant = simps(
            filter_response_interp * filter_wave_interp, filter_wave_interp)
        template_constant = simps(filter_response_interp * template_wave[ind_filter] * template_flux[ind_filter],
                                  template_wave[ind_filter])
        dwave = filter_wave_interp[1:] - filter_wave_interp[:-1]
        wave_eff = np.nansum(dwave * filter_wave_interp[1:] * filter_response_interp[1:]) / \
            np.nansum(dwave * filter_response_interp[1:])

        # get the normalized value at theta.
        u0 = np.interp(theta, angle, surface_brightness)    # mag/arcsec^2
        # target flux in erg/s/cm^2/Hz unit
        u0 = 10**((u0 + 48.6)/(-2.5))
        u0 = u0 * 3e18 / wave_eff**2          # erg/s/cm^2/A/arcsec^2

        factor = u0 * filter_constant / template_constant
        norm_flux = template_flux * factor          # erg/s/cm^2/A/arcsec^2

        self.earthshine_wave = template_wave     # A
        self.earthshine_flux = norm_flux

        return


########################################################################################################################################################################################################################################################

    ###################################################################################
    ##################################################################################

    def CalskyNoise(self, lam):
        """


        Parameters
        ----------
        lam : TYPE
            DESCRIPTION.

        Returns
        -------
        Ns_skynoise : TYPE
            DESCRIPTION.

        """

      # calculate the reference flux
      # calculate sky noise;
        planckh = 6.62620*10**-27  # % erg s;
        cc = 2.99792458*10**17  # in nm/s
        fov2 = 0.01                       # 单个像素的面积 arcsec^2
        # telarea=3.1415926*100*100       # in cm^2，望远镜聚光面积；
        # lam is input wavelength  in nm

        ##########################################
        # self.earthshine_wave        # A
        # self.earthshine_flux        # erg/s/cm^2/A/arcsec^2
        earthshine_flux = np.interp(
            lam*10.0, self.earthshine_wave, self.earthshine_flux)  # flux from zodiacal

        # self.zodiacal_wave             # in A
        # self.zodiacal_flux             # erg/s/cm^2/A/arcsec^2
        zodiacal_flux = np.interp(
            lam*10.0, self.zodiacal_wave, self.zodiacal_flux)  # flux from zodiacal
        fluxlam_sky = (earthshine_flux+zodiacal_flux) * \
            fov2               # erg/s/cm2/A
        ###############

        ephoton = planckh*cc/lam                     # in erg/photon,  cc与lambda单位需要一致；
        Ns_skynoise = fluxlam_sky/ephoton  # in unit of  photons/cm2/s/A

        return Ns_skynoise

        #################################################################################
    ##############

    def sim_lamp_hole_img(self, exposuretime):
        """


        Parameters
        ----------
        exposuretime : TYPE
            DESCRIPTION.

        Returns
        -------
        None.

        """

        sizeout = 1000  # here image has the pixelscale =0.01 arcsec
        ################# create the slicer and slit file for high resolution 0.01arcsec simulation ###################

        maskSlice = dict()
        maskSlit = dict()

        for k in range(32):
            maskSlice[str(k)] = np.zeros((sizeout, sizeout))
            maskSlice[str(k)][20*k+180:20*k+200, int(sizeout/2) -
                              320:int(sizeout/2)+320] = 1

            maskSlit[str(k)] = np.zeros((sizeout, sizeout))
            maskSlit[str(k)][20*k+180:20*k+200, int(sizeout/2) -
                             370:int(sizeout/2)+370] = 1

        self.maskSliceHole = maskSlice
        self.maskSlitHole = maskSlit
        #########################################################################
        ##### load slicer_Qe1K.fits  ####
        slicerfile = self.information['dir_path'] + \
            'IFS_inputdata/Flatfield/slicer_QE_1K_230625.fits'
        da = fits.open(slicerfile)
        self.log.info('hole Sim and slicer 1K QE file:%s' % (slicerfile))
        slicer_Qe = da[0].data

        ##### load hole mask ######
        # load hole mask matrix
        file = self.information['dir_path'] + \
            'IFS_inputdata/Hole/holemask_230612.fits'
        self.log.info('hole mask file is %s' % (file))
        da = fits.open(file)
        HoleMask = da[self.simnumber].data

        # load HgAr data;

        matfn3 = self.information['dir_path']+'IFS_inputdata/HgAr.mat'
        self.log.info('lamp flux file is : %s' % (matfn3))
        da3 = sio.loadmat(matfn3)
        HgArst = da3['HgArst']  # opd unit is in meter
        # load flat data

        exptime = self.information['exptime']  # exposure time

        blue_img = galsim.Image(np.zeros((2048, 4096)), copy=True)
        blue_img.scale = self.pixelscale
        blue_img.setOrigin = (0, 0)

        red_img = galsim.Image(np.zeros((3072, 6144)), copy=True)
        red_img.scale = self.pixelscale
        red_img.setOrigin = (0, 0)

        slice_image = np.zeros((sizeout, sizeout))

        width_blue = 0

        LightSt = np.zeros(6000)
        flux_1 = np.zeros(6000)
        Width_lambda = (1000-350)/6000
        Wave = np.zeros(6000)

        if self.debug:
            alfa = 1000
        else:
            alfa = 1

        #########################################

        for klam in range(6000):

            ilam = klam*alfa

            if self.debug:
                print(ilam)

            if ilam % 100 == 0:
                self.log.info('ilam = %i' % ilam)
                self.log.info('blue image max value = %i' %
                              blue_img.array.max())
                self.log.info('red image max value = %i' % red_img.array.max())

            if ilam >= 6000:
                break

            # the wavelength of the i-th frame data
            lam = 350.0+(1000-350)/6000.0 * ilam

            Wave[ilam] = lam

            #print('i = ',ilam )
            ###############################################

            sumt = 0.0

            for k in range(11):
                t = getSpectrum(HgArst, lam+(k-5) *
                                Width_lambda/11, self.HgArsigma)
                sumt = sumt+t[0]/11
                #print('t=', t)

            # the normalized HgAr light source in the range of 0 and 1.0
            LightSt[ilam] = sumt

            skymag = 6   # mag/arcsec^2 unit

            if ilam == 0:
                # fluxnu1,fluxlam1,fluxphoton1=mag2flux(skymag,633)
                fluxlam1, fluxphoton1 = mag2flux(skymag, 633)

              # save flux information
            flux_1[ilam] = LightSt[ilam]*fluxlam1*self.fov2     # erg/s/cm2/A

            nphoton = flux2photons(flux_1[ilam], lam)

            ns = nphoton*np.ones((sizeout, sizeout))  # photons/cm2/s/A/arcsec2

            slice_image = ns*self.telarea*Width_lambda*10*exptime

            #############################################################

            if slice_image.max() < 0.1:
                continue

            if slice_image.min() < 0:
                self.log.error('Error in datacube, negative values !!!!!!!')
                continue

            ########################
            ##############
            # here Q is 10 times
            Q = 10*lam*1e-3*self.Fnum/self.pixelsize

            # for calibration , there is no primay CSST system, but diffraction effect should consider
            # opd0 rms = 0.0 @632.8nm
            wavefront0 = self.opd0/lam/100000.0

            psf0 = anySampledPSFnew(wavefront0, self.pupil, Q, 256)

            # do convolve with psf0
            conv = fftconvolve(slice_image, psf0, mode='same')

            conv[conv < 0.0] = 0.0

            slice_image2 = conv

            # 3

            # applyHoleMask only for lamp

            img0 = slice_image2*HoleMask

            # opd1 rms =0.075 @632.8nm
            wavefront = self.opd1/lam

            psf1 = anySampledPSFnew(wavefront, self.pupil, Q, 256)

            # do convolve with psf1
            conv = fftconvolve(img0, psf1, mode='same')
            conv[conv < 0.0] = 0.0
            img0 = conv

            #######################################################

        
            # CCD quantum efficiency
            CCD_Qe_lam = np.interp(lam, self.CCD_Qe[:, 0], self.CCD_Qe[:, 1])

            # optical efficiency , convert the wavelength to A
            Qe_blue = np.interp(
                lam, self.optical_blue_Q[:, 0], self.optical_blue_Q[:, 1])

            # optical efficiency,  convert the wavelength to A
            Qe_red = np.interp(
                lam, self.optical_red_Q[:, 0],  self.optical_red_Q[:, 1])

            Qe_blue = Qe_blue*CCD_Qe_lam
            Qe_red = Qe_red * CCD_Qe_lam

            # consider the slice optical efficiency in different slicer channel

            img0 = img0*slicer_Qe
            ##########   do the slice effect  ###################
            for k in range(32):

                # do slice effect to get slice image
                img1 = img0*self.maskSliceHole[str(k)]

                ############ get opd2  and PSF2  ######################
                wavefront2 = self.opd2/lam
                psf2 = anySampledPSFnew(wavefront2, self.pupil, Q, 256)
                # do convolve
                ##################
                conv = fftconvolve(img1, psf2, mode='same')
                # suppress negative numbers
                conv[conv < 0.0] = 0.0
                img2 = conv

                ##############do Slit Mask ###########################

                img2 = img2*self.maskSlitHole[str(k)]

                #########  get opd3 and PSF3 ##########################

                wavefront3 = self.opd3/lam
                psf3 = anySampledPSFnew(wavefront3, self.pupil, Q, 256)

                # do convolve
                ########################################
                conv = fftconvolve(img2, psf3, mode='same')
                # suppress negative numbers
                conv[conv < 0.0] = 0.0

                img3 = conv

                ########################   get subimag   #####################
                subimage = galsim.Image(800, 800)
                subimage.array[:, :] = img3[int(
                    sizeout/2)-400:int(sizeout/2)+400, int(sizeout/2)-400:int(sizeout/2)+400]

                # fits.writeto('subimage.fits',subimage.array,overwrite=True)

                #######   get photons from sub-image     #####################
                subimage.scale = 0.01  # here pixelscale=0.01arcsec

                subimage.setOrigin(0, 0)

                photons = galsim.PhotonArray.makeFromImage(
                    subimage, max_flux=max(img3.max()/10000.0,  1))

                ##############################################################
                # do something for each photons;
                #############################################################
                idx0 = np.where(photons.flux > 1e-3)
                # energy=energy+sum(photons.flux[idx0])     ### totla energy for slice image
                p_num = len(idx0[0])

                ##############################################################

                # find photons for blue channel, and make the flux multiple the optical and CCD efficiency

                if (lam >= 350.0 and lam <= 650.0):
                    # bulue  channel
                    photons_blue = galsim.PhotonArray(
                        N=p_num, x=photons.x[idx0], y=photons.y[idx0], flux=Qe_blue*photons.flux[idx0], wavelength=lam)

                    dx_blue, dy_blue = get_dx_dy_blue(lam)

                    photons_blue.x = photons_blue.x / \
                        (self.pixelscale)+dx_blue+self.slice_blue['px'][k]

                    photons_blue.y = photons_blue.y / \
                        (self.pixelscale)+dy_blue+self.slice_blue['py'][k]

                    photons_blue.addTo(blue_img)


                    # fits.writeto('blueImg.fits',blue_img.array,overwrite=True)

                if (lam >= 560.0) & (lam <= 1000.0):
                    # red channel
                    photons_red = galsim.PhotonArray(
                        N=p_num, x=photons.x[idx0], y=photons.y[idx0], flux=Qe_red*photons.flux[idx0], wavelength=lam)

                    dx_red,  dy_red = get_dx_dy_red(lam)

                    photons_red.x = photons_red.x / \
                        (self.pixelscale)+dx_red+self.slice_red['px'][k]

                    photons_red.y = photons_red.y / \
                        (self.pixelscale)+dy_red+self.slice_red['py'][k]

                    #red_sensor.accumulate(photons_red, red_img)

                    photons_red.addTo(red_img)

                    # energy_red=energy_red+sum(photons_red.flux)

        ####################################################################################

        self.image_b = blue_img.array
        self.image_r = red_img.array

        now = datetime.utcnow()

        data_time = now.strftime("%Y-%m-%d")
        # write the actual file

        bluefile = self.result_path+'/original_Calibration/IFS_'+self.source + \
            '_'+data_time+'_'+str(self.simnumber)+'_B_original.fits'
        redfile = self.result_path+'/original_Calibration/IFS_'+self.source + \
            '_'+data_time+'_'+str(self.simnumber)+'_R_original.fits'

        fits.writeto(bluefile, blue_img.array, overwrite=True)
        fits.writeto(redfile,  red_img.array,  overwrite=True)

        #### save flux   ####
        Flux = flux_1

        hdu1 = fits.PrimaryHDU(Flux)

        dtime = datetime.utcnow().strftime('%Y -%m -%d %H: %M: %S')
        hdu1.header.add_history('Flux data is generated on :'+dtime)
        hdu1.header['FluxUnit'] = ('erg/s/cm2/A', 'erg/s/cm2/A')

        hdu2 = fits.ImageHDU(Wave)

        hdu2.header.append(('WaveUnit', 'nm'))

        newd = fits.HDUList([hdu1, hdu2])

        filename = self.result_path+'/calibration_Data/'+self.source+'_flux.fits'

        if self.simnumber == 1:
            newd.writeto(filename, overwrite=True)

        return
    ##################################################################################

    def sim_sky_img(self, skyfitsfilename, skyRa, skyDec, sky_rot, telRa, telDec,  exposuretime, simnumber):
        """


        Parameters
        ----------
        skyfitsfilename : TYPE
            DESCRIPTION.
        skyRa : TYPE
            DESCRIPTION.
        skyDec : TYPE
            DESCRIPTION.
        sky_rot : TYPE
            DESCRIPTION.
        telRa : TYPE
            DESCRIPTION.
        telDec : TYPE
            DESCRIPTION.
        exposuretime : TYPE
            DESCRIPTION.
        simnumber : TYPE
            DESCRIPTION.

        Returns
        -------
        None.

        """

        # load the input original data cube simulated by Fengshuai

        ############################################################################
        # consider the slice optical efficiency in different slicer channel
        slicer_QE_file = self.information['dir_path'] + \
            'IFS_inputdata/Flatfield/slicer_QE230625.fits'
        da = fits.open(slicer_QE_file)
        self.log.info('slicer_QE_file path is: %s' % (slicer_QE_file))
        slicer_Qe = da[0].data

        #######################################################################

        indatafile = skyfitsfilename  # 'CSSTMock-M49_HST-FOV10R01-Z00.v202012.fits'

        self.log.info('skyfitsin file is:%s' % (skyfitsfilename))
        a = fits.open(indatafile)
        # hdr = a[0].header
        # print(a.info())
        # hdr = a[1].header
        #######################################################################
        self.information['name_obj'] = a[0].header['OBJECT']
        self.information['ra_obj'] = a[0].header['RA']  # in degree
        self.information['dec_obj'] = a[0].header['DEC']  # in degree

        disRa = (telRa-skyRa)/3600.0  # convert unit of arcsec to degree
        disDec = (telDec-skyDec)/3600.0  # convert unit of arcsec to degree
        # RA_PNT0 =OBJ_RA + DIS_RA/cos(OBJ_DEC), DEC_PNT0=  OBJ_DEC DIS_DEC
        self.information['ra_pnt0'] = a[0].header['RA'] + \
            disRa/np.cos(a[0].header['DEC']/180.0*np.pi)
        self.information['dec_pnt0'] = a[0].header['DEC'] + disDec

        ###############   calculate the earthshine and zodiacal noise  ,new code 2023.11.1  ############
        ###############

        ra = self.information['ra_pnt0']
        dec = self.information['dec_pnt0']

        time_jd = time2jd(self.dt)

        x_sat = float(self.orbit_pars[self.orbit_exp_num, 1])
        y_sat = float(self.orbit_pars[self.orbit_exp_num, 2])
        z_sat = float(self.orbit_pars[self.orbit_exp_num, 3])

        wave0, zodi0 = self.zodiacal(
            ra, dec, self.TianCe_day)     # erg/s/cm^2/A/arcsec^2

        # EarthShine from straylight
        sl = StrayLight(self.information['dir_path'], jtime=time_jd, sat=np.array([x_sat, y_sat, z_sat]),
                        radec=np.array([(ra*u.degree).value, (dec*u.degree).value]))

        earth_e = sl.caculateEarthShineFilter(filter='r')
        star_e = sl.caculateStarLightFilter(filter='r')
        angle_earth = earth_angle(time_jd, x_sat, y_sat, z_sat, ra, dec)

        if angle_earth < 0:
            earth_e = 0

        earthshine_wave0, earthshine_flux0 = ill2flux(
            self.information['dir_path'], earth_e+star_e)

        # sample as ifs wavelength
        wave_ifs = np.arange(3500, 10000, 1.0)
        f1 = interp1d(wave0, zodi0)
        zodi_ifs = f1(wave_ifs)

        f2 = interp1d(earthshine_wave0, earthshine_flux0)
        earthshine_ifs = f2(wave_ifs)

        self.zodiacal_wave = wave_ifs    # in A
        self.zodiacal_flux = zodi_ifs

        self.earthshine_wave = wave_ifs    # A
        self.earthshine_flux = earthshine_ifs

        #############################################################################

        self.information['target'] = deg2HMS(
            self.information['ra_obj'], self.information['dec_obj'])

        # main input data
        SpecCube = a[1].data  # spectrum data cube;
        # Cubesize=len(SpecCube[:,0,0])
        # #print('SpecCube data header', hdr)
        # hdr = a[2].header
        # the main input data
        # the relatived wavelength which is converted from Unit A to nm
        Wave = 0.1*a[2].data
        #print('Wave data header', hdr)

        ######################################################################################

        exptime = self.information['exptime']  # exposure time

        dis_dx = ((telRa-skyRa))   # image shift Ra in arcsec
        dis_dy = ((telDec-skyDec))  # image shift Dec in arcsec

        sizeout = len(SpecCube[:, 0, 0])

        blue_img = galsim.Image(np.zeros((2048, 4096)), copy=True)
        blue_img.scale = self.pixelscale
        blue_img.setOrigin = (0, 0)

        red_img = galsim.Image(np.zeros((3072, 6144)), copy=True)
        red_img.scale = self.pixelscale
        red_img.setOrigin = (0, 0)

        blue_sensor = galsim.Sensor()

        red_sensor = galsim.Sensor()

        deltalam = np.mean(np.diff(Wave))

        energy = 0.0

        energy_blue = 0.0
        energy_red = 0.0

        width_blue = 0
        ################################
        ############## doppler effect to photons.wavelength  #############

        # self.orbit_pars
        x_sat = float(self.orbit_pars[self.orbit_exp_num, 1])
        y_sat = float(self.orbit_pars[self.orbit_exp_num, 2])
        z_sat = float(self.orbit_pars[self.orbit_exp_num, 3])
        vx_sat = float(self.orbit_pars[self.orbit_exp_num, 4])
        vy_sat = float(self.orbit_pars[self.orbit_exp_num, 5])
        vz_sat = float(self.orbit_pars[self.orbit_exp_num, 6])

        self.information['POSI0_X'] = x_sat
        self.information['POSI0_Y'] = y_sat
        self.information['POSI0_Z'] = z_sat
        self.information['VELO0_X'] = vx_sat
        self.information['VELO0_Y'] = vy_sat
        self.information['VELO0_Z'] = vz_sat

        theta1 = beta_angle(x_sat, y_sat, z_sat, vx_sat, vy_sat, vz_sat,
                            self.information['ra_obj'], self.information['dec_obj'])

        v1 = np.sqrt(vx_sat**2+vy_sat**2+vz_sat**2)*np.cos(theta1 /
                                                           180.0*np.pi)  # velocity at stat exposure time
        vv1 = LSR_velocity(
            self.information['ra_obj'], self.information['dec_obj'], v1, self.TianCe_day)

        #################################################
        # exposure end time is t2 ;
        t2 = self.dt+timedelta(seconds=self.information['exptime'])
        ### data read time is the exposure end time plus readouttime ###
        # data read end time is t3

        t2jd = time2jd(t2)

        if self.orbit_pars[-1, 0] < t2jd:  # orbit parameters are not in currenct txt file
            self.orbit_file_num = self.orbit_file_num+1
            fn = self.information['dir_path'] + \
                'IFS_inputdata/TianCe/orbit20160925/' + \
                str(self.orbit_file_num)+'.txt'
            self.orbit_pars = np.loadtxt(fn)
            self.orbit_exp_num = 0

        for k in range(self.orbit_exp_num, len(self.orbit_pars), 1):

            if t2jd-self.orbit_pars[k, 0] < 0:
                break
        if k == 0:
            deltaT = jd2time(self.orbit_pars[k, 0])-t2
            p1x = self.orbit_pars[k, 1]-(self.orbit_pars[k+1, 1] -
                                         self.orbit_pars[k, 1])*deltaT.seconds/120
            p1y = self.orbit_pars[k, 2]-(self.orbit_pars[k+1, 2] -
                                         self.orbit_pars[k, 2])*deltaT.seconds/120
            p1z = self.orbit_pars[k, 3]-(self.orbit_pars[k+1, 3] -
                                         self.orbit_pars[k, 3])*deltaT.seconds/120

            p1vx = self.orbit_pars[k, 4]-(self.orbit_pars[k+1, 4] -
                                          self.orbit_pars[k, 4])*deltaT.seconds/120
            p1vx = self.orbit_pars[k, 5]-(self.orbit_pars[k+1, 5] -
                                          self.orbit_pars[k, 5])*deltaT.seconds/120
            p1vx = self.orbit_pars[k, 6]-(self.orbit_pars[k+1, 6] -
                                          self.orbit_pars[k, 6])*deltaT.seconds/120

        else:
            deltaT = jd2time(self.orbit_pars[k, 0])-t2
            p1x = self.orbit_pars[k-1, 1]+(self.orbit_pars[k, 1] -
                                           self.orbit_pars[k-1, 1])*deltaT.seconds/120
            p1y = self.orbit_pars[k-1, 2]+(self.orbit_pars[k, 2] -
                                           self.orbit_pars[k-1, 2])*deltaT.seconds/120
            p1z = self.orbit_pars[k-1, 3]+(self.orbit_pars[k, 3] -
                                           self.orbit_pars[k-1, 3])*deltaT.seconds/120

            p1vx = self.orbit_pars[k-1, 4]+(self.orbit_pars[k, 4] -
                                            self.orbit_pars[k-1, 4])*deltaT.seconds/120
            p1vy = self.orbit_pars[k-1, 5]+(self.orbit_pars[k, 5] -
                                            self.orbit_pars[k-1, 5])*deltaT.seconds/120
            p1vz = self.orbit_pars[k-1, 6]+(self.orbit_pars[k, 6] -
                                            self.orbit_pars[k-1, 6])*deltaT.seconds/120

        #######
        self.information['POSI1_X'] = p1x
        self.information['POSI1_Y'] = p1y
        self.information['POSI1_Z'] = p1z
        self.information['VELO1_X'] = p1vx
        self.information['VELO1_Y'] = p1vy
        self.information['VELO1_Z'] = p1vz

        theta2 = beta_angle(p1x, p1y, p1z, p1vx, p1vy, p1vz,
                            self.information['ra_obj'], self.information['dec_obj'])

        v2 = np.sqrt(p1vx**2+p1vy**2+p1vz**2)*np.cos(theta2 /
                                                     180.0*np.pi)  # velocity at end exposure time
        vv2 = LSR_velocity(
            self.information['ra_obj'], self.information['dec_obj'], v2, self.TianCe_day)

        # get slice and slit mask

        ########################################################################################

        if self.debug:
            alfa = 1000
        else:
            alfa = 1

        for klam in range(6000):

            ilam = klam*alfa

            if ilam >= 6000:
                break

            # print(ilam)

            if self.debug:
                print(ilam)

            if ilam % 100 == 0:

                self.log.info('ilam = %i' % ilam)
                self.log.info('blue image max value = %i' %
                              blue_img.array.max())
                self.log.info('red image max value = %i' % red_img.array.max())

            # print('ilam=',ilam)

            # the wavelength of the i-th frame data
            lam = Wave[ilam]

            if lam < 350:
                continue

            ###############################################
            # get the i-th frame of the input SpecCube data;
            Specimg = SpecCube[:, :, ilam]
            # convert to photons/cm2/s/A
            Nspecimg = SpecCube2photon(Specimg, lam)
            Nskynoise = self.CalskyNoise(lam)

            if self.sky_noise:
                # add sky noise
                Nimg = Nspecimg+Nskynoise

            else:
                Nimg = Nspecimg+0.0

            # multipe the tel area, exposure time, and bandwidh;
            # photons/cm2/s/A to photons, here Nimg size is 100*100
            Nimg = Nimg*self.telarea*exptime*deltalam*10.0

            if Nimg.max() < 0.1:
                continue

            if Nimg.min() < 0:
                self.log.error('Error in datacube, negative values !!!!!!!')
                continue

            ##############################################################################
            # if ilam==100:
            #     fits.writeto('/media/yan//IFSsim/IFSsim Data/original_Img.fits', Nimg, overwrite=True)

           ##########################################################################
           # shift image with photons position and rotate them round the image true center
            img = galsim.Image(Nimg, copy=True)
            img.scale = self.pixelscale
            img.setOrigin(0, 0)

            photons = galsim.PhotonArray.makeFromImage(
                img, max_flux=max(Nimg.max()/10000.0, 1))

            # now shift and rotated photons
            self.information['shift_dx'] = dis_dx
            self.information['shift_dy'] = dis_dy

            photons.x = photons.x-dis_dx  # apply shift to photons position in ra direction
            photons.y = photons.y-dis_dy  # apply shift to photons position in dec direction
            # print(dis_dx)
            # print(dis_dy)

            x2, y2 = rotation_yan(img.true_center.x, img.true_center.y,
                                  photons.x/img.scale, photons.y/img.scale,  sky_rot)

            #print('rotation time=', t2-t1)

            photons2 = galsim.PhotonArray(
                N=len(x2), x=x2*img.scale,  y=y2*img.scale, flux=photons.flux)
            photons = photons2
            # convert the photon image to galsim photons;
            ###################################################################

            image0 = galsim.Image(sizeout, sizeout)
            image0.scale = self.pixelscale
            image0.setOrigin(0, 0)
            #image.setCenter(int(np.mean(photons.x)), int(np.mean(photons.y)))

            rotphotons = galsim.PhotonArray(
                N=len(photons.x),  x=x2, y=y2, flux=photons2.flux)

            sensor = galsim.Sensor()
            sensor.accumulate(rotphotons, image0)

            #####################################

            #     #######
            if ilam == 1000:
                hdu1 = fits.PrimaryHDU(image0.array[50-32:50+32, 50-32:50+32])

                hdu1.header['dis_Ra'] = (dis_dx, 'image shift Ra in arcsec')

                hdu1.header['dis_Dec'] = (dis_dy, 'image shift Ra in arcsec')

                hdu1.header['rotAngle'] = (
                    sky_rot, 'sky rotation relative to telescope anticlockwise in degree')

                #PSFfilename='/media/yan//IFSsim/IFSsim Data/rot_shift_Img.fits'

                #fits.writeto('/media/yan//IFSsim/IFSsim Data/rot_shift_subImg.fits',image0.array[50-32:50+32,50-32:50+32],overwrite=True )
                hdu1.writeto(self.result_path+'/shift_rot_sky/rot_shift_subImg_' +
                             str(simnumber)+'.fits', overwrite=True)
                fits.writeto(self.result_path+'/shift_rot_sky/original_Img_' +
                             str(simnumber)+'.fits', Nimg, overwrite=True)

            #####################################################################
            ### do convolve image0 with PSF0 from primay CSST  ###
            # calculate the PSF0 at this wavelength

            Q = lam*1e-3*self.Fnum/self.pixelsize
            wavefront0 = self.opd0/lam
            # here we choose reshape=False, rotate the wavefront
            wavefront = ndimage.rotate(
                wavefront0, -sky_rot, order=1, reshape=False)

            psf0 = anySampledPSFnew(wavefront, self.pupil, Q, 64)

            #psf01=anySampledPSFnew(wavefront, self.pupil , Q , 64)

            conv = fftconvolve(image0.array, psf0, mode='same')
            conv[conv < 0.0] = 0.0
            img0 = conv

            # opd1 rms =0.075 @632.8nm
            wavefront = self.opd1/lam
            # here we choose reshape=False, rotate the wavefront
            wavefront = ndimage.rotate(
                wavefront, -sky_rot, order=1, reshape=False)
            psf1 = anySampledPSFnew(wavefront, self.pupil, Q, 32)
            # psf1=ndimage.rotate(psf1, -sky_rot, order=1, reshape=False) # here we choose reshape=False, the rotated image will

            # do convolve with psf1
            conv = fftconvolve(img0, psf1, mode='same')
            conv[conv < 0.0] = 0.0
            img0 = conv

            #######################################################

            # energy=energy+img0[50-32:50+32,50-32:50+32].sum()  ## calculate the slice image energy;

            # CCD quantum efficiency
            CCD_Qe_lam = np.interp(lam, self.CCD_Qe[:, 0], self.CCD_Qe[:, 1])

            # optical efficiency , convert the wavelength to A
            Qe_blue = np.interp(
                lam, self.optical_blue_Q[:, 0], self.optical_blue_Q[:, 1])

            # optical efficiency,  convert the wavelength to A
            Qe_red = np.interp(
                lam, self.optical_red_Q[:, 0],  self.optical_red_Q[:, 1])

            Qe_blue = Qe_blue*CCD_Qe_lam

            Qe_red = Qe_red * CCD_Qe_lam

            lam1 = lam*(1+vv1/(3.0*1e5))

            lam2 = lam*(1+vv2/(3.0*1e5))

            img0 = img0*slicer_Qe
            ##########   do the slice effect  ###################
            for k in range(32):

                # do slice effect to get slice image
                img1 = img0*self.maskSlice[str(k)]

                ############ get opd2  and PSF2  ######################
                wavefront2 = self.opd2/lam
                psf2 = anySampledPSFnew(wavefront2, self.pupil, Q, 64)
                # here we choose reshape=False, the rotated image will
                psf2 = ndimage.rotate(psf2, -sky_rot, order=1, reshape=False)
                # do convolve
                ##################
                conv = fftconvolve(img1, psf2, mode='same')
                # suppress negative numbers
                conv[conv < 0.0] = 0.0
                img2 = conv

                ##############do Slit Mask ###########################

                img2 = img2*self.maskSlit[str(k)]

                #########  get opd3 and PSF3 ##########################

                wavefront3 = self.opd3/lam
                psf3 = anySampledPSFnew(wavefront3, self.pupil, Q, 64)
                # here we choose reshape=False, the rotated image will
                psf3 = ndimage.rotate(psf3, -sky_rot, order=1, reshape=False)

                # do convolve
                ########################################
                conv = fftconvolve(img2, psf3, mode='same')
                # suppress negative numbers
                conv[conv < 0.0] = 0.0

                img3 = conv

                #print('covtime =', t2-t1)

               ########################   get subimage                   #####################
                subimage = galsim.Image(80, 80)
                subimage.array[:, :] = img3[int(
                    sizeout/2)-40:int(sizeout/2)+40, int(sizeout/2)-40:int(sizeout/2)+40]

                # fits.writeto('subimage.fits',subimage.array,overwrite=True)

                ########################   get photons from sub-image     #####################
                subimage.scale = self.pixelscale

                subimage.setOrigin(0, 0)

                photons = galsim.PhotonArray.makeFromImage(
                    subimage, max_flux=max(img3.max()/10000.0, 1))

                #############################################################################
                # do something for each photons;
                ##############################################################################
                idx0 = np.where(photons.flux > 1e-3)
                # totla energy for slice image
                energy = energy+sum(photons.flux[idx0])
                p_num = len(idx0[0])

                ###############################################################################################
                ####################### code for test slice is right or not ###################################
                ###############################################################################################
                # photons_2=galsim.PhotonArray(N=p_num, x=photons.x[idx0], y=photons.y[idx0], flux = photons.flux[idx0], wavelength=lam)
                # if k==0:
                #     sliceimage = galsim.Image(100,100)
                #     sliceimage.scale = pixelscale
                #     sliceimage.setOrigin(0,0)
                # #image.setCenter(int(np.mean(photons.x)), int(np.mean(photons.y)))

                # rotphotons=galsim.PhotonArray(N=p_num,  x=photons_2.x/pixelscale, y=photons_2.y/pixelscale, flux = photons_2.flux)

                # sensor = galsim.Sensor()
                # sensor.accumulate(rotphotons, sliceimage)
                #########################     finish testing    #######################################################
                #######################################################################################################
                #####################################
                # find photons for blue channel, and make the flux multiple the optical and CCD efficiency

                np.random.seed(100*self.simnumber)
                wavesample = lam1+(lam2-lam1)*np.random.rand(p_num)

                if (lam >= 350.0 and lam <= 650.0):
                    # bulue  channel
                    photons_blue = galsim.PhotonArray(
                        N=p_num, x=photons.x[idx0], y=photons.y[idx0], flux=Qe_blue*photons.flux[idx0], wavelength=wavesample)

                    dx_blue, dy_blue = get_dx_dy_blue(wavesample)

                    photons_blue.x = photons_blue.x/self.pixelscale + \
                        dx_blue+self.slice_blue['px'][k]

                    photons_blue.y = photons_blue.y/self.pixelscale + \
                        dy_blue+self.slice_blue['py'][k]

                    blue_sensor.accumulate(photons_blue, blue_img)

                    energy_blue = energy_blue+sum(photons_blue.flux)

                  
                    #################

                    # fits.writeto('blueImg.fits',blue_img.array,overwrite=True)

                if (lam >= 560.0) & (lam <= 1000.0):
                    # red channel
                    photons_red = galsim.PhotonArray(
                        N=p_num, x=photons.x[idx0], y=photons.y[idx0], flux=Qe_red*photons.flux[idx0], wavelength=wavesample)

                    dx_red,  dy_red = get_dx_dy_red(wavesample)

                    photons_red.x = photons_red.x/self.pixelscale + \
                        dx_red+self.slice_red['px'][k]

                    photons_red.y = photons_red.y/self.pixelscale + \
                        dy_red+self.slice_red['py'][k]

                    red_sensor.accumulate(photons_red, red_img)

                    energy_red = energy_red+sum(photons_red.flux)

                    ########################################################

            ########################### test code ################################
            ######################################################################
            # fits.writeto('originalImg.fits',Nimg,overwrite=True)
            # fits.writeto('PSFedImg.fits',temp,overwrite=True)
            # fits.writeto('rotedphotonsImg.fits',image.array,overwrite=True)
            # fits.writeto('newsub_rotedphotonsImg.fits',newimage.array,overwrite=True)
            # fits.writeto('slicephotonsImg.fits',sliceimage.array,overwrite=True)

            # pause()
            ######################################################################
            ###################### finish test code   ###########################

        ####################################################################################
        # stray light will cover 2% of input total light;
        if self.sky_noise:

            blue_img.array[:, :] = 0.99 * \
                blue_img.array[:, :]+0.01*energy/2048/4096

            red_img.array[:, :] = 0.99 * \
                red_img.array[:, :] + 0.01*energy/3072/6144

        self.image_b = blue_img.array
        self.image_r = red_img.array

        obsid = simnumber
        bluefile = self.result_path+'/original_sky/IFS_' + \
            self.source+'_'+str(obsid)+'_B.fits'
        redfile = self.result_path+'/original_sky/IFS_' + \
            self.source+'_'+str(obsid)+'_R.fits'

        fits.writeto(bluefile, blue_img.array, overwrite=True)
        fits.writeto(redfile,  red_img.array,  overwrite=True)

        return

#################################################################################################
    def sim_calibration_img(self, exposuretime, source):
        """


        Parameters
        ----------
        exposuretime : TYPE
            DESCRIPTION.
        source : TYPE
            DESCRIPTION.

        Returns
        -------
        None.

        """

        sizeout = 100
        ################# load the hole mask file ###################

        # load HgAr data;
        if self.source == 'LAMP':
            matfn3 = self.information['dir_path']+'IFS_inputdata/HgAr.mat'
            self.log.info('lamp flux file is : %s' % (matfn3))
            da3 = sio.loadmat(matfn3)
            HgArst = da3['HgArst']  # opd unit is in meter
        # load flat data
        if self.source == 'FLAT':
            matfn3 = self.information['dir_path'] + \
                'IFS_inputdata/flat_light.mat'
            self.log.info('flat flux file is : %s' % (matfn3))
            da3 = sio.loadmat(matfn3)
            flat_light = da3['flat_light']  # opd unit is in meter

        exptime = self.information['exptime']  # exposure time

        blue_img = galsim.Image(np.zeros((2048, 4096)), copy=True)
        blue_img.scale = self.pixelscale
        # blue_img.setOrigin=(0,0)

        red_img = galsim.Image(np.zeros((3072, 6144)), copy=True)
        red_img.scale = self.pixelscale
        # red_img.setOrigin=(0,0)

        blue_sensor = galsim.Sensor()

        red_sensor = galsim.Sensor()

        slice_image = np.zeros((sizeout, sizeout))

        LightSt = np.zeros(6000)
        flux_1 = np.zeros(6000)
        Width_lambda = (1000-350)/6000
        Wave = np.zeros(6000)

        # code for slicer position #########33
        ####

        if self.debug:
            alfa = 1000
        else:
            alfa = 1

        for klam in range(6000):

            ilam = klam*alfa

            if self.debug:
                print(ilam)

            # ilam=ilam*200
            if ilam % 100 == 0:
                self.log.info('ilam = %i' % ilam)
                self.log.info('blue image max value = %i' %
                              blue_img.array.max())
                self.log.info('red image max value = %i' % red_img.array.max())

            if ilam >= 6000:
                break

            # the wavelength of the i-th frame data
            lam = 350.0+(1000-350)/6000.0 * ilam

            Wave[ilam] = lam

            # tab['wavelength'][ilam]=lam
            #print('i = ',ilam )
            ###############################################
            if self.source == 'LAMP':

                sumt = 0.0

                for k in range(11):
                    t = getSpectrum(HgArst, lam+(k-5) *
                                    Width_lambda/11, self.HgArsigma)
                    sumt = sumt+t[0]/11
                    #print('t=', t)

                # the normalized HgAr light source in the range of 0 and 1.0
                LightSt[ilam] = sumt

                skymag = 9   # mag/arcsec^2 unit

                if ilam == 0:
                    # fluxnu1,fluxlam1,fluxphoton1=mag2flux(skymag,633)
                    fluxlam1, fluxphoton1 = mag2flux(skymag, 633)

                  # save flux information
                flux_1[ilam] = LightSt[ilam] * \
                    fluxlam1*self.fov2     # erg/s/cm2/A

                nphoton = flux2photons(flux_1[ilam], lam)

                # photons/cm2/s/A/arcsec2
                ns = nphoton*np.ones((sizeout, sizeout))

                slice_image = ns*self.telarea*Width_lambda*10*exptime

# #####################3 transfer: photons/cm2/s/A/arcsec2  to  photons
            if self.source == 'FLAT':
                ##########
                flatS = np.interp(lam, flat_light[:, 0], flat_light[:, 1])

                # the normalized HgAr light source in the range of 0 and 1.0
                LightSt[ilam] = flatS

                skymag = 9   # mag/arcsec^2 unit

                if ilam == 0:
                    fluxlam1, fluxphoton1 = mag2flux(skymag, 633)

                  # save flux information
                flux_1[ilam] = LightSt[ilam] * \
                    fluxlam1*self.fov2     # erg/s/cm2/A

                nphoton = flux2photons(flux_1[ilam], lam)

                # photons/cm2/s/A/arcsec2
                ns = nphoton*np.ones((sizeout, sizeout))

                slice_image = ns*self.telarea*Width_lambda*10*exptime

############################################################################

            if slice_image.max() < 0.1:
                continue

            if slice_image.min() < 0:
                self.log.error('Error in datacube, negative values !!!!!!!')
                continue
#############################################################################

            # for calibration , there is no primay CSST system, but diffraction effect should consider ;

            Q = lam*1e-3*self.Fnum/self.pixelsize

            wavefront0 = self.opd0/lam/100000.0

            psf0 = anySampledPSFnew(wavefront0, self.pupil, Q, 64)

            #psf01=anySampledPSFnew(wavefront, self.pupil , Q , 64)

            conv = fftconvolve(slice_image, psf0, mode='same')

            conv[conv < 0.0] = 0.0

            slice_image2 = conv

            img0 = slice_image2

            ##########################################
            # opd1 rms =0.075 @632.8nm
            wavefront = self.opd1/lam

            psf1 = anySampledPSFnew(wavefront, self.pupil, Q, 64)

            # do convolve with psf1
            conv = fftconvolve(img0, psf1, mode='same')
            conv[conv < 0.0] = 0.0
            img0 = conv

            # CCD quantum efficiency
            CCD_Qe_lam = np.interp(lam, self.CCD_Qe[:, 0], self.CCD_Qe[:, 1])

            # optical efficiency , convert the wavelength to A
            Qe_blue = np.interp(
                lam, self.optical_blue_Q[:, 0], self.optical_blue_Q[:, 1])

            # optical efficiency,  convert the wavelength to A
            Qe_red = np.interp(
                lam, self.optical_red_Q[:, 0],  self.optical_red_Q[:, 1])

            Qe_blue = Qe_blue*CCD_Qe_lam
            Qe_red = Qe_red * CCD_Qe_lam

            # print('time=',(end-start))
            # consider the slice optical efficiency in different slicer channel
            da = fits.open(
                self.information['dir_path']+'IFS_inputdata/Flatfield/slicer_QE230625.fits')
            slicer_Qe = da[0].data
            img0 = img0*slicer_Qe
            ##########   do the slice effect  ###################
            for k in range(32):

                # do slice effect to get slice image
                img1 = img0*self.maskSlice[str(k)]

                ############ get opd2  and PSF2  ######################
                wavefront2 = self.opd2/lam
                psf2 = anySampledPSFnew(wavefront2, self.pupil, Q, 64)
                # do convolve
                ##################
                conv = fftconvolve(img1, psf2, mode='same')
                # suppress negative numbers
                conv[conv < 0.0] = 0.0
                img2 = conv

                ##############do Slit Mask ###########################

                img2 = img2*self.maskSlit[str(k)]

                #########  get opd3 and PSF3 ##########################

                wavefront3 = self.opd3/lam
                psf3 = anySampledPSFnew(wavefront3, self.pupil, Q, 64)

                # do convolve
                ########################################
                conv = fftconvolve(img2, psf3, mode='same')
                # suppress negative numbers
                conv[conv < 0.0] = 0.0

                img3 = conv

                ########################   get subimage                   #####################
                subimage = galsim.Image(80, 80)
                subimage.array[:, :] = img3[int(
                    sizeout/2)-40:int(sizeout/2)+40, int(sizeout/2)-40:int(sizeout/2)+40]

                # fits.writeto('subimage.fits',subimage.array,overwrite=True)

                ########################   get photons from sub-image     #####################
                subimage.scale = self.pixelscale

                subimage.setOrigin(0, 0)

                photons = galsim.PhotonArray.makeFromImage(
                    subimage, max_flux=max(img3.max()/10000.0, 1))

                #############################################################################
                # do something for each photons;
                ##############################################################################
                idx0 = np.where(photons.flux > 1e-2)
                # energy=energy+sum(photons.flux[idx0])     ### totla energy for slice image
                p_num = len(idx0[0])

                ###############################################################################################
                ####################### code for test slice is right or not ###################################
                ###############################################################################################
                # photons_2=galsim.PhotonArray(N=p_num, x=photons.x[idx0], y=photons.y[idx0], flux = photons.flux[idx0], wavelength=lam)
                # if k==0:
                #     sliceimage = galsim.Image(100,100)
                #     sliceimage.scale = pixelscale
                #     sliceimage.setOrigin(0,0)
                # #image.setCenter(int(np.mean(photons.x)), int(np.mean(photons.y)))

                # rotphotons=galsim.PhotonArray(N=p_num,  x=photons_2.x/pixelscale, y=photons_2.y/pixelscale, flux = photons_2.flux)

                # sensor = galsim.Sensor()
                # sensor.accumulate(rotphotons, sliceimage)
                # ########################     finish testing    #######################################################
                # ######################################################################################################

                #####################################
                # find photons for blue channel, and make the flux multiple the optical and CCD efficiency

                if (lam >= 350.0 and lam <= 650.0):
                    # bulue  channel
                    photons_blue = galsim.PhotonArray(
                        N=p_num, x=photons.x[idx0], y=photons.y[idx0], flux=Qe_blue*photons.flux[idx0], wavelength=lam)

                    dx_blue, dy_blue = get_dx_dy_blue(lam)

                    photons_blue.x = photons_blue.x/self.pixelscale + \
                        dx_blue+self.slice_blue['px'][k]

                    photons_blue.y = photons_blue.y/self.pixelscale + \
                        dy_blue+self.slice_blue['py'][k]

                    #photons_blue.y =2048-photons_blue.y

                    blue_sensor.accumulate(photons_blue, blue_img)

                    # energy_blue=energy_blue+sum(photons_blue.flux)

                    # width_blue=width_blue+Width_lambda/32.0

                    # fits.writeto('blueImg.fits',blue_img.array,overwrite=True)

                if (lam >= 560.0) & (lam <= 1000.0):
                    # red channel
                    photons_red = galsim.PhotonArray(
                        N=p_num, x=photons.x[idx0], y=photons.y[idx0], flux=Qe_red*photons.flux[idx0], wavelength=lam)

                    dx_red,  dy_red = get_dx_dy_red(lam)

                    photons_red.x = photons_red.x/self.pixelscale + \
                        dx_red+self.slice_red['px'][k]

                    photons_red.y = photons_red.y/self.pixelscale + \
                        dy_red+self.slice_red['py'][k]

                    #photons_red.y =3072-photons_red.y

                    red_sensor.accumulate(photons_red, red_img)

                    # energy_red=energy_red+sum(photons_red.flux)

        ####################################################################################

        self.image_b = blue_img.array
        self.image_r = red_img.array

        now = datetime.utcnow()

        data_time = now.strftime("%Y-%m-%d")
        # write the actual file

        bluefile = self.result_path+'/original_Calibration/IFS_'+self.source + \
            '_'+data_time+'_'+str(self.simnumber)+'_B_original.fits'
        redfile = self.result_path+'/original_Calibration/IFS_'+self.source + \
            '_'+data_time+'_'+str(self.simnumber)+'_R_original.fits'

        fits.writeto(bluefile, blue_img.array, overwrite=True)
        fits.writeto(redfile,  red_img.array,  overwrite=True)

        #### save flux   ####
        Flux = flux_1

        hdu1 = fits.PrimaryHDU(Flux)

        dtime = datetime.utcnow().strftime('%Y -%m -%d %H: %M: %S')
        hdu1.header.add_history('Flux data is generated on :'+dtime)
        hdu1.header['FluxUnit'] = ('erg/s/cm2/A', 'erg/s/cm2/A')

        hdu2 = fits.ImageHDU(Wave)

        hdu2.header.append(('WaveUnit', 'nm'))

        newd = fits.HDUList([hdu1, hdu2])

        filename = self.result_path+'/calibration_Data/'+self.source+'_flux.fits'

        if self.simnumber == 1:
            newd.writeto(filename, overwrite=True)

        return

    ##########################################################################
    # def saveCCDfullimage(self):

    #         #
    #         bluefile=self.result_path+'/original_sky/IFS_CCD_'+self.source+'_'+str(self.simnumber)+'_B.fits'
    #         redfile =self.result_path+'/original_sky/IFS_CCD_'+self.source+'_'+str(self.simnumber)+'_R.fits'

    #         fits.writeto(bluefile, self.image_b, overwrite=True)
    #         fits.writeto(redfile,  self.image_r, overwrite=True)
#################################################################################################

    def simulate(self, sourcein, simnumber):
        """


        Parameters
        ----------
        sourcein : TYPE
            DESCRIPTION.
        simnumber : TYPE
            DESCRIPTION.

        Returns
        -------
        None.

        """
        # def simulate(self, skyfitsfilename, skyRa, skyDec, sky_rot, telRa, telDec,  exposuretime,simnumber):
        """
        Create a single simulated image of a quadrant defined by the configuration file.
        Will do all steps defined in the config file sequentially.

        :return: None
        """

        self.source = sourcein

        self.simnumber = simnumber
        
        #self.configure(simnumber,dir_path,result_path)  # print the configfile name and path;

        self.debug = self.information['debug']

        self.dt = datetime.utcnow()

        if self.source == 'SCI' or self.source == 'COMP':

            if simnumber <= 50 and simnumber > 20:
                self.information['exptime'] = 300

            if simnumber <= 100 and simnumber > 50:
                self.information['exptime'] = 600

            if simnumber <= 150 and simnumber > 100:
                self.information['exptime'] = 900

            if simnumber <= 200 and simnumber > 150:
                self.information['exptime'] = 1200

            self.skyfilepath = self.information['dir_path'] + \
                self.information['sky_fitsin']

            print('self.skyfilepath = ', self.skyfilepath)

            # self.earthshine_theta=30.0       # in degree

            ##################################################################
            #### load orbit parameters  #####
            flag = 0

            self.dt_num = int(
                self.simnumber*(self.information['exptime']+self.information['readouttime']+125)/120)
            now_dt = datetime.utcnow()
            now_jd = time2jd(now_dt)

            for k in range(1, 50, 1):

                # fn=father_path+'/IFS_inputdata/TianCe/orbit20160925/'+str(k)+'.txt';

                fn = self.information['dir_path'] + \
                    'IFS_inputdata/TianCe/orbit20160925/'+str(k)+'.txt'
                d = np.loadtxt(fn)

                for kk in range(len(d[:, 0])):
                    if now_jd-d[kk, 0] <= 0:
                        flag = 1
                        break
                if flag == 1:
                    break

            ################################

            if kk + self.dt_num < len(d[:, 0]):

                exptime_start_jd = d[kk+self.dt_num, 0]

                self.orbit_pars = d
                self.orbit_file_num = k
                self.orbit_exp_num = kk

            else:

                fn = self.information['dir_path'] + \
                    'IFS_inputdata/TianCe/orbit20160925/'+str(k+1)+'.txt'
                d = np.loadtxt(fn)

                self.orbit_pars = d
                self.orbit_file_num = k+1
                self.orbit_exp_num = self.dt_num

                ##########################################
            ########################################################################
            #self.dt=julian.from_jd(exptime_start_jd, fmt='jd')
            self.dt = jd2time(exptime_start_jd)
            ##################################################################

            # str(self.dt.year)+'-'+str(self.dt.month)+'-'+str(self.dt.day)
            self.TianCe_day = self.dt.strftime("%Y-%m-%d")

            self.TianCe_exp_start = dt2hmd(self.dt)

            self.zodiacal_time = self.TianCe_day

            skyRa = 0
            skyDec = 0

            if simnumber > 1:
                np.random.seed(self.simnumber)
                ud = np.random.random()   # Choose a random rotation in degree
                sky_rot = 2 * (ud-0.5) * 5

                dsmax = np.floor(50*np.cos(sky_rot/180*np.pi))-34
                np.random.seed(11*self.simnumber)
                ud = np.random.random()   # Choose a random shift in arcsec
                telRa = 2 * (ud-0.5) * dsmax * 0.1  # in arcsec

                np.random.seed(123*self.simnumber)
                ud = np.random.random()   # Choose a random shift  in arcsec
                telDec = 2 * (ud-0.5) * dsmax * 0.1  # in arcsec

            sky_rot = 0.0
            telRa = 0
            telDec = 0

            self.sim_sky_img(self.skyfilepath, skyRa, skyDec, 0*sky_rot,
                             0*telRa, 0*telDec,  self.information['exptime'], simnumber)

            self.skyRa = skyRa
            self.skyDec = skyDec
            self.sky_rot = sky_rot

            self.telRa = telRa
            self.telDec = telDec

            self.information['sky_rot'] = sky_rot

        #######################################################################
        elif self.source == 'FLAT':
            self.information['exptime'] = 200+simnumber*100
            self.sim_calibration_img(self.information['exptime'], 'FLAT')
            self.information['sky_rot'] = 0

        ########

        elif self.source == 'LAMP':
            self.information['exptime'] = 200+simnumber*100
            self.information['sky_rot'] = 0

            if self.information['holemask'] == 'yes':
                self.information['exptime'] = 300
                self.sim_lamp_hole_img(self.information['exptime'])
            else:
                self.sim_calibration_img(self.information['exptime'], 'LAMP')

        #########
        elif self.source == 'DARK':
            self.information['sky_rot'] = 0
            self.information['exptime'] = simnumber*3600*24
            self.image_b = np.zeros((2048, 4096))
            self.image_r = np.zeros((3072, 6144))

        elif self.source == 'BIAS':
            self.information['sky_rot'] = 0
            self.information['exptime'] = 0
            self.image_b = np.zeros((2048, 4096))
            self.image_r = np.zeros((3072, 6144))

        else:
            print('Souce is not correct and programe will return')
            exit(1)
###############################################################################
        # save the original image firstly;
        self.image_b_ori = self.image_b+10.0
        self.image_r_ori = self.image_r+10.0

        exptimes = self.information['exposuretimes']

        if self.source == 'SCI' or self.source == 'COMP':
            exptimes = self.information['exposuretimes']
        else:
            exptimes = 1
            self.information['exposuretimes'] = 1

        # if self.debug:
        #     exptimes=1
        if exptimes > 1:
            self.image_b_N = np.zeros((1344, 9672, exptimes))

            self.image_r_N = np.zeros((1856, 13768, exptimes))
        else:
            self.image_b_N = np.zeros((1344, 9672))

            self.image_r_N = np.zeros((1856, 13768))

        for idk in range(exptimes):

            ############  add some effect to images #######################################

            self.image_b = self.image_b_ori-10.0
            self.image_r = self.image_r_ori-10.0

            if self.information['exptime'] > 0.09216:

                self.getFrameTransferImg()
                print('Applying getFrameTransferImg finished.......')

            if self.flatfieldM:
                self.applyflatfield(simnumber)
                print('Applying flatfieldM finished.......')

            if self.source == 'SCI' or self.source == 'COMP':

                if self.cosmicRays:
                    self.addCosmicRays(idk)
                    print('Applying cosmicRays finished.......')

            if self.bleeding:
                self.applyBleeding_yan()
                print('Applying bleeding finished.......')

            if self.darknoise:
                self.applyDarkCurrent()
                print('Applying dark noise finished.......')

                ##################################

            if self.source != 'SCI' and self.source != 'COMP':
                self.applyPoissonNoise()
                print('Applying Possion noise finished.......')

            #################################

            if self.nonlinearity:
                self.applyNonlinearity()
                print('Applying  nonlinearity finished.......')

            # cut original CCD image to four parts by four read out channels and zones
            self.CCDreadout()

            if self.radiationDamage:
                self.applyRadiationDamage()
                print('Applying  radiationDamage finished.......')

            if self.readoutNoise:
                self.applyReadoutNoise()
                print('Applying  readoutNoise finished.......')

            if self.information['exptime'] > 0.09216:
                self.appFrameTransferEffect()
                print('Applying  Frame Transfer Effect finished.......')

            self.electrons2ADU()

            self.applyBias()

            if self.cosmetics:
                self.applyCosmetics()
                print('Applying cosmetics finished.......')

            self.discretise()

            if exptimes > 1:

                self.image_b_N[:, :, idk] = self.image_b[:, :].copy()
                self.image_r_N[:, :, idk] = self.image_r[:, :].copy()
            else:
                self.image_b_N[:, :] = self.image_b[:, :].copy()
                self.image_r_N[:, :] = self.image_r[:, :].copy()

        self.writeOutputs(simnumber)

        #################################################

        self.log.info('Using the following input values:')

        for key, value in self.information.items():
            self.log.info('%s = %s' % (key, value))
        self.log.info('Using the following booleans:')

        for key, value in self.booleans.items():
            self.log.info('%s = %s' % (key, value))

        self.log.info('Finished the ith_Exposure = %i' % (simnumber))
# print('The iLoop= % d simlaiton finished. ' %simnumber)

############################################################################
############################################################################


def runIFSsim(sourcein, configfile, dir_path, result_path, iLoop, debug, applyhole):
    """


    Parameters
    ----------
    sourcein : TYPE
        DESCRIPTION.
    configfile : TYPE
        DESCRIPTION.
    iLoop : TYPE
        DESCRIPTION.
    applyhole : TYPE, optional
        DESCRIPTION. The default is 'no'.

    Returns
    -------
    int
        DESCRIPTION.

    """

    simulate = dict()
    simulate[iLoop] = IFSsimulator(configfile)
    
    simulate[iLoop].configure(sourcein,  dir_path, result_path, iLoop, debug, applyhole)    # load the configfile; 
    

    if applyhole == 'yes' and sourcein == 'LAMP':
        simulate[iLoop].information['holemask'] = 'yes'
    else:
        simulate[iLoop].information['holemask'] = 'no'

    ### dir_path = os.path.join(os.environ['UNIT_TEST_DATA_ROOT'], 'ifs_sim/')
    # simulate[iLoop].information['dir_path'] = dir_path

    simulate[iLoop].information['debug'] = debug
    
    # simulate[iLoop].information['result_path'] = result_path
    
    simulate[iLoop].simulate(sourcein, iLoop)

    return 1

############################## end  ##########################################
