Skip to content
effects.py 28.8 KiB
Newer Older
Fang Yuedong's avatar
Fang Yuedong committed
import galsim
from matplotlib.pyplot import flag
import numpy as np
from numpy.core.fromnumeric import mean, size
from numpy.random import Generator, PCG64
Wei Chengliang's avatar
Wei Chengliang committed
import math
import copy
Fang Yuedong's avatar
Fang Yuedong committed
from numba import jit
from astropy import stats


def AddOverscan(GSImage, overscan=1000, gain=1, widthl=27, widthr=27, widtht=8, widthb=8, read_noise=5):
    """
    Add overscan/gain; gain=e-/ADU
    widthl: left pre-scan width
    widthr: right pre-scan width
    widtht: top over-scan width (the top of nd-array with small row-index)
    widthb: bottom over-scan width (the bottom of nd-array with large row-index)
    """
    imgshape = GSImage.array.shape
    newimg = galsim.Image(imgshape[1]+widthl+widthr, imgshape[0]+widtht+widthb, init_value=0)
    rng = galsim.UniformDeviate()
    NoiseOS = galsim.GaussianNoise(rng, sigma=read_noise)
    newimg.addNoise(NoiseOS)
    newimg = (newimg+overscan)/gain
Wei Chengliang's avatar
Wei Chengliang committed
    newimg.array[widtht:(widtht+imgshape[0]), widthl:(widthl+imgshape[1])] = GSImage.array
Fang Yuedong's avatar
Fang Yuedong committed
    newimg.wcs = GSImage.wcs
    # if GSImage.wcs is not None:
    #     newwcs = GSImage.wcs.withOrigin(galsim.PositionD(widthl,widtht))
    #     newimg.wcs = newwcs
    # else:
    #     pass
    return newimg


def DefectivePixels(GSImage, IfHotPix=True, IfDeadPix=True, fraction=1E-4, seed=20210304, biaslevel=0):
    # Also called bad pixels, including hot pixels and dead pixels
    # Hot Pixel > 20e-/s
    # Dead Pixel < 70%*Mean
    rgf = Generator(PCG64(int(seed*1.1)))
Wei Chengliang's avatar
Wei Chengliang committed
    if IfHotPix is True and IfDeadPix is True:
Fang Yuedong's avatar
Fang Yuedong committed
        HotFraction = rgf.random()             # fraction in total bad pixels
Wei Chengliang's avatar
Wei Chengliang committed
    elif IfHotPix is False and IfDeadPix is False:
Fang Yuedong's avatar
Fang Yuedong committed
        return GSImage
Wei Chengliang's avatar
Wei Chengliang committed
    elif IfHotPix is True:
Fang Yuedong's avatar
Fang Yuedong committed
        HotFraction = 1
    else:
        HotFraction = 0

    NPix = GSImage.array.size
    NPixBad = int(NPix*fraction)
    NPixHot = int(NPix*fraction*HotFraction)
    NPixDead = NPixBad-NPixHot
Wei Chengliang's avatar
Wei Chengliang committed

    NPix_y, NPix_x = GSImage.array.shape
Fang Yuedong's avatar
Fang Yuedong committed
    mean = np.mean(GSImage.array)
    rgp = Generator(PCG64(int(seed)))
Wei Chengliang's avatar
Wei Chengliang committed
    yxposfrac = rgp.random((NPixBad, 2))
Wei Chengliang's avatar
Wei Chengliang committed
    YPositHot = np.array(NPix_y*yxposfrac[0:NPixHot, 0]).astype(np.int32)
    XPositHot = np.array(NPix_x*yxposfrac[0:NPixHot, 1]).astype(np.int32)
    YPositDead = np.array(NPix_y*yxposfrac[NPixHot:, 0]).astype(np.int32)
    XPositDead = np.array(NPix_x*yxposfrac[NPixHot:, 1]).astype(np.int32)
Fang Yuedong's avatar
Fang Yuedong committed

    rgh = Generator(PCG64(int(seed*1.2)))
    rgd = Generator(PCG64(int(seed*1.3)))
Wei Chengliang's avatar
Wei Chengliang committed
    if IfHotPix is True:
        GSImage.array[YPositHot, XPositHot] += rgh.gamma(2, 25*150, size=NPixHot)
    if IfDeadPix is True:
        GSImage.array[YPositDead, XPositDead] = rgd.random(NPixDead)*(mean-biaslevel)*0.7+biaslevel+rgp.standard_normal()*5
Fang Yuedong's avatar
Fang Yuedong committed
    return GSImage


def BadColumns(GSImage, seed=20240309, chipid=1, logger=None):
    # Set bad column values
Wei Chengliang's avatar
Wei Chengliang committed
    ysize, xsize = GSImage.array.shape
Fang Yuedong's avatar
Fang Yuedong committed
    subarr = GSImage.array[int(ysize*0.1):int(ysize*0.12), int(xsize*0.1):int(xsize*0.12)]
    subarr = stats.sigma_clip(subarr, sigma=4, cenfunc='median', maxiters=3, masked=False)
    meanimg = np.median(subarr)
    stdimg = np.std(subarr)
    seed += chipid
    rgn = Generator(PCG64(int(seed)))
    rgcollen = Generator(PCG64(int(seed*1.1)))
    rgxpos = Generator(PCG64(int(seed*1.2)))
    rgdn = Generator(PCG64(int(seed*1.3)))

Wei Chengliang's avatar
Wei Chengliang committed
    nbadsecA, nbadsecD = rgn.integers(low=1, high=5, size=2)
    collen = rgcollen.integers(low=int(ysize*0.1), high=int(ysize*0.7), size=(nbadsecA+nbadsecD))
Fang Yuedong's avatar
Fang Yuedong committed
    xposit = rgxpos.integers(low=int(xsize*0.05), high=int(xsize*0.95), size=(nbadsecA+nbadsecD))
    if logger is not None:
        logger.info(xposit+1)
    else:
        print(xposit+1)
    # signs = 2*rgdn.integers(0,2,size=(nbadsecA+nbadsecD))-1
    # if meanimg>0:
