""" Calibration functions and utilities for raw/engineering conversions in SMILE Data from SMILE-IWF-PL-UM-147-d0-3_SXI_EBox_User_Manual (ID 5233) """ import os import numpy as np import scipy as sp # constants T_ZERO = 273.15 # common ADC coefficients ADC_INPRNG = 7.34783 # V ADC_OFFSET = -1.69565 # V class Dpu: _unit = "V" ADC_P3V9 = "HK_ADC_P3V9" ADC_P3V3 = "HK_ADC_P3V3" ADC_P3V3_LVDS = "HK_ADC_P3V3_LVDS" ADC_P2V5 = "HK_ADC_P2V5" ADC_P1V8 = "HK_ADC_P1V8" ADC_P1V2 = "HK_ADC_P1V2" ADC_REF = "HK_ADC_REF" K_DPU = { Dpu.ADC_P3V9: 2, Dpu.ADC_P3V3: 1, Dpu.ADC_P3V3_LVDS: 1, Dpu.ADC_P2V5: 1, Dpu.ADC_P1V8: 1, Dpu.ADC_P1V2: 1, Dpu.ADC_REF: 1 } class Temp: _unit = "degC" ADC_TEMP1 = "HK_ADC_TEMP1" ADC_TEMP_FEE = "HK_ADC_TEMP_FEE" ADC_TEMP_CCD = "HK_ADC_TEMP_CCD" ADC_PSU_TEMP = "HK_ADC_PSU_TEMP" # Signal specific coefficients class V_T0: CCD = 2.5650 TEMP1 = 2.5770 FEE = 1.2800 class K_T: CCD = 0.00385 TEMP1 = 0.00385 FEE = 0.00385 # interpolation table for nominal operation CCD temperature # (degC, ADC_V, ADU_dec, ADU_hex) CCD_TEMP_TABLE = [ (-140.0, 1.125, 6288, 0x1890), (-135.0, 1.178, 6407, 0x1906), (-130.0, 1.231, 6524, 0x197C), (-125.0, 1.283, 6642, 0x19F1), (-120.0, 1.336, 6759, 0x1A66), (-115.0, 1.388, 6876, 0x1ADB), (-110.0, 1.440, 6992, 0x1B50), (-105.0, 1.493, 7109, 0x1BC4), (-100.0, 1.545, 7225, 0x1C38), (-95.0, 1.596, 7340, 0x1CAC), (-90.0, 1.648, 7456, 0x1D1F), (-85.0, 1.700, 7571, 0x1D92), (-80.0, 1.751, 7686, 0x1E05), (-75.0, 1.803, 7800, 0x1E78), (-70.0, 1.854, 7915, 0x1EEA), (-65.0, 1.905, 8029, 0x1F5D), (-60.0, 1.957, 8143, 0x1FCF), (-55.0, 2.008, 8257, 0x2040), (-50.0, 2.059, 8371, 0x20B2), (-45.0, 2.109, 8484, 0x2123), (-40.0, 2.160, 8597, 0x2195), (-35.0, 2.211, 8710, 0x2206), (-30.0, 2.261, 8823, 0x2276), (-25.0, 2.312, 8936, 0x22E7), (-20.0, 2.362, 9048, 0x2358) ] # interpolation table for PSU temperature # (degC, ADC_V, ADU_dec, ADU_hex) PSU_TEMP = [ (-50.0, 3.237, 10998, 0x2AF6), (-40.0, 3.187, 10887, 0x2A86), (-20.0, 2.960, 10380, 0x288C), (0.0, 2.487, 9326, 0x246D), (20.0, 1.816, 7830, 0x1E95), (25.0, 1.643, 7444, 0x1D13), (40.0, 1.169, 6387, 0x18F3), (60.0, 0.703, 5348, 0x14E4), (80.0, 0.417, 4710, 0x1266), (90.0, 0.323, 4501, 0x1194), (100.0, 0.252, 4343, 0x10F6) ] class Psu: _unit = "A" ADC_I_FEE_ANA = "HK_ADC_I_FEE_ANA" ADC_I_FEE_DIG = "HK_ADC_I_FEE_DIG" ADC_I_DPU = "HK_ADC_I_DPU" ADC_I_RSE = "HK_ADC_I_RSE" ADC_I_HEATER = "HK_ADC_I_HEATER" K_PSU = { Psu.ADC_I_FEE_ANA: 0.3058, Psu.ADC_I_FEE_DIG: 0.1528, Psu.ADC_I_DPU: 0.4913, Psu.ADC_I_RSE: 0.844, Psu.ADC_I_HEATER: 0.4349 } PSU_OFFSET = { Psu.ADC_I_FEE_ANA: 0, Psu.ADC_I_FEE_DIG: 0, Psu.ADC_I_DPU: 0, Psu.ADC_I_RSE: 0, Psu.ADC_I_HEATER: -0.3701 } class Rse: _unit = "degC" RSE_MOTOR_TEMP = "HK_RSE_MOTOR_TEMP" RSE_ELEC_TEMP = "HK_RSE_ELEC_TEMP" # fit polynomial of degree POLY_DEG through CCD ADU-degC relation (operational range) _ccd_temp_adu_array = np.array(CCD_TEMP_TABLE).T # (degC, ADC_V, ADU_dec, ADU_hex) POLY_DEG = 4 _ccd_temp_fit_adu = np.polynomial.polynomial.Polynomial.fit(_ccd_temp_adu_array[2], _ccd_temp_adu_array[0], POLY_DEG).convert() _ccd_temp_fit_adu_inv = np.polynomial.polynomial.Polynomial.fit(_ccd_temp_adu_array[0], _ccd_temp_adu_array[2], POLY_DEG).convert() # cubic-spline interpolation of PSU ADU-degC relation (nominal values) _psu_temp_adu_array = np.array(PSU_TEMP).T # (degC, ADC_V, ADU_dec, ADU_hex) _psu_temp_interp = sp.interpolate.interp1d(_psu_temp_adu_array[2], _psu_temp_adu_array[0], kind='cubic', fill_value='extrapolate') _psu_temp_interp_inv = sp.interpolate.interp1d(_psu_temp_adu_array[0], _psu_temp_adu_array[2], kind='cubic', fill_value='extrapolate') def t_ccd_adu_to_deg_oper(adu, warn=True): if not ((_ccd_temp_adu_array[2].min() <= adu) & (adu <= _ccd_temp_adu_array[2].max())).all() and warn: print('WARNING! Value(s) outside operational range ({:.0f}-{:.0f})!'.format(_ccd_temp_adu_array[2].min(), _ccd_temp_adu_array[2].max())) return _ccd_temp_fit_adu(adu) def t_ccd_deg_to_adu_oper(t, warn=True): if not ((_ccd_temp_adu_array[0].min() <= t) & (t <= _ccd_temp_adu_array[0].max())).all() and warn: print('WARNING! Value(s) outside operational range ({} - {})!'.format(_ccd_temp_adu_array[0].min(), _ccd_temp_adu_array[0].max())) return np.rint(_ccd_temp_fit_adu_inv(t)).astype(int) def t_ccd_adu_to_deg_nonoper(adu): return (adu * ADC_INPRNG / (2 ** 14 - 1) + ADC_OFFSET - V_T0.CCD) / (V_T0.CCD * K_T.CCD) def t_ccd_deg_to_adu_nonoper(t): return np.rint(((t * V_T0.CCD * K_T.CCD - ADC_OFFSET + V_T0.CCD) * (2 ** 14 - 1)) / ADC_INPRNG).astype(int) def t_ccd_adu_to_deg(adu): return np.where(adu <= _ccd_temp_adu_array[2].max(), t_ccd_adu_to_deg_oper(adu, warn=False), t_ccd_adu_to_deg_nonoper(adu)) def t_ccd_deg_to_adu(t): return np.where(t <= _ccd_temp_adu_array[0].max(), t_ccd_deg_to_adu_oper(t, warn=False), t_ccd_deg_to_adu_nonoper(t)) def t_temp1_adu_to_deg(adu): return (adu * ADC_INPRNG / (2 ** 14 - 1) + ADC_OFFSET - V_T0.TEMP1) / (V_T0.TEMP1 * K_T.TEMP1) def t_temp1_deg_to_adu(t): return np.rint(((t * V_T0.TEMP1 * K_T.TEMP1 - ADC_OFFSET + V_T0.TEMP1) * (2 ** 14 - 1)) / ADC_INPRNG).astype(int) def t_fee_adu_to_deg(adu): return (adu * ADC_INPRNG / (2 ** 14 - 1) + ADC_OFFSET - V_T0.FEE) / (V_T0.FEE * K_T.FEE) def t_fee_deg_to_adu(t): return np.rint(((t * V_T0.FEE * K_T.FEE - ADC_OFFSET + V_T0.FEE) * (2 ** 14 - 1)) / ADC_INPRNG).astype(int) def t_rse_adu_to_deg(adu): return (3.908 - np.sqrt(17.59246 - (76.56 / (4096 / adu - 1)))) / 0.00116 def t_rse_deg_to_adu(t): return np.rint(4096 / (76.56 / (17.59246 - (3.908 - 0.00116 * t) ** 2) + 1)).astype(int) def t_psu_adu_to_deg(adu): return _psu_temp_interp(adu) def t_psu_deg_to_adu(t): return _psu_temp_interp_inv(t) def t_adu_to_deg(adu, signal): if signal == Temp.ADC_TEMP_CCD: t = t_ccd_adu_to_deg(adu) elif signal == Temp.ADC_TEMP1: t = t_temp1_adu_to_deg(adu) elif signal == Temp.ADC_TEMP_FEE: t = t_fee_adu_to_deg(adu) elif signal == Temp.ADC_PSU_TEMP: t = t_psu_adu_to_deg(adu) elif signal in (Rse.RSE_MOTOR_TEMP, Rse.RSE_ELEC_TEMP): t = t_rse_adu_to_deg(adu) else: raise ValueError("Unknown signal '{}'".format(signal)) return t def t_deg_to_adu(t, signal): if signal == Temp.ADC_TEMP_CCD: adu = t_ccd_deg_to_adu(t) elif signal == Temp.ADC_TEMP1: adu = t_temp1_deg_to_adu(t) elif signal == Temp.ADC_TEMP_FEE: adu = t_fee_deg_to_adu(t) elif signal == Temp.ADC_PSU_TEMP: adu = t_psu_deg_to_adu(t) elif signal in (Rse.RSE_MOTOR_TEMP, Rse.RSE_ELEC_TEMP): adu = t_rse_deg_to_adu(t) else: raise ValueError("Unknown signal '{}'".format(signal)) return adu def u_dpu_adu_to_volt(adu, signal): return ((adu * ADC_INPRNG) / (2 ** 14 - 1) + ADC_OFFSET) * K_DPU[signal] def u_dpu_volt_to_adu(u, signal): return np.rint(((u / K_DPU[signal] - ADC_OFFSET) * (2 ** 14 - 1)) / ADC_INPRNG).astype(int) def i_psu_adu_to_amp(adu, signal): return ((adu * ADC_INPRNG) / (2 ** 14 - 1) + ADC_OFFSET) * K_PSU[signal] + PSU_OFFSET[signal] def i_psu_amp_to_adu(i, signal): return np.rint((((i - PSU_OFFSET[signal]) / K_PSU[signal] - ADC_OFFSET) * (2 ** 14 - 1)) / ADC_INPRNG).astype(int) def calibrate(adu, signal): """ :param adu: :param signal: :return: """ if signal in SIGNAL_IASW_DBS: signal = SIGNAL_IASW_DBS[signal] if signal in Dpu.__dict__.values(): x = u_dpu_adu_to_volt(adu, signal) elif signal in Temp.__dict__.values() or signal in Rse.__dict__.values(): x = t_adu_to_deg(adu, signal) elif signal in Psu.__dict__.values(): x = i_psu_adu_to_amp(adu, signal) else: raise ValueError("Unknown signal '{}'".format(signal)) return x def decalibrate(x, signal): """ :param x: :param signal: :return: """ if signal in SIGNAL_IASW_DBS: signal = SIGNAL_IASW_DBS[signal] if signal in Dpu.__dict__.values(): adu = u_dpu_volt_to_adu(x, signal) elif signal in Temp.__dict__.values() or signal in Rse.__dict__.values(): adu = t_deg_to_adu(x, signal) elif signal in Psu.__dict__.values(): adu = i_psu_amp_to_adu(x, signal) else: raise ValueError("Unknown signal '{}'".format(signal)) return adu class CalibrationTables: # default ADC limits BOUND_L = 0 BOUND_U = 0x3FFE BOUND_RSE_L = 0x01 BOUND_RSE_U = 0xCD def __init__(self): # temperatures # x = np.linspace(self.BOUND_L, self.BOUND_U, 60, dtype=int) self.temperature = {} for sig in vars(Temp): # CCD TEMP if sig == 'ADC_TEMP_CCD': label = getattr(Temp, sig) lmts = getattr(Limits, sig) x = np.linspace(6000, 11700, 60, dtype=int) self.temperature[label] = np.array([x, t_adu_to_deg(x, label)]) # PSU TEMP elif sig == 'ADC_PSU_TEMP': label = getattr(Temp, sig) lmts = getattr(Limits, sig) x = np.linspace(int(lmts[0]*0.9), 11650, 50, dtype=int) self.temperature[label] = np.array([x, t_adu_to_deg(x, label)]) elif sig.startswith('ADC'): label = getattr(Temp, sig) lmts = getattr(Limits, sig) x = np.linspace(int(lmts[0]*0.9), int(lmts[-1]*1.1), 50, dtype=int) self.temperature[label] = np.array([x, t_adu_to_deg(x, label)]) x = np.linspace(self.BOUND_RSE_L, self.BOUND_RSE_U, 50, dtype=int) for sig in vars(Rse): if sig.startswith('RSE'): label = getattr(Rse, sig) self.temperature[label] = np.array([x, t_adu_to_deg(x, label)]) x = np.linspace(self.BOUND_L, self.BOUND_U, 2, dtype=int) # two points suffice for linear voltage and current calibrations # voltages self.voltage = {} for sig in vars(Dpu): if sig.startswith('ADC'): label = getattr(Dpu, sig) self.voltage[label] = np.array([x, u_dpu_adu_to_volt(x, label)]) # currents self.current = {} for sig in vars(Psu): if sig.startswith('ADC'): label = getattr(Psu, sig) self.current[label] = np.array([x, i_psu_adu_to_amp(x, label)]) def write_to_files(self, path): for k in self.temperature: np.savetxt(os.path.join(path, k + '.dat'), self.temperature[k].T, header=k, fmt=('%5d', '%6.1f')) for k in self.voltage: np.savetxt(os.path.join(path, k + '.dat'), self.voltage[k].T, header=k, fmt=('%5d', '%6.3f')) for k in self.current: np.savetxt(os.path.join(path, k + '.dat'), self.current[k].T, header=k, fmt=('%5d', '%6.3f')) print("Calibration tables written to {}".format(path)) def _plot(self, signal, xmin=BOUND_L, xmax=BOUND_U): if 'plt' not in globals(): raise ModuleNotFoundError("This only works in stand-alone mode") sig = signal[3:] if sig in vars(Dpu): xy = self.voltage[signal] ylabel = 'Voltage [V]' elif sig in vars(Temp): xy = self.temperature[signal] ylabel = 'Temperature [°C]' elif sig in vars(Psu): xy = self.current[signal] ylabel = 'Current [A]' else: raise ValueError("Unknown signal '{}'".format(sig)) xref = np.linspace(xmin, xmax, 1000) yref = calibrate(xref, signal) limits = np.array((np.array(getattr(Limits, sig)), calibrate(np.array(getattr(Limits, sig)), signal))).T print(limits) fl, wl, wu, fu = limits plt.axvspan(xmin, fl[0], alpha=0.25, color='red') plt.axvspan(fl[0], wl[0], alpha=0.5, color='orange') plt.axvspan(wu[0], fu[0], alpha=0.5, color='orange') plt.axvspan(fu[0], xmax, alpha=0.25, color='red') for i in limits: plt.axhline(i[1], ls=':', color='grey') plt.plot(xref, yref, color='grey', lw=0.5) plt.plot(*xy, 'k.', label=signal, ms=4) # plt.legend() plt.xlabel('ADU') plt.ylabel(ylabel) plt.title(signal) plt.grid(True) plt.show() class Limits: # raw operational limits (FAIL_L, WARN_L, WARN_U, FAIL_U) ADC_P3V9 = (0x1D8D, 0x1E67, 0x2119, 0x21F3) ADC_P3V3 = (0x27A2, 0x2912, 0x2DF2, 0x2F62) ADC_P3V3_LVDS = (0x27A2, 0x2912, 0x2DF2, 0x2F62) ADC_P2V5 = (0x215D, 0x2274, 0x26A1, 0x27B8) ADC_P1V8 = (0x1BE0, 0x1CA9, 0x203A, 0x2103) ADC_P1V2 = (0x172C, 0x17B2, 0x1ABE, 0x1B43) ADC_REF = (0x215D, 0x2274, 0x26A1, 0x27B8) ADC_TEMP1 = (0x210F, 0x2259, 0x2B37, 0x2C12) ADC_TEMP_FEE = (0x17EA, 0x188E, 0x1CFD, 0x1D6B) ADC_TEMP_CCD = (0x1968, 0x19DD, 0x1D20, 0x1D93) ADC_I_FEE_ANA = (0xDC4, 0xDC4, 0x1D70, 0x1ECE) ADC_I_FEE_DIG = (0xDC4, 0xDC4, 0x20FB, 0x22B4) ADC_I_DPU = (0xDC4, 0xDC4, 0x20B7, 0x2269) ADC_I_RSE = (0xDC4, 0xDC4, 0x1EA8, 0x2025) ADC_I_HEATER = (0x152E, 0x152E, 0x23B2, 0x24F2) ADC_PSU_TEMP = (0x12A5, 0x13E4, 0x298C, 0x2B08) # raw upper RSE limits RSE_MOTOR_TEMP = 0x96 RSE_ELEC_TEMP = 0x96 # raw ambient CCD limits ADC_TEMP_CCD_AMB = (0x1968, 0x19DD, 0x29DB, 0x2A49) class LimitTables: def __init__(self): # temperatures self.temperature = {} for sig in vars(Temp): if sig.startswith('ADC'): label = getattr(Temp, sig) adu_limits = np.array(getattr(Limits, sig)) self.temperature[label] = np.array([adu_limits, t_adu_to_deg(adu_limits, label)]) for sig in vars(Rse): if sig.startswith('RSE'): label = getattr(Rse, sig) adu_limits = np.array(getattr(Limits, sig)) self.temperature[label] = np.array([adu_limits, t_adu_to_deg(adu_limits, label)]) # voltages self.voltage = {} for sig in vars(Dpu): if sig.startswith('ADC'): label = getattr(Dpu, sig) adu_limits = np.array(getattr(Limits, sig)) self.voltage[label] = np.array([adu_limits, u_dpu_adu_to_volt(adu_limits, label)]) # currents self.current = {} for sig in vars(Psu): if sig.startswith('ADC'): label = getattr(Psu, sig) adu_limits = np.array(getattr(Limits, sig)) self.current[label] = np.array([adu_limits, i_psu_adu_to_amp(adu_limits, label)]) # lookup table for DBS vs IASW naming SIGNAL_IASW_DBS = { "AdcP3V9": Dpu.ADC_P3V9, "AdcP3V3": Dpu.ADC_P3V3, "AdcP3V3LVDS": Dpu.ADC_P3V3_LVDS, "AdcP2V5": Dpu.ADC_P2V5, "AdcP1V8": Dpu.ADC_P1V8, "AdcP1V2": Dpu.ADC_P1V2, "AdcRef": Dpu.ADC_REF, "AdcTemp1": Temp.ADC_TEMP1, "AdcTempFee": Temp.ADC_TEMP_FEE, "AdcTempCcd": Temp.ADC_TEMP_CCD, "AdcPsuTemp": Temp.ADC_PSU_TEMP, "AdcIFeeAna": Psu.ADC_I_FEE_ANA, "AdcIFeeDig": Psu.ADC_I_FEE_DIG, "AdcIDpu": Psu.ADC_I_DPU, "AdcIRse": Psu.ADC_I_RSE, "AdcIHeater": Psu.ADC_I_HEATER, "RseMotorTemp": Rse.RSE_MOTOR_TEMP, "RseElecTemp": Rse.RSE_ELEC_TEMP } SIGNAL_DBS_IASW = {SIGNAL_IASW_DBS[k]: k for k in SIGNAL_IASW_DBS} def cal_pt1000(temp): return cal_ptx(temp, 1000) def cal_pt2000(temp): return cal_ptx(temp, 2000) def cal_ptx(temp, R0): """ Standard DIN EN 60751 PTX transfer curve (-200 - 850°C) :param temp: temperature in °C :return: resistance in Ohm """ A = 3.9083e-3 B = -5.775e-7 C = -4.183e-12 def subzero(): return R0 * (1 + A*temp + B*temp**2 + C*(temp - 100)*temp**3) def abovezero(): return R0 * (1 + A*temp + B*temp**2) if (np.array(temp) < -200).any() or (np.array(temp) > 850).any(): print("WARNING: Value(s) outside calibrated range (-200 - 850°C)!") return np.where(temp > 0, abovezero(), subzero()) _ptx = np.arange(-200, 851) _pty = cal_pt1000(_ptx) _pt1000_curve_inv = sp.interpolate.interp1d(_pty, _ptx, kind='cubic', fill_value='extrapolate') # inverse PT1000 curve for Ohm to °C conversion def t_ccd_fee_adu_to_deg(adu, ccd): """ For CCD temperature reported in FEE HK. Uses PT1000! :param adu: :param ccd: :return: """ if ccd == 2: return _pt1000_curve_inv(adu * FEE_CCD2TsA_gain + FEE_CCD2TsA_offset) elif ccd == 4: return _pt1000_curve_inv(adu * FEE_CCD4TsB_gain + FEE_CCD4TsB_offset) else: raise ValueError("CCD must be either 2 or 4!") def t_ccd_fee_deg_to_adu(t, ccd): """ For CCD temperature reported in FEE HK :param t: :param ccd: :return: """ if ccd == 2: return np.rint((cal_pt1000(t) - FEE_CCD2TsA_offset) / FEE_CCD2TsA_gain).astype(int) elif ccd == 4: return np.rint((cal_pt1000(t) - FEE_CCD4TsB_offset) / FEE_CCD4TsB_gain).astype(int) else: raise ValueError("CCD must be either 2 or 4!") class Fee: CCD2_TS_A = "FRMHKccd2TsA" CCD4_TS_B = "FRMHKccd4TsB" PRT1 = "FRMHKprt1" PRT2 = "FRMHKprt2" PRT3 = "FRMHKprt3" PRT4 = "FRMHKprt4" PRT5 = "FRMHKprt5" CCD4_VOD_MON_E = "FRMHKccd4VodMonE" CCD4_VOG_MON = "FRMHKccd4VogMon" CCD4_VRD_MON_E = "FRMHKccd4VrdMonE" CCD2_VOD_MON_E = "FRMHKccd2VodMonE" CCD2_VOG_MON = "FRMHKccd2VogMon" CCD2_VRD_MON_E = "FRMHKccd2VrdMonE" CCD4_VRD_MON_F = "FRMHKccd4VrdMonF" CCD4_VDD_MON = "FRMHKccd4VddMon" CCD4_VGD_MON = "FRMHKccd4VgdMon" CCD2_VRD_MON_F = "FRMHKccd2VrdMonF" CCD2_VDD_MON = "FRMHKccd2VddMon" CCD2_VGD_MON = "FRMHKccd2VgdMon" VCCD = "FRMHKvccd" VRCLK_MON = "FRMHKvrclkMon" VICLK = "FRMHKviclk" CCD4_VOD_MON_F = "FRMHKccd4VodMonF" P5VB_POS_MON = "FRMHK5vbPosMon" P5VB_NEG_MON = "FRMHK5vbNegMon" P3V3B_MON = "FRMHK3v3bMon" P2V5A_MON = "FRMHK2v5aMon" P3V3D_MON = "FRMHK3v3dMon" P2V5D_MON = "FRMHK2v5dMon" P1V2D_MON = "FRMHK1v2dMon" P5VREF_MON = "FRMHK5vrefMon" VCCD_POS_RAW = "FRMHKvccdPosRaw" VCLK_POS_RAW = "FRMHKvclkPosRaw" VAN1_POS_RAW = "FRMHKvan1PosRaw" VAN3_NEG_MON = "FRMHKvan3NegMon" VAN2_POS_RAW = "FRMHKvan2PosRaw" VDIG_RAW = "FRMHKvdigRaw" IG_HI_MON = "FRMHKigHiMon" CCD2_VOD_MON_F = "FRMHKccd2VodMonF" # FEE HK gains/offsets # EQM FEE_GAIN_OFFSET = { Fee.CCD2_TS_A: (0.048589970854, 326.709603726099), Fee.CCD4_TS_B: (0.048346071846, 317.545999899085), Fee.PRT1: (0.049337666752, 310.304954966437), Fee.PRT2: (0.048871723231, 322.563832689621), Fee.PRT3: (0.048882740559, 322.418053560869), Fee.PRT4: (0.048777132761, 322.321990156487), Fee.PRT5: (0.048683458078, 323.746239172483), Fee.CCD4_VOD_MON_E: (0.000563088127, -0.00209746042908421), Fee.CCD4_VOG_MON: (0.000135181804, -0.166559933290103), Fee.CCD4_VRD_MON_E: (0.000563174116, 0.0193461050916852), Fee.CCD2_VOD_MON_E: (0.000563015464, -0.0097318620270066), Fee.CCD2_VOG_MON: (0.000135565734, -0.164272515606305), Fee.CCD2_VRD_MON_E: (0.000562914749, 0.0221158942564337), Fee.CCD4_VRD_MON_F: (0.000563425754, 0.00833790912991361), Fee.CCD4_VDD_MON: (0.000816121249, 0), Fee.CCD4_VGD_MON: (0.000562165835, 0.0483795532258782), Fee.CCD2_VRD_MON_F: (0.000563631207, -0.00437775179765865), Fee.CCD2_VDD_MON: (0.000815982604, 0), Fee.CCD2_VGD_MON: (0.000556683023, 0.225687270717021), Fee.VCCD: (0.000756606970, 0), Fee.VRCLK_MON: (0.000360316440, 0), Fee.VICLK: (0.000360364766, 0), Fee.CCD4_VOD_MON_F: (0.000562995879, 0.00807772719949895), Fee.P5VB_POS_MON: (0.000092728181, 0), Fee.P5VB_NEG_MON: (-0.000125745208, 0), Fee.P3V3B_MON: (0.000062672872, 0), Fee.P2V5A_MON: (0.000062623239, 0), Fee.P3V3D_MON: (0.000062667814, 0), Fee.P2V5D_MON: (0.000062623117, 0), Fee.P1V2D_MON: (0.000031361075, 0), Fee.P5VREF_MON: (0.000097218804, 0), Fee.VCCD_POS_RAW: (0.000756449617, 0), Fee.VCLK_POS_RAW: (0.000360291117, 0), Fee.VAN1_POS_RAW: (0.000163267788, 0), Fee.VAN3_NEG_MON: (-0.000208630551, 0), Fee.VAN2_POS_RAW: (0.000163196727, 0), Fee.VDIG_RAW: (0.000097250522, 0), Fee.IG_HI_MON: (0.000186900810, 0), Fee.CCD2_VOD_MON_F: (0.000562860544, -0.00642286504851342) } FEE_CCD2TsA_gain, FEE_CCD2TsA_offset = FEE_GAIN_OFFSET[Fee.CCD2_TS_A] FEE_CCD4TsB_gain, FEE_CCD4TsB_offset = FEE_GAIN_OFFSET[Fee.CCD4_TS_B] def cal_fee_hk(adu, signal): """ Calibrate raw FEE HK reading to engineering value @param signal: @param adu: @return: """ if signal not in FEE_GAIN_OFFSET: raise ValueError('Unknown signal "{}"'.format(signal)) gain, offset = FEE_GAIN_OFFSET[signal] val = adu * gain + offset if signal in [Fee.CCD2_TS_A, Fee.CCD4_TS_B, Fee.PRT1, Fee.PRT2, Fee.PRT3, Fee.PRT4, Fee.PRT5]: val = _pt1000_curve_inv(val) return val def calibrate_ext(adu, signal, exception=False): """ Provide unified access to customised calibrations outside MIB. This function shall expose all calibrations in this module that should be accessible by other CCS modules. :param adu: :param signal: :param exception: :return: """ try: return cal_fee_hk(adu, signal) except ValueError: return adu if not exception else None # to disable calibration # return adu if not exception else None class BadPixelMask: """ Convenience functions for handling the SMILE SXI bad pixel mask stored in MRAM """ NROWS = 639 NCOLS = 384 @classmethod def from_bytes(cls, buffer): return np.unpackbits(bytearray(buffer)).reshape((cls.NROWS, cls.NCOLS)) @classmethod def to_bytes(cls, mask: np.ndarray): assert isinstance(mask, np.ndarray) if mask.size != cls.NROWS * cls.NCOLS: raise ValueError("Mask must be array of size {}, is {}.".format(cls.NROWS * cls.NCOLS, mask.size)) return bytes(np.packbits(mask)) @classmethod def gen_mask_array(cls): return np.zeros((cls.NROWS, cls.NCOLS), dtype=int) if __name__ == '__main__': import matplotlib.pyplot as plt ct = CalibrationTables() ct._plot(Temp.ADC_TEMP_CCD) # ct.write_to_files('/home/marko/space/CCS/calibrations') lmt = LimitTables()