Skip to content

Commit

Permalink
Merge pull request #123 from JF1008/qe_radiance_arrays
Browse files Browse the repository at this point in the history
Qe radiance arrays
  • Loading branch information
fariza authored Jul 10, 2018
2 parents 988c489 + 0586217 commit 344d819
Show file tree
Hide file tree
Showing 8 changed files with 188 additions and 176 deletions.
68 changes: 22 additions & 46 deletions emva1288/camera/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ def __init__(self,
temperature_ref=30, # Reference temperature
temperature_doubling=8, # Doubling temperature

wavelength=525, # illumination wavelength
qe=None, # Quantum efficiency for the given wavelength

exposure=1000000, # Exposure time in ns
exposure_min=500000, # Minimum exposure time in ns
exposure_max=500000000, # Maximum exposure time in ns
Expand All @@ -39,8 +37,6 @@ def __init__(self,

u_esat=15000.,

radiance_factor=1.,

dsnu=None,
prnu=None
):
Expand All @@ -67,8 +63,6 @@ def __init__(self,
The doubling temperature (at which the dark
current is two times the reference dark
current).
wavelength : float, optional
The light wavelength hitting the sensor (in nm).
qe : float, optional
Quantum efficiency (between 0 and 1). If None, a simulated
quantum efficiency is choosen with the
Expand Down Expand Up @@ -107,20 +101,13 @@ def __init__(self,
Variance of electrons generated by the electronics
u_esat : float, optional
Full well capacity
radiance_factor : float or array, optional
The factor or an array with the same size of the image
for modifie the radiance. By default 1 for no
modification, but if you want a bayer filter use
:func:`~emva1288.camera.routines.get_bayer_filter`
function.
dsnu : np.array, optional
DSNU image in e^-, array with the same shape of the image
that is added to every image.
prnu : np.array, optional
PRNU image in percentages (1 = 100%), array with the same shape
of the image. Every image is multiplied by it.
"""

self._pixel_area = pixel_area
self._bit_depth = bit_depth
self._img_max = 2 ** int(bit_depth) - 1
Expand All @@ -138,11 +125,10 @@ def __init__(self,

self._temperature_ref = temperature_ref
self._temperature_doubling = temperature_doubling

self._qe = qe
# When no specific qe is provided we simulate one
if qe is None:
self._qe = routines.qe(wavelength)
self._qe = routines.Qe(height=self._height, width=self._width)

self._dark_current_ref = dark_current_ref
self._dark_signal_0 = dark_signal_0
Expand All @@ -166,8 +152,6 @@ def __init__(self,
self._blackoffset = None
self.blackoffset = blackoffset

self._radiance_factor = radiance_factor

self._dsnu = dsnu
self._prnu = prnu

Expand All @@ -176,7 +160,6 @@ def __init__(self,
if prnu is None:
self._prnu = np.ones(self._shape)
self.environment = {'temperature': temperature,
'wavelength': wavelength,
'f_number': f_number}

@property
Expand Down Expand Up @@ -283,7 +266,11 @@ def DSNU(self, value):
# TODO: Conditions to be sure than the dsnu gived has the good shape.
self._dsnu = value

def grab(self, radiance, temperature=None, wavelength=None, f_number=None):
@property
def qe(self):
return self._qe

def grab(self, radiance, temperature=None, f_number=None):
"""
Create an image based on the mean and standard deviation from the
EMVA1288 parameters.
Expand All @@ -292,17 +279,14 @@ def grab(self, radiance, temperature=None, wavelength=None, f_number=None):
Parameters
----------
radiance : float
radiance : ndarray
The sensor's illumination in W/cm^2/sr. This is the only
mandatory argument because it is frequently changed during
an test.
temperature : float, optional
The camera's temperature in degrees Celsius.
If None, the environment's
temperature will be taken.
wavelength : float, optional
The illumination wavelength in nanometers.
If None, the environment's wavelength will be taken.
f_number : float, optional
The optical setup f_number.
If None, the environment's f_number will be taken.
Expand All @@ -317,10 +301,9 @@ def grab(self, radiance, temperature=None, wavelength=None, f_number=None):

###############################
# Light induced electrons image
u_e = self._u_e(radiance, wavelength=wavelength, f_number=f_number)
u_e = self._u_e(radiance, f_number=f_number)
# Noise centred on the number of electrons from the Light
img_e += np.random.poisson(u_e, size=self._shape)

####################################################################
# Electronics induced electrons image and Dark Signal non uniformity
variance = np.sqrt(self._sigma2_dark_0)
Expand All @@ -345,23 +328,17 @@ def grab(self, radiance, temperature=None, wavelength=None, f_number=None):

np.rint(img, img)
np.clip(img, 0, clipping_point, img)

return img.astype(self._img_type)

def _u_e(self, radiance, wavelength=None, f_number=None):
def _u_e(self, radiance, f_number=None):
"""
Light induced electrons image for the exposure time.
"""
# Alteration of the Quantum Efficiency by the
# photon response non uniformity variation (prnu).
qe = self._qe * self._prnu
# Mean number of electrons per pixel during exposure time.
photons_exposure = self.get_photons(radiance, wavelength=wavelength,
f_number=f_number)
# Influence of the radiance fator on the number of photons
photons = self._radiance_factor * photons_exposure
# Electrons generation
u_e = qe * photons
# number of photons for each pixel during exposure time.
photons = self.get_photons(radiance, f_number=f_number)
# copy the filter so we don't alter the camera's filter
u_e = np.sum(np.multiply(photons, self._qe.qe), axis=2)
u_e *= self._prnu
return u_e

def _u_therm(self, temperature=None):
Expand Down Expand Up @@ -409,17 +386,18 @@ def get_radiance_for(self, mean=None, exposure=None):
if not exposure:
exposure = self.exposure
ud = self._u_therm()
ue = (mean / self.K) - ud
up = ue / self._qe
ue = ((mean / self.K) - ud)/len(self._qe.w)
ue *= np.ones(len(self._qe.w))
up = np.zeros((self.height, self.width, len(self._qe.w)))
up[:, :] = ue / self._qe.qe.mean()
radiance = routines.get_radiance(exposure,
self.environment['wavelength'],
up,
self.pixel_area,
self.environment['f_number'])
self.environment['f_number'],
self._qe.w)
return radiance

def get_photons(self, radiance, exposure=None,
wavelength=None, f_number=None):
def get_photons(self, radiance, exposure=None, f_number=None):
"""Computes the number of photons received by one pixel.
Uses the :func:`~emva1288.camera.routines.get_photons` function
Expand All @@ -441,10 +419,8 @@ def get_photons(self, radiance, exposure=None,
exposure = self.exposure
if f_number is None:
f_number = self.environment['f_number']
if wavelength is None:
wavelength = self.environment['wavelength']
return routines.get_photons(exposure,
wavelength,
radiance,
self.pixel_area,
self._qe.w,
f_number)
8 changes: 3 additions & 5 deletions emva1288/camera/dataset_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,11 @@ def __init__(self,
self.cam = Cam(**kwargs)
# set the camera parameters for the test
self.cam.exposure = self.cam.exposure_min

# If no blackoffset/gain are specified find them according to standard
if 'blackoffset' not in kwargs:
self.cam.blackoffset = _get_emva_blackoffset(self.cam)
if 'K' not in kwargs:
self.cam.K = _get_emva_gain(self.cam)

# create test points
points = PointsGenerator(self.cam,
radiance_min=radiance_min,
Expand Down Expand Up @@ -157,13 +155,13 @@ def _is_point_spatial_test(self, i):

def _get_descriptor_line(self, exposure, radiance):
"""Create the line introducing a test point images in descriptor."""
if radiance == 0.0:
if np.mean(radiance) == 0.0:
# dark image
return "d %.1f" % exposure
# bright image
# round photons count to three decimals
return "b %.1f %.3f" % (exposure,
round(self.cam.get_photons(radiance), 3))
return "b %.1f %.3f" % (exposure, np.round(np.sum(
self.cam.get_photons(radiance), axis=2).mean(), 3))

def _get_image_names(self, number, L):
"""Create an image filename."""
Expand Down
22 changes: 12 additions & 10 deletions emva1288/camera/points_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ def __init__(self,
self._radiance_max = radiance_max
self._gain = gain or self._cam.K
self._blackref = blackref or self._cam.blackoffset

if self._exposure is None:
# Get radiance for saturation at maximal exposure time
# Only if it is for an exposure time variation test
Expand All @@ -72,7 +71,7 @@ def __init__(self,
self._cam.exposure = self._exposure
self._cam.K = self._gain
self._cam.blackoffset = self._blackref
m = self._cam.grab(0.0).mean()
m = self._cam.grab(0).mean()
target = (self._cam.img_max - m) / self._steps + m
self._radiance_min = self._cam.get_radiance_for(mean=target)
self._radiance_max = self._cam.get_radiance_for()
Expand All @@ -84,16 +83,16 @@ def __init__(self,
def _get_points(self):
spatial = OrderedDict()
temporal = OrderedDict()

rad = np.zeros((self._cam._height, self._cam._width,
len(self._cam.qe.w)))
if self._exposure is None:
# Exposure time variation
# round to only have one decimal
exposures = np.round(np.linspace(self._exposure_min,
self._exposure_max,
self._steps), 1)
# only one radiance
radiances = [self._radiance, 0.0]

radiances = [self._radiance, rad]
# Main loop to compute points
for n, texp in enumerate(exposures):
if self._is_point_spatial(n):
Expand All @@ -103,13 +102,16 @@ def _get_points(self):
else:
# Photons variations
# only one exposure time
radiances = np.linspace(self._radiance_min,
self._radiance_max,
self._steps).tolist()
rad_i = np.ones((self._cam._height, self._cam._width,
len(self._cam.qe.w)))
factors = np.linspace(self._radiance_min.mean(),
self._radiance_max.mean(),
self._steps)
radiances = [rad_i * factor for factor in factors]
# round to only have one decimal
self._exposure = round(self._exposure)
radiances.append(0.0)
spatial[self._exposure] = [radiances[self._steps // 2], 0.0]
radiances.append(rad)
spatial[self._exposure] = [radiances[self._steps // 2], rad]
temporal[self._exposure] = radiances

return {'spatial': spatial, 'temporal': temporal}
Expand Down
Loading

0 comments on commit 344d819

Please sign in to comment.