Wei Chengliang's avatar
Wei Chengliang committed
    dn = rgdn.integers(low=np.abs(meanimg)*1.3+50, high=np.abs(meanimg)*2+150, size=(nbadsecA+nbadsecD))  # *signs
Fang Yuedong's avatar
Fang Yuedong committed
    # elif meanimg<0:
    #     dn = rgdn.integers(low=meanimg*2-150, high=meanimg*1.3-50, size=(nbadsecA+nbadsecD)) #*signs
    for badcoli in range(nbadsecA):
Wei Chengliang's avatar
Wei Chengliang committed
        GSImage.array[(ysize-collen[badcoli]):ysize, xposit[badcoli]:(xposit[badcoli]+1)] = (np.abs(np.random.normal(0, stdimg*2, (collen[badcoli], 1)))+dn[badcoli])
Fang Yuedong's avatar
Fang Yuedong committed
    for badcoli in range(nbadsecD):
Wei Chengliang's avatar
Wei Chengliang committed
        GSImage.array[0:collen[badcoli+nbadsecA], xposit[badcoli+nbadsecA]:(xposit[badcoli+nbadsecA]+1)] = (np.abs(np.random.normal(0, stdimg*2, (collen[badcoli+nbadsecA], 1)))+dn[badcoli+nbadsecA])
Fang Yuedong's avatar
Fang Yuedong committed
    return GSImage


Wei Chengliang's avatar
Wei Chengliang committed
def AddBiasNonUniform16(GSImage, bias_level=500, nsecy=2, nsecx=8, seed=202102, logger=None):
Fang Yuedong's avatar
Fang Yuedong committed
    # Generate Bias and its non-uniformity, and add the 16 bias values to the GS-Image
    rg = Generator(PCG64(int(seed)))
    Random16 = (rg.random(nsecy*nsecx)-0.5)*20
Wei Chengliang's avatar
Wei Chengliang committed
    if int(bias_level) == 0:
        BiasLevel = np.zeros((nsecy, nsecx))
    elif bias_level > 0:
Wei Chengliang's avatar
Wei Chengliang committed
        BiasLevel = Random16.reshape((nsecy, nsecx)) + bias_level
Fang Yuedong's avatar
Fang Yuedong committed
    if logger is not None:
        msg = str(" Biases of 16 channels: " + str(BiasLevel))
        logger.info(msg)
    else:
Wei Chengliang's avatar
Wei Chengliang committed
        print(" Biases of 16 channels:\n", BiasLevel)
Fang Yuedong's avatar
Fang Yuedong committed
    arrshape = GSImage.array.shape
    secsize_x = int(arrshape[1]/nsecx)
    secsize_y = int(arrshape[0]/nsecy)
    for rowi in range(nsecy):
        for coli in range(nsecx):
Wei Chengliang's avatar
Wei Chengliang committed
            GSImage.array[rowi*secsize_y:(rowi+1)*secsize_y, coli*secsize_x:(coli+1)*secsize_x] += BiasLevel[rowi, coli]
Fang Yuedong's avatar
Fang Yuedong committed
    return GSImage


def MakeBiasNcomb(npix_x, npix_y, bias_level=500, ncombine=1, read_noise=5, gain=1, seed=202102, logger=None):
    # Start with 0 value bias GS-Image
Wei Chengliang's avatar
Wei Chengliang committed
    ncombine = int(ncombine)
Fang Yuedong's avatar
Fang Yuedong committed
    BiasSngImg0 = galsim.Image(npix_x, npix_y, init_value=0)
Wei Chengliang's avatar
Wei Chengliang committed
    BiasSngImg = AddBiasNonUniform16(BiasSngImg0,
                                     bias_level=bias_level,
Wei Chengliang's avatar
Wei Chengliang committed
                                     nsecy=2, nsecx=8,
Wei Chengliang's avatar
Wei Chengliang committed
                                     seed=int(seed),
                                     logger=logger)
Fang Yuedong's avatar
Fang Yuedong committed
    BiasCombImg = BiasSngImg*ncombine
    rng = galsim.UniformDeviate()
    NoiseBias = galsim.GaussianNoise(rng=rng, sigma=read_noise*ncombine**0.5)
    BiasCombImg.addNoise(NoiseBias)
    if ncombine == 1:
        BiasTag = 'Single'
        pass
Wei Chengliang's avatar
Wei Chengliang committed
    elif ncombine > 1:
Fang Yuedong's avatar
Fang Yuedong committed
        BiasCombImg /= ncombine
        BiasTag = 'Combine'
    # BiasCombImg.replaceNegative(replace_value=0)
    # BiasCombImg.quantize()
    return BiasCombImg, BiasTag


Wei Chengliang's avatar
Wei Chengliang committed
def ApplyGainNonUniform16(GSImage, gain=1, nsecy=2, nsecx=8, seed=202102, logger=None):
Fang Yuedong's avatar
Fang Yuedong committed
    # Generate Gain non-uniformity, and multipy the different factors (mean~1 with sigma~1%) to the GS-Image
    rg = Generator(PCG64(int(seed)))
    Random16 = (rg.random(nsecy*nsecx)-0.5)*0.04+1   # sigma~1%
Wei Chengliang's avatar
Wei Chengliang committed
    Gain16 = Random16.reshape((nsecy, nsecx))/gain
Fang Yuedong's avatar
Fang Yuedong committed
    gain_array = np.ones(nsecy*nsecx)*gain
    if logger is not None:
        msg = str("Gain of 16 channels: " + str(Gain16))
        logger.info(msg)
    else:
Wei Chengliang's avatar
Wei Chengliang committed
        print("Gain of 16 channels: ", Gain16)
Fang Yuedong's avatar
Fang Yuedong committed
    arrshape = GSImage.array.shape
    secsize_x = int(arrshape[1]/nsecx)
    secsize_y = int(arrshape[0]/nsecy)
    for rowi in range(nsecy):
        for coli in range(nsecx):
Wei Chengliang's avatar
Wei Chengliang committed
            GSImage.array[rowi*secsize_y:(rowi+1)*secsize_y, coli*secsize_x:(coli+1)*secsize_x] *= Gain16[rowi, coli]
            gain_array[rowi*nsecx+coli] = 1/Gain16[rowi, coli]
