DeepTrackAI / DeepTrack2

DeepTrack2
MIT License
162 stars 50 forks source link

Generating Poisson noise #188

Closed pulga10 closed 1 year ago

pulga10 commented 1 year ago

Could you elaborate a bit on the way you're adding Poisson noise to the simulated images? I think in principle it should be enough to just multiply image (centered around 1 with contrast deviations in percent) by the expected background level in photoelectrons and then pass it as argument to np.random.poisson(). I don't understand how to achieve this using snr and background as well as the concept behind your implementation.

This is how it's implemented in the version I have installed:

class Poisson(Noise):
    """Adds Poisson-distributed noise to an image

    Parameters
    ----------
    snr : float
        Signal to noise ratio of the final image. The signal is determined
        by the peak value of the image.
    background : float
        Value to be be used as the background. This is used to calculate the
        signal of the image.
    """

    def __init__(
        self,
        *args,
        snr: PropertyLike[float] = 100,
        background: PropertyLike[float] = 0,
        max_val=1e8,
        **kwargs
    ):
        super().__init__(
            *args, snr=snr, background=background, max_val=max_val, **kwargs
        )

    def get(self, image, snr, background, max_val, **kwargs):
        image[image < 0] = 0
        immax = np.max(image)
        peak = np.abs(immax - background)

        rescale = snr ** 2 / peak ** 2
        rescale = np.clip(rescale, 1e-10, max_val / np.abs(immax))
        try:
            noisy_image = Image(np.random.poisson(image * rescale) / rescale)
            noisy_image.merge_properties_from(image)
            return noisy_image
        except ValueError:
            raise ValueError(
                "Numpy poisson function errored due to too large value. Set max_val in dt.Poisson to a lower value to fix."
            )
BenjaminMidtvedt commented 1 year ago

You're right. If you want a physically correct simulation, you can simply do noised_pipeline = expected_photons_per_pixel >> np.random.poisson where expected_photons_per_pixel can be calculated as simply as expected_photons_per_pixel = optics(particles) * A + b. b is ~100 for cmos, and A depends on your illumination.

The implementation of dt.Poisson is a remnant from when deeptrack was more focused on getting qualitatively reasonable results. The focus was on achieving reasonable results without knowing all the physical parameters of the system.

pulga10 commented 1 year ago

you can simply do noised_pipeline = expected_photons_per_pixel >> np.random.poisson

Great, thanks for pointing me to the solution!

expected_photons_per_pixel = optics(particles) * A + b. b is ~100 for cmos, and A depends on your illumination.

What is b here and why is it ~100 for a CMOS?

BenjaminMidtvedt commented 1 year ago

It's the number of spurious detection your camera will make per frame, in the absence of any light. Differs from camera to camera. 100 I've found to be pretty reasonable for standard microscopy cameras, but you can tune it to your system of course!

pulga10 commented 1 year ago

Ah, I see. Read noise & dark current of modern CMOS is typically way beyond 10 electrons per pixel, so to me that's completely irrelevant. Thank you!