import numpy as np import galsim import os, sys import astropy.constants as cons from astropy.table import Table from ._util import eObs, integrate_sed_bandpass, getNormFactorForSpecWithABMAG, getObservedSED, getABMAG,convolveGaussXorders from .SpecDisperser import SpecDisperser from .MockObject import MockObject from scipy import interpolate class Galaxy(MockObject): def __init__(self, param, rotation=None): super().__init__(param) self.thetaR = self.param["theta"] self.bfrac = self.param["bfrac"] self.hlr_disk = self.param["hlr_disk"] self.hlr_bulge = self.param["hlr_bulge"] # Extract ellipticity components self.e_disk = galsim.Shear(g=self.param["ell_disk"], beta=self.thetaR*galsim.degrees) self.e_bulge = galsim.Shear(g=self.param["ell_bulge"], beta=self.thetaR*galsim.degrees) self.e_total = galsim.Shear(g=self.param["ell_tot"], beta=self.thetaR*galsim.degrees) self.e1_disk, self.e2_disk = self.e_disk.g1, self.e_disk.g2 self.e1_bulge, self.e2_bulge = self.e_bulge.g1, self.e_bulge.g2 self.e1_total, self.e2_total = self.e_total.g1, self.e_total.g2 if rotation is not None: self.rotateEllipticity(rotation) def load_SED(self, survey_type, sed_path, cosids=None, objtypes=None, sed_templates=None, normFilter=None, target_filt=None): if survey_type == "photometric": norm_thr_rang_ids = normFilter['SENSITIVITY'] > 0.001 if sed_templates is None: # Read SED data directly itype = objtypes[cosids==self.sed_type][0] sed_file = os.path.join(sed_path, itype + "_ID%s.sed"%(self.sed_type)) if not os.path.exists(sed_file): raise ValueError("!!! No SED found.") sed_data = Table.read(sed_file, format="ascii") wave, flux = sed_data["observedLambda"].data, sed_data["observedFlux"].data else: # Load SED from templates sed_data = sed_templates[self.sed_type] # redshift, intrinsic extinction sed_data = getObservedSED( sedCat=sed_data, redshift=self.z, av=self.param['av'], redden=self.param['redden']) wave, flux = sed_data[0], sed_data[1] flux_photon = flux * (wave / (cons.h.value * cons.c.value)) * 1e-13 sed_photon = Table(np.array([wave, flux_photon]).T, names=('WAVELENGTH', 'FLUX')) # Get scaling factor for SED sedNormFactor = getNormFactorForSpecWithABMAG(ABMag=self.param['mag_use_normal'], spectrum=sed_photon, norm_thr=normFilter, sWave=np.floor(normFilter[norm_thr_rang_ids][0][0]), eWave=np.ceil(normFilter[norm_thr_rang_ids][-1][0])) sed_photon = np.array([sed_photon['WAVELENGTH'], sed_photon['FLUX']*sedNormFactor]).T # Convert to galsim.SED object spec = galsim.LookupTable(x=np.array(sed_photon[:, 0]), f=np.array(sed_photon[:, 1]), interpolant='nearest') self.sed = galsim.SED(spec, wave_type='A', flux_type='1', fast=False) # Get magnitude interFlux = integrate_sed_bandpass(sed=self.sed, bandpass=target_filt.bandpass_full) self.param['mag_%s'%target_filt.filter_type] = getABMAG( interFlux=interFlux, bandpass=target_filt.bandpass_full) # print('mag_use_normal = ', self.param['mag_use_normal']) # print('mag_%s = '%target_filt.filter_type, self.param['mag_%s'%target_filt.filter_type]) # print('redshift = %.3f'%(self.z)) # print('sed_type = %d, av = %.2f, redden = %d'%(self.sed_type, self.param['av'], self.param['redden'])) elif survey_type == "spectroscopic": if sed_templates is None: self.sedPhotons(sed_path=sed_path, cosids=cosids, objtypes=objtypes) else: sed_data = sed_templates[self.sed_type] sed_data = getObservedSED( sedCat=sed_data, redshift=self.z, av=self.param['av'], redden=self.param['redden']) speci = interpolate.interp1d(sed_data[0], sed_data[1]) lamb = np.arange(2500, 10001 + 0.5, 0.5) y = speci(lamb) # erg/s/cm2/A --> photo/s/m2/A all_sed = y * lamb / (cons.h.value * cons.c.value) * 1e-13 self.sed = Table(np.array([lamb, all_sed]).T, names=('WAVELENGTH', 'FLUX')) def unload_SED(self): """(Test) free up SED memory """ del self.sed def sedPhotons(self, sed_path, cosids, objtypes): itype = objtypes[cosids == self.sed_type][0] sed_file = os.path.join(sed_path, itype + "_ID%s.sed" % (self.sed_type)) if not os.path.exists(sed_file): raise ValueError("!!! No SED found.") sed = Table.read(sed_file, format="ascii") spec_data = {} f_orig = sed["observedFlux"].data w_orig = sed["observedLambda"].data speci = interpolate.interp1d(w_orig, f_orig) lamb = np.arange(2500, 10001 + 0.5, 0.5) y = speci(lamb) # erg/s/cm2/A --> photo/s/m2/A all_sed = y * lamb / (cons.h.value * cons.c.value) * 1e-13 self.sed = Table(np.array([lamb, all_sed]).T, names=('WAVELENGTH', 'FLUX')) def getGSObj_multiband(self, tel, psf_list, bandpass_list, filt, nphotons_tot=None, g1=0, g2=0, exptime=150.): if len(psf_list) != len(bandpass_list): raise ValueError("!!!The number of PSF profiles and the number of bandpasses must be equal.") objs = [] if nphotons_tot == None: nphotons_tot = self.getElectronFluxFilt(filt, tel, exptime) # print("nphotons_tot = ", nphotons_tot) try: full = integrate_sed_bandpass(sed=self.sed, bandpass=filt.bandpass_full) except Exception as e: print(e) return -1 for i in range(len(bandpass_list)): bandpass = bandpass_list[i] try: sub = integrate_sed_bandpass(sed=self.sed, bandpass=bandpass) except Exception as e: print(e) return -1 ratio = sub/full if not (ratio == -1 or (ratio != ratio)): nphotons = ratio * nphotons_tot else: return -1 psf = psf_list[i] disk = galsim.Sersic(n=1.0, half_light_radius=self.hlr_disk, flux=1.0) disk_shape = galsim.Shear(g1=self.e1_disk, g2=self.e2_disk) disk = disk.shear(disk_shape) bulge = galsim.Sersic(n=4.0, half_light_radius=self.hlr_bulge, flux=1.0) bulge_shape = galsim.Shear(g1=self.e1_bulge, g2=self.e2_bulge) bulge = bulge.shear(bulge_shape) gal = self.bfrac * bulge + (1.0 - self.bfrac) * disk gal = gal.withFlux(nphotons) gal_shear = galsim.Shear(g1=g1, g2=g2) gal = gal.shear(gal_shear) gal = galsim.Convolve(psf, gal) objs.append(gal) final = galsim.Sum(objs) return final def drawObj_multiband(self, tel, pos_img, psf_model, bandpass_list, filt, chip, nphotons_tot=None, g1=0, g2=0, exptime=150.): if nphotons_tot == None: nphotons_tot = self.getElectronFluxFilt(filt, tel, exptime) # print("nphotons_tot = ", nphotons_tot) try: full = integrate_sed_bandpass(sed=self.sed, bandpass=filt.bandpass_full) except Exception as e: print(e) return False nphotons_sum = 0 photons_list = [] xmax, ymax = 0, 0 # print('hlr_disk = %.4f, hlr_bulge = %.4f'%(self.hlr_disk, self.hlr_bulge)) big_galaxy = False if self.hlr_disk > 3.0: # Very big galaxy big_galaxy = True # (TEST) Galsim Parameters if self.getMagFilter(filt) <= 15 and (not big_galaxy): folding_threshold = 5.e-4 else: folding_threshold = 5.e-3 gsp = galsim.GSParams(folding_threshold=folding_threshold) for i in range(len(bandpass_list)): bandpass = bandpass_list[i] try: sub = integrate_sed_bandpass(sed=self.sed, bandpass=bandpass) except Exception as e: print(e) # return False continue ratio = sub/full if not (ratio == -1 or (ratio != ratio)): nphotons = ratio * nphotons_tot else: # return False continue nphotons_sum += nphotons # print("nphotons_sub-band_%d = %.2f"%(i, nphotons)) psf, pos_shear = psf_model.get_PSF(chip=chip, pos_img=pos_img, bandpass=bandpass, folding_threshold=folding_threshold) disk = galsim.Sersic(n=1.0, half_light_radius=self.hlr_disk, flux=1.0, gsparams=gsp) disk_shape = galsim.Shear(g1=self.e1_disk, g2=self.e2_disk) disk = disk.shear(disk_shape) bulge = galsim.Sersic(n=4.0, half_light_radius=self.hlr_bulge, flux=1.0, gsparams=gsp) bulge_shape = galsim.Shear(g1=self.e1_bulge, g2=self.e2_bulge) bulge = bulge.shear(bulge_shape) gal = self.bfrac * bulge + (1.0 - self.bfrac) * disk gal = gal.withFlux(nphotons) gal_shear = galsim.Shear(g1=g1, g2=g2) gal = gal.shear(gal_shear) if self.hlr_disk < 10.0: # Not apply PSF for very big galaxy gal = galsim.Convolve(psf, gal) # Use (explicit) stamps to draw stamp = gal.drawImage(wcs=self.localWCS, method='phot', offset=self.offset, save_photons=True) xmax = max(xmax, stamp.xmax) ymax = max(ymax, stamp.ymax) photons = stamp.photons photons.x += self.x_nominal photons.y += self.y_nominal photons_list.append(photons) # print('xmax = %d, ymax = %d '%(xmax, ymax)) stamp = galsim.ImageF(int(xmax*1.1), int(ymax*1.1)) stamp.wcs = self.localWCS stamp.setCenter(self.x_nominal, self.y_nominal) bounds = stamp.bounds & chip.img.bounds stamp[bounds] = chip.img[bounds] if not big_galaxy: for i in range(len(photons_list)): if i == 0: chip.sensor.accumulate(photons_list[i], stamp) else: chip.sensor.accumulate(photons_list[i], stamp, resume=True) else: sensor = galsim.Sensor() for i in range(len(photons_list)): if i == 0: sensor.accumulate(photons_list[i], stamp) else: sensor.accumulate(photons_list[i], stamp, resume=True) # print(stamp.array.sum()) # chip.img[bounds] += stamp[bounds] chip.img[bounds] = stamp[bounds] # print("nphotons_sum = ", nphotons_sum) del photons_list del stamp return True, pos_shear def drawObj_slitless(self, tel, pos_img, psf_model, bandpass_list, filt, chip, nphotons_tot=None, g1=0, g2=0, exptime=150., normFilter=None): norm_thr_rang_ids = normFilter['SENSITIVITY'] > 0.001 sedNormFactor = getNormFactorForSpecWithABMAG(ABMag=self.param['mag_use_normal'], spectrum=self.sed, norm_thr=normFilter, sWave=np.floor(normFilter[norm_thr_rang_ids][0][0]), eWave=np.ceil(normFilter[norm_thr_rang_ids][-1][0])) if sedNormFactor == 0: return False normalSED = Table(np.array([self.sed['WAVELENGTH'], self.sed['FLUX'] * sedNormFactor]).T, names=('WAVELENGTH', 'FLUX')) big_galaxy = False if self.hlr_disk > 3.0: # Very big galaxy big_galaxy = True if self.getMagFilter(filt) <= 15 and (not big_galaxy): folding_threshold = 5.e-4 else: folding_threshold = 5.e-3 gsp = galsim.GSParams(folding_threshold=folding_threshold) # nphotons_sum = 0 xOrderSigPlus = {'A':1.3909419820029296,'B':1.4760376591236062,'C':4.035447379743442,'D':5.5684364343742825,'E':16.260021029735388} for i in range(len(bandpass_list)): bandpass = bandpass_list[i] psf, pos_shear = psf_model.get_PSF(chip=chip, pos_img=pos_img, bandpass=bandpass, folding_threshold=folding_threshold) disk = galsim.Sersic(n=1.0, half_light_radius=self.hlr_disk, flux=1.0, gsparams=gsp) disk_shape = galsim.Shear(g1=self.e1_disk, g2=self.e2_disk) disk = disk.shear(disk_shape) bulge = galsim.Sersic(n=4.0, half_light_radius=self.hlr_bulge, flux=1.0, gsparams=gsp) bulge_shape = galsim.Shear(g1=self.e1_bulge, g2=self.e2_bulge) bulge = bulge.shear(bulge_shape) gal = self.bfrac * bulge + (1.0 - self.bfrac) * disk gal = gal.withFlux(tel.pupil_area * exptime) gal_shear = galsim.Shear(g1=g1, g2=g2) gal = gal.shear(gal_shear) gal = galsim.Convolve(psf, gal) starImg = gal.drawImage(wcs=self.localWCS) origin_star = [self.y_nominal - (starImg.center.y - starImg.ymin), self.x_nominal - (starImg.center.x - starImg.xmin)] # print(self.y_nominal, starImg.center.y, starImg.ymin) sdp = SpecDisperser(orig_img=starImg, xcenter=self.x_nominal-chip.bound.xmin, ycenter=self.y_nominal-chip.bound.ymin, origin=origin_star, tar_spec=normalSED, band_start=bandpass.blue_limit * 10, band_end=bandpass.red_limit * 10, conf=chip.sls_conf[1], isAlongY=0) spec_orders = sdp.compute_spec_orders() for k, v in spec_orders.items(): img_s = v[0] img_s,orig_off = convolveGaussXorders(img_s,xOrderSigPlus[k]) origin_order_x = v[1]-orig_off origin_order_y = v[2]-orig_off specImg = galsim.ImageF(img_s) photons = galsim.PhotonArray.makeFromImage(specImg) photons.x += origin_order_x photons.y += origin_order_y xlen_imf = int(specImg.xmax - specImg.xmin + 1) ylen_imf = int(specImg.ymax - specImg.ymin + 1) stamp = galsim.ImageF(xlen_imf, ylen_imf) stamp.wcs = self.localWCS stamp.setOrigin(origin_order_x, origin_order_y) bounds = stamp.bounds & chip.img.bounds if bounds.area() == 0: continue stamp[bounds] = chip.img[bounds] if not big_galaxy: chip.sensor.accumulate(photons, stamp) else: sensor = galsim.Sensor() sensor.accumulate(photons, stamp) # chip.sensor.accumulate(photons, stamp) chip.img[bounds] = stamp[bounds] del stamp del sdp del spec_orders del psf return True, pos_shear def getGSObj(self, psf, g1=0, g2=0, flux=None, filt=None, tel=None, exptime=150.): if flux == None: flux = self.getElectronFluxFilt(filt, tel, exptime) disk = galsim.Sersic(n=1.0, half_light_radius=self.hlr_disk, flux=1.0) disk_shape = galsim.Shear(g1=self.e1_disk, g2=self.e2_disk) disk = disk.shear(disk_shape) bulge = galsim.Sersic(n=4.0, half_light_radius=self.hlr_bulge, flux=1.0) bulge_shape = galsim.Shear(g1=self.e1_bulge, g2=self.e2_bulge) bulge = bulge.shear(bulge_shape) gal = self.bfrac * bulge + (1.0 - self.bfrac) * disk gal = gal.withFlux(flux) gal_shear = galsim.Shear(g1=g1, g2=g2) gal = gal.shear(gal_shear) final = galsim.Convolve(psf, gal) return final def rotateEllipticity(self, rotation): if rotation == 1: self.e1_disk, self.e2_disk, self.e1_bulge, self.e2_bulge, self.e1_total, self.e2_total = -self.e2_disk, self.e1_disk, -self.e2_bulge, self.e1_bulge, -self.e2_total, self.e1_total if rotation == 2: self.e1_disk, self.e2_disk, self.e1_bulge, self.e2_bulge, self.e1_total, self.e2_total = -self.e1_disk, -self.e2_disk, -self.e1_bulge, -self.e2_bulge, -self.e1_total, -self.e2_total if rotation == 3: self.e1_disk, self.e2_disk, self.e1_bulge, self.e2_bulge, self.e1_total, self.e2_total = self.e2_disk, -self.e1_disk, self.e2_bulge, -self.e1_bulge, self.e2_total, -self.e1_total def drawObject(self, img, final, noise_level=0.0, flux=None, filt=None, tel=None, exptime=150.): """ Override the method in parent class Need to constrain the size of image stamp for extended objects """ isUpdated = True if flux == None: flux = self.getElectronFluxFilt(filt, tel, exptime) stamp = final.drawImage(wcs=self.localWCS, offset=self.offset) stamp_arr = stamp.array mask = (stamp_arr >= 0.001*noise_level) # why 0.001? err = int(np.sqrt(mask.sum())) if np.mod(err, 2) == 1: err += 1 # if err == 1: if err == 0: subSize = 16 # why 16? else: subSize = max([err, 16]) fluxRatio = flux / stamp_arr[mask].sum() final = final.withScaledFlux(fluxRatio) imgSub = galsim.ImageF(subSize, subSize) # Draw with FFT # stamp = final.drawImage(image=imgSub, wcs=self.localWCS, offset=self.offset) # Draw with Photon Shoot stamp = final.drawImage(image=imgSub, wcs=self.localWCS, method='phot', offset=self.offset) stamp.setCenter(self.x_nominal, self.y_nominal) if np.sum(np.isnan(stamp.array)) >= 1: stamp.setZero() bounds = stamp.bounds & img.bounds if bounds.area() == 0: isUpdated = False else: img[bounds] += stamp[bounds] return img, stamp, isUpdated def getObservedEll(self, g1=0, g2=0): e1_obs, e2_obs, e_obs, theta = eObs(self.e1_total, self.e2_total, g1, g2) return self.e1_total, self.e2_total, g1, g2, e1_obs, e2_obs