Fang Yuedong's avatar
Fang Yuedong committed
    return GSImage, gain_array


Wei Chengliang's avatar
Wei Chengliang committed
def GainsNonUniform16(GSImage, gain=1, nsecy=2, nsecx=8, seed=202102, logger=None):
Fang Yuedong's avatar
Fang Yuedong committed
    # Generate Gain non-uniformity, and multipy the different factors (mean~1 with sigma~1%) to the GS-Image
    rg = Generator(PCG64(int(seed)))
    Random16 = (rg.random(nsecy*nsecx)-0.5)*0.04+1   # sigma~1%
Wei Chengliang's avatar
Wei Chengliang committed
    Gain16 = Random16.reshape((nsecy, nsecx))/gain
Fang Yuedong's avatar
Fang Yuedong committed
    if logger is not None:
        msg = str(seed-20210202, "Gains of 16 channels: " + str(Gain16))
        logger.info(msg)
    else:
        print(seed-20210202, "Gains of 16 channels:\n", Gain16)
    # arrshape = GSImage.array.shape
    # secsize_x = int(arrshape[1]/nsecx)
    # secsize_y = int(arrshape[0]/nsecy)
    # for rowi in range(nsecy):
    #     for coli in range(nsecx):
    #         GSImage.array[rowi*secsize_y:(rowi+1)*secsize_y,coli*secsize_x:(coli+1)*secsize_x] *= Gain16[rowi,coli]
    # return GSImage
    return Gain16


def MakeFlatSmooth(GSBounds, seed):
    rg = Generator(PCG64(int(seed)))
Wei Chengliang's avatar
Wei Chengliang committed
    r1, r2, r3, r4 = rg.random(4)
Fang Yuedong's avatar
Fang Yuedong committed
    a1 = -0.5 + 0.2*r1
    a2 = -0.5 + 0.2*r2
    a3 = r3+5
    a4 = r4+5
Wei Chengliang's avatar
Wei Chengliang committed
    xmin, xmax, ymin, ymax = GSBounds.getXMin(), GSBounds.getXMax(), GSBounds.getYMin(), GSBounds.getYMax()
Fang Yuedong's avatar
Fang Yuedong committed
    Flty, Fltx = np.mgrid[ymin:(ymax+1), xmin:(xmax+1)]
    rg = Generator(PCG64(int(seed)))
Wei Chengliang's avatar
Wei Chengliang committed
    p1, p2, bg = rg.poisson(1000, 3)
Fang Yuedong's avatar
Fang Yuedong committed
    Fltz = 0.6*1e-7*(a1 * (Fltx-p1) ** 2 + a2 * (Flty-p2) ** 2 - a3*Fltx - a4*Flty) + bg*20
    FlatImg = galsim.ImageF(Fltz)
    return FlatImg


def MakeFlatNcomb(flat_single_image, ncombine=1, read_noise=5, gain=1, overscan=500, biaslevel=500, seed_bias=20210311, logger=None):
Wei Chengliang's avatar
Wei Chengliang committed
    ncombine = int(ncombine)
Fang Yuedong's avatar
Fang Yuedong committed
    FlatCombImg = flat_single_image*ncombine
    rng = galsim.UniformDeviate()
    NoiseFlatPoi = galsim.PoissonNoise(rng=rng, sky_level=0)
    FlatCombImg.addNoise(NoiseFlatPoi)
    NoiseFlatReadN = galsim.GaussianNoise(rng=rng, sigma=read_noise*ncombine**0.5)
    FlatCombImg.addNoise(NoiseFlatReadN)
    # NoiseFlat = galsim.CCDNoise(rng, gain=gain, read_noise=read_noise*ncombine**0.5, sky_level=0)
    for i in range(ncombine):
        FlatCombImg = AddBiasNonUniform16(
Wei Chengliang's avatar
Wei Chengliang committed
            FlatCombImg,
            bias_level=biaslevel,
            nsecy=2, nsecx=8,
Fang Yuedong's avatar
Fang Yuedong committed
            seed=seed_bias,
            logger=logger)
    if ncombine == 1:
        FlatTag = 'Single'
        pass
Wei Chengliang's avatar
Wei Chengliang committed
    elif ncombine > 1:
Fang Yuedong's avatar
Fang Yuedong committed
        FlatCombImg /= ncombine
        FlatTag = 'Combine'
    # FlatCombImg.replaceNegative(replace_value=0)
    # FlatCombImg.quantize()
    return FlatCombImg, FlatTag


def MakeDarkNcomb(npix_x, npix_y, overscan=500, bias_level=500, seed_bias=202102, darkpsec=0.02, exptime=150, ncombine=10, read_noise=5, gain=1, logger=None):
Wei Chengliang's avatar
Wei Chengliang committed
    ncombine = int(ncombine)
Fang Yuedong's avatar
Fang Yuedong committed
    darkpix = darkpsec*exptime
    DarkSngImg = galsim.Image(npix_x, npix_y, init_value=darkpix)
    rng = galsim.UniformDeviate()
    NoiseDarkPoi = galsim.PoissonNoise(rng=rng, sky_level=0)
    NoiseReadN = galsim.GaussianNoise(rng=rng, sigma=read_noise*ncombine**0.5)
    DarkCombImg = DarkSngImg*ncombine
    DarkCombImg.addNoise(NoiseDarkPoi)
    DarkCombImg.addNoise(NoiseReadN)
    for i in range(ncombine):
        DarkCombImg = AddBiasNonUniform16(
Wei Chengliang's avatar
Wei Chengliang committed
            DarkCombImg,
            bias_level=bias_level,
Wei Chengliang's avatar
Wei Chengliang committed
            nsecy=2, nsecx=8,
Fang Yuedong's avatar
Fang Yuedong committed
            seed=int(seed_bias),
            logger=logger)
    if ncombine == 1:
        DarkTag = 'Single'
        pass
Wei Chengliang's avatar
Wei Chengliang committed
    elif ncombine > 1:
Fang Yuedong's avatar
Fang Yuedong committed
        DarkCombImg /= ncombine
        DarkTag = 'Combine'
    # DarkCombImg.replaceNegative(replace_value=0)
    # DarkCombImg.quantize()
    return DarkCombImg, DarkTag


