def save(data, grid_def, filename, ninjo_product_name=None, **kwargs): """MPOP's interface to Ninjo TIFF writer. Temporary work around until mpop is fully integrated. :Parameters: geo_image : mpop.imageo.geo_image.GeoImage See MPOP's documentation. filename : str The name of the TIFF file to be created :Keywords: ninjo_product_name : str Optional index to Ninjo configuration file. kwargs : dict See _write """ data = clip_to_data_type(data, DTYPE_UINT8) area_def = FakeAreaDef(grid_def) # Some Ninjo tiff names kwargs['gradient'], kwargs['axis_intercept'] = dkind2grad[ kwargs["data_kind"]] kwargs['physic_unit'] = dkind2mpop_physical[kwargs["data_kind"]] kwargs['image_dt'] = kwargs.pop("begin_time") kwargs['transparent_pix'] = kwargs.pop("fill_value") kwargs['is_calibrated'] = True kwargs['ninjo_product_name'] = kwargs.pop("product_name") # reorder data to (Y, X, 3) from (3, Y, X) if data.ndim > 2: data = numpy.dstack(data) ninjo_write(data, filename, area_def, ninjo_product_name, **kwargs)
def create_awips2_netcdf3(filename, image, start_dt, depictor_name, channel, source_name, satellite_name, **grid_info): nc_name = os.path.abspath(filename) nc = Dataset(nc_name, mode='w', format="NETCDF3_CLASSIC") y_dim = nc.createDimension("y", size=image.shape[0]) x_dim = nc.createDimension("x", size=image.shape[1]) time_var = nc.createVariable("validTime", "f8") time_var.units = "seconds since 1970-1-1 00:00:00.00 0:00" time_var[:] = float(calendar.timegm( start_dt.utctimetuple())) + float(start_dt.microsecond) / 1e6 image_var = nc.createVariable("image", "i1", ("y", "x")) image_var.set_auto_maskandscale(False) image_var[:] = clip_to_data_type(image, DTYPE_UINT8) nc.depictorName = depictor_name nc.channel = channel nc.source = source_name nc.satelliteName = satellite_name for k, v in grid_info.items(): attr_name = GRID_ATTR_NAME.get(k, k) attr_type = GRID_ATTR_TYPE.get(k, None) LOG.debug("Setting grid information for NetCDF file: %s -> %s", attr_name, v) if attr_type is not None: v = attr_type(v) setattr(nc, attr_name, v) nc.sync() nc.close() LOG.debug("Data transferred into NC file correctly")
def _prep_data(self, data: xr.DataArray, dtype: np.dtype, fill_value) -> da.Array: fill = data.attrs.get("_FillValue", np.nan) if fill_value is None: fill_value = fill final_data = data.data if self.enhancer and np.issubdtype( data.dtype, np.floating) and not np.issubdtype(dtype, np.floating): # going from float -> int and the data was enhanced # scale the data to fit the integer dtype rmin, rmax = np.iinfo(dtype).min, np.iinfo(dtype).max final_data = final_data * (rmax - rmin) + rmin final_data = clip_to_data_type(final_data, dtype) same_fill = np.isnan(fill) and np.isnan( fill_value) or fill == fill_value if data.dtype == dtype and same_fill: return final_data final_data = final_data.astype(dtype) if same_fill: return final_data fill_mask = np.isnan(final_data) if np.isnan( fill) else final_data == fill final_data = da.where(fill_mask, fill_value, final_data) return final_data
def create_awips2_netcdf3(filename, image, start_dt, depictor_name, channel, source_name, satellite_name, **grid_info): nc_name = os.path.abspath(filename) nc = Dataset(nc_name, mode='w', format="NETCDF3_CLASSIC") y_dim = nc.createDimension("y", size=image.shape[0]) x_dim = nc.createDimension("x", size=image.shape[1]) time_var = nc.createVariable("validTime", "f8") time_var.units = "seconds since 1970-1-1 00:00:00.00 0:00" time_var[:] = float(calendar.timegm(start_dt.utctimetuple())) + float(start_dt.microsecond)/1e6 image_var = nc.createVariable("image", "i1", ("y", "x")) image_var.set_auto_maskandscale(False) image_var[:] = clip_to_data_type(image, DTYPE_UINT8) nc.depictorName = depictor_name nc.channel = channel nc.source = source_name nc.satelliteName = satellite_name for k, v in grid_info.items(): attr_name = GRID_ATTR_NAME.get(k, k) attr_type = GRID_ATTR_TYPE.get(k, None) LOG.debug("Setting grid information for NetCDF file: %s -> %s", attr_name, v) if attr_type is not None: v = attr_type(v) setattr(nc, attr_name, v) nc.sync() nc.close() LOG.debug("Data transferred into NC file correctly")
def create_output_from_product(self, gridded_product, output_pattern=None, data_type=None, inc_by_one=None, fill_value=None, **kwargs): inc_by_one = inc_by_one or False data_type = data_type or gridded_product["data_type"] fill_value = fill_value or gridded_product["fill_value"] same_fill = numpy.isnan(fill_value) and numpy.isnan(gridded_product["fill_value"]) or fill_value == gridded_product["fill_value"] grid_def = gridded_product["grid_definition"] if not output_pattern: output_pattern = DEFAULT_OUTPUT_PATTERN if "{" in output_pattern: # format the filename of_kwargs = gridded_product.copy(as_dict=True) of_kwargs["data_type"] = data_type output_filename = self.create_output_filename(output_pattern, grid_name=grid_def["grid_name"], rows=grid_def["height"], columns=grid_def["width"], **of_kwargs) else: output_filename = output_pattern if os.path.isfile(output_filename): if not self.overwrite_existing: LOG.error("Geotiff file already exists: %s", output_filename) raise RuntimeError("Geotiff file already exists: %s" % (output_filename,)) else: LOG.warning("Geotiff file already exists, will overwrite: %s", output_filename) # if we have a floating point data type, then scaling doesn't make much sense if data_type == gridded_product["data_type"] and same_fill: LOG.info("Saving product %s to binary file %s", gridded_product["product_name"], output_filename) shutil.copyfile(gridded_product["grid_data"], output_filename) return output_filename elif numpy.issubclass_(data_type, numpy.floating): # we didn't rescale any data, but we need to convert it data = gridded_product.get_data_array() else: try: LOG.debug("Scaling %s data to fit data type", gridded_product["product_name"]) data = self.rescaler.rescale_product(gridded_product, data_type, inc_by_one=inc_by_one, fill_value=fill_value) data = clip_to_data_type(data, data_type) except ValueError: if not self.keep_intermediate and os.path.isfile(output_filename): os.remove(output_filename) raise LOG.info("Saving product %s to binary file %s", gridded_product["product_name"], output_filename) data = data.astype(data_type) fill_mask = gridded_product.get_data_mask() data[fill_mask] = fill_value data.tofile(output_filename) return output_filename
def create_output_from_product(self, gridded_product, output_pattern=None, data_type=None, inc_by_one=None, fill_value=None, **kwargs): inc_by_one = inc_by_one or False data_type = data_type or gridded_product["data_type"] fill_value = fill_value or gridded_product["fill_value"] same_fill = numpy.isnan(fill_value) and numpy.isnan(gridded_product["fill_value"]) or fill_value == gridded_product["fill_value"] grid_def = gridded_product["grid_definition"] if not output_pattern: output_pattern = DEFAULT_OUTPUT_PATTERN if "{" in output_pattern: # format the filename of_kwargs = gridded_product.copy(as_dict=True) of_kwargs["data_type"] = data_type output_filename = self.create_output_filename(output_pattern, grid_name=grid_def["grid_name"], rows=grid_def["height"], columns=grid_def["width"], **of_kwargs) else: output_filename = output_pattern if os.path.isfile(output_filename): if not self.overwrite_existing: LOG.error("Geotiff file already exists: %s", output_filename) raise RuntimeError("Geotiff file already exists: %s" % (output_filename,)) else: LOG.warning("Geotiff file already exists, will overwrite: %s", output_filename) # if we have a floating point data type, then scaling doesn't make much sense if data_type == gridded_product["data_type"] and same_fill: LOG.info("Saving product %s to binary file %s", gridded_product["product_name"], output_filename) shutil.copyfile(gridded_product["grid_data"], output_filename) return output_filename elif numpy.issubclass_(data_type, numpy.floating): # we didn't rescale any data, but we need to convert it data = gridded_product.get_data_array() else: try: LOG.debug("Scaling %s data to fit data type") data = self.rescaler.rescale_product(gridded_product, data_type, inc_by_one=inc_by_one, fill_value=fill_value) data = clip_to_data_type(data, data_type) except StandardError: if not self.keep_intermediate and os.path.isfile(output_filename): os.remove(output_filename) raise LOG.info("Saving product %s to binary file %s", gridded_product["product_name"], output_filename) data = data.astype(data_type) fill_mask = gridded_product.get_data_mask() data[fill_mask] = fill_value data.tofile(output_filename) return output_filename
def create_ninjo_tiff(image_data, output_fn, **kwargs): """Create a NinJo compatible TIFF file with the tags used by the DWD's version of NinJo. Also stores the image as tiles on disk and creates a multi-resolution/pyramid/overview set of images (deresolution: 2,4,8,16). :Parameters: image_data : 2D numpy array Satellite image data to be put into the NinJo compatible tiff output_fn : str The name of the TIFF file to be created :Keywords: data_kind : int polar2grid constant describing the sensor type of the image data, such as DKIND_REFLECTANCE or DKIND_BTEMP. This is optional, but if not specified then certain keywords below are required. If it is specified then a default can be determined for some of the keywords (such as `physic_value`). cmap : tuple/list of 3 lists of uint16's Individual RGB arrays describing the color value for the corresponding data value. For example, image data with a data type of unsigned 8-bit integers have 256 possible values (0-255). So each list in cmap will have 256 values ranging from 0 to 65535 (2**16 - 1). (default linear B&W colormap) sat_id : int DWD NinJo Satellite ID number chan_id : int DWD NinJo Satellite Channel ID number data_source : str String describing where the data came from (SSEC, EUMCAST) tile_width : int Width of tiles on disk (default 512) tile_length : int Length of tiles on disk (default 512) data_cat : str NinJo specific data category - data_cat[0] = P (polar) or G (geostat) - data_cat[1] = O (original) or P (product) - data_cat[2:4] = RN or RB or RA or RN or AN (Raster, Bufr, ASCII, NIL) Example: 'P**N' or 'GORN' or 'GPRN' or 'PPRN' (default 'P**N') pixel_xres : float Nadir view pixel resolution in degrees longitude pixel_yres : float Nadir view pixel resolution in degrees latitude origin_lat : float Top left corner latitude origin_lon : float Top left corner longitude image_dt : datetime object Python datetime object describing the date and time of the image data provided in UTC projection : str NinJo compatible projection name (NPOL,PLAT,etc.) meridian_west : float Western image border (default 0.0) meridian_east : float Eastern image border (default 0.0) radius_a : float Large/equatorial radius of the earth (default <not written>) radius_b : float Small/polar radius of the earth (default <not written>) ref_lat1 : float Reference latitude 1 (default <not written>) ref_lat2 : float Reference latitude 2 (default <not written>) central_meridian : float Central Meridian (default <not written>) physic_value : str Physical value type. Examples: - Temperature = 'T' - Albedo = 'ALBEDO' Defaults to appropriate value based on `data_kind`, see `itype2physical` Specifying this overrides the default of `itype2physical`. If `data_kind` is not specified then this keyword is required. physic_unit : str Physical value units. Examples: - 'CELSIUS' - '%' Defaults to appropriate value based on `data_kind`, see `itype2physical` Specifying this overrides the default of `itype2physical`. If `data_kind` is not specified then this keyword is required. min_gray_val : int Minimum gray value (default 0) max_gray_val : int Maximum gray value (default 255) gradient : float Gradient/Slope Defaults to appropriate value based on `data_kind`, see `itype2grad` Specifying this overrides the default of `itype2grad`. If `data_kind` is not specified then this keyword is required. axis_intercept : float Axis Intercept Defaults to appropriate value based on `data_kind`, see `itype2grad` Specifying this overrides the default of `itype2grad`. If `data_kind` is not specified then this keyword is required. altitude : float Altitude of the data provided (default 0.0) is_atmo_corrected : bool Is the data atmosphere corrected? (True/1 for yes) (default False/0) is_calibrated : bool Is the data calibrated? (True/1 for yes) (default False/0) is_normalized : bool Is the data normalized (True/1 for yes) (default False/0) description : str Description string to be placed in the output TIFF (optional) :Raises: KeyError : if required keyword is not provided """ LOG.debug("Creating NinJo TIFF file '%s'" % (output_fn, )) out_tiff = TIFF.open(output_fn, "w") image_data = clip_to_data_type(image_data, DTYPE_UINT8) # Extract keyword arguments data_kind = kwargs.pop("data_kind", None) # called as a backend if data_kind is not None and (data_kind not in dkind2physical or data_kind not in dkind2grad): # Must do the check here since it matters when pulling out physic value LOG.warning( "'data_kind' is not known to the ninjo tiff creator, it will be ignored" ) data_kind = None cmap = kwargs.pop("cmap", None) sat_id = int(kwargs.pop("sat_id")) chan_id = int(kwargs.pop("chan_id")) data_source = str(kwargs.pop("data_source")) tile_width = int(kwargs.pop("tile_width", 512)) tile_length = int(kwargs.pop("tile_length", 512)) data_cat = str(kwargs.pop("data_cat", "P**N")) pixel_xres = float(kwargs.pop("pixel_xres")) pixel_yres = float(kwargs.pop("pixel_yres")) origin_lat = float(kwargs.pop("origin_lat")) origin_lon = float(kwargs.pop("origin_lon")) image_dt = kwargs.pop("image_dt") projection = kwargs.pop("projection") meridian_west = float(kwargs.pop("meridian_west", 0.0)) meridian_east = float(kwargs.pop("meridian_east", 0.0)) radius_a = kwargs.pop("radius_a", None) radius_b = kwargs.pop("radius_b", None) ref_lat1 = kwargs.pop("ref_lat1", None) ref_lat2 = kwargs.pop("ref_lat2", None) central_meridian = kwargs.pop("central_meridian", None) min_gray_val = int(kwargs.pop("min_gray_val", 0)) max_gray_val = int(kwargs.pop("max_gray_val", 255)) altitude = float(kwargs.pop("altitude", 0.0)) is_atmo_corrected = int(bool(kwargs.pop("is_atmo_corrected", 0))) is_calibrated = int(bool(kwargs.pop("is_calibrated", 0))) is_normalized = int(bool(kwargs.pop("is_normalized", 0))) description = kwargs.pop("description", None) # Special cases if data_kind is not None: physic_value, physic_unit = dkind2physical[data_kind] gradient, axis_intercept = dkind2grad[data_kind] physic_value = kwargs.pop("physic_value", physic_value) physic_unit = kwargs.pop("physic_unit", physic_unit) gradient = float(kwargs.pop("gradient", gradient)) axis_intercept = float(kwargs.pop("axis_intercept", axis_intercept)) else: physic_value = kwargs.pop("physic_value") physic_unit = kwargs.pop("physic_unit") gradient = float(kwargs.pop("gradient")) axis_intercept = float(kwargs.pop("axis_intercept")) # Keyword checks / verification if cmap is None: if data_kind == "brightness_temperature": cmap = get_default_lw_colortable() else: cmap = get_default_sw_colortable() elif len(cmap) != 3: LOG.error("Colormap (cmap) must be a list of 3 lists (RGB), not %d" % len(cmap)) if len(data_cat) != 4: LOG.error("NinJo data type must be 4 characters") raise ValueError("NinJo data type must be 4 characters") if data_cat[0] not in ["P", "G"]: LOG.error( "NinJo data type's first character must be 'P' or 'G' not '%s'" % data_cat[0]) raise ValueError( "NinJo data type's first character must be 'P' or 'G' not '%s'" % data_cat[0]) if data_cat[1] not in ["O", "P"]: LOG.error( "NinJo data type's second character must be 'O' or 'P' not '%s'" % data_cat[1]) raise ValueError( "NinJo data type's second character must be 'O' or 'P' not '%s'" % data_cat[1]) if data_cat[2:4] not in ["RN", "RB", "RA", "BN", "AN"]: LOG.error( "NinJo data type's last 2 characters must be one of %s not '%s'" % ("['RN','RB','RA','BN','AN']", data_cat[2:4])) raise ValueError( "NinJo data type's last 2 characters must be one of %s not '%s'" % ("['RN','RB','RA','BN','AN']", data_cat[2:4])) if description is not None and len(description) >= 1000: LOG.error("NinJo description must be less than 1000 characters") raise ValueError("NinJo description must be less than 1000 characters") file_dt = datetime.utcnow() file_epoch = calendar.timegm(file_dt.timetuple()) image_epoch = calendar.timegm(image_dt.timetuple()) def _write_oneres(image_data, pixel_xres, pixel_yres, subfile=False): LOG.debug("Writing tag data for a resolution of the output file '%s'" % (output_fn, )) ### Write Tag Data ### # Built ins out_tiff.SetField("ImageWidth", image_data.shape[1]) out_tiff.SetField("ImageLength", image_data.shape[0]) out_tiff.SetField("BitsPerSample", 8) out_tiff.SetField("Compression", libtiff.COMPRESSION_LZW) out_tiff.SetField("Photometric", libtiff.PHOTOMETRIC_PALETTE) out_tiff.SetField("Orientation", libtiff.ORIENTATION_TOPLEFT) out_tiff.SetField("SamplesPerPixel", 1) out_tiff.SetField("SMinSampleValue", 0) out_tiff.SetField("SMaxsampleValue", 255) out_tiff.SetField("PlanarConfig", libtiff.PLANARCONFIG_CONTIG) out_tiff.SetField("ColorMap", cmap) # Basic B&W colormap out_tiff.SetField("TileWidth", tile_width) out_tiff.SetField("TileLength", tile_length) out_tiff.SetField("SampleFormat", libtiff.SAMPLEFORMAT_UINT) # NinJo specific tags if description is not None: out_tiff.SetField("Description", description) out_tiff.SetField("ModelPixelScale", [pixel_xres, pixel_yres]) out_tiff.SetField("ModelTiePoint", [0.0, 0.0, 0.0, origin_lon, origin_lat, 0.0]) out_tiff.SetField("NinjoName", "NINJO") out_tiff.SetField("SatelliteNameID", sat_id) out_tiff.SetField("DateID", image_epoch) out_tiff.SetField("CreationDateID", file_epoch) out_tiff.SetField("ChannelID", chan_id) out_tiff.SetField("HeaderVersion", 2) out_tiff.SetField("FileName", output_fn) out_tiff.SetField("DataType", data_cat) out_tiff.SetField("SatelliteNumber", "\x00") # Hardcoded to 0 out_tiff.SetField("ColorDepth", 8) # Hardcoded to 8 out_tiff.SetField("DataSource", data_source) out_tiff.SetField("XMinimum", 1) out_tiff.SetField("XMaximum", image_data.shape[1]) out_tiff.SetField("YMinimum", 1) out_tiff.SetField("YMaximum", image_data.shape[0]) out_tiff.SetField("Projection", projection) out_tiff.SetField("MeridianWest", meridian_west) out_tiff.SetField("MeridianEast", meridian_east) if radius_a is not None: out_tiff.SetField("EarthRadiusLarge", float(radius_a)) if radius_b is not None: out_tiff.SetField("EarthRadiusSmall", float(radius_b)) out_tiff.SetField("GeodeticDate", "\x00") # ---? if ref_lat1 is not None: out_tiff.SetField("ReferenceLatitude1", ref_lat1) if ref_lat2 is not None: out_tiff.SetField("ReferenceLatitude2", ref_lat2) if central_meridian is not None: out_tiff.SetField("CentralMeridian", central_meridian) out_tiff.SetField("PhysicValue", physic_value) out_tiff.SetField("PhysicUnit", physic_unit) out_tiff.SetField("MinGrayValue", min_gray_val) out_tiff.SetField("MaxGrayValue", max_gray_val) out_tiff.SetField("Gradient", gradient) out_tiff.SetField("AxisIntercept", axis_intercept) out_tiff.SetField("Altitude", altitude) out_tiff.SetField("IsAtmosphereCorrected", is_atmo_corrected) out_tiff.SetField("IsCalibrated", is_calibrated) out_tiff.SetField("IsNormalized", is_normalized) ### Write Base Data Image ### out_tiff.write_tiles(image_data) out_tiff.WriteDirectory() ### Write multi-resolution overviews ### out_tiff.SetDirectory(0) _write_oneres(image_data, pixel_xres, pixel_yres) out_tiff.SetDirectory(1) _write_oneres(image_data[::2, ::2], pixel_xres * 2, pixel_yres * 2) out_tiff.SetDirectory(2) _write_oneres(image_data[::4, ::4], pixel_xres * 4, pixel_yres * 4) out_tiff.SetDirectory(3) _write_oneres(image_data[::8, ::8], pixel_xres * 8, pixel_yres * 8) out_tiff.SetDirectory(4) _write_oneres(image_data[::16, ::16], pixel_xres * 16, pixel_yres * 16) out_tiff.close() LOG.info("Successfully created a NinJo tiff file: '%s'" % (output_fn, )) return
def create_geotiff(data, output_filename, proj4_str, geotransform, etype=gdal.GDT_UInt16, compress=None, quicklook=False, tiled=False, blockxsize=None, blockysize=None, **kwargs): """Function that creates a geotiff from the information provided. """ log_level = logging.getLogger('').handlers[0].level or 0 LOG.info("Creating geotiff '%s'" % (output_filename,)) # Find the number of bands provided if isinstance(data, (list, tuple)): num_bands = len(data) elif len(data.shape) == 2: num_bands = 1 else: num_bands = data.shape[0] # We only know how to handle gray scale, RGB, and RGBA if num_bands not in [1, 3, 4]: msg = "Geotiff backend doesn't know how to handle data of shape '%r'" % (data.shape,) LOG.error(msg) raise ValueError(msg) options = [] if num_bands == 1: options.append("PHOTOMETRIC=MINISBLACK") elif num_bands == 3: options.append("PHOTOMETRIC=RGB") elif num_bands == 4: options.append("PHOTOMETRIC=RGB") if compress is not None and compress != "NONE": options.append("COMPRESS=%s" % (compress,)) if tiled: options.append("TILED=YES") if blockxsize is not None: options.append("BLOCKXSIZE=%d" % (blockxsize,)) if blockysize is not None: options.append("BLOCKYSIZE=%d" % (blockysize,)) # Creating the file will truncate any pre-existing file LOG.debug("Creation Geotiff with options %r", options) if num_bands == 1: gtiff = gtiff_driver.Create(output_filename, data.shape[1], data.shape[0], bands=num_bands, eType=etype, options=options) else: gtiff = gtiff_driver.Create(output_filename, data[0].shape[1], data[0].shape[0], bands=num_bands, eType=etype, options=options) gtiff.SetGeoTransform(geotransform) srs = _proj4_to_srs(proj4_str) gtiff.SetProjection(srs.ExportToWkt()) for idx in range(num_bands): gtiff_band = gtiff.GetRasterBand(idx + 1) if num_bands == 1: band_data = data else: band_data = data[idx] # Clip data to datatype, otherwise let it go and see what happens # XXX: This might need to operate on colors as a whole or # do a linear scaling. No one should be scaling data to outside these # ranges anyway if etype == gdal.GDT_UInt16: band_data = clip_to_data_type(band_data, np.uint16) elif etype == gdal.GDT_Byte: band_data = clip_to_data_type(band_data, np.uint8) if log_level <= logging.DEBUG: LOG.debug("Data min: %f, max: %f" % (band_data.min(), band_data.max())) # Write the data if gtiff_band.WriteArray(band_data) != 0: LOG.error("Could not write band 1 data to geotiff '%s'" % (output_filename,)) raise ValueError("Could not write band 1 data to geotiff '%s'" % (output_filename,)) if quicklook: png_filename = output_filename.replace(os.path.splitext(output_filename)[1], ".png") png_driver = gdal.GetDriverByName("PNG") png_driver.CreateCopy(png_filename, gtiff) # Garbage collection/destructor should close the file properly return gtiff
def create_ninjo_tiff(image_data, output_fn, **kwargs): """Create a NinJo compatible TIFF file with the tags used by the DWD's version of NinJo. Also stores the image as tiles on disk and creates a multi-resolution/pyramid/overview set of images (deresolution: 2,4,8,16). :Parameters: image_data : 2D numpy array Satellite image data to be put into the NinJo compatible tiff output_fn : str The name of the TIFF file to be created :Keywords: data_kind : int polar2grid constant describing the sensor type of the image data, such as DKIND_REFLECTANCE or DKIND_BTEMP. This is optional, but if not specified then certain keywords below are required. If it is specified then a default can be determined for some of the keywords (such as `physic_value`). cmap : tuple/list of 3 lists of uint16's Individual RGB arrays describing the color value for the corresponding data value. For example, image data with a data type of unsigned 8-bit integers have 256 possible values (0-255). So each list in cmap will have 256 values ranging from 0 to 65535 (2**16 - 1). (default linear B&W colormap) sat_id : int DWD NinJo Satellite ID number chan_id : int DWD NinJo Satellite Channel ID number data_source : str String describing where the data came from (SSEC, EUMCAST) tile_width : int Width of tiles on disk (default 512) tile_length : int Length of tiles on disk (default 512) data_cat : str NinJo specific data category - data_cat[0] = P (polar) or G (geostat) - data_cat[1] = O (original) or P (product) - data_cat[2:4] = RN or RB or RA or RN or AN (Raster, Bufr, ASCII, NIL) Example: 'P**N' or 'GORN' or 'GPRN' or 'PPRN' (default 'P**N') pixel_xres : float Nadir view pixel resolution in degrees longitude pixel_yres : float Nadir view pixel resolution in degrees latitude origin_lat : float Top left corner latitude origin_lon : float Top left corner longitude image_dt : datetime object Python datetime object describing the date and time of the image data provided in UTC projection : str NinJo compatible projection name (NPOL,PLAT,etc.) meridian_west : float Western image border (default 0.0) meridian_east : float Eastern image border (default 0.0) radius_a : float Large/equatorial radius of the earth (default <not written>) radius_b : float Small/polar radius of the earth (default <not written>) ref_lat1 : float Reference latitude 1 (default <not written>) ref_lat2 : float Reference latitude 2 (default <not written>) central_meridian : float Central Meridian (default <not written>) physic_value : str Physical value type. Examples: - Temperature = 'T' - Albedo = 'ALBEDO' Defaults to appropriate value based on `data_kind`, see `itype2physical` Specifying this overrides the default of `itype2physical`. If `data_kind` is not specified then this keyword is required. physic_unit : str Physical value units. Examples: - 'CELSIUS' - '%' Defaults to appropriate value based on `data_kind`, see `itype2physical` Specifying this overrides the default of `itype2physical`. If `data_kind` is not specified then this keyword is required. min_gray_val : int Minimum gray value (default 0) max_gray_val : int Maximum gray value (default 255) gradient : float Gradient/Slope Defaults to appropriate value based on `data_kind`, see `itype2grad` Specifying this overrides the default of `itype2grad`. If `data_kind` is not specified then this keyword is required. axis_intercept : float Axis Intercept Defaults to appropriate value based on `data_kind`, see `itype2grad` Specifying this overrides the default of `itype2grad`. If `data_kind` is not specified then this keyword is required. altitude : float Altitude of the data provided (default 0.0) is_atmo_corrected : bool Is the data atmosphere corrected? (True/1 for yes) (default False/0) is_calibrated : bool Is the data calibrated? (True/1 for yes) (default False/0) is_normalized : bool Is the data normalized (True/1 for yes) (default False/0) description : str Description string to be placed in the output TIFF (optional) :Raises: KeyError : if required keyword is not provided """ LOG.debug("Creating NinJo TIFF file '%s'" % (output_fn,)) out_tiff = TIFF.open(output_fn, "w") image_data = clip_to_data_type(image_data, DTYPE_UINT8) # Extract keyword arguments data_kind = kwargs.pop("data_kind", None) # called as a backend if data_kind is not None and (data_kind not in dkind2physical or data_kind not in dkind2grad): # Must do the check here since it matters when pulling out physic value LOG.warning("'data_kind' is not known to the ninjo tiff creator, it will be ignored") data_kind = None cmap = kwargs.pop("cmap", None) sat_id = int(kwargs.pop("sat_id")) chan_id = int(kwargs.pop("chan_id")) data_source = str(kwargs.pop("data_source")) tile_width = int(kwargs.pop("tile_width", 512)) tile_length = int(kwargs.pop("tile_length", 512)) data_cat = str(kwargs.pop("data_cat", "P**N")) pixel_xres = float(kwargs.pop("pixel_xres")) pixel_yres = float(kwargs.pop("pixel_yres")) origin_lat = float(kwargs.pop("origin_lat")) origin_lon = float(kwargs.pop("origin_lon")) image_dt = kwargs.pop("image_dt") projection = kwargs.pop("projection") meridian_west = float(kwargs.pop("meridian_west", 0.0)) meridian_east = float(kwargs.pop("meridian_east", 0.0)) radius_a = kwargs.pop("radius_a", None) radius_b = kwargs.pop("radius_b", None) ref_lat1 = kwargs.pop("ref_lat1", None) ref_lat2 = kwargs.pop("ref_lat2", None) central_meridian = kwargs.pop("central_meridian", None) min_gray_val = int(kwargs.pop("min_gray_val", 0)) max_gray_val = int(kwargs.pop("max_gray_val", 255)) altitude = float(kwargs.pop("altitude", 0.0)) is_atmo_corrected = int(bool(kwargs.pop("is_atmo_corrected", 0))) is_calibrated = int(bool(kwargs.pop("is_calibrated", 0))) is_normalized = int(bool(kwargs.pop("is_normalized", 0))) description = kwargs.pop("description", None) # Special cases if data_kind is not None: physic_value,physic_unit = dkind2physical[data_kind] gradient,axis_intercept = dkind2grad[data_kind] physic_value = kwargs.pop("physic_value", physic_value) physic_unit = kwargs.pop("physic_unit", physic_unit) gradient = float(kwargs.pop("gradient", gradient)) axis_intercept = float(kwargs.pop("axis_intercept", axis_intercept)) else: physic_value = kwargs.pop("physic_value") physic_unit = kwargs.pop("physic_unit") gradient = float(kwargs.pop("gradient")) axis_intercept = float(kwargs.pop("axis_intercept")) # Keyword checks / verification if cmap is None: if data_kind == "brightness_temperature": cmap = get_default_lw_colortable() else: cmap = get_default_sw_colortable() elif len(cmap) != 3: LOG.error("Colormap (cmap) must be a list of 3 lists (RGB), not %d" % len(cmap)) if len(data_cat) != 4: LOG.error("NinJo data type must be 4 characters") raise ValueError("NinJo data type must be 4 characters") if data_cat[0] not in ["P", "G"]: LOG.error("NinJo data type's first character must be 'P' or 'G' not '%s'" % data_cat[0]) raise ValueError("NinJo data type's first character must be 'P' or 'G' not '%s'" % data_cat[0]) if data_cat[1] not in ["O", "P"]: LOG.error("NinJo data type's second character must be 'O' or 'P' not '%s'" % data_cat[1]) raise ValueError("NinJo data type's second character must be 'O' or 'P' not '%s'" % data_cat[1]) if data_cat[2:4] not in ["RN","RB","RA","BN","AN"]: LOG.error("NinJo data type's last 2 characters must be one of %s not '%s'" % ("['RN','RB','RA','BN','AN']", data_cat[2:4])) raise ValueError("NinJo data type's last 2 characters must be one of %s not '%s'" % ("['RN','RB','RA','BN','AN']", data_cat[2:4])) if description is not None and len(description) >= 1000: LOG.error("NinJo description must be less than 1000 characters") raise ValueError("NinJo description must be less than 1000 characters") file_dt = datetime.utcnow() file_epoch = calendar.timegm(file_dt.timetuple()) image_epoch = calendar.timegm(image_dt.timetuple()) def _write_oneres(image_data, pixel_xres, pixel_yres, subfile=False): LOG.debug("Writing tag data for a resolution of the output file '%s'" % (output_fn,)) ### Write Tag Data ### # Built ins out_tiff.SetField("ImageWidth", image_data.shape[1]) out_tiff.SetField("ImageLength", image_data.shape[0]) out_tiff.SetField("BitsPerSample", 8) out_tiff.SetField("Compression", libtiff.COMPRESSION_LZW) out_tiff.SetField("Photometric", libtiff.PHOTOMETRIC_PALETTE) out_tiff.SetField("Orientation", libtiff.ORIENTATION_TOPLEFT) out_tiff.SetField("SamplesPerPixel", 1) out_tiff.SetField("SMinSampleValue", 0) out_tiff.SetField("SMaxsampleValue", 255) out_tiff.SetField("PlanarConfig", libtiff.PLANARCONFIG_CONTIG) out_tiff.SetField("ColorMap", cmap) # Basic B&W colormap out_tiff.SetField("TileWidth", tile_width) out_tiff.SetField("TileLength", tile_length) out_tiff.SetField("SampleFormat", libtiff.SAMPLEFORMAT_UINT) # NinJo specific tags if description is not None: out_tiff.SetField("Description", description) out_tiff.SetField("ModelPixelScale", [pixel_xres,pixel_yres]) out_tiff.SetField("ModelTiePoint", [0.0, 0.0, 0.0, origin_lon, origin_lat, 0.0]) out_tiff.SetField("NinjoName", "NINJO") out_tiff.SetField("SatelliteNameID", sat_id) out_tiff.SetField("DateID", image_epoch) out_tiff.SetField("CreationDateID", file_epoch) out_tiff.SetField("ChannelID", chan_id) out_tiff.SetField("HeaderVersion", 2) out_tiff.SetField("FileName", output_fn) out_tiff.SetField("DataType", data_cat) out_tiff.SetField("SatelliteNumber", "\x00") # Hardcoded to 0 out_tiff.SetField("ColorDepth", 8) # Hardcoded to 8 out_tiff.SetField("DataSource", data_source) out_tiff.SetField("XMinimum", 1) out_tiff.SetField("XMaximum", image_data.shape[1]) out_tiff.SetField("YMinimum", 1) out_tiff.SetField("YMaximum", image_data.shape[0]) out_tiff.SetField("Projection", projection) out_tiff.SetField("MeridianWest", meridian_west) out_tiff.SetField("MeridianEast", meridian_east) if radius_a is not None: out_tiff.SetField("EarthRadiusLarge", float(radius_a)) if radius_b is not None: out_tiff.SetField("EarthRadiusSmall", float(radius_b)) out_tiff.SetField("GeodeticDate", "\x00") # ---? if ref_lat1 is not None: out_tiff.SetField("ReferenceLatitude1", ref_lat1) if ref_lat2 is not None: out_tiff.SetField("ReferenceLatitude2", ref_lat2) if central_meridian is not None: out_tiff.SetField("CentralMeridian", central_meridian) out_tiff.SetField("PhysicValue", physic_value) out_tiff.SetField("PhysicUnit", physic_unit) out_tiff.SetField("MinGrayValue", min_gray_val) out_tiff.SetField("MaxGrayValue", max_gray_val) out_tiff.SetField("Gradient", gradient) out_tiff.SetField("AxisIntercept", axis_intercept) out_tiff.SetField("Altitude", altitude) out_tiff.SetField("IsAtmosphereCorrected", is_atmo_corrected) out_tiff.SetField("IsCalibrated", is_calibrated) out_tiff.SetField("IsNormalized", is_normalized) ### Write Base Data Image ### out_tiff.write_tiles(image_data) out_tiff.WriteDirectory() ### Write multi-resolution overviews ### out_tiff.SetDirectory(0) _write_oneres(image_data, pixel_xres, pixel_yres) out_tiff.SetDirectory(1) _write_oneres(image_data[::2,::2], pixel_xres*2, pixel_yres*2) out_tiff.SetDirectory(2) _write_oneres(image_data[::4,::4], pixel_xres*4, pixel_yres*4) out_tiff.SetDirectory(3) _write_oneres(image_data[::8,::8], pixel_xres*8, pixel_yres*8) out_tiff.SetDirectory(4) _write_oneres(image_data[::16,::16], pixel_xres*16, pixel_yres*16) out_tiff.close() LOG.info("Successfully created a NinJo tiff file: '%s'" % (output_fn,)) return