-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Created SNR vs Wavelength notebook #104
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -65,7 +65,7 @@ def get_size(self) -> tuple: | |
cubesat frame.""" | ||
assert self.n_px is not None, "n_px must be specified." | ||
assert self.pitch is not None, "Pitch must be specified." | ||
|
||
size = (self.n_px * self.pitch, self.n_px * self.pitch) | ||
|
||
return size | ||
|
@@ -95,25 +95,29 @@ def get_dark_noise(self): | |
"""Get the dark noise of the sensor.""" | ||
assert self.i_dark is not None, "i_dark must be specified." | ||
assert self.dt is not None, "dt amplitude must be specified." | ||
|
||
dark_noise = self.i_dark * self.dt | ||
|
||
return dark_noise | ||
|
||
def get_quantization_noise(self): | ||
"""Get the quantization noise of the sensor.""" | ||
|
||
# print("n well", self.n_well) | ||
# print("n bit", self.n_bit) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's remove unused print statements to keep the code clean. |
||
assert self.n_well is not None, "n_well must be specified." | ||
assert self.n_bit is not None, "n_bit must be specified." | ||
quant_noise = (1 / math.sqrt(12)) * self.n_well / 2**self.n_bit | ||
|
||
quant_noise = (1 / math.sqrt(12)) * self.n_well / 2**self.n_bit.value | ||
|
||
return quant_noise | ||
|
||
def get_noise(self, signal): | ||
"""Get the net noise of the sensor.""" | ||
assert self.n_bin is not None, "n_bin must be specified." | ||
assert self.noise_read is not None, "noise_read must be specified." | ||
|
||
noise = np.sqrt( | ||
signal | ||
+ self.n_bin * self.get_dark_noise() ** 2 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,6 @@ | ||
# stdlib | ||
import math | ||
|
||
# external | ||
import astropy.constants as const | ||
import astropy.units as unit | ||
|
@@ -69,6 +72,19 @@ def get_transmittance(self): | |
|
||
return transmittance | ||
|
||
def get_ratio_cropped_light_through_slit(self): | ||
"""Get the ratio of the light passing through slit to the image of the | ||
foreoptic.""" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion:
|
||
assert ( | ||
self.foreoptic.image_diameter is not None | ||
), "Foreoptic image diameter must be set." | ||
|
||
effective_width = min(self.slit.size[0], self.foreoptic.image_diameter) | ||
effective_slit_area = effective_width * self.slit.size[1] | ||
ratio = effective_slit_area / self.foreoptic.get_image_area() | ||
|
||
return ratio | ||
|
||
def get_signal_to_noise(self, radiance: LUT, wavelength): | ||
"""Get the signal to noise ratio of the system. | ||
|
||
|
@@ -77,37 +93,78 @@ def get_signal_to_noise(self, radiance: LUT, wavelength): | |
wavelength: Wavelength(s) at which to evaluate SNR. | ||
|
||
""" | ||
assert self.sensor is not None, "A sensor component must be specified." | ||
assert self.sensor is not None, "A sensor component must be specified." | ||
assert self.foreoptic is not None, "A foreoptic component must be specified." | ||
assert self.slit is not None, "A slit component must be specified." | ||
|
||
signal = ( | ||
(const.pi / 4) | ||
* (wavelength / (const.h * const.c)) | ||
* (self.sensor.get_pixel_area() / self.foreoptic.f_number**2) | ||
* self.sensor.efficiency(wavelength) | ||
* self.get_transmittance() | ||
* self.slit.get_aperture_area() | ||
# print("wavelength", wavelength) | ||
# print("ratio", self.get_ratio_cropped_light_through_slit()) | ||
# print("pixel area", self.sensor.get_pixel_area()) | ||
# print("f num", self.foreoptic.get_f_number()) | ||
# print("QE", self.sensor.efficiency(wavelength).decompose() * unit.electron) | ||
# print("efficiency", self.sensor.efficiency(wavelength)) | ||
# print("transmittance", self.get_transmittance()) | ||
# print("area", self.slit.get_area()) | ||
# print("radiance", radiance(wavelength)) | ||
# print("sensor dt", self.sensor.dt) | ||
# print("n bin", self.sensor.n_bin) | ||
# print("dark noise", self.sensor.get_dark_noise()) | ||
# print("quantization noise", self.sensor.get_quantization_noise()) | ||
# print("noise read", self.sensor.noise_read) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's remove commented code once its no longer needed. |
||
|
||
signal1 = ( | ||
(wavelength / (const.h * const.c)) | ||
* radiance(wavelength) | ||
* (math.pi / 4) | ||
* self.sensor.dt | ||
) | ||
) # [sr-1 m-3] | ||
signal2 = self.sensor.get_pixel_area() / ( | ||
((self.foreoptic.get_f_number()).decompose()) ** 2 * 1 / unit.sr | ||
) # [m2 sr] | ||
signal3 = ( | ||
(self.sensor.efficiency(wavelength)).decompose() | ||
* unit.electron | ||
* self.get_transmittance() | ||
) # [electron] | ||
signal4 = ( | ||
self.get_ratio_cropped_light_through_slit() | ||
* (800 * 10 ** (-9)) | ||
* unit.meter | ||
) # [dimensionless * m] | ||
|
||
noise = self.sensor.get_noise(signal) | ||
signal = signal1 * signal2 * signal3 * signal4 | ||
|
||
print("signal", signal.decompose()) | ||
|
||
print("shot noise sqr", signal.decompose() * unit.electron) | ||
print( | ||
"dark noise sqr", | ||
self.sensor.n_bin * (self.sensor.get_dark_noise() * unit.pix) ** 2, | ||
) | ||
print("quantization noise sqr", (self.sensor.get_quantization_noise()) ** 2) | ||
print("read noise sqr", self.sensor.n_bin * (self.sensor.noise_read) ** 2) | ||
|
||
noise = np.sqrt( | ||
(signal * unit.electron) | ||
+ self.sensor.n_bin * (self.sensor.get_dark_noise() * unit.pix) ** 2 | ||
+ self.sensor.get_quantization_noise() ** 2 | ||
+ self.sensor.n_bin * self.sensor.noise_read**2 | ||
) | ||
|
||
snr = signal / noise | ||
|
||
return snr | ||
return snr.decompose() | ||
|
||
def get_FOV(self) -> np.ndarray[float, float]: | ||
"""Get the field of view vector. | ||
|
||
A vector that defines the angular extent that can be imaged by the payload in | ||
the along-track and the across-track directions. | ||
|
||
""" | ||
assert self.slit is not None, "A slit component must be specified." | ||
assert self.foreoptic is not None, "A foreoptic component must be specified." | ||
|
||
fov = 2 * np.arctan(self.slit.size / (2 * self.foreoptic.focal_length)) | ||
|
||
return fov | ||
|
@@ -124,7 +181,9 @@ def get_iFOV(self) -> np.ndarray[float, float]: | |
def get_sensor_spatial_resolution(self, target_distance): | ||
"""Get the sensor-limited spatial resolution.""" | ||
|
||
spatial_resolution = target_distance * self.sensor.pitch / self.foreoptic.focal_length | ||
spatial_resolution = ( | ||
target_distance * self.sensor.pitch / self.foreoptic.focal_length | ||
) | ||
|
||
return spatial_resolution.decompose() | ||
|
||
|
@@ -148,7 +207,11 @@ def get_swath( | |
return swath | ||
|
||
def get_optical_spatial_resolution(self, wavelength, target_distance, skew_angle=0): | ||
"""Get the optically-limited spatial resolution. Aka GRD (ground-resolved distance)""" | ||
"""Get the optically-limited spatial resolution. | ||
|
||
Aka GRD (ground-resolved distance) | ||
|
||
""" | ||
assert self.foreoptic is not None, "A foreoptic component must be specified." | ||
|
||
optical_spatial_resolution = ( | ||
|
@@ -165,7 +228,8 @@ def get_spatial_resolution(self, wavelength, target_distance, skew_angle=0): | |
return self.spatial_resolution | ||
|
||
sensor_spatial_resolution = self.get_sensor_spatial_resolution( | ||
target_distance=target_distance) | ||
target_distance=target_distance | ||
) | ||
|
||
optical_spatial_resolution = self.get_optical_spatial_resolution( | ||
wavelength=wavelength, | ||
|
@@ -240,7 +304,6 @@ def get_pointing_accuracy_constraint( | |
|
||
return constraint_angle | ||
|
||
|
||
|
||
class FINCHEye(HyperspectralImager): | ||
def __init__( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,81 +0,0 @@ | ||
"""Tests for Slit component.""" | ||
# stdlib | ||
import logging | ||
|
||
# external | ||
import numpy as np | ||
import pytest | ||
|
||
# project | ||
from payload_designer.components import slits | ||
|
||
LOG = logging.getLogger(__name__) | ||
|
||
|
||
def test_get_horizontal_field_of_view(): | ||
"""Test ThinFocuser.get_horizontal_field_of_view().""" | ||
|
||
# parameters | ||
l_s = 1 | ||
f = 70 | ||
|
||
# component instantiation | ||
slit = slits.Slit(l_s=l_s, f=f) | ||
|
||
# evaluation | ||
fov = slit.get_horizontal_field_of_view() | ||
LOG.info(f"Horizontal field of view: {fov}") | ||
|
||
assert fov == pytest.approx(360 * np.arctan(1 / 140) / np.pi) | ||
|
||
|
||
def test_get_vertical_field_of_view(): | ||
"""Test ThinFocuser.get_vertical_field_of_view().""" | ||
|
||
# parameters | ||
w_s = 0.01 | ||
f = 70 | ||
|
||
# component instantiation | ||
slit = slits.Slit(w_s=w_s, f=f) | ||
|
||
# evaluation | ||
fov = slit.get_vertical_field_of_view() | ||
LOG.info(f"Vertical field of view: {fov}") | ||
|
||
assert fov == pytest.approx(360 * np.arctan(1 / 14000) / np.pi) | ||
|
||
|
||
def test_get_image_width(): | ||
"""Test ThinFocuser.get_image_width().""" | ||
# w_i = np.power(np.multiply(np.power(self.m, 2), np.power(self.w_s, 2)) + np.power(self.w_o, 2), 0.2) | ||
# parameters | ||
m = 1 | ||
w_s = 0.01 | ||
w_o = 0.05 | ||
|
||
# component instantiation | ||
slit = slits.Slit(m=m, w_s=w_s, w_o=w_o) | ||
|
||
# evaluation | ||
w = slit.get_image_width() | ||
LOG.info(f"Image width: {w}") | ||
|
||
assert w == pytest.approx(np.sqrt(13 / 5000)) | ||
|
||
|
||
def test_get_slit_width(): | ||
"""Test ThinFocuser.get_slit_width().""" | ||
# w_s = np.divide(self.w_d, self.m) | ||
# parameters | ||
w_d = 5 | ||
m = 1 | ||
|
||
# component instantiation | ||
slit = slits.Slit(w_d=w_d, m=m) | ||
|
||
# evaluation | ||
w = slit.get_slit_width() | ||
LOG.info(f"Slit width: {w}") | ||
|
||
assert w == 5 | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like this method already exists on line 40. Let's remove this duplicate.