call to parent object moved to ASensor

This commit is contained in:
Lukas Klass 2020-07-07 09:11:21 +02:00
parent 0f803a3484
commit 02b3ed2f73
3 changed files with 201 additions and 28 deletions

View File

@ -3,6 +3,8 @@ import astropy.units as u
from abc import abstractmethod from abc import abstractmethod
from ..Entry import Entry from ..Entry import Entry
from typing import Union from typing import Union
from ..SpectralQty import SpectralQty
from ...lib.logger import logger
class ASensor: class ASensor:
@ -21,7 +23,25 @@ class ASensor:
""" """
self._parent = parent self._parent = parent
@abstractmethod def __calcIncomingRadiation(self):
"""
Trigger the radiation transportation pipeline in order to calculate the received radiation.
Returns
-------
background : SpectralQty
The received background radiation
signal : SpectralQty
The received signal radiation
obstruction : float
The obstruction factor of the aperture as ratio A_ob / A_ap
"""
logger.info("Calculating incoming background radiation", extra={"spinning": True})
background = self._parent.calcBackground()
logger.info("Calculating incoming signal radiation", extra={"spinning": True})
signal, obstruction = self._parent.calcSignal()
return background, signal, obstruction
@u.quantity_input(exp_time="time") @u.quantity_input(exp_time="time")
def getSNR(self, exp_time: u.Quantity) -> u.dimensionless_unscaled: def getSNR(self, exp_time: u.Quantity) -> u.dimensionless_unscaled:
""" """
@ -37,9 +57,34 @@ class ASensor:
snr : Quantity snr : Quantity
The calculated SNR The calculated SNR
""" """
pass background, signal, obstruction = self.__calcIncomingRadiation()
return self.calcSNR(background, signal, obstruction, exp_time)
@abstractmethod @abstractmethod
@u.quantity_input(exp_time="time")
def calcSNR(self, background: SpectralQty, signal: SpectralQty, obstruction: float,
exp_time: u.Quantity) -> u.dimensionless_unscaled:
"""
Calculate the signal to noise ratio (SNR) for the given exposure time.
Parameters
----------
background : SpectralQty
The received background radiation
signal : SpectralQty
The received signal radiation
obstruction : float
The obstruction factor of the aperture as ratio A_ob / A_ap
exp_time : time-Quantity
The exposure time to calculate the SNR for.
Returns
-------
snr : Quantity
The calculated SNR
"""
pass
@u.quantity_input(snr=u.dimensionless_unscaled) @u.quantity_input(snr=u.dimensionless_unscaled)
def getExpTime(self, snr: u.Quantity) -> u.s: def getExpTime(self, snr: u.Quantity) -> u.s:
""" """
@ -55,9 +100,33 @@ class ASensor:
exp_time : Quantity exp_time : Quantity
The necessary exposure time in seconds. The necessary exposure time in seconds.
""" """
pass background, signal, obstruction = self.__calcIncomingRadiation()
return self.calcExpTime(background, signal, obstruction, snr)
@abstractmethod @abstractmethod
@u.quantity_input(snr=u.dimensionless_unscaled)
def calcExpTime(self, background: SpectralQty, signal: SpectralQty, obstruction: float, snr: u.Quantity) -> u.s:
"""
Calculate the necessary exposure time in order to achieve the given SNR.
Parameters
----------
background : SpectralQty
The received background radiation
signal : SpectralQty
The received signal radiation
obstruction : float
The obstruction factor of the aperture as ratio A_ob / A_ap
snr : Quantity
The SNR for which the necessary exposure time shall be calculated.
Returns
-------
exp_time : Quantity
The necessary exposure time in seconds.
"""
pass
@u.quantity_input(exp_time="time", snr=u.dimensionless_unscaled, target_brightness=u.mag) @u.quantity_input(exp_time="time", snr=u.dimensionless_unscaled, target_brightness=u.mag)
def getSensitivity(self, exp_time: u.Quantity, snr: u.Quantity, target_brightness: u.Quantity) -> u.mag: def getSensitivity(self, exp_time: u.Quantity, snr: u.Quantity, target_brightness: u.Quantity) -> u.mag:
""" """
@ -72,6 +141,36 @@ class ASensor:
target_brightness : Quantity target_brightness : Quantity
The target brightness in magnitudes. The target brightness in magnitudes.
Returns
-------
sensitivity: Quantity
The sensitivity as limiting apparent star magnitude in mag.
"""
background, signal, obstruction = self.__calcIncomingRadiation()
return self.calcSensitivity(background, signal, obstruction, exp_time, snr, target_brightness)
@abstractmethod
@u.quantity_input(exp_time="time", snr=u.dimensionless_unscaled, target_brightness=u.mag)
def calcSensitivity(self, background: SpectralQty, signal: SpectralQty, obstruction: float, exp_time: u.Quantity,
snr: u.Quantity, target_brightness: u.Quantity) -> u.mag:
"""
Calculate the sensitivity of the telescope detector combination.
Parameters
----------
background : SpectralQty
The received background radiation
signal : SpectralQty
The received signal radiation
obstruction : float
The obstruction factor of the aperture as ratio A_ob / A_ap
exp_time : Quantity
The exposure time in seconds.
snr : Quantity
The SNR for which the sensitivity time shall be calculated.
target_brightness : Quantity
The target brightness in magnitudes.
Returns Returns
------- -------
sensitivity: Quantity sensitivity: Quantity