def PRNU_Img(xsize, ysize, sigma=0.01, seed=202101):
    rg = Generator(PCG64(int(seed)))
Wei Chengliang's avatar
Wei Chengliang committed
    prnuarr = rg.normal(1, sigma, (ysize, xsize))
Fang Yuedong's avatar
Fang Yuedong committed
    prnuimg = galsim.ImageF(prnuarr)
    return prnuimg


def NonLinearity(GSImage, beta1=5E-7, beta2=0):
    NonLinear_f = lambda x, beta_1, beta_2: x - beta_1*x*x + beta_2*x*x*x
    GSImage.applyNonlinearity(NonLinear_f, beta1, beta2)
    return GSImage


Wei Chengliang's avatar
Wei Chengliang committed
# Saturation & Bleeding Start#
Fang Yuedong's avatar
Fang Yuedong committed
def BleedingTrail(aa, yy):
Wei Chengliang's avatar
Wei Chengliang committed
    if aa < 0.2:
        aa = 0.2
Fang Yuedong's avatar
Fang Yuedong committed
    else:
        pass
    try:
        fy = 0.5*(math.exp(math.log(yy+1)**3/aa)+np.exp(-1*math.log(yy+1)**3/aa))
Wei Chengliang's avatar
Wei Chengliang committed
        faa = 0.5*(math.e+1/math.e)
Fang Yuedong's avatar
Fang Yuedong committed
        trail_frac = 1-0.1*(fy-1)/(faa-1)
    except Exception as e:
        print(e)
        trail_frac = 1

    return trail_frac

Fang Yuedong's avatar
Fang Yuedong committed
def MakeTrail(imgarr, satuyxtuple, charge, fullwell=9e4, direction='up', trailcutfrac=0.9):
    '''
    direction: "up" or "down". For "up", bleeds along Y-decreasing direction; for "down", bleeds along Y-increasing direction.
    '''
Wei Chengliang's avatar
Wei Chengliang committed
    yi, xi = satuyxtuple
Fang Yuedong's avatar
Fang Yuedong committed
    aa = np.log(charge/fullwell)**3              # scale length of the bleeding trail
    yy = 1

Wei Chengliang's avatar
Wei Chengliang committed
    while charge > 0:
        if yi < 0 or yi > imgarr.shape[0]-1:
Fang Yuedong's avatar
Fang Yuedong committed
            break
Wei Chengliang's avatar
Wei Chengliang committed
        if yi == 0 or yi == imgarr.shape[0]-1:
            imgarr[yi, xi] = fullwell
Fang Yuedong's avatar
Fang Yuedong committed
            break
Wei Chengliang's avatar
Wei Chengliang committed
        if direction == 'up':
            if imgarr[yi-1, xi] >= fullwell:
                imgarr[yi, xi] = fullwell
Wei Chengliang's avatar
Wei Chengliang committed
                yi -= 1
Fang Yuedong's avatar
Fang Yuedong committed
                continue
Wei Chengliang's avatar
Wei Chengliang committed
        elif direction == 'down':
            if imgarr[yi+1, xi] >= fullwell:
                imgarr[yi, xi] = fullwell
                yi += 1
Fang Yuedong's avatar
Fang Yuedong committed
                continue
Wei Chengliang's avatar
Wei Chengliang committed
        if aa <= 1:
Wei Chengliang's avatar
Wei Chengliang committed
            while imgarr[yi, xi] >= fullwell:
                imgarr[yi, xi] = fullwell
                if direction == 'up':
                    imgarr[yi-1, xi] += charge
                    charge = imgarr[yi-1, xi]-fullwell
                    yi -= 1
                    if yi < 0:
Fang Yuedong's avatar
Fang Yuedong committed
                        break
Wei Chengliang's avatar
Wei Chengliang committed
                elif direction == 'down':
                    imgarr[yi+1, xi] += charge
                    charge = imgarr[yi+1, xi]-fullwell
                    yi += 1
                    if yi > imgarr.shape[0]:
Fang Yuedong's avatar
Fang Yuedong committed
                        break
        else:
            # calculate bleeding trail:
Wei Chengliang's avatar
Wei Chengliang committed
            trail_frac = BleedingTrail(aa, yy)
Fang Yuedong's avatar
Fang Yuedong committed

            # put charge upwards
Wei Chengliang's avatar
Wei Chengliang committed
            if trail_frac >= 0.99:
                imgarr[yi, xi] = fullwell
                if direction == 'up':
                    yi -= 1
                elif direction == 'down':
                    yi += 1
Fang Yuedong's avatar
Fang Yuedong committed
                yy += 1
            else:
Wei Chengliang's avatar
Wei Chengliang committed
                if trail_frac < trailcutfrac:
Fang Yuedong's avatar
Fang Yuedong committed
                    break
                charge = fullwell*trail_frac
Wei Chengliang's avatar
Wei Chengliang committed
                imgarr[yi, xi] += charge
                if imgarr[yi, xi] > fullwell:
                    imgarr[yi, xi] = fullwell

                if direction == 'up':
                    yi -= 1
                elif direction == 'down':
                    yi += 1
Fang Yuedong's avatar
Fang Yuedong committed
                yy += 1

    return imgarr


def ChargeFlow(imgarr, fullwell=9E4):
Wei Chengliang's avatar
Wei Chengliang committed
    size_y, size_x = imgarr.shape
Wei Chengliang's avatar
Wei Chengliang committed
    satupos_y, satupos_x = np.where(imgarr > fullwell)
Fang Yuedong's avatar
Fang Yuedong committed

Wei Chengliang's avatar
Wei Chengliang committed
    if satupos_y.shape[0] == 0:
Fang Yuedong's avatar
Fang Yuedong committed
        # make no change for the image array
        return imgarr
    elif satupos_y.shape[0]/imgarr.size > 0.5:
        imgarr.fill(fullwell)
        return imgarr

    chargedict = {}
    imgarrorig = copy.deepcopy(imgarr)

