def __init__(self, filename, mode='r'): """Open a tiff file. see: libtiff.TIFF.open() """ self.tiff = TIFF.open(filename, mode) self.tiff.ninjo_tags_dict = ninjo_tags_dict self.tiff.ninjo_tags = ninjo_tags
def _write(image_data, output_fn, write_rgb=False, **kwargs): """Proudly Found Elsewhere (PFE) https://github.com/davidh-ssec/polar2grid by David Hoese. 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 or 3D numpy array Satellite image data to be put into the NinJo compatible tiff An 3D array (HxWx3) is expected for a RGB image. filename : str The name of the TIFF file to be created :Keywords: 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' 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' physic_unit : str Physical value units. Examples: - 'CELSIUS' - '%' min_gray_val : int Minimum gray value (default 0) max_gray_val : int Maximum gray value (default 255) gradient : float Gradient/Slope axis_intercept : float Axis Intercept 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) transparent_pix : int Transparent pixel value (default -1) :Raises: KeyError : if required keyword is not provided """ def _raise_value_error(text): log.error(text) raise ValueError(text) def _default_colormap(reverse=False): # Basic B&W colormap if reverse: return [[ x*256 for x in range(255, -1, -1) ]]*3 return [[ x*256 for x in range(256) ]]*3 def _eval_or_none(key, eval_func): try: return eval_func(kwargs[key]) except KeyError: return None log.info("Creating output file '%s'" % (output_fn,)) tiff = TIFF.open(output_fn, "w") # Extract keyword arguments 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")) 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 = str(kwargs.pop("projection")) meridian_west = float(kwargs.pop("meridian_west", 0.0)) meridian_east = float(kwargs.pop("meridian_east", 0.0)) radius_a = _eval_or_none("radius_a", float) radius_b = _eval_or_none("radius_b", float) ref_lat1 = _eval_or_none("ref_lat1", float) ref_lat2 = _eval_or_none("ref_lat2", float) central_meridian = _eval_or_none("central_meridian", float) 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_blac_corrected = int(bool(kwargs.pop("is_blac_corrected", 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 = _eval_or_none("description", str) physic_value = str(kwargs.pop("physic_value", 'None')) physic_unit = str(kwargs.pop("physic_unit", 'None')) gradient = float(kwargs.pop("gradient", 1.0)) axis_intercept = float(kwargs.pop("axis_intercept", 0.0)) transparent_pix = int(kwargs.pop("transparent_pix", -1)) # Keyword checks / verification if not cmap: if physic_value == 'T': reverse = True else: reverse = False cmap = _default_colormap(reverse) if len(cmap) != 3: _raise_value_error("Colormap (cmap) must be a list of 3 lists (RGB), not %d" % len(cmap)) if len(data_cat) != 4: _raise_value_error("NinJo data type must be 4 characters") if data_cat[0] not in ["P", "G"]: _raise_value_error("NinJo data type's first character must be 'P' or 'G' not '%s'" % data_cat[0]) if data_cat[1] not in ["O", "P"]: _raise_value_error("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"]: _raise_value_error("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): log.info("Writing tags and data for a resolution %dx%d" % image_data.shape[:2]) # Write Tag Data # Built ins tiff.SetField("ImageWidth", image_data.shape[1]) tiff.SetField("ImageLength", image_data.shape[0]) tiff.SetField("BitsPerSample", 8) tiff.SetField("Compression", libtiff.COMPRESSION_DEFLATE) if write_rgb: tiff.SetField("Photometric", libtiff.PHOTOMETRIC_RGB) tiff.SetField("SamplesPerPixel", 3) else: tiff.SetField("Photometric", libtiff.PHOTOMETRIC_PALETTE) tiff.SetField("SamplesPerPixel", 1) tiff.SetField("ColorMap", cmap) tiff.SetField("Orientation", libtiff.ORIENTATION_TOPLEFT) tiff.SetField("SMinSampleValue", 0) tiff.SetField("SMaxsampleValue", 255) tiff.SetField("PlanarConfig", libtiff.PLANARCONFIG_CONTIG) tiff.SetField("TileWidth", tile_width) tiff.SetField("TileLength", tile_length) tiff.SetField("SampleFormat", libtiff.SAMPLEFORMAT_UINT) # NinJo specific tags if description is not None: tiff.SetField("Description", description) if MODEL_PIXEL_SCALE_COUNT == 3: tiff.SetField("ModelPixelScale", [pixel_xres, pixel_yres, 0.0]) else: tiff.SetField("ModelPixelScale", [pixel_xres, pixel_yres]) tiff.SetField("ModelTiePoint", [0.0, 0.0, 0.0, origin_lon, origin_lat, 0.0]) tiff.SetField("Magic", "NINJO") tiff.SetField("SatelliteNameID", sat_id) tiff.SetField("DateID", image_epoch) tiff.SetField("CreationDateID", file_epoch) tiff.SetField("ChannelID", chan_id) tiff.SetField("HeaderVersion", 2) tiff.SetField("FileName", output_fn) tiff.SetField("DataType", data_cat) tiff.SetField("SatelliteNumber", "\x00") # Hardcoded to 0 if write_rgb: tiff.SetField("ColorDepth", 24) elif cmap: tiff.SetField("ColorDepth", 16) else: tiff.SetField("ColorDepth", 8) tiff.SetField("DataSource", data_source) tiff.SetField("XMinimum", 1) tiff.SetField("XMaximum", image_data.shape[1]) tiff.SetField("YMinimum", 1) tiff.SetField("YMaximum", image_data.shape[0]) tiff.SetField("Projection", projection) tiff.SetField("MeridianWest", meridian_west) tiff.SetField("MeridianEast", meridian_east) if radius_a is not None: tiff.SetField("EarthRadiusLarge", float(radius_a)) if radius_b is not None: tiff.SetField("EarthRadiusSmall", float(radius_b)) #tiff.SetField("GeodeticDate", "\x00") # ---? if ref_lat1 is not None: tiff.SetField("ReferenceLatitude1", ref_lat1) if ref_lat2 is not None: tiff.SetField("ReferenceLatitude2", ref_lat2) if central_meridian is not None: tiff.SetField("CentralMeridian", central_meridian) tiff.SetField("PhysicValue", physic_value) tiff.SetField("PhysicUnit", physic_unit) tiff.SetField("MinGrayValue", min_gray_val) tiff.SetField("MaxGrayValue", max_gray_val) tiff.SetField("Gradient", gradient) tiff.SetField("AxisIntercept", axis_intercept) tiff.SetField("Altitude", altitude) tiff.SetField("IsBlackLineCorrection", is_blac_corrected) tiff.SetField("IsAtmosphereCorrected", is_atmo_corrected) tiff.SetField("IsCalibrated", is_calibrated) tiff.SetField("IsNormalized", is_normalized) tiff.SetField("TransparentPixel", transparent_pix) # Write Base Data Image tiff.write_tiles(image_data) tiff.WriteDirectory() # Write multi-resolution overviews (or not) tiff.SetDirectory(0) _write_oneres(image_data, pixel_xres, pixel_yres) for index, scale in enumerate((2, 4, 8, 16)): shape = (image_data.shape[0]/scale, image_data.shape[1]/scale) if shape[0] > tile_width and shape[1] > tile_length: tiff.SetDirectory(index + 1) _write_oneres(image_data[::scale,::scale], pixel_xres*scale, pixel_yres*scale) tiff.close() log.info("Successfully created a NinJo tiff file: '%s'" % (output_fn,))
def _write(image_data, output_fn, write_rgb=False, **kwargs): """Proudly Found Elsewhere (PFE) https://github.com/davidh-ssec/polar2grid by David Hoese. 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 or 3D numpy array Satellite image data to be put into the NinJo compatible tiff An 3D array (HxWx3) is expected for a RGB image. filename : str The name of the TIFF file to be created :Keywords: 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' 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' physic_unit : str Physical value units. Examples: - 'CELSIUS' - '%' min_gray_val : int Minimum gray value (default 0) max_gray_val : int Maximum gray value (default 255) gradient : float Gradient/Slope axis_intercept : float Axis Intercept 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) transparent_pix : int Transparent pixel value (default -1) :Raises: KeyError : if required keyword is not provided """ def _raise_value_error(text): log.error(text) raise ValueError(text) def _default_colormap(reverse=False): # Basic B&W colormap if reverse: return [[x * 256 for x in range(255, -1, -1)]] * 3 return [[x * 256 for x in range(256)]] * 3 def _eval_or_none(key, eval_func): try: return eval_func(kwargs[key]) except KeyError: return None log.info("Creating output file '%s'" % (output_fn, )) tiff = TIFF.open(output_fn, "w") # Extract keyword arguments 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")) 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 = str(kwargs.pop("projection")) meridian_west = float(kwargs.pop("meridian_west", 0.0)) meridian_east = float(kwargs.pop("meridian_east", 0.0)) radius_a = _eval_or_none("radius_a", float) radius_b = _eval_or_none("radius_b", float) ref_lat1 = _eval_or_none("ref_lat1", float) ref_lat2 = _eval_or_none("ref_lat2", float) central_meridian = _eval_or_none("central_meridian", float) 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_blac_corrected = int(bool(kwargs.pop("is_blac_corrected", 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 = _eval_or_none("description", str) physic_value = str(kwargs.pop("physic_value", 'None')) physic_unit = str(kwargs.pop("physic_unit", 'None')) gradient = float(kwargs.pop("gradient", 1.0)) axis_intercept = float(kwargs.pop("axis_intercept", 0.0)) transparent_pix = int(kwargs.pop("transparent_pix", -1)) # Keyword checks / verification if not cmap: if physic_value == 'T': reverse = True else: reverse = False cmap = _default_colormap(reverse) if len(cmap) != 3: _raise_value_error( "Colormap (cmap) must be a list of 3 lists (RGB), not %d" % len(cmap)) if len(data_cat) != 4: _raise_value_error("NinJo data type must be 4 characters") if data_cat[0] not in ["P", "G"]: _raise_value_error( "NinJo data type's first character must be 'P' or 'G' not '%s'" % data_cat[0]) if data_cat[1] not in ["O", "P"]: _raise_value_error( "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"]: _raise_value_error( "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): log.info("Writing tags and data for a resolution %dx%d" % image_data.shape[:2]) # Write Tag Data # Built ins tiff.SetField("ImageWidth", image_data.shape[1]) tiff.SetField("ImageLength", image_data.shape[0]) tiff.SetField("BitsPerSample", 8) tiff.SetField("Compression", libtiff.COMPRESSION_DEFLATE) if write_rgb: tiff.SetField("Photometric", libtiff.PHOTOMETRIC_RGB) tiff.SetField("SamplesPerPixel", 3) else: tiff.SetField("Photometric", libtiff.PHOTOMETRIC_PALETTE) tiff.SetField("SamplesPerPixel", 1) tiff.SetField("ColorMap", cmap) tiff.SetField("Orientation", libtiff.ORIENTATION_TOPLEFT) tiff.SetField("SMinSampleValue", 0) tiff.SetField("SMaxsampleValue", 255) tiff.SetField("PlanarConfig", libtiff.PLANARCONFIG_CONTIG) tiff.SetField("TileWidth", tile_width) tiff.SetField("TileLength", tile_length) tiff.SetField("SampleFormat", libtiff.SAMPLEFORMAT_UINT) # NinJo specific tags if description is not None: tiff.SetField("Description", description) if MODEL_PIXEL_SCALE_COUNT == 3: tiff.SetField("ModelPixelScale", [pixel_xres, pixel_yres, 0.0]) else: tiff.SetField("ModelPixelScale", [pixel_xres, pixel_yres]) tiff.SetField("ModelTiePoint", [0.0, 0.0, 0.0, origin_lon, origin_lat, 0.0]) tiff.SetField("Magic", "NINJO") tiff.SetField("SatelliteNameID", sat_id) tiff.SetField("DateID", image_epoch) tiff.SetField("CreationDateID", file_epoch) tiff.SetField("ChannelID", chan_id) tiff.SetField("HeaderVersion", 2) tiff.SetField("FileName", output_fn) tiff.SetField("DataType", data_cat) tiff.SetField("SatelliteNumber", "\x00") # Hardcoded to 0 if write_rgb: tiff.SetField("ColorDepth", 24) elif cmap: tiff.SetField("ColorDepth", 16) else: tiff.SetField("ColorDepth", 8) tiff.SetField("DataSource", data_source) tiff.SetField("XMinimum", 1) tiff.SetField("XMaximum", image_data.shape[1]) tiff.SetField("YMinimum", 1) tiff.SetField("YMaximum", image_data.shape[0]) tiff.SetField("Projection", projection) tiff.SetField("MeridianWest", meridian_west) tiff.SetField("MeridianEast", meridian_east) if radius_a is not None: tiff.SetField("EarthRadiusLarge", float(radius_a)) if radius_b is not None: tiff.SetField("EarthRadiusSmall", float(radius_b)) #tiff.SetField("GeodeticDate", "\x00") # ---? if ref_lat1 is not None: tiff.SetField("ReferenceLatitude1", ref_lat1) if ref_lat2 is not None: tiff.SetField("ReferenceLatitude2", ref_lat2) if central_meridian is not None: tiff.SetField("CentralMeridian", central_meridian) tiff.SetField("PhysicValue", physic_value) tiff.SetField("PhysicUnit", physic_unit) tiff.SetField("MinGrayValue", min_gray_val) tiff.SetField("MaxGrayValue", max_gray_val) tiff.SetField("Gradient", gradient) tiff.SetField("AxisIntercept", axis_intercept) tiff.SetField("Altitude", altitude) tiff.SetField("IsBlackLineCorrection", is_blac_corrected) tiff.SetField("IsAtmosphereCorrected", is_atmo_corrected) tiff.SetField("IsCalibrated", is_calibrated) tiff.SetField("IsNormalized", is_normalized) tiff.SetField("TransparentPixel", transparent_pix) # Write Base Data Image tiff.write_tiles(image_data) tiff.WriteDirectory() # Write multi-resolution overviews (or not) tiff.SetDirectory(0) _write_oneres(image_data, pixel_xres, pixel_yres) for index, scale in enumerate((2, 4, 8, 16)): shape = (image_data.shape[0] / scale, image_data.shape[1] / scale) if shape[0] > tile_width and shape[1] > tile_length: tiff.SetDirectory(index + 1) _write_oneres(image_data[::scale, ::scale], pixel_xres * scale, pixel_yres * scale) tiff.close() log.info("Successfully created a NinJo tiff file: '%s'" % (output_fn, ))