View File

@ -6,6 +6,7 @@ import numpy as np
from astropy.constants import k_B from astropy.constants import k_B
from typing import Union from typing import Union
from ...lib.logger import logger from ...lib.logger import logger
from ..SpectralQty import SpectralQty
class Heterodyne(ASensor): class Heterodyne(ASensor):
@ -51,12 +52,19 @@ class Heterodyne(ASensor):
super().__init__(parent) super().__init__(parent)
@u.quantity_input(exp_time="time") @u.quantity_input(exp_time="time")
def getSNR(self, exp_time: u.Quantity) -> u.dimensionless_unscaled: def calcSNR(self, background: SpectralQty, signal: SpectralQty, obstruction: float,
exp_time: u.Quantity) -> u.dimensionless_unscaled:
""" """
Calculate the signal to background ratio (SNR) for the given exposure time using the CCD-equation. Calculate the signal to background ratio (SNR) for the given exposure time using the CCD-equation.
Parameters Parameters
---------- ----------
background : SpectralQty
The received background radiation
signal : SpectralQty
The received signal radiation
obstruction : float
The obstruction factor of the aperture as ratio A_ob / A_ap
exp_time : time-Quantity exp_time : time-Quantity
The exposure time to calculate the SNR for. The exposure time to calculate the SNR for.
@ -66,7 +74,7 @@ class Heterodyne(ASensor):
The calculated SNR as dimensionless quantity The calculated SNR as dimensionless quantity
""" """
# Calculate the signal and background temperatures # Calculate the signal and background temperatures
t_signal, t_background = self.calcTemperatures() t_signal, t_background = self.calcTemperatures(background, signal, obstruction)
t_sys = 2 * (t_background + self.__receiver_temp) t_sys = 2 * (t_background + self.__receiver_temp)
# Calculate the noise bandwidth # Calculate the noise bandwidth
delta_nu = self.__lambda_line.to(u.Hz, equivalencies=u.spectral()) / ( delta_nu = self.__lambda_line.to(u.Hz, equivalencies=u.spectral()) / (
@ -87,12 +95,18 @@ class Heterodyne(ASensor):
return snr return snr
@u.quantity_input(snr=u.dimensionless_unscaled) @u.quantity_input(snr=u.dimensionless_unscaled)
def getExpTime(self, snr: u.Quantity) -> u.s: def calcExpTime(self, background: SpectralQty, signal: SpectralQty, obstruction: float, snr: u.Quantity) -> u.s:
""" """
Calculate the necessary exposure time in order to achieve the given SNR. Calculate the necessary exposure time in order to achieve the given SNR.
Parameters Parameters
---------- ----------
background : SpectralQty
The received background radiation
signal : SpectralQty
The received signal radiation
obstruction : float
The obstruction factor of the aperture as ratio A_ob / A_ap
snr : Quantity snr : Quantity
The SNR for which the necessary exposure time shall be calculated as dimensionless quantity. The SNR for which the necessary exposure time shall be calculated as dimensionless quantity.
@ -102,7 +116,7 @@ class Heterodyne(ASensor):
The necessary exposure time in seconds. The necessary exposure time in seconds.
""" """
# Calculate the signal and background temperatures # Calculate the signal and background temperatures
t_signal, t_background = self.calcTemperatures() t_signal, t_background = self.calcTemperatures(background, signal, obstruction)
t_sys = 2 * (t_background + self.__receiver_temp) t_sys = 2 * (t_background + self.__receiver_temp)
# Calculate the noise bandwidth # Calculate the noise bandwidth
delta_nu = self.__lambda_line.to(u.Hz, equivalencies=u.spectral()) / ( delta_nu = self.__lambda_line.to(u.Hz, equivalencies=u.spectral()) / (
@ -123,12 +137,19 @@ class Heterodyne(ASensor):
return exp_time return exp_time
@u.quantity_input(exp_time="time", snr=u.dimensionless_unscaled, target_brightness=u.mag) @u.quantity_input(exp_time="time", snr=u.dimensionless_unscaled, target_brightness=u.mag)
def getSensitivity(self, exp_time: u.Quantity, snr: u.Quantity, target_brightness: u.Quantity) -> u.mag: def calcSensitivity(self, background: SpectralQty, signal: SpectralQty, obstruction: float, exp_time: u.Quantity,
snr: u.Quantity, target_brightness: u.Quantity) -> u.mag:
""" """
Calculate the sensitivity of the telescope detector combination. Calculate the sensitivity of the telescope detector combination.
Parameters Parameters
---------- ----------
background : SpectralQty
The received background radiation
signal : SpectralQty
The received signal radiation
obstruction : float
The obstruction factor of the aperture as ratio A_ob / A_ap
exp_time : Quantity exp_time : Quantity
The exposure time in seconds. The exposure time in seconds.
snr : Quantity snr : Quantity
@ -142,7 +163,7 @@ class Heterodyne(ASensor):
The sensitivity as limiting apparent star magnitude in mag. The sensitivity as limiting apparent star magnitude in mag.
""" """
# Calculate the signal and background temperatures # Calculate the signal and background temperatures
t_signal, t_background = self.calcTemperatures() t_signal, t_background = self.calcTemperatures(background, signal, obstruction)
t_sys = 2 * (t_background + self.__receiver_temp) t_sys = 2 * (t_background + self.__receiver_temp)
# Calculate the noise bandwidth # Calculate the noise bandwidth
delta_nu = self.__lambda_line.to(u.Hz, equivalencies=u.spectral()) / ( delta_nu = self.__lambda_line.to(u.Hz, equivalencies=u.spectral()) / (
@ -193,10 +214,19 @@ class Heterodyne(ASensor):
logger.info(prefix + "Antenna temperature: %1.2e K" % t_signal.value) logger.info(prefix + "Antenna temperature: %1.2e K" % t_signal.value)
logger.info("-------------------------------------------------------------------------------------------------") logger.info("-------------------------------------------------------------------------------------------------")
def calcTemperatures(self): def calcTemperatures(self, background: SpectralQty, signal: SpectralQty, obstruction: float):
""" """
Calculate the noise temperatures of the signal and the background radiation. Calculate the noise temperatures of the signal and the background radiation.
Parameters
----------
background : SpectralQty
The received background radiation
signal : SpectralQty
The received signal radiation
obstruction : float
The obstruction factor of the aperture as ratio A_ob / A_ap
Returns Returns
------- -------
t_signal : u.Quantity t_signal : u.Quantity
@ -205,12 +235,11 @@ class Heterodyne(ASensor):
The background temperature in Kelvins. The background temperature in Kelvins.
""" """
logger.info("Calculating the system temperature.") logger.info("Calculating the system temperature.")
t_background = (self._parent.calcBackground().rebin(self.__lambda_line).qty.to( t_background = (background.rebin(self.__lambda_line).qty.to(
u.W / (u.m ** 2 * u.Hz * u.sr), equivalencies=u.spectral_density(self.__lambda_line)) * u.W / (u.m ** 2 * u.Hz * u.sr), equivalencies=u.spectral_density(self.__lambda_line)) *
self.__lambda_line ** 2 / (2 * k_B) * u.sr).decompose() self.__lambda_line ** 2 / (2 * k_B) * u.sr).decompose()
# Calculate the incoming photon current of the target # Calculate the incoming photon current of the target
logger.info("Calculating the signal temperature.") logger.info("Calculating the signal temperature.")
signal, obstruction = self._parent.calcSignal()
size = "extended" if signal.qty.unit.is_equivalent(u.W / (u.m ** 2 * u.nm * u.sr)) else "point" size = "extended" if signal.qty.unit.is_equivalent(u.W / (u.m ** 2 * u.nm * u.sr)) else "point"
if size == "point": if size == "point":
signal = signal.rebin(self.__lambda_line).qty.to(u.W / (u.m ** 2 * u.Hz), signal = signal.rebin(self.__lambda_line).qty.to(u.W / (u.m ** 2 * u.Hz),

View File

@ -93,12 +93,19 @@ class Imager(ASensor):
common_conf.psf.osf, pixel_size) common_conf.psf.osf, pixel_size)
@u.quantity_input(exp_time="time") @u.quantity_input(exp_time="time")
def getSNR(self, exp_time: u.Quantity) -> u.dimensionless_unscaled: def calcSNR(self, background: SpectralQty, signal: SpectralQty, obstruction: float,
exp_time: u.Quantity) -> u.dimensionless_unscaled:
""" """
Calculate the signal to background ratio (SNR) for the given exposure time using the CCD-equation. Calculate the signal to background ratio (SNR) for the given exposure time using the CCD-equation.
Parameters Parameters
---------- ----------
background : SpectralQty
The received background radiation
signal : SpectralQty
The received signal radiation
obstruction : float
The obstruction factor of the aperture as ratio A_ob / A_ap
exp_time : time-Quantity exp_time : time-Quantity
The exposure time to calculate the SNR for. The exposure time to calculate the SNR for.
@ -108,7 +115,8 @@ class Imager(ASensor):
The calculated SNR as dimensionless quantity The calculated SNR as dimensionless quantity
""" """
# Calculate the electron currents # Calculate the electron currents
signal_current, background_current, read_noise, dark_current = self.__exposePixels() signal_current, background_current, read_noise, dark_current = self.__exposePixels(background, signal,
obstruction)
# Calculate the SNR using the CCD-equation # Calculate the SNR using the CCD-equation
logger.info("Calculating the SNR...", extra={"spinning": True}) logger.info("Calculating the SNR...", extra={"spinning": True})
snr = signal_current.sum() * exp_time / np.sqrt( snr = signal_current.sum() * exp_time / np.sqrt(
@ -123,12 +131,18 @@ class Imager(ASensor):
return snr.value * u.dimensionless_unscaled return snr.value * u.dimensionless_unscaled
@u.quantity_input(snr=u.dimensionless_unscaled) @u.quantity_input(snr=u.dimensionless_unscaled)
def getExpTime(self, snr: u.Quantity) -> u.s: def calcExpTime(self, background: SpectralQty, signal: SpectralQty, obstruction: float, snr: u.Quantity) -> u.s:
""" """
Calculate the necessary exposure time in order to achieve the given SNR. Calculate the necessary exposure time in order to achieve the given SNR.
Parameters Parameters
---------- ----------
background : SpectralQty
The received background radiation
signal : SpectralQty
The received signal radiation
obstruction : float
The obstruction factor of the aperture as ratio A_ob / A_ap
snr : Quantity snr : Quantity
The SNR for which the necessary exposure time shall be calculated as dimensionless quantity. The SNR for which the necessary exposure time shall be calculated as dimensionless quantity.
@ -138,7 +152,8 @@ class Imager(ASensor):
The necessary exposure time in seconds. The necessary exposure time in seconds.
""" """
# Calculate the electron currents # Calculate the electron currents
signal_current, background_current, read_noise, dark_current = self.__exposePixels() signal_current, background_current, read_noise, dark_current = self.__exposePixels(background, signal,
obstruction)
logger.info("Calculating the exposure time...", extra={"spinning": True}) logger.info("Calculating the exposure time...", extra={"spinning": True})
# Calculate the electron currents for all pixels # Calculate the electron currents for all pixels
signal_current_tot = signal_current.sum() signal_current_tot = signal_current.sum()
@ -160,12 +175,19 @@ class Imager(ASensor):
return exp_time return exp_time
@u.quantity_input(exp_time="time", snr=u.dimensionless_unscaled, target_brightness=u.mag) @u.quantity_input(exp_time="time", snr=u.dimensionless_unscaled, target_brightness=u.mag)
def getSensitivity(self, exp_time: u.Quantity, snr: u.Quantity, target_brightness: u.Quantity) -> u.mag: def calcSensitivity(self, background: SpectralQty, signal: SpectralQty, obstruction: float, exp_time: u.Quantity,
snr: u.Quantity, target_brightness: u.Quantity) -> u.mag:
""" """
Calculate the sensitivity of the telescope detector combination. Calculate the sensitivity of the telescope detector combination.
Parameters Parameters
---------- ----------
background : SpectralQty
The received background radiation
signal : SpectralQty
The received signal radiation
obstruction : float
The obstruction factor of the aperture as ratio A_ob / A_ap
exp_time : Quantity exp_time : Quantity
The exposure time in seconds. The exposure time in seconds.
snr : Quantity snr : Quantity
@ -179,7 +201,8 @@ class Imager(ASensor):
The sensitivity as limiting apparent star magnitude in mag. The sensitivity as limiting apparent star magnitude in mag.
""" """
# Calculate the electron currents # Calculate the electron currents
signal_current, background_current, read_noise, dark_current = self.__exposePixels() signal_current, background_current, read_noise, dark_current = self.__exposePixels(background, signal,
obstruction)
logger.info("Calculating the sensitivity...", extra={"spinning": True}) logger.info("Calculating the sensitivity...", extra={"spinning": True})
# Fix the physical units of the SNR # Fix the physical units of the SNR
snr = snr * u.electron ** 0.5 snr = snr * u.electron ** 0.5
@ -299,10 +322,20 @@ class Imager(ASensor):
hdul = fits.HDUList([hdu, signal_hdu, background_hdu, read_noise_hdu, dark_hdu]) hdul = fits.HDUList([hdu, signal_hdu, background_hdu, read_noise_hdu, dark_hdu])
hdul.writeto(os.path.join(path, "results.fits"), overwrite=True) hdul.writeto(os.path.join(path, "results.fits"), overwrite=True)
def __exposePixels(self) -> Tuple[u.Quantity, u.Quantity, u.Quantity, u.Quantity]: def __exposePixels(self, background: SpectralQty, signal: SpectralQty,
obstruction: float) -> Tuple[u.Quantity, u.Quantity, u.Quantity, u.Quantity]:
""" """
Expose the pixels and calculate the signal and noise electron currents per pixel. Expose the pixels and calculate the signal and noise electron currents per pixel.
Parameters
----------
background : SpectralQty
The received background radiation
signal : SpectralQty
The received signal radiation
obstruction : float
The obstruction factor of the aperture as ratio A_ob / A_ap
Returns Returns
------- -------
signal_current : Quantity signal_current : Quantity
@ -316,7 +349,8 @@ class Imager(ASensor):
""" """
# Calculate the total incoming electron current # Calculate the total incoming electron current
logger.info("Calculating incoming electron current...", extra={"spinning": True}) logger.info("Calculating incoming electron current...", extra={"spinning": True})
signal_current, size, obstruction, background_current = self.__calcIncomingElectronCurrent() signal_current, size, obstruction, background_current = self.__calcIncomingElectronCurrent(background, signal,
obstruction)
# info("Finished calculating incoming electron current", extra={"spinning": False}) # info("Finished calculating incoming electron current", extra={"spinning": False})
# Initialize a new PixelMask # Initialize a new PixelMask
mask = PixelMask(self.__pixel_geometry, self.__pixel_size, self.__center_offset) mask = PixelMask(self.__pixel_geometry, self.__pixel_size, self.__center_offset)
@ -335,7 +369,7 @@ class Imager(ASensor):
else: else:
# Calculate the diameter of the photometric aperture from the given contained energy # Calculate the diameter of the photometric aperture from the given contained energy
logger.info("Calculating the diameter of the photometric aperture...", logger.info("Calculating the diameter of the photometric aperture...",
extra={"spinning": True}) extra={"spinning": True})
d_photometric_ap = self.__calcPhotometricAperture(obstruction) d_photometric_ap = self.__calcPhotometricAperture(obstruction)
# Mask the pixels to be exposed # Mask the pixels to be exposed
mask.createPhotometricAperture(self.__shape, d_photometric_ap / 2) mask.createPhotometricAperture(self.__shape, d_photometric_ap / 2)
@ -354,11 +388,13 @@ class Imager(ASensor):
logger.info("The radius of the photometric aperture is %.2f pixels. This equals the FWHM" % ( logger.info("The radius of the photometric aperture is %.2f pixels. This equals the FWHM" % (
d_photometric_ap.value / 2)) d_photometric_ap.value / 2))
elif self.__contained_energy.lower() == "min": elif self.__contained_energy.lower() == "min":
logger.info("The radius of the photometric aperture is %.2f pixels. This equals the first minimum" % ( logger.info(
d_photometric_ap.value / 2)) "The radius of the photometric aperture is %.2f pixels. This equals the first minimum" % (
d_photometric_ap.value / 2))
else: else:
logger.info("The radius of the photometric aperture is %.2f pixels. This equals %.0f%% encircled energy" % logger.info(
(d_photometric_ap.value / 2, self.__contained_energy)) "The radius of the photometric aperture is %.2f pixels. This equals %.0f%% encircled energy" % (
d_photometric_ap.value / 2, self.__contained_energy))
logger.info("The photometric aperture contains " + str(np.count_nonzero(mask)) + " pixels.") logger.info("The photometric aperture contains " + str(np.count_nonzero(mask)) + " pixels.")
if size.lower() != "extended": if size.lower() != "extended":
# Map the PSF onto the pixel mask in order to get the relative irradiance of each pixel # Map the PSF onto the pixel mask in order to get the relative irradiance of each pixel
@ -399,10 +435,20 @@ class Imager(ASensor):
d_photometric_ap = observation_angle / pixel_fov d_photometric_ap = observation_angle / pixel_fov
return d_photometric_ap * u.pix return d_photometric_ap * u.pix
def __calcIncomingElectronCurrent(self) -> Tuple[u.Quantity, str, float, u.Quantity]: def __calcIncomingElectronCurrent(self, background: SpectralQty, signal: SpectralQty,
obstruction: float) -> Tuple[u.Quantity, str, float, u.Quantity]:
""" """
Calculate the detected electron current of the signal and the background. Calculate the detected electron current of the signal and the background.
Parameters
----------
background : SpectralQty
The received background radiation
signal : SpectralQty
The received signal radiation
obstruction : float
The obstruction factor of the aperture as ratio A_ob / A_ap
Returns Returns
------- -------
signal_current : Quantity signal_current : Quantity
@ -416,11 +462,10 @@ class Imager(ASensor):
""" """
# Calculate the photon current of the background # Calculate the photon current of the background
logger.info("Calculating the background photon current.") logger.info("Calculating the background photon current.")
background_photon_current = self._parent.calcBackground() * np.pi * ( background_photon_current = background * np.pi * (
self.__pixel_size.to(u.m) ** 2 / u.pix) / (4 * self.__f_number ** 2 + 1) * (1 * u.sr) self.__pixel_size.to(u.m) ** 2 / u.pix) / (4 * self.__f_number ** 2 + 1) * (1 * u.sr)
# Calculate the incoming photon current of the target # Calculate the incoming photon current of the target
logger.info("Calculating the signal photon current.") logger.info("Calculating the signal photon current.")
signal, obstruction = self._parent.calcSignal()
size = "extended" if signal.qty.unit.is_equivalent(u.W / (u.m ** 2 * u.nm * u.sr)) else "point" size = "extended" if signal.qty.unit.is_equivalent(u.W / (u.m ** 2 * u.nm * u.sr)) else "point"
if size == "point": if size == "point":
signal_photon_current = signal * np.pi * (self.__common_conf.d_aperture() / 2) ** 2 signal_photon_current = signal * np.pi * (self.__common_conf.d_aperture() / 2) ** 2