Wei Chengliang's avatar
Wei Chengliang committed
    for yi, xi in zip(satupos_y, satupos_x):
        yxidx = ''.join([str(yi), str(xi)])
        chargedict[yxidx] = imgarrorig[yi, xi]-fullwell
Fang Yuedong's avatar
Fang Yuedong committed

Wei Chengliang's avatar
Wei Chengliang committed
    for yi, xi in zip(satupos_y, satupos_x):
        yxidx = ''.join([str(yi), str(xi)])
Fang Yuedong's avatar
Fang Yuedong committed
        satcharge = chargedict[yxidx]
        chargeup = ((np.random.random()-0.5)*0.05+0.5)*satcharge
        chargedn = satcharge - chargeup

        try:
            # Charge Clump moves up
Wei Chengliang's avatar
Wei Chengliang committed
            if yi >= 0 and yi < imgarr.shape[0]:
Wei Chengliang's avatar
Wei Chengliang committed
                imgarr = MakeTrail(imgarr, (yi, xi), chargeup, fullwell=9e4, direction='up', trailcutfrac=0.9)
Fang Yuedong's avatar
Fang Yuedong committed
                # Charge Clump moves down
Wei Chengliang's avatar
Wei Chengliang committed
                imgarr = MakeTrail(imgarr, (yi, xi), chargedn, fullwell=9e4, direction='down', trailcutfrac=0.9)
Fang Yuedong's avatar
Fang Yuedong committed
        except Exception as e:
Wei Chengliang's avatar
Wei Chengliang committed
            print(e, '@pix ', (yi+1, xi+1))
Fang Yuedong's avatar
Fang Yuedong committed
            return imgarr
    return imgarr

Fang Yuedong's avatar
Fang Yuedong committed
def SaturBloom(GSImage, nsect_x=1, nsect_y=1, fullwell=9e4):
    """
    To simulate digital detector's saturation and blooming effect. The blooming is along the read-out direction, perpendicular to the charge transfer direction. Charge clumpy overflows the pixel well will flow to two oposite directions with nearly same charges.
    Work together with chargeflow() function.
    Parameters:
      GSImage: a GalSim Image of a chip;
      nsect_x: number of sections divided along x direction;
      nsect_y: number of sections divided along y direction.
    Return: a saturated image array with the same size of input.
    """
    imgarr = GSImage.array
    size_y, size_x = imgarr.shape
    subsize_y = int(size_y/nsect_y)
    subsize_x = int(size_x/nsect_x)

    for i in range(nsect_y):
        for j in range(nsect_x):
            subimg = imgarr[subsize_y*i:subsize_y*(i+1), subsize_x*j:subsize_x*(j+1)]

            subimg = ChargeFlow(subimg, fullwell=fullwell)

            imgarr[subsize_y*i:subsize_y*(i+1), subsize_x*j:subsize_x*(j+1)] = subimg

    return GSImage

Wei Chengliang's avatar
Wei Chengliang committed
#    Saturation & Bleeding End    #
Fang Yuedong's avatar
Fang Yuedong committed


def readout16(GSImage, rowi=0, coli=0, overscan_value=0):
    # readout image as 16 outputs of sub-images plus prescan & overscan.
    # assuming image width and height sizes are both even.
Wei Chengliang's avatar
Wei Chengliang committed
    # assuming image has 2 columns and 8 rows of output channels.
Fang Yuedong's avatar
Fang Yuedong committed
    # 00  01
    # 10  11
    # 20  21
    # ...
    # return: GS Image Object
Wei Chengliang's avatar
Wei Chengliang committed
    npix_y, npix_x = GSImage.array.shape
Fang Yuedong's avatar
Fang Yuedong committed
    subheight = int(8+npix_y/2+8)
    subwidth = int(16+npix_x/8+27)
    OutputSubimg = galsim.ImageUS(subwidth, subheight, init_value=overscan_value)
Wei Chengliang's avatar
Wei Chengliang committed
    if rowi < 4 and coli == 0:
Fang Yuedong's avatar
Fang Yuedong committed
        subbounds = galsim.BoundsI(1, int(npix_x/2),  int(npix_y/8*rowi+1), int(npix_y/8*(rowi+1)))
        subbounds = subbounds.shift(galsim.PositionI(GSImage.bounds.getXMin()-1, GSImage.bounds.getYMin()-1))
        subimg = GSImage[subbounds]
Wei Chengliang's avatar
Wei Chengliang committed
        OutputSubimg.array[27:int(npix_y/8)+27, 8:int(npix_x/2)+8] = subimg.array
    elif rowi < 4 and coli == 1:
Fang Yuedong's avatar
Fang Yuedong committed
        subbounds = galsim.BoundsI(npix_x/2+1, npix_x,  npix_y/8*rowi+1, npix_y/8*(rowi+1))
        subbounds = subbounds.shift(galsim.PositionI(GSImage.bounds.getXMin()-1, GSImage.bounds.getYMin()-1))
        subimg = GSImage[subbounds]
Wei Chengliang's avatar
Wei Chengliang committed
        OutputSubimg.array[27:int(npix_y/8)+27, 8:int(npix_x/2)+8] = subimg.array
    elif rowi >= 4 and rowi < 8 and coli == 0:
Fang Yuedong's avatar
Fang Yuedong committed
        subbounds = galsim.BoundsI(1, npix_x/2,  npix_y/8*rowi+1, npix_y/8*(rowi+1))
        subbounds = subbounds.shift(galsim.PositionI(GSImage.bounds.getXMin()-1, GSImage.bounds.getYMin()-1))
        subimg = GSImage[subbounds]
Wei Chengliang's avatar
Wei Chengliang committed
        OutputSubimg.array[16:int(npix_y/8)+16, 8:int(npix_x/2)+8] = subimg.array
    elif rowi >= 4 and rowi < 8 and coli == 1:
Fang Yuedong's avatar
Fang Yuedong committed
        subbounds = galsim.BoundsI(npix_x/2+1, npix_x,  npix_y/8*rowi+1, npix_y/8*(rowi+1))
        subbounds = subbounds.shift(galsim.PositionI(GSImage.bounds.getXMin()-1, GSImage.bounds.getYMin()-1))
        subimg = GSImage[subbounds]
