simulator.py

import numpy as np
from src.finder import Finder, gaussian2D
from src.streak import model


class Simulator:
    """
    The Simulator class creates images with "realistic" streaks in them:
    streaks have width according to the PSF size, and white noise background added.

    The key parameters to input before making a streak are:
    -x1,x2,y1,y2: the streak start and end points (normalized units!) e.g, x1=0.1, x2=0.2, y1=0, y2=1
    -im_size: the image size as a scalar (makes square images only, for now). Use powers of 2. Default is 512.
    -bg_noise_var: noise variance (counts^2 per pixel). Default is 1.
    -psf_sigma: width parameter of the PSF. default is 2.
    -intensity: counts/unit length (diagonal lines have slightly larger value per pixel)

    You can also turn on/off the source noise (use_source_noise) or background by
    setting bg_noise_var to zero.

    The streaks are automatically input to the Finder object, that returns (hopefully)
    with the right streak parameters.
    """

    def __init__(self):

        # objects
        self.finder = Finder()

        # outputs
        self.image_clean = None
        self.image = None
        self.psf = None

        # switches
        self.im_size = 512  # assume we want to make square images
        self.intensity = 10.0
        self.bg_noise_var = 1.0
        self.use_source_noise = True
        self.psf_sigma = 2.0

        # adding stars
        self.number_stars = 0
        self.star_brightness = 100
        self.star_power_law = 2

        # these parameters are in units of image size
        self.x1 = 0.0
        self.x2 = 1.0
        self.y1 = 0.0
        self.y2 = 1.0

        self.verbosity = 1

    @property
    def L(self):
        # return math.sqrt((self.x2-self.x1)**2+(self.y2-self.y1)**2)
        x1 = min(max(self.x1, 0), 1)
        x2 = min(max(self.x2, 0), 1)
        y1 = min(max(self.y1, 0), 1)
        y2 = min(max(self.y2, 0), 1)

        return np.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)

    @property
    def th(self):
        return np.degrees(np.arctan2(self.y2 - self.y1, self.x2 - self.x1))

    @property
    def a(self):
        if self.x1 == self.x2:
            return np.nan
        else:
            return (self.y2 - self.y1) / (self.x2 - self.x1)

    @property
    def b(self):
        return self.y2 - self.a * self.x2

    @property
    def x0(self):
        if self.x1 == self.x2:
            return self.x1
        elif self.a == 0:
            return np.nan
        else:
            return -self.b / self.a

    @property
    def midpoint_x(self):
        return (self.x2 + self.x1) / 2

    @property
    def midpoint_y(self):
        return (self.y2 + self.y1) / 2

    @property
    def trig_factor(self):
        return max(abs(np.cos(np.radians(self.th))), abs(np.sin(np.radians(self.th))))

    def clear(self):
        self.image_clean = []
        self.image = []
        self.psf = []

    def is_vertical(self):

        val1 = 45 <= self.th <= 135
        val2 = -135 <= self.th <= -45

        return val1 or val2

    def calc_snr(self):

        snr = self.intensity * np.sqrt(self.L * self.im_size / self.bg_noise_var)

        snr = snr / np.sqrt(2 * np.sqrt(np.pi) * self.psf_sigma)

        return abs(snr)

    def make_image(self):

        if self.verbosity > 1:
            print("make_image()")

        self.clear()
        self.make_clean()
        self.image_clean += self.add_stars(
            number=self.number_stars,
            brightness=self.star_brightness,
            power_law=self.star_power_law,
        )
        self.add_noise()

    def make_clean(self):
        self.image_clean = self.intensity * model(
            self.im_size,
            self.x1 * self.im_size,
            self.x2 * self.im_size,
            self.y1 * self.im_size,
            self.y2 * self.im_size,
            self.psf_sigma,
        )

    def add_noise(self):

        if self.verbosity > 2:
            print("add_noise()")

        if np.isscalar(self.im_size):
            size = (self.im_size, self.im_size)
        bg = np.ones(size, dtype=np.float32) * self.bg_noise_var

        if self.use_source_noise:
            var = bg + np.abs(self.image_clean)
        else:
            var = bg

        self.image = np.random.poisson(var).astype(np.float32) - self.bg_noise_var

    def add_stars(self, number=10, brightness=100, power_law=0):
        """
        Generate randomly placed stars with brightness
        chosen from a power law (or a constant brightness).
        The stars are placed in random locations,
        and their shape is consistent with the psf of the image.
        Poisson noise is added to each source.

        Parameters
        ----------
        number: int
            How many stars to be added. Default 10.
        brightness: float
            The total photon count for the brightest
            star (or all stars, if power_law=0).
            Default is 100.
            If power_law is not zero, will randomly
            choose the brightness based on the power
            law index given.
            Each star will have Poisson noise added
            on top of its brightness during add_noise().
        power_law: float
            Index of the power law distribution of
            brightnesses (total counts).
            Default is zero (all stars the same).
        Returns
        -------
        im: np.array of floats
            an image with stars, without noise.
        """

        im = np.zeros((self.im_size, self.im_size), dtype=np.float32)
        for i in range(number):
            x = np.random.uniform(-0.5, 0.5) * self.im_size
            y = np.random.uniform(-0.5, 0.5) * self.im_size
            if power_law == 0:
                b = brightness
            else:
                b = np.random.pareto(power_law) * brightness

            g = (
                gaussian2D(
                    sigma_x=self.psf_sigma,
                    size=self.im_size,
                    offset_x=x,
                    offset_y=y,
                    norm=1,
                )
                * b
            )

            im += g

        return im

    def find(self):

        if self.verbosity > 1:
            print("find()")

        self.finder.input(self.image, psf=self.psf_sigma, variance=self.bg_noise_var)

    def run(self):
        if self.verbosity > 1:
            print("run()")
        self.clear()
        self.make_image()
        self.find()

        if len(self.finder.streaks) == 0:
            print(f"No streaks found. Maximal S/N= {self.finder.data.best_snr}")
        else:
            if self.verbosity:
                self.print()
                for s in self.finder.streaks:
                    s.print()

    def randomly(self):
        x = np.random.rand(2)
        y = np.random.rand(2)
        self.x1 = min(x)
        self.x2 = max(x)
        self.y1 = min(y)
        self.y2 = max(y)

    def print(self):
        print(
            f"SIMULATED : S/N= {self.calc_snr():.2f} | "
            f"I= {self.intensity:.2f} | "
            f"L= {self.L * self.im_size:.1f} | "
            f"th= {self.th:.2f} | "
            f"x0= {self.x0 * self.im_size:.2f}"
        )


# test (reload object)
if __name__ == "__main__":

    import matplotlib.pyplot as plt

    print("This is a test for Simulator and Finder...")
    s = Simulator()
    s.verbosity = 1

    # s.x1 = 3.0 / 8
    # s.x2 = 0.5
    # s.y1 = 1 / 3
    # s.y2 = 1.0 / 2

    # s.x1 = 0.2
    # s.y1 = 0.01
    # s.x2 = 1
    # s.y2 = 1.5
    s.randomly()
    s.number_stars = 1000
    s.star_brightness = 50
    s.finder.use_subtract_mean = False
    s.run()

    fig, ax = plt.subplots()
    ax.imshow(s.image)
    if s.finder.streaks:
        for st in s.finder.streaks:
            st.plot_lines(ax)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值