def blackbody_radiance(self, T, spectral=True): """Calculate integrated radiance for blackbody at temperature T :param T: Temperature [K]. This can be either a python number, or a numpy ndarray, on a ureg quantity encompassing either. :param spectral: Parameter to control whether to return spectral radiance or radiance. See self.integrate_radiances for details. Returns quantity ndarray with blackbody radiance in desired unit. Note that this is an ndarray with dimension (1,) even if you passin a scalar. """ try: T = T.to("K") except AttributeError: T = ureg.Quantity(T, "K") T = ureg.Quantity(numpy.atleast_1d(T), T.u) # fails if T is multidimensional shp = T.shape return self.integrate_radiances(self.frequency, planck_f( self.frequency[numpy.newaxis, :], T.reshape((-1, ))[:, numpy.newaxis]), spectral=spectral).reshape(shp)
def _S_radsi_to_K(S, u_radK): """Convert covariance matrix in radiance to one in BT This is done by first converting covariance matrices to correlation matrices using one set of uncertainties, then back with another. Parameters ---------- S : xarray.DataArray Covariance matrix in rad_si units u_radK : xarray.DataArray Uncertainties in brightness temperature in K. Returns ------- ureg.Quantity Covariance matrix in brightness temperature """ S_L = ureg.Quantity(S.values, rad_u["si"]**2) uRe = ureg.Quantity(numpy.sqrt(numpy.diag(S_L)), rad_u["si"]) R = S_L / (uRe[:, numpy.newaxis] * uRe[numpy.newaxis, :]) S_BT = R * (u_radK[:, numpy.newaxis] * u_radK[numpy.newaxis, :]) return S_BT
def integrate_radiances(self, f, L, spectral=True): """From a spectrum of radiances and a SRF, calculate channel (spectral) radiance The spectral response function may not be specified on the same grid as the spectrum of radiances. Therefore, this function interpolates the spectral response function onto the grid of the radiances. This is less bad than the reverse, because a spectral response function tends to be more smooth than a spectrum. **Approximations:** * Interpolation of spectral response function onto frequency grid on which radiances are specified. :param ndarray f: Frequencies for spectral radiances [Hz] :param ndarray L: Spectral radiances [W m^-2 sr^-1 Hz^-1]. Can be in radiance units of various kinds. Make sure this is consistent with the spectral response function. Innermost dimension must correspond to frequencies. :param bool spectral: If true, return spectral radiance [W m^-2 sr^-1 Hz^-1]. If false, return radiance [W m^-2 sr^-1]. Defaults to True. :returns: Channel (spectral) radiance according to 'spectral' """ # Interpolate onto common frequency grid. The spectral response # function is more smooth so less harmed by interpolation, so I # interpolate the SRF. fnc = scipy.interpolate.interp1d(self.frequency, self.W, bounds_error=False, fill_value=0.0) #w_on_L_grid = fnc(f) * (1 / ureg.Hz) w_on_L_grid = ureg.Quantity(fnc(f), ureg.dimensionless) # * (1 / ureg.Hz) df = ureg.Quantity(numpy.diff(f), f.u) w1p = w_on_L_grid[1:] L1p = L[:, 1:] # ch_BT = (w_on_L_grid * L_f).sum(-1) / (w_on_L_grid.sum()) # due to numexpr limitation, do sum seperately # and due to numexpr bug, explicitly consider zero-dim case # see https://github.com/pydata/numexpr/issues/229 if L.shape[0] == 0: ch_rad = numpy.empty(dtype="f8", shape=L.shape[:-1]) else: # ch_rad = numexpr.evaluate("sum(w_on_L_grid * L, {:d})".format( ch_rad = numexpr.evaluate( "sum(w1p * L1p * df, {:d})".format(L.ndim - 1)) ch_rad = ureg.Quantity(ch_rad, w1p.u * L1p.u * df.u) if spectral: return ch_rad / (w1p * df).sum() else: return ch_rad
def _add_harm_for_iasi(self, harm, channel, ok): # fill X1 # self.ds["ref_radiance"] contains IASI radiances; need to use # this to simulate HIRS radiance for MetOp-A freq = ureg.Quantity(numpy.loadtxt(self.hiasi.freqfile), ureg.Hz) specrad_wn = UADA(self.ds.isel(line=ok)["ref_radiance"]) specrad_f = specrad_wn.to(rad_u["si"], "radiance") srf = typhon.physics.units.em.SRF.fromRTTOV("metop_2", "hirs", channel) L = srf.integrate_radiances( freq, ureg.Quantity(specrad_f.values, specrad_f.attrs["units"])) harm["X1"] = (("M", "m1"), L[:, numpy.newaxis].astype("f4")) # fill Ur1. Uncertainties in refeence not considered. # Arbitrarily put at 1‰. harm["Ur1"] = (("M", "m1"), (harm["X1"] * 0.001).astype("f4")) # fill Us1. harm["Us1"] = (("M", "m1"), numpy.zeros((harm.dims["M"], 1), dtype="f4")) # fill uncertainty_type1 harm["uncertainty_type1"] = (("m1", ), numpy.array([1], dtype="i4")) # fill time1 harm["time1"] = (("M", ), self.ds.isel(line=ok)["mon_time"]) # and w-matrix stuff harm["w_matrix_use1"] = (("m1", ), numpy.array([0], dtype="i4")) harm["u_matrix_use1"] = (("m1", ), numpy.array([0], dtype="i4")) # add diagnostics # harm[f"nominal_measurand2"] = (("M",), # self.ds["mon_radiance"].sel(ch_mon=channel)) harm[f"lon1"] = (("M", ), self.ds[f"ref_longitude"][ok]) harm[f"lat1"] = (("M", ), self.ds[f"ref_latitude"][ok]) # harm[f"nominal_measurand_original1"] = harm[f"nominal_measurand1"] harm[f"column1"] = (("M", ), self.ds[f"ref_column"][ok]) harm[f"row1"] = (("M", ), self.ds[f"ref_row"][ok]) harm[f"matchup_distance"] = ((), 0)
def specrad_frequency_to_planck_bt(L, f): """Convert spectral radiance per frequency to brightness temperature This function converts monochromatic spectral radiance per frequency to Planck brightness temperature. This is calculated by inverting the Planck function. Note that this function is NOT correct to estimate polychromatic brightness temperatures such as channel brightness temperatures. For this, you need the spectral response function — see the SRF class. :param L: Spectral radiance [W m^-2 sr^-1 Hz^-1] :param f: Corresponding frequency [Hz] :returns: Planck brightness temperature [K]. """ # f needs to be double to prevent overflow f = numpy.asarray(f).astype(numpy.float64) if L.size > 1500000: logger.debug("Doing actual BT conversion: {:,} spectra * {:,} " "frequencies = {:,} radiances".format( L.size // L.shape[-1], f.size, L.size)) # BT = (h * f) / (k * numpy.log((2*h*f**3)/(L * c**2) + 1)) BT = numexpr.evaluate("(h * f) / (k * log((2*h*f**3)/(L * c**2) + 1))") BT = numpy.ma.masked_invalid(BT) if L.size > 1500000: logger.debug("(done)") return ureg.Quantity(BT, ureg.K)
def fromRTTOV(cls, sat, instr, ch): """Read SRF from RTTOV format files. Requires that in the TYPHONRC configuration file, the fields 'srf_rttov' in the section corresponding to instrument `instr` are defined to point to the respective files in RTTOV format. Within those definitions, {sat:s} will be substituted with the satellite name and {ch:>02d} with the channel number. For example, in typhonrc, we might have: [hirs] srf_rttov = /path/to/rtcoef_{sat:s}_hirs-shifted_src_ch{ch:>02d}.txt Arguments: sat [str] Satellite name, such as 'noaa-15' instr [str] Instrument name, such as 'hirs'. ch [int] Channel number (start counting at 1). """ cf = config.conf[instr] M = numpy.loadtxt(cf["srf_rttov"].format(sat=sat, ch=ch), skiprows=4) wn = ureg.Quantity(M[:, 0], 1 / ureg.cm) W = M[:, 1] return cls(wn, W)
def __init__(self, f, W): """Initialise SRF object. You can either initialise an SRF from scratch, or use the classmethod `fromArtsXML` to read it from a file. A toy example on initiating it from scratch: >>> from typhon.physics.units.common import ureg >>> from typhon.physics.units.em import SRF >>> srf = SRF(ureg.Quantity(numpy.array([200, 200.1, 200.2, 200.3, 200.4, 200.5]), 'GHz'), numpy.array([0, 0.5, 1, 1, 0.5, 0])) >>> R_300 = srf.blackbody_radiance(ureg.Quantity(300, 'K')) >>> print(R_300) [ 3.63716781e-15] watt / hertz / meter ** 2 / steradian >>> print(R_300.to("K", "radiance", srf=srf)) [ 300.] kelvin You can also pass in other spectroscopic units (wavenumber, wavelength) that will be converted internally to frequency: >>> srf = SRF(ureg.Quantity(numpy.array([10.8, 10.9, 11.0, 11.1, 11.2, 11.3]), 'um'), numpy.array([0, 0.5, 1, 1, 0.5, 0])) >>> R_300 = srf.blackbody_radiance(ureg.Quantity(numpy.atleast_1d(250), 'K')) >>> print(R_300) [ 1.61922509e-12] watt / hertz / meter ** 2 / steradian >>> print(R_300.to("cm * mW / m**2 / sr", "radiance")) [ 48.54314703] centimeter * milliwatt / meter ** 2 / steradian >>> print(R_300.to("K", "radiance", srf=srf)) [ 300.] kelvin :param ndarray f: Array of frequencies. Can be either a pure ndarray, which will be assumed to be in Hz, or a ureg quantity. :param ndarray W: Array of associated weights. """ try: self.frequency = f.to("Hz", "sp") except AttributeError: self.frequency = ureg.Quantity(f, "Hz") self.W = W
def calc_y_for_srf_shift(Δλ, y_master, srf0, L_spectral_db, f_spectra, y_ref, unit=ureg.um, regression_type=sklearn.linear_model.LinearRegression, regression_args={"fit_intercept": True}, predict_quantity="bt", u_y_ref=None, u_y_target=None): """Calculate radiances or BTs estimating y_target from y_master assuming srf0 shifts by Δλ Try to estimate y_target from y_master, assuming that y_master are bts or radiances corresponding to spectral response function srf0. For the estimate, use a database described by L_spectral_db and f_spectra. This database will be used to train a regression from the original bt (y_master, due to srf0) to a shifted bt (due to srf0 shifted by Δλ). The regression is trained using the spectral database (L_spectral_db, f_spectra, bt_ref). This function then applies the regression to y_master. This function is designed to be called by :func:`calc_cost_for_srf_shift`, which in turn is designed to be called repeatedly within an optimisation framework (see :func:`estimate_srf_shift`). Therefore, as much as possible is precalculated before calling this. Hence, you also need to pass bt_ref, which equals integrated brightness temperatures for the reference satellite, corresponding to f_spectra and L_spectral_db. This estimate considers one channel at a time. It may be more optimal to estimate multiple channels at a time. This is to be done. Parameters ---------- Δλ : Quantity or float shift in SRF. Will be converted to the unit (typically µm or nm) from the pint user registry (see later argument). Scalar. y_master : (N, k) Quantity ndarray Brightness temperatures [K] or radiances [radiance units] for reference satellite. N samples, k channels. Quantity must be consistent with the one described by predict_quantity. These are used in the actual prediction; the training is performed with y_ref. For example, if we are predicting NOAA18-8 from NOAA19-1-12 in a test setting, y_master would correspond to NOAA19-1-12 test data, and y_ref would be the same for training data. In the real world, y_ref is still simulated, but y_master are actual measurements. srf0 : typhon.physics.units.em.SRF SRF relative to which the shift is to be calculated. L_spectral_db (M, l) ndarray Database of spectra (such as from IASI) to use. Should be in spectral radiance per frequency units [W / (m^2 sr Hz)]. M spectra with l radiances each. f_spectra : (l, ) Quantity ndarray frequencies corresponding to L_spectral_db [Hz]. 1-D with length l. y_ref : (M, k) Quantity ndarray : Predictands used to train regressions, i.e. the training database. This information follows directly from L_spectral_db and SRFs on the reference satellite, but it is an expensive calculation so should be pre-calculated. If predicting from the same satellite, at least one channel will correspond to srf0, such that:: # in case of radiances L = srf0.integrate_radiances(f_spectra, L_spectral_db) # in case of BTs bt = srf0.channel_radiance2bt(L) but this is not the case if one satellite is predicted from another. unit : Unit unit from pint unit registry. Defaults to ureg.um. regression_type : scikit-learn regressor Type of regression. Defaults to sklearn.linear_model.LinearRegression. Other good option would be sklearn.cross_decomposition.PLSRegression. As long as regression_type(\*\*regression_args) behaves like those two (with .fit and .predict), it should be OK. regression_args : dict Keyword arguments to pass on to regressor. For example, for sklearn.linear_model.LinearRegression you would want to at least pass ``{"fit_intercept": True}``. For sklearn.cross_decomposition.PLSRegression you might use ``{"n_components": 9, "scale": False}``. Please refer to scikit-learn documentation. Returns ------- y: ndarray estimates for shifted y_master or y_master values """ try: Δλ = Δλ.to(unit) except AttributeError: Δλ = ureg.Quantity(Δλ, unit) srf_sh = srf0.shift(Δλ) L_target = srf_sh.integrate_radiances(f_spectra, L_spectral_db) if predict_quantity == "bt": y_target = srf_sh.channel_radiance2bt(L_target) elif predict_quantity == "radiance": y_target = L_target else: raise ValueError( "Invalid predict_quantity: {:s}".format(predict_quantity)) # sklearn wants 2-D inputs even when there is only a single predictand # atleast_2d makes it (1, N), I need (N, 1), but transposing the # result of atleast_2d would be wrong if the array was already 2D to # begin with if y_master.ndim == 1: y_master = y_master[:, numpy.newaxis] if y_ref.ndim == 1: y_ref = y_ref[:, numpy.newaxis] #clf = sklearn.linear_model.LinearRegression(fit_intercept=True) if issubclass(regression_type, sklearn.base.RegressorMixin): clf = regression_type(**regression_args) clf.fit(y_ref.m, y_target.m) return ureg.Quantity(clf.predict(y_master.m).squeeze(), y_master.u) elif issubclass(regression_type, scipy.odr.odrpack.ODR): try: sx = u_y_ref.to(y_ref.u, "radiance").m.squeeze() sy = u_y_target.to(u_y_target.u, "radiance").m.squeeze() except pint.DimensionalityError: # probably noise in other unit # here, those are really only used as weights, so same-unit # is not essential? Otherwise I need to convert them, a # non-trivial step. sx = u_y_ref.m.squeeze() sy = u_y_target.m.squeeze() # meaningful initial guess: new value equal to old channel, rest 0 # β0 has a offset and then one slope for each channel # don't set to zero, says odrpack guide section 1.E. β0 = numpy.zeros(shape=(y_ref.shape[1] + 1, )) + 0.001 samech = abs((y_target[:, numpy.newaxis] - y_ref)).mean(0).argmin() β0[samech + 1] = 1 if (sx == 0).any() or (sy == 0).any(): mydata = scipy.odr.RealData(y_ref.m.T, y_target.m) else: mydata = scipy.odr.RealData(y_ref.m.T, y_target.m, sx=sx, sy=sy) myodr = scipy.odr.ODR(mydata, scipy.odr.multilinear, beta0=β0) myout = myodr.run() if not any( x in myout.stopreason for x in { "Sum of squares convergence", "Iteration limit reached", "Parameter convergence", "Both sum of squares and parameter convergence" }): raise ODRFitError("ODR fitting did not converge. " "Stop reason: {:s}".format(myout.stopreason[0])) return ureg.Quantity(myout.beta[0], y_master.u) + ( myout.beta[numpy.newaxis, 1:] * y_master).sum(1) else: raise ValueError("Unknown type of regression!")
def plot_rself_test(h, ds, temperatures, channels, regr_type, regr_args, tit, fn): model = models.RSelfTemperature(h, temperatures, (regr_type, regr_args)) #view_space = M[h.scantype_fieldname] == h.typ_space #M = M[view_space] N = len(channels) (ncol, nrow) = typhon.plots.get_subplot_arrangement(N) (f, ax_all) = matplotlib.pyplot.subplots(nrow, ncol, sharex=False, figsize=(2 + 3 * ncol, 3 + 2.5 * nrow), squeeze=False) for (a, c) in zip(ax_all.ravel(), channels): model.fit(ds, c) (X, Y_ref, Y_pred) = model.test(ds, c) xloc = Y_ref.to(rad_u["ir"], "radiance") yloc = (Y_pred - Y_ref).to(rad_u["ir"], "radiance") rng = [ scipy.stats.scoreatpercentile(xloc[~xloc.isnull()], [1, 99]), scipy.stats.scoreatpercentile(yloc[~xloc.isnull()], [1, 99]) ] unmasked = (~xloc.isnull()) & (~yloc.isnull()) a.hexbin(xloc[unmasked], yloc[unmasked], cmap="viridis", mincnt=1, gridsize=20, extent=[rng[0][0], rng[0][1], rng[1][0], rng[1][1]]) # a.hist2d(xloc, yloc, # cmap="viridis", cmin=1, # bins=20, range=rng) typhon.plots.plot_distribution_as_percentiles( a, xloc[unmasked], yloc[unmasked], nbins=20, color="tan", ptiles=[5, 25, 50, 75, 95], linestyles=[":", "--", "-", "--", ":"]) rmse = numpy.sqrt(((Y_pred - Y_ref).to(rad_u["ir"], "radiance")**2).mean()) rmse = ureg.Quantity(rmse.values, rmse.attrs["units"]) a.annotate(f"RMSE: {rmse.round(2):~}", xy=(.99, .99), xycoords='axes fraction', horizontalalignment='right', verticalalignment="top") a.set_title("Ch. {:d}".format(c)) a.grid(axis="both", color="white") #a.set_aspect("equal", "box", "C") a.plot(rng, [0, 0], 'k-', linewidth=0.5) if a in ax_all[:, 0]: a.set_ylabel("Calib. offset (Estimate-Reference)\n[{:~}]".format( rad_u["ir"])) if a in ax_all[-1, :]: a.set_xlabel("Calib. offset (Reference)\n[{:~}]".format( rad_u["ir"])) for ax in (a.xaxis, a.yaxis): ax.set_major_locator( matplotlib.ticker.MaxNLocator(nbins=4, prune=None)) # for a in ax_all.ravel()[:len(channels)]: a.set_xlim(rng[0]) a.set_ylim(rng[1]) for a in ax_all.ravel()[len(channels):]: a.set_visible(False) f.suptitle(tit) f.subplots_adjust(hspace=0.3) graphics.print_or_show(f, False, fn)
def evaluate(self, ds, ch): """Apply self-emission model to data Does a simple linear interpolation of the Rself value from surrounding calibartion lines. Rself will be zero at the first calibration time and will be estimate at the second calibrated time to match the observed space view signal given the gain etc. The values will then be interpolated in time to generate the new values of Rself. The final curves will give a sawtooth variation starting near zero to a maximum just before the next calibration observation. This method: - extracts the predictor from the source scanlines dataset (time) - converts this to the right format, including masking bad lines - estimate the predictand (space counts) for all lines assuming a linear model Parameters ---------- ds : xarray.Dataset Dataset containing the L1B scanlines for which the self-emission is to be estimated ch : int Channel for which to estimate self-emission. Returns ------- X : xarray.Dataset Predictor that was used to evaluate Y : `typhon.physics.units.tools.UnitsAwareDataArray` Estimates of self-emission for all scanlines in ds """ # # Get the temperatures to estimate uncertainty # # Make sure temperatures are in sync with space counts etc. # so ensures both space and iwct observations available # views_space = xarray.DataArray( ds["scantype"].values == self.hirs.typ_space, coords=ds["scantype"].coords) views_iwct = xarray.DataArray( ds["scantype"].values == self.hirs.typ_iwt, coords=ds["scantype"].coords) dsi = self.hirs.dist_space_iwct space_followed_by_iwct = (views_space[:-dsi].variable & views_iwct[dsi:].variable) ds_temp = ds.isel(time=slice(None, -dsi)).isel( time=space_followed_by_iwct) start = True L = [] for t_fld in self.temperatures: t_fld = _tovs_defs.temperature_names.get(t_fld, t_fld) x = ds["temperature_{:s}".format(t_fld)] for dim in set(x.dims) - {"time"}: x = x.mean(dim=dim, keep_attrs=True) newT = x.astype("f8") L.append(x.astype("f8")) # prevent X⁴ precision loss # # Convert Temperatures to radiance # Rad = self.hirs.srfs[ch - 1].blackbody_radiance( ureg.Quantity(newT.values, ureg.K)) gd = (newT > 340.) if numpy.sum(gd) > 0: Rad[gd] = numpy.nan if start: npR = numpy.zeros((1, len(Rad)), dtype=numpy.float32) npRout = numpy.zeros((1, len(Rad)), dtype=numpy.float32) npR[0, :] = Rad[:] start = False else: npRout[0, :] = Rad npR = numpy.append(npR, npRout, axis=0) # Needed as predictors for T_outlier routines X = xarray.merge(L) Tname = "temperature_{:s}".format(self.temperatures[0]) R = UADA(npR,\ dims=['ntemp','time'],\ coords={'time':ds[Tname].coords['time'].values,\ 'ntemp':numpy.arange(len(self.temperatures))},\ attrs={"units": str(rad_u["si"])} ) start = True L = [] for t_fld in self.temperatures: t_fld = _tovs_defs.temperature_names.get(t_fld, t_fld) x = ds_temp["temperature_{:s}".format(t_fld)] for dim in set(x.dims) - {"time"}: x = x.mean(dim=dim, keep_attrs=True) newT = x.astype("f8") L.append(x.astype("f8")) # prevent X⁴ precision loss # # Convert Temperatures to radiance # Radspace = self.hirs.srfs[ch - 1].blackbody_radiance( ureg.Quantity(newT.values, ureg.K)) gd = (newT > 340.) if numpy.sum(gd) > 0: Radspace[gd] = numpy.nan if start: npRspace = numpy.zeros((1, len(Radspace)), dtype=numpy.float32) npRspaceout = numpy.zeros((1, len(Radspace)), dtype=numpy.float32) npRspace[0, :] = Radspace[:] start = False else: npRspaceout[0, :] = Radspace npRspace = numpy.append(npRspace, npRspaceout, axis=0) # Needed as predictors for T_outlier routines X = xarray.merge(L) Tname = "temperature_{:s}".format(self.temperatures[0]) R_space = UADA(npRspace,\ dims=['ntemp','time'],\ coords={'time':ds_temp[Tname].coords['time'].values,\ 'ntemp':numpy.arange(len(self.temperatures))},\ attrs={"units": str(rad_u["si"])} ) # # Get where different views are # views_space = xarray.DataArray(ds["scantype"].values == \ tovs.HIRS.typ_space, \ coords=ds["scantype"].coords) # views_iwct = xarray.DataArray(ds["scantype"].values == \ # tovs.HIRS.typ_iwt, \ # coords=ds["scantype"].coords) views_Earth = xarray.DataArray(ds["scantype"].values == \ tovs.HIRS.typ_Earth, \ coords=ds["scantype"].coords) # # Get counts # (counts_space, counts_iwct) = self.hirs.extract_calibcounts(ds, ch) # # Get gain/offset/non-linear etc. # (time, offset, slope, a2) = self.hirs.calculate_offset_and_slope(ds, ch, self.hirs.srfs[ch - 1]) if not numpy.array_equal(numpy.isfinite(offset), numpy.isfinite(slope)): raise ValueError("Expecting offset and slope to have same " "finite values, but I got disappointed.") # Check to see if we have a non-linear term try: a_2 = a2.values[0] except: a_2 = 0. # # Note use median here as Gerrit does - presumably for 'outlier' cases # sometimes seen in calibration views # slope_val = slope.median('scanpos').values # # Get Rself at calibration points based on previous gain # Rself_cal = numpy.zeros((counts_space.values.shape[0]-1,\ counts_space.values.shape[1])) for pos in range(counts_space.values.shape[1]): Rself_cal[:,pos]=slope_val[pos]*\ (counts_space.values[1:,pos]-counts_space.values[0:-1,pos])+\ a_2*(counts_space.values[1:,pos]**2-\ counts_space.values[0:-1,pos]**2) # # Gerrits code seems to use the median values so we will as well # Rself_med = numpy.zeros(Rself_cal.shape[0]) for pos in range(Rself_cal.shape[0]): Rself_med[pos] = numpy.median(Rself_cal[pos, :]) scanpos_time = ds["time"][views_Earth] Rearth = R.values[:, views_Earth] Rspace = R_space.values[:, :] # # Get combination of input Ts that mimic observed self # emission - used to estimate uncertainty, not Rself # itself # Note fit to delta radiances to radiances themselves # p_estRself, u_est, ok = self.estimate_Rself(Rspace, Rself_med) # # Loop round calibration cycle data to get Rself at earth view # locations # # Also # Estimate uncertainty from varition of other temperatures as # percentage from linear model and apply mean percentage to # Rself to get uncertainty # Rself = numpy.zeros(shape=(scanpos_time.coords["time"].size,), \ dtype="f4") uRself = numpy.zeros(shape=(scanpos_time.coords["time"].size,), \ dtype="f4") estR = numpy.zeros(shape=(scanpos_time.coords["time"].size,), \ dtype="f4") Rself.fill(numpy.nan) uRself.fill(numpy.nan) estR.fill(numpy.nan) for i in range(len(time.values) - 1): gd = (scanpos_time.values >= time.values[i]) & \ (scanpos_time.values <= time.values[i+1]) if numpy.sum(gd) > 0: dtime = self.get_np_seconds(time.values[i + 1] - time.values[i]) Rself_slope = Rself_med[i] / dtime dtime = self.get_np_seconds(scanpos_time.values[gd] - time.values[i]) Rself[gd] = Rself_slope * dtime if ok: T = Rearth[:, gd] Tdata = numpy.zeros(T.shape[1]) Tdata[:] = p_estRself[0] for j in range(len(p_estRself) - 1): Tdata = Tdata + p_estRself[j + 1] * (T[j, :] - Rspace[j, i]) estR[gd] = Tdata zero_point = Tdata[0] Tdata = Tdata - zero_point U_slope = (Tdata[Tdata.shape[0] - 1] - Tdata[0]) / Tdata.shape[0] Uval = numpy.zeros(Tdata.shape[0]) Uval[:] = 0. for j in range(Tdata.shape[0]): if numpy.all(numpy.isfinite(T[:, j])): # # Uncertainty is deviation from a straight line # Uval[j] = numpy.abs((Tdata[j] - U_slope * j)) else: Uval[j] == 0. uRself[gd] = Uval # # Set minimum uncertainty at that from fit of data # gd = (numpy.isfinite(uRself) & (uRself < u_est)) if numpy.sum(gd) > 0: uRself[gd] = u_est # Fill all lines with u_est if no calculation done gd = (~numpy.isfinite(Rself) | ~numpy.isfinite(uRself)) if numpy.sum(gd) > 0: uRself[gd] = u_est # a = numpy.zeros((len(Rself),8)) # a[:,0] = Rself # a[:,1] = uRself # a[:,2] = Rearth[0,:] # a[:,3] = Rearth[1,:] # a[:,4] = Rearth[2,:] # a[:,5] = Rearth[3,:] # a[:,6] = Rearth[4,:] # a[:,7] = estR[:] # numpy.savetxt('test.dat',a) Y_pred = UADA(Rself,\ coords=scanpos_time.coords["time"].coords, \ attrs={"units": str(rad_u["si"])}) # # Force u_Rself to be a single value as this is what # Gerrits code expects unfortunately - subsequent code # seems hardwired to this assumption... # uRself = numpy.mean(uRself) rself_time = [scanpos_time.values[0],\ scanpos_time.values[len(scanpos_time.values)-1]] return rself_time, X, Y_pred, uRself