Wei Chengliang's avatar
Wei Chengliang committed
        OutputSubimg.array[16:int(npix_y/8)+16, 8:int(npix_x/2)+8] = subimg.array
Fang Yuedong's avatar
Fang Yuedong committed
    else:
        print("\n\033[31mError: "+"Wrong rowi or coli assignment. Permitted: 0<=rowi<=7, 0<=coli<=1."+"\033[0m\n")
        return OutputSubimg
    return OutputSubimg


def CTE_Effect(GSImage, threshold=27, direction='column'):
    # Devide the image into 4 sections and apply CTE effect with different trail directions.
    # GSImage: a GalSim Image object.
Wei Chengliang's avatar
Wei Chengliang committed
    size_y, size_x = GSImage.array.shape
Fang Yuedong's avatar
Fang Yuedong committed
    size_sect_y = int(size_y/2)
    size_sect_x = int(size_x/2)
    imgarr = GSImage.array
    if direction == 'column':
Wei Chengliang's avatar
Wei Chengliang committed
        imgarr[0:size_sect_y, :] = CTEModelColRow(imgarr[0:size_sect_y, :], trail_direction='down', direction='column', threshold=threshold)
Wei Chengliang's avatar
Wei Chengliang committed
        imgarr[size_sect_y:size_y, :] = CTEModelColRow(imgarr[size_sect_y:size_y, :], trail_direction='up', direction='column', threshold=threshold)
Fang Yuedong's avatar
Fang Yuedong committed
    elif direction == 'row':
Wei Chengliang's avatar
Wei Chengliang committed
        imgarr[:, 0:size_sect_x] = CTEModelColRow(imgarr[:, 0:size_sect_x], trail_direction='right', direction='row', threshold=threshold)
        imgarr[:, size_sect_x:size_x] = CTEModelColRow(imgarr[:, size_sect_x:size_x], trail_direction='left', direction='row', threshold=threshold)
Fang Yuedong's avatar
Fang Yuedong committed
    return GSImage


@jit()
Wei Chengliang's avatar
Wei Chengliang committed
def CTEModelColRow(img, trail_direction='up', direction='column', threshold=27):
Fang Yuedong's avatar
Fang Yuedong committed

Wei Chengliang's avatar
Wei Chengliang committed
    # total trail flux vs (pixel flux)^1/2 is approximately linear
    # total trail flux = trail_a * (pixel flux)^1/2 + trail_b
    # trail pixel flux = pow(0.5,x)/0.5, normalize to 1
Wei Chengliang's avatar
Wei Chengliang committed
    trail_a = 5.651803799619966
Fang Yuedong's avatar
Fang Yuedong committed
    trail_b = -2.671933068990729

    sh1 = img.shape[0]
    sh2 = img.shape[1]
    n_img = img*0
Wei Chengliang's avatar
Wei Chengliang committed
    idx = np.where(img < threshold)
Fang Yuedong's avatar
Fang Yuedong committed
    if len(idx[0]) == 0:
        pass
Wei Chengliang's avatar
Wei Chengliang committed
    elif len(idx[0]) > 0:
Fang Yuedong's avatar
Fang Yuedong committed
        n_img[idx] = img[idx]

Wei Chengliang's avatar
Wei Chengliang committed
    yidx, xidx = np.where(img >= threshold)
Fang Yuedong's avatar
Fang Yuedong committed
    if len(yidx) == 0:
        pass
Wei Chengliang's avatar
Wei Chengliang committed
    elif len(yidx) > 0:
Fang Yuedong's avatar
Fang Yuedong committed
        # print(index)
Wei Chengliang's avatar
Wei Chengliang committed
        for i, j in zip(yidx, xidx):
            f = img[i, j]
Fang Yuedong's avatar
Fang Yuedong committed
            trail_f = (np.sqrt(f)*trail_a + trail_b)*0.5
            # trail_f=5E-5*f**1.5
            xy_num = 10
            all_trail = np.zeros(xy_num)
Wei Chengliang's avatar
Wei Chengliang committed
            xy_upstr = np.arange(1, xy_num, 1)
Fang Yuedong's avatar
Fang Yuedong committed

            # all_trail_pix = np.sum(pow(0.5,xy_upstr)/0.5)
            all_trail_pix = 0
            for m in xy_upstr:
Wei Chengliang's avatar
Wei Chengliang committed
                a1 = 12.97059491
Wei Chengliang's avatar
Wei Chengliang committed
                b1 = 0.54286652
                c1 = 0.69093105
                a2 = 2.77298856
                b2 = 0.11231055
                c2 = -0.01038675
Fang Yuedong's avatar
Fang Yuedong committed
                # t_pow = 0
Wei Chengliang's avatar
Wei Chengliang committed
                am = 1
                bm = 1
Fang Yuedong's avatar
Fang Yuedong committed
                t_pow = am*np.exp(-bm*m)
                # if m < 5:
                #     t_pow = a1*np.exp(-b1*m)+c1
                # else:
                #     t_pow = a2*np.exp(-b2*m)+c2
Wei Chengliang's avatar
Wei Chengliang committed
                if t_pow < 0:
Fang Yuedong's avatar
Fang Yuedong committed
                    t_pow = 0

                all_trail_pix += t_pow
                all_trail[m] = t_pow
            trail_pix_eff = trail_f/all_trail_pix
            all_trail = trail_pix_eff*all_trail
            all_trail[0] = f - trail_f
            
Wei Chengliang's avatar
Wei Chengliang committed
            for m in np.arange(0, xy_num, 1):
Fang Yuedong's avatar
Fang Yuedong committed
                if direction == 'column':
                    if trail_direction == 'down':
                        y_pos = i + m
                    elif trail_direction == 'up':
                        y_pos = i - m
Wei Chengliang's avatar
Wei Chengliang committed
                    if y_pos < 0 or y_pos >= sh1:
Fang Yuedong's avatar
Fang Yuedong committed
                        break
Wei Chengliang's avatar
Wei Chengliang committed
                    n_img[y_pos, j] = n_img[y_pos, j] + all_trail[m]
Fang Yuedong's avatar
Fang Yuedong committed
                elif direction == 'row':
                    if trail_direction == 'left':
                        x_pos = j - m
                    elif trail_direction == 'right':
                        x_pos = j + m
