From 02b3ed2f733bc2cd0a257812e994dd0240b08654 Mon Sep 17 00:00:00 2001 From: LukasK13 Date: Tue, 7 Jul 2020 09:11:21 +0200 Subject: [PATCH] call to parent object moved to ASensor --- esbo_etc/classes/sensor/ASensor.py | 105 +++++++++++++++++++++++++- esbo_etc/classes/sensor/Heterodyne.py | 47 +++++++++--- esbo_etc/classes/sensor/Imager.py | 77 +++++++++++++++---- 3 files changed, 201 insertions(+), 28 deletions(-) diff --git a/esbo_etc/classes/sensor/ASensor.py b/esbo_etc/classes/sensor/ASensor.py index 113beb6..28ccf33 100644 --- a/esbo_etc/classes/sensor/ASensor.py +++ b/esbo_etc/classes/sensor/ASensor.py @@ -3,6 +3,8 @@ import astropy.units as u from abc import abstractmethod from ..Entry import Entry from typing import Union +from ..SpectralQty import SpectralQty +from ...lib.logger import logger class ASensor: @@ -21,7 +23,25 @@ class ASensor: """ 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") def getSNR(self, exp_time: u.Quantity) -> u.dimensionless_unscaled: """ @@ -37,9 +57,34 @@ class ASensor: snr : Quantity The calculated SNR """ - pass + background, signal, obstruction = self.__calcIncomingRadiation() + return self.calcSNR(background, signal, obstruction, exp_time) @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) def getExpTime(self, snr: u.Quantity) -> u.s: """ @@ -55,9 +100,33 @@ class ASensor: exp_time : Quantity The necessary exposure time in seconds. """ - pass + background, signal, obstruction = self.__calcIncomingRadiation() + return self.calcExpTime(background, signal, obstruction, snr) @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) 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 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 ------- sensitivity: Quantity diff --git a/esbo_etc/classes/sensor/Heterodyne.py b/esbo_etc/classes/sensor/Heterodyne.py index 224256e..c03775e 100644 --- a/esbo_etc/classes/sensor/Heterodyne.py +++ b/esbo_etc/classes/sensor/Heterodyne.py @@ -6,6 +6,7 @@ import numpy as np from astropy.constants import k_B from typing import Union from ...lib.logger import logger +from ..SpectralQty import SpectralQty class Heterodyne(ASensor): @@ -51,12 +52,19 @@ class Heterodyne(ASensor): super().__init__(parent) @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. 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. @@ -66,7 +74,7 @@ class Heterodyne(ASensor): The calculated SNR as dimensionless quantity """ # 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) # Calculate the noise bandwidth delta_nu = self.__lambda_line.to(u.Hz, equivalencies=u.spectral()) / ( @@ -87,12 +95,18 @@ class Heterodyne(ASensor): return snr @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. 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 as dimensionless quantity. @@ -102,7 +116,7 @@ class Heterodyne(ASensor): The necessary exposure time in seconds. """ # 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) # Calculate the noise bandwidth delta_nu = self.__lambda_line.to(u.Hz, equivalencies=u.spectral()) / ( @@ -123,12 +137,19 @@ class Heterodyne(ASensor): return exp_time @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. 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 @@ -142,7 +163,7 @@ class Heterodyne(ASensor): The sensitivity as limiting apparent star magnitude in mag. """ # 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) # Calculate the noise bandwidth 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("-------------------------------------------------------------------------------------------------") - def calcTemperatures(self): + def calcTemperatures(self, background: SpectralQty, signal: SpectralQty, obstruction: float): """ 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 ------- t_signal : u.Quantity @@ -205,12 +235,11 @@ class Heterodyne(ASensor): The background temperature in Kelvins. """ 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)) * self.__lambda_line ** 2 / (2 * k_B) * u.sr).decompose() # Calculate the incoming photon current of the target 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" if size == "point": signal = signal.rebin(self.__lambda_line).qty.to(u.W / (u.m ** 2 * u.Hz), diff --git a/esbo_etc/classes/sensor/Imager.py b/esbo_etc/classes/sensor/Imager.py index 7113aa9..fbb1fda 100644 --- a/esbo_etc/classes/sensor/Imager.py +++ b/esbo_etc/classes/sensor/Imager.py @@ -93,12 +93,19 @@ class Imager(ASensor): common_conf.psf.osf, pixel_size) @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. 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. @@ -108,7 +115,8 @@ class Imager(ASensor): The calculated SNR as dimensionless quantity """ # 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 logger.info("Calculating the SNR...", extra={"spinning": True}) snr = signal_current.sum() * exp_time / np.sqrt( @@ -123,12 +131,18 @@ class Imager(ASensor): return snr.value * 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. 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 as dimensionless quantity. @@ -138,7 +152,8 @@ class Imager(ASensor): The necessary exposure time in seconds. """ # 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}) # Calculate the electron currents for all pixels signal_current_tot = signal_current.sum() @@ -160,12 +175,19 @@ class Imager(ASensor): return exp_time @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. 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 @@ -179,7 +201,8 @@ class Imager(ASensor): The sensitivity as limiting apparent star magnitude in mag. """ # 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}) # Fix the physical units of the SNR 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.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. + 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 ------- signal_current : Quantity @@ -316,7 +349,8 @@ class Imager(ASensor): """ # Calculate the total incoming electron current 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}) # Initialize a new PixelMask mask = PixelMask(self.__pixel_geometry, self.__pixel_size, self.__center_offset) @@ -335,7 +369,7 @@ class Imager(ASensor): else: # Calculate the diameter of the photometric aperture from the given contained energy logger.info("Calculating the diameter of the photometric aperture...", - extra={"spinning": True}) + extra={"spinning": True}) d_photometric_ap = self.__calcPhotometricAperture(obstruction) # Mask the pixels to be exposed 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" % ( d_photometric_ap.value / 2)) elif self.__contained_energy.lower() == "min": - logger.info("The radius of the photometric aperture is %.2f pixels. This equals the first minimum" % ( - d_photometric_ap.value / 2)) + logger.info( + "The radius of the photometric aperture is %.2f pixels. This equals the first minimum" % ( + d_photometric_ap.value / 2)) else: - logger.info("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 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.") if size.lower() != "extended": # 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 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. + 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 ------- signal_current : Quantity @@ -416,11 +462,10 @@ class Imager(ASensor): """ # Calculate the photon current of the background 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) # Calculate the incoming photon current of the target 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" if size == "point": signal_photon_current = signal * np.pi * (self.__common_conf.d_aperture() / 2) ** 2