From 914be42f6bc105576d3d5bc80530991b7b980127 Mon Sep 17 00:00:00 2001 From: Fang Yuedong Date: Thu, 12 Jun 2025 21:20:01 +0000 Subject: [PATCH 1/6] release v3.2.1 --- README.md | 8 ++++ .../sky_background/data/Zodiacal_map1.dat | 38 +++++++++---------- setup.py | 2 +- 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index b28a437..34b451f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,14 @@ # CSST主巡天仿真软件 ## 重要更新或问题修复: +* 2025.05.21: 更新至v3.2版本,内容包括: + * 加入亮星PSF外插模块 + * 加入串扰模块 + * 仿真内存优化 + * 头文件关键字更新 + * 在无缝光谱PSF模型中添加电子弥散 + * 更新PEP8格式规范 + * BUG修复:截断星等计算、深场计算、无缝光谱PSF、饱和溢出和坏像列在通道间的拖尾截断等 * 2024.08.03: 更新至v3.1版本,内容包括: * 加入对导星稳像过程的仿真 * 加入银河系消光的仿真 diff --git a/observation_sim/sky_background/data/Zodiacal_map1.dat b/observation_sim/sky_background/data/Zodiacal_map1.dat index 04935da..e4c2a13 100644 --- a/observation_sim/sky_background/data/Zodiacal_map1.dat +++ b/observation_sim/sky_background/data/Zodiacal_map1.dat @@ -1,20 +1,20 @@ 0 0 5 10 15 20 25 30 45 60 75 90 -0 30000 150000 7000 3140 1610 985 640 275 150 100 76 -5 20000 12000 6000 2940 1540 945 625 271 150 100 76 -10 16000 8000 4740 2470 1370 865 590 264 148 100 76 -15 11500 6780 3440 1860 1110 755 525 251 146 100 76 -20 6400 4480 2410 1410 910 635 454 237 141 99 76 -25 3840 2830 1730 1100 749 545 410 223 136 97 76 -30 2480 1870 1220 845 615 467 365 207 131 95 76 -35 1650 1270 910 680 510 397 320 193 125 93 76 -40 1180 940 700 530 416 338 282 179 120 92 76 -45 910 730 555 442 356 292 250 166 116 90 76 -60 505 442 352 292 243 209 183 134 104 86 76 -75 338 317 269 227 196 172 151 116 93 82 76 -90 259 251 225 193 166 147 132 104 86 79 76 -105 212 210 197 170 150 133 119 96 82 77 76 -120 188 186 177 154 138 125 113 90 77 74 76 -135 179 178 166 147 134 122 110 90 77 73 76 -150 179 178 165 148 137 127 116 96 79 72 76 -165 196 192 179 165 151 141 131 104 82 72 76 -180 230 212 195 178 163 148 134 105 83 72 76 \ No newline at end of file +0 30000 15000 7000 3140 1610 985 640 275 150 100 50 +5 20000 12000 6000 2940 1540 945 625 271 150 100 50 +10 16000 8000 4740 2470 1370 865 590 264 148 100 50 +15 11500 6780 3440 1860 1110 755 525 251 146 100 50 +20 6400 4480 2410 1410 910 635 454 237 141 99 50 +25 3840 2830 1730 1100 749 545 410 223 136 97 50 +30 2480 1870 1220 845 615 467 365 207 131 95 50 +35 1650 1270 910 680 510 397 320 193 125 93 50 +40 1180 940 700 530 416 338 282 179 120 92 50 +45 910 730 555 442 356 292 250 166 116 90 50 +60 505 442 352 292 243 209 183 134 104 86 50 +75 338 317 269 227 196 172 151 116 93 82 50 +90 259 251 225 193 166 147 132 104 86 79 50 +105 212 210 197 170 150 133 119 96 82 77 50 +120 188 186 177 154 138 125 113 90 77 74 50 +135 179 178 166 147 134 122 110 90 77 73 50 +150 179 178 165 148 137 127 116 96 79 72 50 +165 196 192 179 165 151 141 131 104 82 72 50 +180 230 212 195 178 163 148 134 105 83 72 50 \ No newline at end of file diff --git a/setup.py b/setup.py index e8c338c..468c8f6 100644 --- a/setup.py +++ b/setup.py @@ -76,7 +76,7 @@ with open("requirements.txt", "r") as f: ] setup(name='csst_msc_sim', - version='3.1.0', + version='3.2.1', packages=find_packages(), # install_requires=[ # # 'numpy>=1.18.5', -- GitLab From 53dc436c643a02d5c549174baf7fd9f22a29f150 Mon Sep 17 00:00:00 2001 From: Fang Yuedong Date: Tue, 9 Dec 2025 05:49:18 +0000 Subject: [PATCH 2/6] release_v3.3.1 --- catalog/C10_Catalog.py | 3 +- observation_sim/config/ChipOutput.py | 2 +- observation_sim/config/header/ImageHeader.py | 4 ++ .../instruments/data/sls_conf/CSST_GU2.conf | 6 +-- .../instruments/data/sls_conf/CSST_GU4.conf | 6 +-- observation_sim/sim_steps/readout_output.py | 2 +- setup.py | 2 +- tools/get_pointing_accuracy.py | 2 +- tools/imgCropping.py | 38 +++++++++++++++++++ 9 files changed, 54 insertions(+), 11 deletions(-) create mode 100644 tools/imgCropping.py diff --git a/catalog/C10_Catalog.py b/catalog/C10_Catalog.py index 3168b17..45f7a0d 100644 --- a/catalog/C10_Catalog.py +++ b/catalog/C10_Catalog.py @@ -311,7 +311,8 @@ class Catalog(CatalogBase): param['bulgemass'] = gals['bulgemass'][igals] param['diskmass'] = gals['diskmass'][igals] - param['size'] = gals['size'][igals] + np.random.seed(int(pix_id)+cat_id+igals) + param['size'] = (gals['size'][igals] * ((1.+gals['redshift'][igals])**(0.4))) * np.random.uniform(0.7, 1.3) if param['size'] > self.max_size: self.max_size = param['size'] diff --git a/observation_sim/config/ChipOutput.py b/observation_sim/config/ChipOutput.py index 5a08d7e..87aa566 100755 --- a/observation_sim/config/ChipOutput.py +++ b/observation_sim/config/ChipOutput.py @@ -73,7 +73,7 @@ class ChipOutput(object): self.hdr += additional_column_names def create_output_file(self): - if self.pointing_type == 'WIDE' or self.pointing_type == 'DEEP': + if self.pointing_type == 'WIDE' or self.pointing_type == 'DEEP' or self.pointing_type == 'CALF' or self.pointing_type == 'CALSP' or self.pointing_type == 'CALSS': self.cat = open(os.path.join(self.subdir, self.cat_name), "w") self.logger.info("Creating catalog file %s ...\n" % (os.path.join(self.subdir, self.cat_name))) diff --git a/observation_sim/config/header/ImageHeader.py b/observation_sim/config/header/ImageHeader.py index 97b46f5..69f34fb 100644 --- a/observation_sim/config/header/ImageHeader.py +++ b/observation_sim/config/header/ImageHeader.py @@ -556,6 +556,10 @@ def generateExtensionHeader(chip, xlen=9216, ylen=9232, ra=60, dec=-40, pa=-23.4 h_ext['GAIN14'] = chip.gain_channel[13] h_ext['GAIN15'] = chip.gain_channel[14] h_ext['GAIN16'] = chip.gain_channel[15] + h_ext['PSCAN1'] = chip.prescan_x + h_ext['PSCAN2'] = chip.prescan_y + h_ext['OSCAN1'] = chip.overscan_x + h_ext['OSCAN2'] = chip.overscan_y h_ext['RON01'] = readout h_ext['RON02'] = readout h_ext['RON03'] = readout diff --git a/observation_sim/instruments/data/sls_conf/CSST_GU2.conf b/observation_sim/instruments/data/sls_conf/CSST_GU2.conf index 8be12ce..50e8e77 100644 --- a/observation_sim/instruments/data/sls_conf/CSST_GU2.conf +++ b/observation_sim/instruments/data/sls_conf/CSST_GU2.conf @@ -36,7 +36,7 @@ MMAG_MARK_B 30 # Trace description # DYDX_ORDER_B 0 -DYDX_B_0 82.91782550095616 -0.017450074030956903 -0.00034211980919903514 -5.229479577962483e-10 6.618188334096981e-10 4.5712407536340074e-11 +DYDX_B_0 82.91742083784611 -0.0174512487471819 -0.00034120894910458606 -1.298330068637783e-10 4.340249496308753e-10 1.0373044457577981e-11 # # X and Y Offsets # @@ -46,8 +46,8 @@ YOFF_B 0.0 # Dispersion solution # DISP_ORDER_B 1 -DLDP_B_0 7763276.642055119 -20266.736198394356 1255.309384604854 5.066171367500259 1.2647000472616756 -0.25632575767112603 -DLDP_B_1 -143465.8971715863 684.9306681233783 -157.3534822656011 -0.2303808137854618 0.03539958163679847 0.005924035996712881 +DLDP_B_0 448897.8581289202 -69.05924554341267 -200.37444114671905 0.04003968509481386 -0.027512617424472103 5.068370683947331e-05 +DLDP_B_1 10160.6234805349 4.430359702933667 0.09374704569926708 -0.000854410958383644 -2.8642365418529523e-05 -2.659603621537505e-06 # SENSITIVITY_B GU2.Throughput.0st.fits # diff --git a/observation_sim/instruments/data/sls_conf/CSST_GU4.conf b/observation_sim/instruments/data/sls_conf/CSST_GU4.conf index ee60aba..acafccb 100644 --- a/observation_sim/instruments/data/sls_conf/CSST_GU4.conf +++ b/observation_sim/instruments/data/sls_conf/CSST_GU4.conf @@ -36,7 +36,7 @@ MMAG_MARK_B 30 # Trace description # DYDX_ORDER_B 0 -DYDX_B_0 80.91986075321823 -0.01744761042150647 -0.0003580191107231064 -2.627009138987314e-10 -6.375179298238856e-10 2.763897714355112e-09 +DYDX_B_0 80.93796454255751 -0.017461724678154614 -0.00035745607061872265 1.586088715327381e-09 2.83632657494108e-10 2.4337528594084163e-09 # # X and Y Offsets # @@ -46,8 +46,8 @@ YOFF_B 0.0 # Dispersion solution # DISP_ORDER_B 1 -DLDP_B_0 52318605.038736925 -49905.61041920374 -325.3071290800653 11.683982888283492 -0.734011022951611 0.0798352458204433 -DLDP_B_1 -1489154.0375472226 1428.4941310271706 32.346965719777806 -0.3034273708620226 -0.01153922199462758 -0.0009324181582693394 +DLDP_B_0 560974.2568255321 4.836837898676734 -270.2027083117478 0.00017749467347293046 -3.42705736301515e-05 -2.4253188230594427e-06 +DLDP_B_1 15499.999999924863 8.661894315733124e-11 5.382306809541636e-12 -1.7061292984259495e-14 -6.679353210055169e-15 1.0401633823282333e-15 # SENSITIVITY_B GU4.Throughput.0st.fits # diff --git a/observation_sim/sim_steps/readout_output.py b/observation_sim/sim_steps/readout_output.py index f21461d..db527de 100644 --- a/observation_sim/sim_steps/readout_output.py +++ b/observation_sim/sim_steps/readout_output.py @@ -20,7 +20,7 @@ def add_prescan_overscan(self, chip, filt, tel, pointing, catalog, obs_param): if obs_param["add_dark"] is True: ny = int(chip.npix_y/2) base_dark = (ny-1)*(chip.readout_time/ny)*chip.dark_noise - chip.img.array[(chip.prescan_y+ny):-(chip.prescan_y+ny), :] = base_dark + chip.img.array[(chip.prescan_y+ny):-(chip.prescan_y+ny), :] += base_dark return chip, filt, tel, pointing diff --git a/setup.py b/setup.py index dc4702c..0d5ef40 100644 --- a/setup.py +++ b/setup.py @@ -76,7 +76,7 @@ with open("requirements.txt", "r") as f: ] setup(name='csst_msc_sim', - version='3.3.0', + version='3.3.1', packages=find_packages(), # install_requires=[ # # 'numpy>=1.18.5', diff --git a/tools/get_pointing_accuracy.py b/tools/get_pointing_accuracy.py index 7c53134..99aff91 100644 --- a/tools/get_pointing_accuracy.py +++ b/tools/get_pointing_accuracy.py @@ -130,7 +130,7 @@ def cal_FoVcenter_1P_ecliptic(lon_ecl_FieldCenter, lat_ecl_FieldCenter, chipID=1 return ra_PointCenter, dec_PointCenter, lon_ecl_PointCenter, lat_ecl_PointCenter -def getChipCenterRaDec(chipID=1, p_ra=60., p_dec=-40.): +def getChipCenterRaDec(chipID=1, p_ra=60., p_dec=-40., pa=23.5): chip = Chip(chipID) h_ext = ImageHeader.generateExtensionHeader( diff --git a/tools/imgCropping.py b/tools/imgCropping.py new file mode 100644 index 0000000..42f4bd7 --- /dev/null +++ b/tools/imgCropping.py @@ -0,0 +1,38 @@ +import os +import sys +import numpy as np +from astropy.io import fits + +PRESCAN_X, IMAGE_X, OVERSCAN_X = 27, 1152, 71 +PRESCAN_Y, IMAGE_Y, OVERSCAN_Y = 0, 4616, 84 +BLOCK_WIDTH = PRESCAN_X + IMAGE_X + OVERSCAN_X + + +def process_single_file(filename): + with fits.open(filename) as hdul: + data = hdul[1].data + + data = data[PRESCAN_Y:PRESCAN_Y + IMAGE_Y, :] + blocks = [data[:, i * BLOCK_WIDTH + PRESCAN_X:(i + 1) * BLOCK_WIDTH - OVERSCAN_X] for i in range(16)] + + blocks_a = np.concatenate(blocks[:4], axis=1) + blocks_b = np.concatenate([np.fliplr(b) for b in blocks[4:8]], axis=1) + blocks_c = np.concatenate([np.flipud(np.fliplr(b)) for b in blocks[11:7:-1]], axis=1) + blocks_d = np.concatenate([np.flipud(b) for b in blocks[15:11:-1]], axis=1) + + blocks_dc = np.concatenate([blocks_d, blocks_c], axis=1) + blocks_ab = np.concatenate([blocks_a, blocks_b], axis=1) + blocks_final = np.concatenate([blocks_ab, blocks_dc], axis=0) + + output_path = os.path.splitext(fn)[0]+'_cropping.fits' + fits.writeto(output_path, blocks_final, overwrite=True) + print(f"OK:{output_path}") + + +if __name__ == "__main__": + if len(sys.argv) > 1: + fn = sys.argv[1] + else: + fn = 'CSST_MSC_MS_WIDE_20281024132057_20281024132327_10100484833_08_L0_V01.fits' + + process_single_file(fn) -- GitLab From e29d36c669d32acda0c84612ab131a796c9c0aa4 Mon Sep 17 00:00:00 2001 From: yuedong0607 Date: Tue, 3 Mar 2026 21:00:46 -0500 Subject: [PATCH 3/6] add ghosts --- config/obs_config_SCI.yaml | 11 +- observation_sim/mock_objects/Ghost.py | 198 +++++++++++++++++++++++ observation_sim/mock_objects/__init__.py | 1 + observation_sim/sim_steps/__init__.py | 2 + observation_sim/sim_steps/add_ghost.py | 86 ++++++++++ 5 files changed, 295 insertions(+), 3 deletions(-) create mode 100644 observation_sim/mock_objects/Ghost.py create mode 100644 observation_sim/sim_steps/add_ghost.py diff --git a/config/obs_config_SCI.yaml b/config/obs_config_SCI.yaml index fb8f538..da3f078 100644 --- a/config/obs_config_SCI.yaml +++ b/config/obs_config_SCI.yaml @@ -9,14 +9,14 @@ ############################################### # Observation type -obs_type: "SCI" +obs_type: "WIDE" obs_type_code: "101" obs_id: "00000001" # this setting will only be used if pointing list file is not given # Define list of chips # run_chips: [6,7,8,9,11,12,13,14,15,16,17,18,19,20,22,23,24,25] # Photometric chips #run_chips: [1,2,3,4,5,10,21,26,27,28,29,30] # Spectroscopic chips -run_chips: [17, 22] +run_chips: [17] # Define observation sequence call_sequence: @@ -28,6 +28,11 @@ call_sequence: shutter_effect: YES flat_fielding: YES field_dist: YES + + ghost_SCI: + mag_threshold: 16 + field_dist: YES + # Accumulate fluxes from sky background sky_background: # [Optional]: exposure time of the pointing will be used as default. @@ -83,5 +88,5 @@ call_sequence: gain_16channel: YES # Output the final image quantization_and_output: - format_output: YES + format_output: NO ... diff --git a/observation_sim/mock_objects/Ghost.py b/observation_sim/mock_objects/Ghost.py new file mode 100644 index 0000000..ac1901c --- /dev/null +++ b/observation_sim/mock_objects/Ghost.py @@ -0,0 +1,198 @@ +""" +Ghost image calculation (single-class refactor) + +This module keeps the original behavior but wraps everything into ONE class: `Ghost`. + +All lengths are in meters. +""" + +from __future__ import annotations + +from typing import Dict, Tuple + +import numpy as np +import galsim + + +class Ghost: + """ + Single-class ghost image model. + + Usage + ----- + ghost = Ghost() # uses built-in defaults + result = ghost.calculate(detector_id=13, x0=0.0, y0=0.0) + + The returned dict contains: + - "ghosts": ndarray shape (3, 4) with rows [x, y, radius, relative_energy] + - "angle_x_deg": float + - "angle_y_deg": float + - "wave_name": str (band name) + """ + + # ----------- Default optical constants (meters) ----------- + DEFAULT_ZP = 4.593 + DEFAULT_RP = 0.16367 + DEFAULT_D1 = 0.005 + DEFAULT_D2 = 0.008 + DEFAULT_PIXEL = 1.0e-5 # kept for completeness; unused by formula + + def __init__( + self, + *, + Zp: float = DEFAULT_ZP, + Rp: float = DEFAULT_RP, + D1: float = DEFAULT_D1, + D2: float = DEFAULT_D2, + pixel: float = DEFAULT_PIXEL, + ) -> None: + self.Zp = float(Zp) + self.Rp = float(Rp) + self.D1 = float(D1) + self.D2 = float(D2) + self.pixel = float(pixel) + + # ---- Wave table (from original script) ---- + # Stored as: wave_num -> (wave_name, wave_length, nd, tf1, rf1, tf2, rf2, TD, RD) + self._waves: Dict[int, Tuple[str, float, float, float, float, float, float, float, float]] = { + 0: ("nuv", 2.9e-7, 1.495, 0.825, 0.0093, 0.825, 0.0093, 0.555, 0.15), + 1: ("u", 3.6e-7, 1.473, 0.954, 0.0138, 0.954, 0.0138, 0.56, 0.15), + 2: ("g", 4.8e-7, 1.463, 0.98, 0.0197, 0.98, 0.0197, 0.8, 0.2), + 3: ("r", 6.2e-7, 1.458, 0.98, 0.015, 0.98, 0.015, 0.91, 0.09), + 4: ("i", 7.5e-7, 1.454, 0.985, 0.01, 0.985, 0.01, 0.889, 0.11), + 5: ("z", 9.1e-7, 1.452, 0.99, 0.0085, 0.99, 0.0085, 0.6, 0.1), + 6: ("y", 9.7e-7, 1.451, 0.99, 0.0035, 0.99, 0.0035, 0.36, 0.1), + } + + # ---- Detector -> wave_num mapping (from original script) ---- + self._detector_to_wave_num: Dict[int, int] = { + 6: 6, + 7: 4, + 8: 2, + 9: 3, + 11: 5, + 12: 0, + 13: 0, + 14: 1, + 15: 6, + 16: 6, + 17: 1, + 18: 0, + 19: 0, + 20: 5, + 22: 3, + 23: 2, + 24: 4, + 25: 6, + } + + # ----------------------------- + # Helpers + # ----------------------------- + + def _get_wave_tuple(self, detector_id: int) -> Tuple[str, float, float, float, float, float, float, float, float]: + """ + Return wave properties tuple for a detector_id. + Raises KeyError with a friendly message if unknown. + """ + try: + wave_num = self._detector_to_wave_num[detector_id] + except KeyError as e: + known = ", ".join(map(str, sorted(self._detector_to_wave_num.keys()))) + raise KeyError(f"Unknown detector_id={detector_id}. Known ids: {known}") from e + + return self._waves[wave_num] + + # ----------------------------- + # Public API + # ----------------------------- + + def calculate(self, detector_id: int, x0: float, y0: float) -> Dict[str, object]: + """ + Calculate ghost images for a given detector and focal-plane coordinate. + + Parameters + ---------- + detector_id : int + Detector identifier. + x0, y0 : float + Absolute focal-plane coordinates of the image point [m]. + + Returns + ------- + dict with keys: + ghosts: ndarray (3, 4) rows [ghost_x, ghost_y, ghost_radius, relative_energy] + angle_x_deg: float + angle_y_deg: float + wave_name: str + """ + wave_name, _wave_length, nd, tf1, rf1, tf2, rf2, TD, RD = self._get_wave_tuple(detector_id) + + # Slope defined by pupil geometry + u = self.Rp / self.Zp + + # Ghost radii (physical lengths) + r1 = 2.0 * self.D1 * u / nd + r2 = 2.0 * self.D2 * u + r3 = 2.0 * (self.D1 / nd + self.D2) * u + + # Relative energy terms + m1 = rf1 * rf2 + m2 = rf2 * RD + m3 = rf1 * RD * (tf2 ** 2) + + # NOTE: Original script uses a fixed +0.313 x-offset; preserve behavior. + denom = self.Zp - self.D1 * (1.0 - 1.0 / nd) + angle_x = (float(x0) + 0.313) / denom + angle_y = float(y0) / denom + + # Angles inside the filter + af_x = angle_x / nd + af_y = angle_y / nd + + # Lateral shifts on the focal plane + xd1 = np.tan(af_x) * 2.0 * self.D1 + yd1 = np.tan(af_y) * 2.0 * self.D1 + xd2 = np.tan(angle_x) * 2.0 * self.D2 + yd2 = np.tan(angle_y) * 2.0 * self.D2 + + # Combined shifts + xd3 = xd1 + xd2 + yd3 = yd1 + yd2 + + # Ghost positions + x1, y1 = float(x0) + xd1, float(y0) + yd1 + x2, y2 = float(x0) + xd2, float(y0) + yd2 + x3, y3 = float(x0) + xd3, float(y0) + yd3 + + ghosts = [ + [x1, y1, r1, m1], + [x2, y2, r2, m2], + [x3, y3, r3, m3], + ] + + return { + "ghosts": ghosts, + "angle_x_deg": float(angle_x * 180.0 / np.pi), + "angle_y_deg": float(angle_y * 180.0 / np.pi), + "wave_name": wave_name, + } + + def draw(self, image, pos_pix, r_pix, flux=1.0): + x_pix, y_pix = pos_pix.x, pos_pix.y + profile = galsim.TopHat(radius=r_pix, flux=flux) + profile.drawImage( + image = image, + center = galsim.PositionD(x_pix, y_pix), + add_to_image=True + ) + return image + + +if __name__ == "__main__": + g = Ghost() + + # Original sanity checks + print(g.calculate(13, 0.0, 0.0)) + print(g.calculate(6, 0.19233, -0.24876)) + print(g.calculate(25, -0.19233, 0.24876)) diff --git a/observation_sim/mock_objects/__init__.py b/observation_sim/mock_objects/__init__.py index c6c567f..b823415 100755 --- a/observation_sim/mock_objects/__init__.py +++ b/observation_sim/mock_objects/__init__.py @@ -6,3 +6,4 @@ from .Star import Star from .Stamp import Stamp from .FlatLED import FlatLED from .ExtinctionMW import ExtinctionMW +from .Ghost import Ghost diff --git a/observation_sim/sim_steps/__init__.py b/observation_sim/sim_steps/__init__.py index 7669fe4..7a48274 100644 --- a/observation_sim/sim_steps/__init__.py +++ b/observation_sim/sim_steps/__init__.py @@ -17,10 +17,12 @@ class SimSteps: from .add_brighter_fatter_CTE import add_brighter_fatter, apply_CTE from .readout_output import add_prescan_overscan, add_readout_noise, apply_gain, quantization_and_output, add_crosstalk from .add_LED_flat import add_LED_Flat + from .add_ghost import add_ghosts_SCI SIM_STEP_TYPES = { "scie_obs": "add_objects", + "ghost_SCI": "add_ghosts_SCI", "sky_background": "add_sky_background", "cosmic_rays": "add_cosmic_rays", "PRNU_effect": "apply_PRNU", diff --git a/observation_sim/sim_steps/add_ghost.py b/observation_sim/sim_steps/add_ghost.py new file mode 100644 index 0000000..2acc619 --- /dev/null +++ b/observation_sim/sim_steps/add_ghost.py @@ -0,0 +1,86 @@ +import galsim + +from observation_sim.mock_objects import Ghost +from observation_sim.psf import FieldDistortion + +def add_ghosts_SCI(self, chip, filt, tel, pointing, catalog, obs_param): + # Load catalogues + if catalog is None: + self.chip_output.Log_error( + "Catalog interface class must be specified for SCIE-OBS") + raise ValueError( + "Catalog interface class must be specified for SCIE-OBS") + cat = catalog(config=self.overall_config, chip=chip, + pointing=pointing, chip_output=self.chip_output, filt=filt) + + # Apply field distortion model + if obs_param["field_dist"] is True: + fd_model = FieldDistortion(chip=chip, img_rot=pointing.img_pa.deg) + else: + fd_model = None + + # Get chip WCS + if not hasattr(self, 'h_ext'): + _, _ = self.prepare_headers(chip=chip, pointing=pointing) + + chip_wcs = galsim.FitsWCS(header=self.h_ext) + ghost_model = Ghost() + + # Loop over objects + for j in range(len(cat.objs)): + obj = cat.objs[j] + + try: + sed_data = cat.load_sed(obj) + norm_filt = cat.load_norm_filt(obj) + + obj.sed, obj.param["mag_%s" % filt.filter_type.lower()], obj.param["flux_%s" % filt.filter_type.lower()] = cat.convert_sed( + mag=obj.param["mag_use_normal"], + sed=sed_data, + target_filt=filt, + norm_filt=norm_filt, + mu=obj.mu + ) + except Exception as e: + traceback.print_exc() + self.chip_output.Log_error(e) + continue + + # Select only bright stars + if obj.type != 'star' or obj.getMagFilter(filt) >= obs_param["mag_threshold"]: + continue + + # Get position of object on the focal plane + pos_img, _, _, _, fd_shear = obj.getPosImg_Offset_WCS( + img=chip.img, fdmodel=fd_model, chip=chip, verbose=False, chip_wcs=chip_wcs, img_header=self.h_ext, ra_offset=self.ra_offset, dec_offset=self.dec_offset) + + # [TODO] For now, only consider objects which their centers (after field distortion) are projected within the focal plane + if pos_img is None: + self.chip_output.Log_info('obj_ra = %.6f, obj_dec = %.6f, obj_ra_orig = %.6f, obj_dec_orig = %.6f' % ( + obj.ra, obj.dec, obj.ra_orig, obj.dec_orig)) + self.chip_output.Log_error("Object missed: %s" % (obj.id)) + missed_obj += 1 + obj.unload_SED() + continue + + # Get number of photons for total photons for the object + nphotons_tot = obj.getElectronFluxFilt(filt, tel, pointing.exp_time) + + x_m = pos_img.x * chip.pix_size * 1e-3 + y_m = pos_img.y * chip.pix_size * 1e-3 + + ghost_list = ghost_model.calculate(chip.chipID, x_m, y_m)["ghosts"] + + for ghost in ghost_list: + x, y, r, ratio = ghost + factor = (1e-3) * chip.pix_size + x_pix = x / factor + y_pix = y / factor + r_pix = r / factor + ghost_pos = obj.getRealPos(chip.img, global_x=x_pix, global_y=y_pix, + img_real_wcs=obj.chip_wcs) + # print(f"star at {obj.getRealPos(chip.img, global_x=pos_img.x, global_y=pos_img.y, img_real_wcs=obj.chip_wcs)}") + # print("ghost info: ", ghost_pos.x, ghost_pos.y, r_pix, ratio * nphotons_tot) + # chip.img = ghost_model.draw(chip.img, ghost_pos, r_pix, flux=ratio * nphotons_tot) + + return chip, filt, tel, pointing \ No newline at end of file -- GitLab From 63affddae66622013d1072d4171dd6ec3c7f69ce Mon Sep 17 00:00:00 2001 From: yuedong0607 Date: Tue, 3 Mar 2026 21:21:49 -0500 Subject: [PATCH 4/6] fix a typo --- observation_sim/sim_steps/add_ghost.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/observation_sim/sim_steps/add_ghost.py b/observation_sim/sim_steps/add_ghost.py index 2acc619..1571704 100644 --- a/observation_sim/sim_steps/add_ghost.py +++ b/observation_sim/sim_steps/add_ghost.py @@ -81,6 +81,6 @@ def add_ghosts_SCI(self, chip, filt, tel, pointing, catalog, obs_param): img_real_wcs=obj.chip_wcs) # print(f"star at {obj.getRealPos(chip.img, global_x=pos_img.x, global_y=pos_img.y, img_real_wcs=obj.chip_wcs)}") # print("ghost info: ", ghost_pos.x, ghost_pos.y, r_pix, ratio * nphotons_tot) - # chip.img = ghost_model.draw(chip.img, ghost_pos, r_pix, flux=ratio * nphotons_tot) + chip.img = ghost_model.draw(chip.img, ghost_pos, r_pix, flux=ratio * nphotons_tot) return chip, filt, tel, pointing \ No newline at end of file -- GitLab From dc56a253d577f1065a10e831d3ea9bf8787961ec Mon Sep 17 00:00:00 2001 From: yuedong0607 Date: Wed, 4 Mar 2026 19:57:04 -0500 Subject: [PATCH 5/6] removed missed_obj count in add_ghost.py --- observation_sim/sim_steps/add_ghost.py | 1 - 1 file changed, 1 deletion(-) diff --git a/observation_sim/sim_steps/add_ghost.py b/observation_sim/sim_steps/add_ghost.py index 1571704..ad76bec 100644 --- a/observation_sim/sim_steps/add_ghost.py +++ b/observation_sim/sim_steps/add_ghost.py @@ -59,7 +59,6 @@ def add_ghosts_SCI(self, chip, filt, tel, pointing, catalog, obs_param): self.chip_output.Log_info('obj_ra = %.6f, obj_dec = %.6f, obj_ra_orig = %.6f, obj_dec_orig = %.6f' % ( obj.ra, obj.dec, obj.ra_orig, obj.dec_orig)) self.chip_output.Log_error("Object missed: %s" % (obj.id)) - missed_obj += 1 obj.unload_SED() continue -- GitLab From f92ea9e9343fa1049deb1cd13950f0547d53565c Mon Sep 17 00:00:00 2001 From: yuedong0607 Date: Thu, 5 Mar 2026 03:12:19 -0500 Subject: [PATCH 6/6] bug fix: chip reset origin for ghosts drawing --- observation_sim/mock_objects/Ghost.py | 7 ++++--- observation_sim/sim_steps/add_ghost.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/observation_sim/mock_objects/Ghost.py b/observation_sim/mock_objects/Ghost.py index ac1901c..14392bc 100644 --- a/observation_sim/mock_objects/Ghost.py +++ b/observation_sim/mock_objects/Ghost.py @@ -178,15 +178,16 @@ class Ghost: "wave_name": wave_name, } - def draw(self, image, pos_pix, r_pix, flux=1.0): + def draw_on_chip(self, chip, pos_pix, r_pix, flux=1.0): x_pix, y_pix = pos_pix.x, pos_pix.y profile = galsim.TopHat(radius=r_pix, flux=flux) + chip.img.setOrigin(0, 0) profile.drawImage( - image = image, + image = chip.img, center = galsim.PositionD(x_pix, y_pix), add_to_image=True ) - return image + chip.img.setOrigin(chip.bound.xmin, chip.bound.ymin) if __name__ == "__main__": diff --git a/observation_sim/sim_steps/add_ghost.py b/observation_sim/sim_steps/add_ghost.py index ad76bec..7bd4b0b 100644 --- a/observation_sim/sim_steps/add_ghost.py +++ b/observation_sim/sim_steps/add_ghost.py @@ -80,6 +80,6 @@ def add_ghosts_SCI(self, chip, filt, tel, pointing, catalog, obs_param): img_real_wcs=obj.chip_wcs) # print(f"star at {obj.getRealPos(chip.img, global_x=pos_img.x, global_y=pos_img.y, img_real_wcs=obj.chip_wcs)}") # print("ghost info: ", ghost_pos.x, ghost_pos.y, r_pix, ratio * nphotons_tot) - chip.img = ghost_model.draw(chip.img, ghost_pos, r_pix, flux=ratio * nphotons_tot) + ghost_model.draw_on_chip(chip, ghost_pos, r_pix, flux=ratio * nphotons_tot) return chip, filt, tel, pointing \ No newline at end of file -- GitLab