Exemple #1
0
    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
Exemple #2
0
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,))