def calibrate_rad_to_bt(self, radiance, key): """IR channel calibration.""" # using the method from PUG section Converting from Effective Radiance to Brightness Temperature for IR Channels measured = self.get_channel_measured_group_path(key['name']) vc = self[measured + "/radiance_to_bt_conversion_coefficient_wavenumber"] a = self[measured + "/radiance_to_bt_conversion_coefficient_a"] b = self[measured + "/radiance_to_bt_conversion_coefficient_b"] c1 = self[measured + "/radiance_to_bt_conversion_constant_c1"] c2 = self[measured + "/radiance_to_bt_conversion_constant_c2"] for v in (vc, a, b, c1, c2): if v == v.attrs.get("FillValue", default_fillvals.get(v.dtype.str[1:])): logger.error("{:s} set to fill value, cannot produce " "brightness temperatures for {:s}.".format( v.attrs.get( "long_name", "at least one necessary coefficient"), measured)) return radiance * np.nan nom = c2 * vc denom = a * np.log(1 + (c1 * vc**3) / radiance) res = nom / denom - b / a return res
def _ir_calibrate(self, radiance, measured, root): """IR channel calibration.""" coef = self[measured + "/radiance_unit_conversion_coefficient"] wl_c = self[root + "/central_wavelength_actual"] a = self[measured + "/radiance_to_bt_conversion_coefficient_a"] b = self[measured + "/radiance_to_bt_conversion_coefficient_b"] c1 = self[measured + "/radiance_to_bt_conversion_constant_c1"] c2 = self[measured + "/radiance_to_bt_conversion_constant_c2"] for v in (coef, wl_c, a, b, c1, c2): if v == v.attrs.get("FillValue", default_fillvals.get(v.dtype.str[1:])): logger.error("{:s} set to fill value, cannot produce " "brightness temperatures for {:s}.".format( v.attrs.get( "long_name", "at least one necessary coefficient"), root)) return xr.DataArray(da.full(shape=radiance.shape, chunks=radiance.chunks, fill_value=np.nan), dims=radiance.dims, coords=radiance.coords, attrs=radiance.attrs) Lv = radiance * coef vc = 1e6 / wl_c # from wl in um to wn in m^-1 nom = c2 * vc denom = a * np.log(1 + (c1 * vc**3) / Lv) res = nom / denom - b / a res.attrs["units"] = "K" return res
def calibrate_rad_to_refl(self, radiance, key): """VIS channel calibration.""" measured = self.get_channel_measured_group_path(key['name']) cesi = self[measured + "/channel_effective_solar_irradiance"] if cesi == cesi.attrs.get("FillValue", default_fillvals.get(cesi.dtype.str[1:])): logger.error( "channel effective solar irradiance set to fill value, " "cannot produce reflectance for {:s}.".format(measured)) return radiance * np.nan sun_earth_distance = np.mean( self["state/celestial/earth_sun_distance"]) / 149597870.7 # [AU] # TODO remove this check when old versions of IDPF test data (<v5) are deprecated. if sun_earth_distance < 0.9 or sun_earth_distance > 1.1: logger.info( 'The variable state/celestial/earth_sun_distance contains unexpected values' '(mean value is {} AU). Defaulting to 1 AU for reflectance calculation.' ''.format(sun_earth_distance)) sun_earth_distance = 1 res = 100 * radiance * np.pi * sun_earth_distance**2 / cesi return res
def _get_aux_data_lut_vector(self, aux_data_name): """Load the lut vector of an auxiliary variable.""" lut = self[AUX_DATA[aux_data_name]] fv = default_fillvals.get(lut.dtype.str[1:], np.nan) lut = lut.where(lut != fv) return lut
def get_dataset(self, key, info=None): """Load a dataset.""" logger.debug('Reading {} from {}'.format(key.name, self.filename)) # Get the dataset # Get metadata for given dataset measured, root = self.get_channel_dataset(key.name) radlab = measured + "/effective_radiance" data = self[radlab] attrs = data.attrs.copy() info = info.copy() fv = attrs.pop("FillValue", default_fillvals.get(data.dtype.str[1:], np.nan)) vr = attrs.pop("valid_range", [-np.inf, np.inf]) if key.calibration == "counts": attrs["_FillValue"] = fv nfv = fv else: nfv = np.nan data = data.where(data >= vr[0], nfv) data = data.where(data <= vr[1], nfv) if key.calibration == "counts": # from package description, this just means not applying add_offset # and scale_factor attrs.pop("scale_factor") attrs.pop("add_offset") data.attrs["units"] = "1" res = data else: data = (data * attrs.pop("scale_factor", 1) + attrs.pop("add_offset", 0)) if key.calibration in ("brightness_temperature", "reflectance"): res = self.calibrate(data, key, measured, root) else: res = data data.attrs["units"] = attrs["units"] # pre-calibration units no longer apply info.pop("units") attrs.pop("units") self.nlines, self.ncols = res.shape res.attrs.update(key.to_dict()) res.attrs.update(info) res.attrs.update(attrs) return res
def get_dataset(self, key, info=None): """Load a dataset.""" logger.debug('Reading {} from {}'.format(key.name, self.filename)) # Get the dataset # Get metadata for given dataset measured = self.get_channel_measured_group_path(key.name) data = self[measured + "/effective_radiance"] attrs = data.attrs.copy() info = info.copy() fv = attrs.pop("FillValue", default_fillvals.get(data.dtype.str[1:], np.nan)) vr = attrs.get("valid_range", [-np.inf, np.inf]) if key.calibration == "counts": attrs["_FillValue"] = fv nfv = fv else: nfv = np.nan data = data.where(data >= vr[0], nfv) data = data.where(data <= vr[1], nfv) res = self.calibrate(data, key) # pre-calibration units no longer apply info.pop("units") attrs.pop("units") res.attrs.update(key.to_dict()) res.attrs.update(info) res.attrs.update(attrs) res.attrs["platform_name"] = self._platform_name_translate.get( self["/attr/platform"], self["/attr/platform"]) # remove unpacking parameters for calibrated data if key.calibration in ['brightness_temperature', 'reflectance']: res.attrs.pop("add_offset") res.attrs.pop("warm_add_offset") res.attrs.pop("scale_factor") res.attrs.pop("warm_scale_factor") # remove attributes from original file which don't apply anymore res.attrs.pop('long_name') return res
def calibrate_rad_to_refl(self, radiance, key): """VIS channel calibration.""" measured = self.get_channel_measured_group_path(key['name']) cesi = self[measured + "/channel_effective_solar_irradiance"] if cesi == cesi.attrs.get("FillValue", default_fillvals.get(cesi.dtype.str[1:])): logger.error( "channel effective solar irradiance set to fill value, " "cannot produce reflectance for {:s}.".format(measured)) return radiance * np.nan sun_earth_distance = np.mean( self["state/celestial/earth_sun_distance"]) / 149597870.7 # [AU] res = 100 * radiance * np.pi * sun_earth_distance**2 / cesi return res
def _vis_calibrate(self, radiance, measured): """VIS channel calibration.""" # radiance to reflectance taken as in mipp/xrit/MSG.py # again FCI User Guide is not clear on how to do this cesilab = measured + "/channel_effective_solar_irradiance" cesi = self[cesilab] if cesi == cesi.attrs.get("FillValue", default_fillvals.get(cesi.dtype.str[1:])): logger.error( "channel effective solar irradiance set to fill value, " "cannot produce reflectance for {:s}.".format(measured)) return xr.DataArray(da.full(shape=radiance.shape, chunks=radiance.chunks, fill_value=np.nan), dims=radiance.dims, coords=radiance.coords, attrs=radiance.attrs) sirr = float(cesi) res = radiance / sirr * 100 res.attrs["units"] = "%" return res
def _get_dataset_measurand(self, key, info=None): """Load dataset corresponding to channel measurement. Load a dataset when the key refers to a measurand, whether uncalibrated (counts) or calibrated in terms of brightness temperature, radiance, or reflectance. """ # Get the dataset # Get metadata for given dataset measured = self.get_channel_measured_group_path(key['name']) data = self[measured + "/effective_radiance"] attrs = data.attrs.copy() info = info.copy() fv = attrs.pop("FillValue", default_fillvals.get(data.dtype.str[1:], np.nan)) vr = attrs.get("valid_range", [-np.inf, np.inf]) if key['calibration'] == "counts": attrs["_FillValue"] = fv nfv = fv else: nfv = np.nan data = data.where(data >= vr[0], nfv) data = data.where(data <= vr[1], nfv) res = self.calibrate(data, key) # pre-calibration units no longer apply attrs.pop("units") # For each channel, the effective_radiance contains in the # "ancillary_variables" attribute the value "pixel_quality". In # FileYAMLReader._load_ancillary_variables, satpy will try to load # "pixel_quality" but is lacking the context from what group to load # it: in the FCI format, each channel group (data/<channel>/measured) has # its own data variable 'pixel_quality'. # Until we can have multiple pixel_quality variables defined (for # example, with https://github.com/pytroll/satpy/pull/1088), rewrite # the ancillary variable to include the channel. See also # https://github.com/pytroll/satpy/issues/1171. if "pixel_quality" in attrs["ancillary_variables"]: attrs["ancillary_variables"] = attrs[ "ancillary_variables"].replace("pixel_quality", key['name'] + "_pixel_quality") else: raise ValueError( "Unexpected value for attribute ancillary_variables, " "which the FCI file handler intends to rewrite (see " "https://github.com/pytroll/satpy/issues/1171 for why). " f"Expected 'pixel_quality', got {attrs['ancillary_variables']:s}" ) res.attrs.update(key.to_dict()) res.attrs.update(info) res.attrs.update(attrs) res.attrs["platform_name"] = self._platform_name_translate.get( self["/attr/platform"], self["/attr/platform"]) # remove unpacking parameters for calibrated data if key['calibration'] in ['brightness_temperature', 'reflectance']: res.attrs.pop("add_offset") res.attrs.pop("warm_add_offset") res.attrs.pop("scale_factor") res.attrs.pop("warm_scale_factor") # remove attributes from original file which don't apply anymore res.attrs.pop('long_name') return res