From e5b6e3b70091e5771ba4d4f69d1422174a214987 Mon Sep 17 00:00:00 2001 From: LukasK13 Date: Fri, 26 Jun 2020 19:15:43 +0200 Subject: [PATCH] Bugfix: calculate extended target signal using spectral radiance values --- esbo_etc/classes/IRadiant.py | 4 +- .../optical_component/AOpticalComponent.py | 8 +-- esbo_etc/classes/sensor/Imager.py | 65 ++++++++++--------- esbo_etc/classes/target/ATarget.py | 11 +--- esbo_etc/classes/target/BlackBodyTarget.py | 30 +++++---- esbo_etc/classes/target/FileTarget.py | 9 +-- 6 files changed, 61 insertions(+), 66 deletions(-) diff --git a/esbo_etc/classes/IRadiant.py b/esbo_etc/classes/IRadiant.py index 977ee5a..4daf6ad 100644 --- a/esbo_etc/classes/IRadiant.py +++ b/esbo_etc/classes/IRadiant.py @@ -9,7 +9,7 @@ class IRadiant(ABC): in the beam. """ @abstractmethod - def calcSignal(self) -> Tuple[SpectralQty, str, float]: + def calcSignal(self) -> Tuple[SpectralQty, float]: """ Calculate the signal coming from the component @@ -17,8 +17,6 @@ class IRadiant(ABC): ------- signal : SpectralQty The emitted, reflected or transmitted signal - size : str - The size of the target. obstruction : float The obstruction factor as A_ob / A_ap. """ diff --git a/esbo_etc/classes/optical_component/AOpticalComponent.py b/esbo_etc/classes/optical_component/AOpticalComponent.py index e131702..b640b55 100644 --- a/esbo_etc/classes/optical_component/AOpticalComponent.py +++ b/esbo_etc/classes/optical_component/AOpticalComponent.py @@ -51,7 +51,7 @@ class AOpticalComponent(IRadiant): self.__obstructor_temp = obstructor_temp self.__obstructor_emissivity = obstructor_emissivity - def calcSignal(self) -> Tuple[SpectralQty, str, float]: + def calcSignal(self) -> Tuple[SpectralQty, float]: """ Calculate the spectral flux density of the target's signal @@ -59,17 +59,15 @@ class AOpticalComponent(IRadiant): ------- signal : SpectralQty The spectral flux density of the target's signal - size : str - The size of the target. obstruction : float The obstruction factor as A_ob / A_ap. """ - signal, size, obstruction = self.__parent.calcSignal() + signal, obstruction = self.__parent.calcSignal() logger.info("Calculating signal for class '" + self.__class__.__name__ + "'.") signal = self._propagate(signal) * (1 - self.__obstruction) obstruction = obstruction + self.__obstruction logger.debug(signal) - return signal, size, obstruction + return signal, obstruction def calcBackground(self) -> SpectralQty: """ diff --git a/esbo_etc/classes/sensor/Imager.py b/esbo_etc/classes/sensor/Imager.py index 312d809..9c3d5c5 100644 --- a/esbo_etc/classes/sensor/Imager.py +++ b/esbo_etc/classes/sensor/Imager.py @@ -420,8 +420,14 @@ class Imager(ASensor): 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, size, obstruction = self._parent.calcSignal() - signal_photon_current = signal * np.pi * (self.__common_conf.d_aperture() / 2) ** 2 + 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 + else: + signal_photon_current = signal * np.pi * self.__pixel_size.to(u.m) ** 2 / ( + 4 * self.__f_number ** 2 + 1) * (1 * u.sr) + print(signal_photon_current) # Calculate the electron current of the background and thereby handling the photon energy as lambda-function background_current = ( background_photon_current / (lambda wl: (const.h * const.c / wl).to(u.W * u.s) / u.photon) * @@ -499,31 +505,30 @@ class Imager(ASensor): return "pixel -> well_capacity: " + mes # Check photometric aperture - if conf.astroscene.target.size == "point": - if not hasattr(sensor, "photometric_aperture"): - setattr(sensor, "photometric_aperture", Entry(shape=Entry(val="circle"), - contained_energy=Entry(val="FWHM"))) - if hasattr(sensor.photometric_aperture, "contained_pixels"): - mes = sensor.photometric_aperture.contained_pixels.check_quantity("val", u.pix) - if mes is not None: - return "photometric_aperture -> contained_pixels: " + mes - else: - if not hasattr(sensor.photometric_aperture, "shape"): - return "Missing container 'shape'." - mes = sensor.photometric_aperture.shape.check_selection("val", ["square", "circle"]) - if mes is not None: - return "photometric_aperture -> shape: " + mes - if not hasattr(sensor.photometric_aperture, "contained_energy"): - return "Missing container 'contained_energy'." - mes = sensor.photometric_aperture.contained_energy.check_float("val") - if mes is not None: - if conf.common.psf().lower() == "airy": - mes = sensor.photometric_aperture.contained_energy.check_selection("val", - ["peak", "FWHM", "fwhm", - "min"]) - if mes is not None: - return "photometric_aperture -> contained_energy: " + mes - else: - mes = sensor.photometric_aperture.contained_energy.check_selection("val", ["FWHM", "fwhm"]) - if mes is not None: - return "photometric_aperture -> contained_energy: " + mes + if not hasattr(sensor, "photometric_aperture"): + setattr(sensor, "photometric_aperture", Entry(shape=Entry(val="circle"), + contained_energy=Entry(val="FWHM"))) + if hasattr(sensor.photometric_aperture, "contained_pixels"): + mes = sensor.photometric_aperture.contained_pixels.check_quantity("val", u.pix) + if mes is not None: + return "photometric_aperture -> contained_pixels: " + mes + else: + if not hasattr(sensor.photometric_aperture, "shape"): + return "Missing container 'shape'." + mes = sensor.photometric_aperture.shape.check_selection("val", ["square", "circle"]) + if mes is not None: + return "photometric_aperture -> shape: " + mes + if not hasattr(sensor.photometric_aperture, "contained_energy"): + return "Missing container 'contained_energy'." + mes = sensor.photometric_aperture.contained_energy.check_float("val") + if mes is not None: + if conf.common.psf().lower() == "airy": + mes = sensor.photometric_aperture.contained_energy.check_selection("val", + ["peak", "FWHM", "fwhm", + "min"]) + if mes is not None: + return "photometric_aperture -> contained_energy: " + mes + else: + mes = sensor.photometric_aperture.contained_energy.check_selection("val", ["FWHM", "fwhm"]) + if mes is not None: + return "photometric_aperture -> contained_energy: " + mes diff --git a/esbo_etc/classes/target/ATarget.py b/esbo_etc/classes/target/ATarget.py index 5e2906b..706531a 100644 --- a/esbo_etc/classes/target/ATarget.py +++ b/esbo_etc/classes/target/ATarget.py @@ -15,7 +15,7 @@ class ATarget(IRadiant): @abstractmethod @u.quantity_input(wl_bins="length") - def __init__(self, sfd: SpectralQty, wl_bins: u.Quantity, size: str = "Point"): + def __init__(self, sfd: SpectralQty, wl_bins: u.Quantity): """ Initialize a new target @@ -25,12 +25,9 @@ class ATarget(IRadiant): The spectral flux density of the target wl_bins : length-Quantity The bins to be used for evaluating spectral quantities. - size : str - The size of the target. Can be either point or extended. """ self.__sfd = sfd self.__wl_bins = wl_bins - self.__size = size def calcBackground(self) -> SpectralQty: """ @@ -46,7 +43,7 @@ class ATarget(IRadiant): logger.debug(background) return background - def calcSignal(self) -> Tuple[SpectralQty, str, float]: + def calcSignal(self) -> Tuple[SpectralQty, float]: """ Calculate the spectral flux density of the target's signal @@ -54,14 +51,12 @@ class ATarget(IRadiant): ------- signal : SpectralQty The spectral flux density of the target's signal - size : str - The size of the target. obstruction : float The obstruction factor as A_ob / A_ap. """ logger.info("Calculating signal for class '" + self.__class__.__name__ + "'.") logger.debug(self.__sfd) - return self.__sfd, self.__size, 0.0 + return self.__sfd, 0.0 @staticmethod @abstractmethod diff --git a/esbo_etc/classes/target/BlackBodyTarget.py b/esbo_etc/classes/target/BlackBodyTarget.py index f383312..03f280e 100644 --- a/esbo_etc/classes/target/BlackBodyTarget.py +++ b/esbo_etc/classes/target/BlackBodyTarget.py @@ -24,9 +24,9 @@ class BlackBodyTarget(ATarget): M=dict(wl=4800 * u.nm, sfd=2.07e-14 * u.W / (u.m ** 2 * u.nm)), N=dict(wl=10200 * u.nm, sfd=1.23e-15 * u.W / (u.m ** 2 * u.nm))) - @u.quantity_input(wl_bins='length', temp=[u.Kelvin, u.Celsius], mag=u.mag) - def __init__(self, wl_bins: u.Quantity, temp: u.Quantity = 5778 * u.K, - mag: u.Quantity = 0 * u.mag, band: str = "V", size: str = "Point"): + @u.quantity_input(wl_bins='length', temp=[u.Kelvin, u.Celsius], mag=[u.mag, u.mag / u.sr]) + def __init__(self, wl_bins: u.Quantity, temp: u.Quantity = 5778 * u.K, mag: u.Quantity = 0 * u.mag, + band: str = "V"): """ Initialize a new black body point source @@ -36,12 +36,11 @@ class BlackBodyTarget(ATarget): Wavelengths used for binning temp : Quantity in Kelvin / Celsius Temperature of the black body - mag : Quantity in mag - Desired apparent magnitude of the point source + mag : Quantity in mag or mag / sr + Desired apparent magnitude of the black body source. If the magnitude is given in mag / sr or an equivalent + unit, an extended source will be assumed. band : str Band used for fitting the planck curve to a star of 0th magnitude. Can be one of [U, B, V, R, I, J, H, K]. - size : str - The size of the target. Can be either point or extended Returns ------- @@ -53,12 +52,18 @@ class BlackBodyTarget(ATarget): # Calculate the correction factor for a star of 0th magnitude using the spectral flux density # for the central wavelength of the given band - factor = self._band[band.upper()]["sfd"] / (bb(self._band[band.upper()]["wl"]) * u.sr) * u.sr + if mag.unit.is_equivalent(u.mag / u.sr): + solid_angle_unit = (u.mag / mag.unit) + mag = mag * solid_angle_unit + factor = self._band[band.upper()]["sfd"] / (bb(self._band[band.upper()]["wl"]) * ( + solid_angle_unit.to(u.sr) * u.sr)) + else: + factor = self._band[band.upper()]["sfd"] / (bb(self._band[band.upper()]["wl"]) * u.sr) * u.sr # Calculate spectral flux density for the given wavelengths and scale it for a star of the given magnitude sfd = bb(wl_bins) * factor * 10 ** (- 2 / 5 * mag / u.mag) # / 1.195 * 1.16 # scaling for AETC validation # Initialize super class - super().__init__(SpectralQty(wl_bins, sfd), wl_bins, size) + super().__init__(SpectralQty(wl_bins, sfd), wl_bins) @staticmethod def check_config(conf: Entry) -> Union[None, str]: @@ -80,10 +85,9 @@ class BlackBodyTarget(ATarget): return mes mes = conf.check_quantity("mag", u.mag) if mes is not None: - return mes + mes = conf.check_quantity("mag", u.mag / u.sr) + if mes is not None: + return mes mes = conf.check_selection("band", ["U", "B", "V", "R", "I", "J", "H", "K", "L", "M", "N"]) if mes is not None: return mes - mes = conf.check_selection("size", ["point", "extended"]) - if mes is not None: - return mes diff --git a/esbo_etc/classes/target/FileTarget.py b/esbo_etc/classes/target/FileTarget.py index 9d11783..b207bd8 100644 --- a/esbo_etc/classes/target/FileTarget.py +++ b/esbo_etc/classes/target/FileTarget.py @@ -11,7 +11,7 @@ class FileTarget(ATarget): """ @u.quantity_input(wl_bins="length") - def __init__(self, file: str, wl_bins: u.Quantity, size: str = "Point"): + def __init__(self, file: str, wl_bins: u.Quantity): """ Initialize a new target from a file containing the spectral flux density values @@ -24,13 +24,11 @@ class FileTarget(ATarget): will be read from the column headers or otherwise assumed to be *nm* and *W / m^2 / nm*. wl_bins : length-Quantity Wavelengths used for binning - size : str - The size of the target. Can be either point or extended. """ # Create spectral quantity from file sfd = SpectralQty.fromFile(file, u.nm, u.W / (u.m ** 2 * u.nm)) # Initialize the super class - super().__init__(sfd, wl_bins, size) + super().__init__(sfd, wl_bins) @staticmethod def check_config(conf: Entry) -> Union[None, str]: @@ -50,6 +48,3 @@ class FileTarget(ATarget): mes = conf.check_file("file") if mes is not None: return mes - mes = conf.check_selection("size", ["point", "extended"]) - if mes is not None: - return mes