示例#1
0
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)
示例#2
0
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")
示例#3
0
    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
示例#4
0
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")
示例#5
0
    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
示例#6
0
    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
示例#7
0
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
示例#8
0
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
示例#9
0
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