Wei Chengliang's avatar
Wei Chengliang committed
                    if x_pos < 0 or x_pos >= sh2:
Fang Yuedong's avatar
Fang Yuedong committed
                        break
Wei Chengliang's avatar
Wei Chengliang committed
                    n_img[i, x_pos] = n_img[i, x_pos] + all_trail[m]
Fang Yuedong's avatar
Fang Yuedong committed

    return n_img


Wei Chengliang's avatar
Wei Chengliang committed
# ---------- For Cosmic-Ray Simulation ------------
# ---------- Zhang Xin ----------------------------
Fang Yuedong's avatar
Fang Yuedong committed
def getYValue(collection, x):
    index = 0;
    if (collection.shape[1] == 2):
Wei Chengliang's avatar
Wei Chengliang committed
        while(x>collection[index, 0] and index < collection.shape[0]):
Fang Yuedong's avatar
Fang Yuedong committed
            index= index + 1;
        if (index == collection.shape[0] or index == 0):
            return 0;

        deltX = collection[index, 0] - collection[index-1, 0];
        deltY = collection[index, 1] - collection[index-1, 1];

        if deltX == 0:
            return (collection[index, 1] + collection[index-1, 1])/2.0
        else:
            a = deltY/deltX;
Wei Chengliang's avatar
Wei Chengliang committed
            return a * (x - collection[index-1, 0]) + collection[index-1, 1];
Fang Yuedong's avatar
Fang Yuedong committed
    return 0;


Wei Chengliang's avatar
Wei Chengliang committed
def selectCosmicRayCollection(attachedSizes, xLen, yLen, cr_pixelRatio, CR_max_size):
Fang Yuedong's avatar
Fang Yuedong committed

    normalRay = 0.90
    nnormalRay = 1-normalRay
    max_nrayLen = 100
    pixelNum = int(xLen *  yLen * cr_pixelRatio * normalRay );
    pixelNum_n = int(xLen *  yLen * cr_pixelRatio * nnormalRay )
    CRPixelNum = 0;
    
Wei Chengliang's avatar
Wei Chengliang committed
    maxValue = max(attachedSizes[:, 1])
Fang Yuedong's avatar
Fang Yuedong committed
    maxValue += 0.1;

    cr_event_num = 0;
    CRs = np.zeros(pixelNum);
    while (CRPixelNum < pixelNum):
        x = CR_max_size * np.random.random();
        y = maxValue * np.random.random();
        if (y <= getYValue(attachedSizes, x)):
            CRs[cr_event_num] = np.ceil(x);
            cr_event_num = cr_event_num + 1;
            CRPixelNum = CRPixelNum + round(x);

    while (CRPixelNum < pixelNum + pixelNum_n):
        nx = np.random.random()*(max_nrayLen-CR_max_size)+CR_max_size
        CRs[cr_event_num] = np.ceil(nx);
        cr_event_num = cr_event_num + 1;
        CRPixelNum = CRPixelNum + np.ceil(nx);

    return   CRs[0:cr_event_num];


Wei Chengliang's avatar
Wei Chengliang committed
def defineEnergyForCR(cr_event_size, seed = 12345):
Fang Yuedong's avatar
Fang Yuedong committed
    import random
    sigma = 0.6 / 2.355;
    mean = 3.3;
    random.seed(seed)
    energys = np.zeros(cr_event_size);
    for i in np.arange(cr_event_size):
        energy_index = random.normalvariate(mean,sigma);
        energys[i] = pow(10, energy_index);

    return energys;

def convCR(CRmap=None, addPSF=None, sp_n = 4):
    sh = CRmap.shape

    # sp_n = 4
    subCRmap = np.zeros(np.array(sh)*sp_n)
    pix_v0 = 1/(sp_n*sp_n)
    for i in np.arange(sh[0]):
        i_st = sp_n*i
        for j in np.arange(sh[1]):
            if CRmap[i,j] ==0:
                continue
            j_st = sp_n*j
Wei Chengliang's avatar
Wei Chengliang committed
            pix_v1 = CRmap[i, j]*pix_v0
Fang Yuedong's avatar
Fang Yuedong committed
            for m in np.arange(sp_n):
                for n in np.arange(sp_n):
                    subCRmap[i_st+m, j_st + n] = pix_v1

    m_size = addPSF.shape[0]

    subCRmap_n = np.zeros(np.array(subCRmap.shape) + m_size -1)

    for i in np.arange(subCRmap.shape[0]):
        for j in np.arange(subCRmap.shape[1]):
Wei Chengliang's avatar
Wei Chengliang committed
            if subCRmap[i, j]>0:
                convPix = addPSF*subCRmap[i, j]
                subCRmap_n[i:i+m_size, j:j+m_size] += convPix
Fang Yuedong's avatar
Fang Yuedong committed

    CRmap_n = np.zeros((np.array(subCRmap_n.shape)/sp_n).astype(np.int32))
    sh_n = CRmap_n.shape

    for i in np.arange(sh_n[0]):
        i_st = sp_n*i
        for j in np.arange(sh_n[1]):
            p_v = 0
            j_st=sp_n*j
            for m in np.arange(sp_n):
                for n in np.arange(sp_n):
                    p_v += subCRmap_n[i_st+m, j_st + n]

Wei Chengliang's avatar
Wei Chengliang committed
            CRmap_n[i, j] = p_v
Fang Yuedong's avatar
Fang Yuedong committed

    return CRmap_n


def produceCR_Map(xLen, yLen, exTime, cr_pixelRatio, gain, attachedSizes, seed=20210317):
    # Return: an 2-D numpy array
    # attachedSizes = np.loadtxt('./wfc-cr-attachpixel.dat');
    np.random.seed(seed)
    CR_max_size = 20.0;
    cr_size = selectCosmicRayCollection(attachedSizes, xLen, yLen, cr_pixelRatio, CR_max_size);

    cr_event_size = cr_size.shape[0];
    cr_energys = defineEnergyForCR(cr_event_size,seed = seed);

    CRmap = np.zeros([yLen, xLen]);

