Source code for emva1288.process.results

# -*- coding: utf-8 -*-
# Copyright (c) 2014 The EMVA1288 Authors. All rights reserved.
# Use of this source code is governed by a GNU GENERAL PUBLIC LICENSE that can
# be found in the LICENSE file.

"""Compute EMVA1288 values from data
This class takes the data from data.Data1288 and compute the actual EMVA1288
values.
"""

import logging
import numpy as np
from emva1288.process import routines
from scipy.ndimage import convolve


[docs]class Results1288(object): """Class used to process data and to generate pdf report using LaTeX. When properties from this class are computed, their docstring are also parsed using the :func:`~emva1288.process.routines.cls_1288_info` function to retrieve more data informations (like units or full name). """ ######################################################################### # Docstrings with .. emva1288:: flags are used when computing the # # properties to add relevant information to data for further processing # # like plotting or creating a report # #########################################################################
[docs] def __init__(self, data, pixel_area=None, index_u_ysat=None, loglevel=logging.DEBUG): """Results computation init method. This class uses a :class:`python:logging.Logger` object to display informations for users. Parameters ---------- data : dict The data dictionary to compute the results from. pixel_area : float, optional The area of one pixel in um^2. index_u_ysat : int, optional The index of the u_y array at which we consider that the camera saturates. This is used if forcing the saturation point is necessary. loglevel : int, optional The level for the :class:`python:logging.Logger` object. """ logging.basicConfig() self.log = logging.getLogger('Results') self.log.setLevel(loglevel) self.temporal = data['temporal'] self.spatial = data['spatial'] self.pixel_area = pixel_area self._s2q = 1.0 / 12.0 self._index_start = 0 self._index_sensitivity_min = 0 self._histogram_Qmax = 256 # Maximum number of bins in histograms # Sometimes we need to force the saturation point # in those cases pass the index in the initialization of Results1288 self._index_u_ysat = index_u_ysat
@property def s2q(self): """Quantification noise.""" return self._s2q @property def index_start(self): """The array's starting index. .. emva1288:: :Section: info :Short: Start array index """ return self._index_start @property def index_u_ysat(self): """Index of saturation. .. emva1288:: :Section: sensitivity :Short: Saturation index """ if self._index_u_ysat: return self._index_u_ysat max_ = 0 max_i = 0 # we have to loop backwards because sometimes we have some # noise pics that really bother the computation s2_y = self.temporal['s2_y'] for i in range(len(s2_y) - 1, -1, -1): # Check that is not a local max if (s2_y[i] >= max_) or \ (s2_y[abs(i - 1)] >= max_): max_ = s2_y[i] max_i = i elif (s2_y[i] < max_) and \ (s2_y[abs(i - 1)] < max_): break self._index_u_ysat = max_i return self._index_u_ysat @property def index_sensitivity_max(self): """Index for linear fits in sensitivity part of the standard (70% of saturation). .. emva1288:: :Section: sensitivity :Short: Sensitivity fit maximum index """ Y = self.temporal['u_y'] - self.temporal['u_ydark'] m = 0.7 * (Y[self.index_u_ysat]) return max(np.argwhere(Y <= m))[0] @property def index_sensitivity_min(self): """Sensitivity minimum index. Index for linear fits in sensitivity part of the standard (70% of saturation) .. emva1288:: :Section: sensitivity :Short: Sensitivity fit minimum index """ return self._index_sensitivity_min @property def R(self): """Responsivity. Slope of the (u_y - u_y_dark) Vs u_p. Fit with offset = 0 Uses the :func:`~emva1288.process.routines.LinearB0` function to make the fit. .. emva1288:: :Section: sensitivity :Short: Responsivity :Symbol: R :Unit: DN/p """ Y = self.temporal['u_y'] - self.temporal['u_ydark'] X = self.temporal['u_p'] val, _error = routines.LinearB0(X[self.index_sensitivity_min: self.index_sensitivity_max + 1], Y[self.index_sensitivity_min: self.index_sensitivity_max + 1]) return val[0] @property def K(self): """Overall system gain. Slope of (s2_y - s2_y_dark) Vs (u_y - u_y_dark). Fit with offset = 0. Uses the :func:`~emva1288.process.routines.LinearB0` to make the fit. .. emva1288:: :Section: sensitivity :Short: System gain :Symbol: K :Unit: $DN/e^-$ :LatexName: K """ X = self.temporal['u_y'] - self.temporal['u_ydark'] Y = self.temporal['s2_y'] - self.temporal['s2_ydark'] val, _error = routines.LinearB0(X[self.index_sensitivity_min: self.index_sensitivity_max + 1], Y[self.index_sensitivity_min: self.index_sensitivity_max + 1]) return val[0]
[docs] def inverse_K(self): """Inverse of overall system gain. .. emva1288:: :Section: sensitivity :Short: Inverse of overall system gain :Symbol: 1/K :Unit: $e^-/DN$ :LatexName: InvK """ return 1. / self.K
@property def QE(self): r"""Quantum efficiency. It is retrieved as the ratio of the responsivity to the overall system gain. .. emva1288:: :Section: sensitivity :Short: Quantum efficiency :Symbol: $\eta$ :Unit: \% :LatexName: QE """ return 100.0 * self.R / self.K @property def sigma_y_dark(self): r"""Temporal Dark Noise. Uses :func:`~emva1288.process.routines.LinearB` to make the fit. .. emva1288:: :Section: sensitivity :Short: Temporal Dark Noise :Symbol: $\sigma_{y.dark}$ :Unit: DN :LatexName: SigmaYDark """ if len(np.unique(self.temporal['texp'])) <= 2: s2_ydark = self.temporal['s2_ydark'][0] else: fit, _error = routines.LinearB(self.temporal['texp'], self.temporal['s2_ydark']) s2_ydark = fit[1] # Lower limit for the temporal dark noise # The temporal dark noise in this range is dominated by the # quantization noise if s2_ydark < 0.24: s2_ydark = 0.24 return np.sqrt(s2_ydark) @property def sigma_d(self): r"""Temporal Dark Noise. .. emva1288:: :Section: sensitivity :Short: Temporal Dark Noise :Symbol: $\sigma_d$ :Unit: $e^-$ :LatexName: SigmaDark """ return np.sqrt((self.sigma_y_dark ** 2) - self._s2q) / self.K @property def u_p_min(self): r"""Absolute sensitivity threshold. .. emva1288:: :Section: sensitivity :Short: Absolute sensitivity threshold :Symbol: $\mu_{p.min}$ :Unit: $p$ :LatexName: UPMin """ return (100.0 / self.QE) * ((self.sigma_y_dark / self.K) + 0.5) @property def u_p_min_area(self): r"""Sensitivity threshold per pixel area. Returns None if pixel area is not defined or 0. .. emva1288:: :Section: sensitivity :Short: Sensitivity threshold :Symbol: $\mu_{p.min.area}$ :Unit: $p/\mu m^2$ :LatexName: UPMin """ if not self.pixel_area: return None return self.u_p_min / self.pixel_area @property def u_e_min(self): r"""Sensitivity threshold. .. emva1288:: :Section: sensitivity :Short: Sensitivity threshold :Symbol: $\mu_{e.min}$ :Unit: $e^-$ :LatexName: UEMin """ return self.QE * self.u_p_min / 100.0 @property def u_e_min_area(self): r"""Sensitivity threshold per pixel area. Returns None if the pixel area is not defined or 0. .. emva1288:: :Section: sensitivity :Short: Sensitivity threshold :Symbol: $\mu_{e.min.area}$ :Unit: $e^-/\mu m^2$ :LatexName: UEMin """ if not self.pixel_area: return None return self.u_e_min / self.pixel_area @property def u_p_sat(self): r"""Saturation Capacity. .. emva1288:: :Section: sensitivity :Short: Saturation Capacity :Symbol: $\mu_{p.sat}$ :Unit: $p$ :LatexName: UPSat """ return self.temporal['u_p'][self.index_u_ysat] @property def u_p_sat_area(self): r"""Saturation Capacity per pixel area. Returns None if pixel area is not defined or 0. .. emva1288:: :Section: sensitivity :Short: Saturation Capacity :Symbol: $\mu_{p.sat.area}$ :Unit: $p/\mu m^2$ :LatexName: UPSat """ if not self.pixel_area: return None return self.u_p_sat / self.pixel_area @property def u_e_sat(self): r"""Saturation Capacity. Number of electrons at saturation. .. emva1288:: :Section: sensitivity :Short: Saturation Capacity :Symbol: $\mu_{e.sat}$ :Unit: $e^-$ :LatexName: UESat """ return self.QE * self.u_p_sat / 100.0 @property def u_e_sat_area(self): r"""Saturation Capacity per pixel area. Returns None if pixel area is not defined or 0. .. emva1288:: :Section: sensitivity :Short: Saturation Capacity :Symbol: $\mu_{e.sat.area}$ :Unit: $e^-/\mu m^2$ :LatexName: UESat """ if not self.pixel_area: return None return self.u_e_sat / self.pixel_area @property def SNR_max(self): """Maximum Signal-to-Noise Ratio. .. emva1288:: :Section: sensitivity :Short: Signal-to-Noise Ratio :Symbol: $SNR_{max}$ :LatexName: SNRMax """ return np.sqrt(self.u_e_sat)
[docs] def SNR_max_dB(self): """Maximum Signal to Noise Ratio in Db. .. emva1288:: :Section: sensitivity :Short: Maximum Signal to Noise Ratio in Db :Symbol: $SNR_{max.dB}$ :Unit: dB :LatexName: SNRMaxDB """ return 20. * np.log10(self.SNR_max)
[docs] def SNR_max_bit(self): """Maximum Signal to Noise Ratio in Bits. .. emva1288:: :Section: sensitivity :Short: Maximum Signal to Noise Ratio in Bits :Symbol: $SNR_{max.bit}$ :Unit: bit :LatexName: SNRMaxBit """ return np.log2(self.SNR_max)
[docs] def inverse_SNR_max(self): r"""Inverse Maximum Signal to Noise Ratio. .. emva1288:: :Section: sensitivity :Short: Inverse Maximum Signal to Noise Ratio :Symbol: $SNR_{max}^{-1}$ :Unit: \% :LatexName: InvSNRMax """ return 100. / self.SNR_max
@property def DR(self): """Dynamic Range. Defined as the saturation capacity divided by the absolute sensitivity threshold. The greater this number is, the greater the operational range of a camera (between the dark noise level and the saturation level). .. emva1288:: :Section: sensitivity :Short: Dynamic Range :Symbol: DR :LatexName: DR """ return self.u_p_sat / self.u_p_min
[docs] def DR_dB(self): """Dynamic Range in deciBels. It is defined as 20 * log_10 ( Dynamic Range ). .. emva1288:: :Section: sensitivity :Short: Dynamic Range :Symbol: $DR_{dB}$ :Unit: dB :LatexName: DRDB """ return 20. * np.log10(self.DR)
@property def index_linearity_min(self): """Linearity fit minimun index. Minimum index for linear fit (5% of saturation). .. emva1288:: :Section: linearity :Short: Linearity fit minimun index """ Y = self.temporal['u_y'] - self.temporal['u_ydark'] vmin = 0.05 * (Y[self.index_u_ysat]) return min(np.argwhere(Y >= vmin))[0] @property def index_linearity_max(self): """Linearity fit maximum index. Maximum index for linear fit (95% of saturation). .. emva1288:: :Section: linearity :Short: Linearity fit maximum index """ Y = self.temporal['u_y'] - self.temporal['u_ydark'] vmax = 0.95 * (Y[self.index_u_ysat]) return max(np.argwhere(Y <= vmax))[0]
[docs] def linearity(self): """Returns a dictionary containing linearity information. It fits the mean digital signal in function of the mean photon count (Linear fit) using the EMVA1288 standard for linear fit. Returns ------- dict : Linearity dictionary. The keys are: - *'fit_slope'* : The slope of the linear fit. - *'fit_offset'* : The offset of the fit. - *'relative_deviation'* : The relative deviation of the real data from the fit (in %) for the whole array. - *'linearity_error_min'* : The minimal value of the relative deviation. - *'linearity_error_max'* : The maximal value of the relative deviation. """ Y = self.temporal['u_y'] - self.temporal['u_ydark'] X = self.temporal['u_p'] # The maximum index has to be included, this is the reason for the +1 imax = self.index_linearity_max + 1 imin = self.index_linearity_min ################################################################## # Following the emva1288 standart for the computation of the fit # ################################################################## X_ = X[imin: imax] Y_ = Y[imin: imax] xy = np.sum(X_ / Y_) xy2 = np.sum(X_ / (Y_ ** 2)) x2y2 = np.sum((X_ / Y_) ** 2) _y = np.sum(1. / Y_) _y2 = np.sum(1. / (Y_ ** 2)) b = ((xy * xy2) - (x2y2 * _y)) / ((xy2 ** 2) - (x2y2 * _y2)) a = (xy - (b * xy2)) / x2y2 # The equivalent numpy polyfit is # a, b = np.polynomial.polynomial.polyfit(X_, Y_, 1, w=1 / Y_) dev = 100. * (Y - (a * X + b)) / (a * X + b) lin = {} lin['fit_slope'] = a lin['fit_offset'] = b lin['relative_deviation'] = dev lin['linearity_error_min'] = np.min(dev[imin: imax]) lin['linearity_error_max'] = np.max(dev[imin: imax]) return lin
@property def LE_min(self): r""" Min Linearity error. .. emva1288:: :Section: linearity :Short: Min Linearity error :Symbol: $LE_{min}$ :Unit: \% :LatexName: LEMin """ return self.linearity()['linearity_error_min'] @property def LE_max(self): r"""Max Linearity error. .. emva1288:: :Section: linearity :Short: Max Linearity error :Symbol: $LE_{max}$ :Unit: \% :LatexName: LEMax """ return self.linearity()['linearity_error_max'] @property def u_I_var_DN(self): r"""Dark Current from variance. The dark current from variance (in DN/s) is the slope of the dark signal variance as a function of the exposure time divided by the system gain. Returns NaN if the slope is negative. .. emva1288:: :Section: dark_current :Short: Dark Current from variance :Symbol: $\mu_{I.var.DN}$ :Unit: $DN/s$ :LatexName: UIVar """ if len(np.unique(self.temporal['texp'])) <= 2: return np.nan fit, _error = routines.LinearB(self.temporal['texp'], self.temporal['s2_ydark']) if fit[0] < 0: return np.nan # Multiply by 10^9 because exposure times are in nanoseconds return fit[0] / self.K * 1e9 @property def u_I_var(self): r"""Dark Current from variance. The dark current from variance (in e-/s) is the dark current from variance (in DN/s) divided by the system gain. Returns NaN if the fit slope is negative. .. emva1288:: :Section: dark_current :Short: Dark Current from variance :Symbol: $\mu_{I.var}$ :Unit: $e^-/s$ :LatexName: UIVar """ ui = self.u_I_var_DN if ui is np.nan: return np.nan return ui / self.K @property def u_I_mean_DN(self): r"""Dark Current from mean. The dark current from mean is the slope of the dark signal mean in function of the exposure time. Returns NaN if the number of different exposure times is less than 3. .. emva1288:: :Section: dark_current :Short: Dark Current from mean :Symbol: $\mu_{I.mean.DN}$ :Unit: $DN/s$ """ if len(np.unique(self.temporal['texp'])) <= 2: return np.nan fit, _error = routines.LinearB(self.temporal['texp'], self.temporal['u_ydark']) # Multiply by 10 ^ 9 because exposure time in nanoseconds return fit[0] * (10 ** 9) @property def u_I_mean(self): r"""Dark Current from mean. The dark current from mean is the slope of the dark signal mean in function of the exposure times divided by the overall system gain. Returns NaN if the number of different exposure times is less than 3. .. emva1288:: :Section: dark_current :Short: Dark Current from mean :Symbol: $\mu_{I.mean}$ :Unit: $e^-/s$ """ ui = self.u_I_mean_DN if ui is np.nan: return np.nan return ui / self.K @property def sigma_2_y_stack(self): r"""Temporal variance stack. Mean value of the bright variance image. .. emva1288:: :Section: spatial :Short: Temporal variance stack :Symbol: $\sigma^2_{y.stack}$ :Unit: DN2 """ return self.spatial['var_mean'] @property def sigma_2_y_stack_dark(self): r"""Temporal variance stack dark. Mean value of the dark variance image. .. emva1288:: :Section: spatial :Short: Temporal variance stack dark :Symbol: $\sigma^2_{y.stack.dark}$ :Unit: DN2 """ return self.spatial['var_mean_dark'] @property def s_2_y_measured(self): """Spatial variance measure. Variance value of the bright variance image. .. emva1288:: :Section: spatial :Short: Spatial variance measure :Symbol: $s^2_{y.measured}$ :Unit: DN2 """ # ddof = 1 (delta degrees of freedom) accounts for the minus 1 in the # divisor for the calculation of variance return self.spatial['avg_var'] @property def s_2_y_measured_dark(self): """Spatial variance measured dark. Variance value of the dark variance image. .. emva1288:: :Section: spatial :Short: Spatial variance measured dark :Symbol: $s^2_{y.measured.dark}$ :Unit: DN2 """ return self.spatial['avg_var_dark'] @property def s_2_y(self): """Spatial variance from image. .. emva1288:: :Section: spatial :Short: Spatial variance from image :Symbol: $s^2_{y}$ :Unit: DN2 """ return self.s_2_y_measured - (self.sigma_2_y_stack / self.spatial['L']) # eqn(36) @property def s_2_y_dark(self): """Spatial variance from image, .. emva1288:: :Section: spatial :Short: Spatial variance from image :Symbol: $s^2_{y}$ :Unit: DN2 """ return self.s_2_y_measured_dark - (self.sigma_2_y_stack_dark / self.spatial['L_dark']) @property def s_2_y_cav(self): """Average of column spatial variance from image. .. emva1288:: :Section: spatial :Short: Spatial variance from image :Symbol: $s^2_{y.cav}$ :Unit: DN2 """ return self.spatial['avg_var_cav'] @property def s_2_y_cav_dark(self): """Average of column spatial variance from image. .. emva1288:: :Section: spatial :Short: Spatial variance from image :Symbol: $s^2_{y.cav.dark}$ :Unit: DN2 """ return self.spatial['avg_var_cav_dark'] @property def s_2_y_rav(self): """Average of row spatial variance from image. .. emva1288:: :Section: spatial :Short: Spatial variance from image :Symbol: $s^2_{y.rav}$ :Unit: DN2 """ return self.spatial['avg_var_rav'] @property def s_2_y_rav_dark(self): """Average of row spatial variance from image. .. emva1288:: :Section: spatial :Short: Spatial variance from image :Symbol: $s^2_{y.rav.dark}$ :Unit: DN2 """ return self.spatial['avg_var_rav_dark'] @property def s_2_y_col(self): """Column spatial variance from image. .. emva1288:: :Section: spatial :Short: Spatial variance from image :Symbol: $s^2_{y.col}$ :Unit: DN2 """ M = self.spatial['M'] N = self.spatial['N'] para_1 = (M * N - M) / (M * N - M - N) para_2 = N / (M * N - M - N) s_2_y_col = para_1 * self.s_2_y_cav - para_2 * (self.s_2_y - self.s_2_y_rav) return s_2_y_col @property def s_2_y_col_dark(self): """Column spatial variance from dark image. .. emva1288:: :Section: spatial :Short: Spatial variance from image :Symbol: $s^2_{y.col.dark}$ :Unit: DN2 """ M = self.spatial['M_dark'] N = self.spatial['N_dark'] para_1 = (M * N - M) / (M * N - M - N) para_2 = N / (M * N - M - N) s_2_y_col_dark = para_1 * self.s_2_y_cav_dark - para_2 * (self.s_2_y_dark - self.s_2_y_rav_dark) return s_2_y_col_dark @property def s_2_y_row(self): """Row spatial variance from image. .. emva1288:: :Section: spatial :Short: Spatial variance from image :Symbol: $s^2_{y.row}$ :Unit: DN2 """ M = self.spatial['M'] N = self.spatial['N'] para_1 = (M * N - N) / (M * N - M - N) para_2 = M / (M * N - M - N) s_2_y_row = para_1 * self.s_2_y_rav - para_2 * (self.s_2_y - self.s_2_y_cav) return s_2_y_row @property def s_2_y_row_dark(self): """Row spatial variance from dark image. .. emva1288:: :Section: spatial :Short: Spatial variance from image :Symbol: $s^2_{y.row.dark}$ :Unit: DN2 """ M = self.spatial['M_dark'] N = self.spatial['N_dark'] para_1 = (M * N - N) / (M * N - M - N) para_2 = M / (M * N - M - N) s_2_y_row_dark = para_1 * self.s_2_y_rav_dark - para_2 * (self.s_2_y_dark - self.s_2_y_cav_dark) return s_2_y_row_dark @property def s_2_y_pixel(self): """Pixel spatial variance from image. .. emva1288:: :Section: spatial :Short: Spatial variance from image :Symbol: $s^2_{y.pixel}$ :Unit: DN2 """ M = self.spatial['M'] N = self.spatial['N'] para = M * N / (M * N - M - N) s_y_pixel = para*(self.s_2_y - self.s_2_y_cav - self.s_2_y_rav) return s_y_pixel @property def s_2_y_pixel_dark(self): """Pixel spatial variance from dark image. .. emva1288:: :Section: spatial :Short: Spatial variance from image :Symbol: $s^2_{y.pixel.dark}$ :Unit: DN2 """ M = self.spatial['M_dark'] N = self.spatial['N_dark'] para = M * N / (M * N - M - N) s_2_y_pixel_dark = para*(self.s_2_y_dark - self.s_2_y_cav_dark - self.s_2_y_rav_dark) return s_2_y_pixel_dark @property def DSNU1288(self): """DSNU overall. Dark Signal NonUniformity (in e^-) is defined as the standard deviation of the dark signal divided by the overall system gain. If the variance is negative, it will return NaN instead of an imaginary number. .. emva1288:: :Section: spatial :Short: DSNU :Symbol: $DSNU_{1288}$ :Unit: $e^-$ :LatexName: DSNU """ if self.s_2_y_dark < 0: return np.nan return np.sqrt(self.s_2_y_dark) / self.K
[docs] def DSNU1288_DN(self): """DSNU in DN. Defined as the DSNU in e^- multiplied by the overall system gain. Returns NaN if the dark signal variance is negative. Returns ------- float : The DSNU in DN. .. emva1288:: :Section: spatial :Short: DSNU in DN :Symbol: $DSNU_{1288.DN}$ :Unit: DN :LatexName: DSNUDN """ if self.s_2_y_dark < 0: return np.nan return np.sqrt(self.s_2_y_dark)
@property def DSNU1288_row(self): """DSNU in rows. Dark Signal NonUniformity (in e^-) is defined as the standard deviation of the dark signal divided by the overall system gain. If the variance is negative, it will return NaN instead of an imaginary number. .. emva1288:: :Section: spatial :Short: DSNUrow :Symbol: $DSNU_{1288.row}$ :Unit: $e^-$ :LatexName: DSNUrow """ if self.s_2_y_row_dark < 0: return np.nan return np.sqrt(self.s_2_y_row_dark) / self.K @property def DSNU1288_col(self): """DSNU in columns. Dark Signal NonUniformity (in e^-) is defined as the standard deviation of the dark signal divided by the overall system gain. If the variance is negative, it will return NaN instead of an imaginary number. .. emva1288:: :Section: spatial :Short: DSNUcol :Symbol: $DSNU_{1288.col}$ :Unit: $e^-$ :LatexName: DSNUcol """ if self.s_2_y_col_dark < 0: return np.nan return np.sqrt(self.s_2_y_col_dark) / self.K @property def DSNU1288_pixel(self): """DSNU in pixel. Dark Signal NonUniformity (in e^-) is defined as the standard deviation of the dark signal divided by the overall system gain. If the variance is negative, it will return NaN instead of an imaginary number. .. emva1288:: :Section: spatial :Short: DSNUpixel :Symbol: $DSNU_{1288.pixel}$ :Unit: $e^-$ :LatexName: DSNUpixel """ if self.s_2_y_pixel_dark < 0: return np.nan return np.sqrt(self.s_2_y_pixel_dark) / self.K @property def PRNU1288(self): r"""PRNU overall. Photo Response NonUniformity (in %) is defined as the square root of the difference between the spatial variance of a bright image (or from an average of bright images to remove temporal difformities) and the spatial variance of dark signal, divided by the difference between the mean of a bright image and the mean of a dark image. .. emva1288:: :Section: spatial :Short: PRNU :Symbol: $PRNU_{1288}$ :Unit: \% :LatexName: PRNU """ return (np.sqrt(self.s_2_y - self.s_2_y_dark) * 100 / (self.spatial['avg_mean'] - self.spatial['avg_mean_dark'])) @property def PRNU1288_row(self): r"""PRNU in row. Photo Response NonUniformity (in %) is defined as the square root of the difference between the spatial variance of a bright image (or from an average of bright images to remove temporal difformities) and the spatial variance of dark signal, divided by the difference between the mean of a bright image and the mean of a dark image. .. emva1288:: :Section: spatial :Short: PRNUrow :Symbol: $PRNU_{1288.row}$ :Unit: \% :LatexName: PRNUrow """ return (np.sqrt(self.s_2_y_row - self.s_2_y_row_dark) * 100 / (self.spatial['avg_mean'] - self.spatial['avg_mean_dark'])) @property def PRNU1288_col(self): r"""PRNU in column. Photo Response NonUniformity (in %) is defined as the square root of the difference between the spatial variance of a bright image (or from an average of bright images to remove temporal difformities) and the spatial variance of dark signal, divided by the difference between the mean of a bright image and the mean of a dark image. .. emva1288:: :Section: spatial :Short: PRNUcol :Symbol: $PRNU_{1288.col}$ :Unit: \% :LatexName: PRNUcol """ return (np.sqrt(self.s_2_y_col - self.s_2_y_col_dark) * 100 / (self.spatial['avg_mean'] - self.spatial['avg_mean_dark'])) @property def PRNU1288_pixel(self): r"""PRNU in pixel. Photo Response NonUniformity (in %) is defined as the square root of the difference between the spatial variance of a bright image (or from an average of bright images to remove temporal difformities) and the spatial variance of dark signal, divided by the difference between the mean of a bright image and the mean of a dark image. .. emva1288:: :Section: spatial :Short: PRNUpixel :Symbol: $PRNU_{1288.pixel}$ :Unit: \% :LatexName: PRNUpixel """ return (np.sqrt(self.s_2_y_pixel - self.s_2_y_pixel_dark) * 100 / (self.spatial['avg_mean'] - self.spatial['avg_mean_dark'])) @property def histogram_PRNU(self): """PRNU histogram. Uses the :func:`~emva1288.process.routines.high_pass_filter` function to filter the image and then uses the :func:`~emva1288.process.routines.Histogram1288` function to make the histogram. .. emva1288:: :Section: defect_pixel :Short: PRNU histogram """ # For prnu, perform the convolution y = self.spatial['sum'] - self.spatial['sum_dark'] # Applying the filter on the image data = routines.high_pass_filter(y, 5) y = data['img'] h = routines.Histogram1288(y, self._histogram_Qmax) # Rescale the bins h['bins'] /= (self.spatial['L'] * data['multiplicator']) return h @property def histogram_PRNU_accumulated(self): """Accumulated PRNU histogram. Uses the :func:`~emva1288.process.routines.high_pass_filter` function to filter the image and then uses the :func:`~emva1288.process.routines.Histogram1288` function to make the histogram. .. emva1288:: :Section: defect_pixel :Short: accumulated PRNU histogram """ # For prnu, perform the convolution y = self.spatial['sum'] - self.spatial['sum_dark'] data = routines.high_pass_filter(y, 5) y = data['img'] # For the accumulated histogram substract the mean y = np.abs(y - int(np.mean(y))) h = routines.Histogram1288(y, self._histogram_Qmax) # Rescale the bins h['bins'] /= (self.spatial['L'] * data['multiplicator']) # Perform the cumulative summation h['values'] = np.cumsum(h['values'][::-1])[::-1] # we want it as percentage of pixels h['values'] = 100 * h['values'] / y.size return h @property def histogram_DSNU(self): """DSNU histogram. Uses the :func:`~emva1288.process.routines.Histogram1288` function to make the histogram. .. emva1288:: :Section: defect_pixel :Short: DSNU histogram """ # For dsnu, the image is just the dark image, upscaled to have # only integers y = self.spatial['sum_dark'] h = routines.Histogram1288(y, self._histogram_Qmax) # Rescale the bins, this is due to upscaling the average image to have # only integers scale = self.spatial['L_dark'] h['bins'] /= scale # The image is not centered around zero, so we shift the bins h['bins'] -= (y.mean() / scale) return h @property def histogram_DSNU_accumulated(self): """Accumulated DSNU histogram. Uses the :func:`~emva1288.process.routines.Histogram1288` function to make the histogram. .. emva1288:: :Section: defect_pixel :Short: accumulated DSNU histogram """ # Dark image upscaled to have only integers y = self.spatial['sum_dark'] # For the accumulated dsnu histogram, substract the mean from the image y = np.abs(y - int(np.mean(y))) h = routines.Histogram1288(y, self._histogram_Qmax) # Rescale the bins h['bins'] /= (self.spatial['L_dark']) # Perform the cumulative summation (the ::-1 means backwards) # because the cumsum function is performed contrary to what we need h['values'] = np.cumsum(h['values'][::-1])[::-1] # we want it as percentage of pixels h['values'] = 100 * h['values'] / y.size return h
[docs] def xml(self, filename=None): # pragma: no cover """Method that writes the results in xml format to a file. Parameters ---------- filename : str, optional The file to write the results. If None, the xml string won't be written but will be returned instead. Returns ------- str : If the xml string is not written into a file, it is returned. """ results = self.results_by_section if not filename: return routines.dict_to_xml(results) routines.dict_to_xml(results, filename=filename)
@property def results(self): """Dictionnary with all the values and metadata for EMVA1288 values. It uses the :func:`~emva1288.process.routines.obj_to_dict` to compute all the results at once. """ return routines.obj_to_dict(self) @property def results_by_section(self): # pragma: no cover """Results ordered by section.""" return routines._sections_first(self.results)
[docs] def print_results(self): # pragma: no cover """Print results to the screen.""" results = self.results_by_section for section, attributes in results.items(): print('*' * 50) print(section) print('-' * 50) for attribute, info in attributes.items(): if 'value' not in info: continue print('{:<50}{:<30}{:>10}'.format(info.get('short'), str(info.get('symbol')), str(info.get('value')))) print('*' * 50) print(' ')