Wei Chengliang's avatar
Wei Chengliang committed
    # produce conv kernel
Fang Yuedong's avatar
Fang Yuedong committed
    from astropy.modeling.models import Gaussian2D
    o_size = 4
    sp_n = 8

    m_size = o_size*sp_n+1
    m_cen = o_size*sp_n/2
    sigma_psf = 0.2*sp_n
    addPSF_ = Gaussian2D(1, m_cen, m_cen, sigma_psf, sigma_psf)
    yp, xp = np.mgrid[0:m_size, 0:m_size]
    addPSF = addPSF_(xp, yp)
    convKernel = addPSF/addPSF.sum()

Wei Chengliang's avatar
Wei Chengliang committed
    #---------------------------------
Fang Yuedong's avatar
Fang Yuedong committed


    for i in np.arange(cr_event_size):
        xPos = round((xLen - 1)* np.random.random());
        yPos = round((yLen - 1)* np.random.random());
        cr_lens = int(cr_size[i]);
        if cr_lens ==0:
            continue;
        pix_energy = cr_energys[i]/gain/cr_lens;
        pos_angle = 1/2*math.pi*np.random.random();

        crMatrix = np.zeros([cr_lens+1, cr_lens + 1])

        for j in np.arange(cr_lens):
            x_n = int(np.cos(pos_angle)*j - np.sin(pos_angle)*0);
            if x_n < 0:
                x_n = x_n + cr_lens+1
            y_n = int(np.sin(pos_angle)*j + np.cos(pos_angle)*0);
Wei Chengliang's avatar
Wei Chengliang committed
            if x_n < 0 or x_n > cr_lens or y_n < 0 or y_n > cr_lens:
Fang Yuedong's avatar
Fang Yuedong committed
                continue;
Wei Chengliang's avatar
Wei Chengliang committed
            crMatrix[y_n, x_n] = pix_energy;
Fang Yuedong's avatar
Fang Yuedong committed

        crMatrix_n = convCR(crMatrix, convKernel, sp_n)
        # crMatrix_n = crMatrix

        xpix = np.arange(crMatrix_n.shape[0]) + int(xPos)
        ypix = np.arange(crMatrix_n.shape[1]) + int(yPos)

        sh = CRmap.shape
        okx = (xpix >= 0) & (xpix < sh[1])
        oky = (ypix >= 0) & (ypix < sh[0])

        sly = slice(ypix[oky].min(), ypix[oky].max()+1)
        slx = slice(xpix[okx].min(), xpix[okx].max()+1)
Wei Chengliang's avatar
Wei Chengliang committed
        CRmap[sly, slx] += crMatrix_n[oky, :][:, okx]
Fang Yuedong's avatar
Fang Yuedong committed
    return CRmap.astype(np.int32), cr_event_size


def ShutterEffectArr(GSImage, t_exp=150, t_shutter=1.3, dist_bearing=735, dt=1E-3):
    # Generate Shutter-Effect normalized image
    # t_shutter: time of shutter movement
    # dist_bearing: distance between two bearings of shutter leaves
    # dt: delta_t of sampling

    from scipy import interpolate

    SampleNumb = int(t_shutter/dt+1)
    DistHalf = dist_bearing/2

    t = np.arange(SampleNumb)*dt
    a_arr = 5.84*np.sin(2*math.pi/t_shutter*t)
    v = np.zeros(SampleNumb)
    theta = np.zeros(SampleNumb)
    x = np.arange(SampleNumb)/(SampleNumb-1)*dist_bearing
    s = np.zeros(SampleNumb)
    s1 = np.zeros(SampleNumb)
    s2 = np.zeros(SampleNumb)
    brt  = np.zeros(SampleNumb)
    idx = np.arange(SampleNumb)
    sidx = np.zeros(SampleNumb)
    s1idx = np.zeros(SampleNumb)
    s2idx = np.zeros(SampleNumb)

    v[0] = 0
    theta[0] = 0

    for i in range(SampleNumb-1):
        v[i+1] = v[i]+a_arr[i]*dt
        theta[i+1] = theta[i]+v[i]*dt
        s1[i] = DistHalf*np.cos(theta[i])
        s2[i] = dist_bearing-DistHalf*np.cos(theta[i])
        s1idx[i] = int(s1[i]/dist_bearing*(SampleNumb))
        s2idx[i] = int(s2[i]/dist_bearing*(SampleNumb))
Wei Chengliang's avatar
Wei Chengliang committed
        brt[(idx > s1idx[i]) & (idx < s2idx[i])] += dt
Fang Yuedong's avatar
Fang Yuedong committed

Wei Chengliang's avatar
Wei Chengliang committed
    if t_exp > t_shutter*2:
Fang Yuedong's avatar
Fang Yuedong committed
        brt = brt*2+(t_exp-t_shutter*2)
    else:
        brt = brt*2

    x = (x-dist_bearing/2)*100

    intp = interpolate.splrep(x, brt, s=0)

    xmin = GSImage.bounds.getXMin()
    xmax = GSImage.bounds.getXMax()
    ymin = GSImage.bounds.getYMin()
    ymax = GSImage.bounds.getYMax()
Wei Chengliang's avatar
Wei Chengliang committed
    if xmin < np.min(x) or xmax > np.max(x):
Fang Yuedong's avatar
Fang Yuedong committed
        raise LookupError("Out of focal-plane bounds in X-direction.")
Wei Chengliang's avatar
Wei Chengliang committed
    if ymin < -25331 or ymax > 25331:
Fang Yuedong's avatar
Fang Yuedong committed
        raise LookupError("Out of focal-plane bounds in Y-direction.")
    sizex = xmax-xmin+1
    sizey = ymax-ymin+1
    xnewgrid = np.mgrid[xmin:(xmin+sizex)]
    expeffect = interpolate.splev(xnewgrid, intp, der=0)
Zhang Xin's avatar
Zhang Xin committed
    expeffect /= t_exp
Wei Chengliang's avatar
Wei Chengliang committed
    exparrnormal = np.tile(expeffect, (sizey, 1))
Fang Yuedong's avatar
Fang Yuedong committed
    # GSImage *= exparrnormal

    return exparrnormal