Пример #1
0
def test_parse_bands(arr):
    fa = parse_operations("gamma 1,2 0.95")[0]
    fb = parse_operations("gamma Rg 0.95")[0]
    assert np.array_equal(fa(arr), fb(arr))

    with pytest.raises(ValueError):
        parse_operations("gamma 7,8,9 1.05")
Пример #2
0
def _postprocess(
    tile: numpy.ndarray,
    mask: numpy.ndarray,
    rescale: str = None,
    color_formula: str = None,
) -> Tuple[numpy.ndarray, numpy.ndarray]:
    """Post-process tile data."""
    if rescale:
        rescale_arr = list(map(float, rescale.split(",")))
        rescale_arr = list(_chunks(rescale_arr, 2))
        if len(rescale_arr) != tile.shape[0]:
            rescale_arr = ((rescale_arr[0]),) * tile.shape[0]

        for bdx in range(tile.shape[0]):
            tile[bdx] = numpy.where(
                mask,
                linear_rescale(
                    tile[bdx], in_range=rescale_arr[bdx], out_range=[0, 255]
                ),
                0,
            )
        tile = tile.astype(numpy.uint8)

    if color_formula:
        # make sure one last time we don't have
        # negative value before applying color formula
        tile[tile < 0] = 0
        for ops in parse_operations(color_formula):
            tile = scale_dtype(ops(to_math_type(tile)), numpy.uint8)

    return tile, mask
Пример #3
0
def _postprocess(
    tile: numpy.ndarray,
    mask: numpy.ndarray,
    tilesize: int,
    rescale: str = None,
    color_formula: str = None,
) -> Tuple[numpy.ndarray, numpy.ndarray]:

    if tile is None:
        # Return empty tile
        tile = numpy.zeros((1, tilesize, tilesize), dtype=numpy.uint8)
        mask = numpy.zeros((tilesize, tilesize), dtype=numpy.uint8)
    else:
        if rescale:
            rescale_arr = (tuple(map(float,
                                     rescale.split(","))), ) * tile.shape[0]
            for bdx in range(tile.shape[0]):
                tile[bdx] = numpy.where(
                    mask,
                    linear_rescale(tile[bdx],
                                   in_range=rescale_arr[bdx],
                                   out_range=[0, 255]),
                    0,
                )
            tile = tile.astype(numpy.uint8)

        if color_formula:
            # make sure one last time we don't have
            # negative value before applying color formula
            tile[tile < 0] = 0
            for ops in parse_operations(color_formula):
                tile = scale_dtype(ops(to_math_type(tile)), numpy.uint8)

    return tile, mask
Пример #4
0
def post_process_tile(
    tile: numpy.ndarray,
    mask: numpy.ndarray,
    rescale: str = None,
    color_formula: str = None,
) -> Tuple[numpy.ndarray, numpy.ndarray]:
    """Tile data post processing."""
    if rescale:
        rescale_arr = (tuple(map(float, rescale.split(","))), ) * tile.shape[0]
        for bdx in range(tile.shape[0]):
            tile[bdx] = numpy.where(
                mask,
                linear_rescale(tile[bdx],
                               in_range=rescale_arr[bdx],
                               out_range=[0, 255]),
                0,
            )
        tile = tile.astype(numpy.uint8)

    if color_formula:
        if issubclass(tile.dtype.type, numpy.floating):
            tile = tile.astype(numpy.int16)

        # make sure one last time we don't have
        # negative value before applying color formula
        tile[tile < 0] = 0
        for ops in parse_operations(color_formula):
            tile = scale_dtype(ops(to_math_type(tile)), numpy.uint8)

    return tile
Пример #5
0
def color(jobs, out_dtype, src_path, dst_path, operations, creation_options):
    with rasterio.open(src_path) as src:
        opts = src.profile.copy()
        windows = [(window, ij) for ij, window in src.block_windows()]

    opts.update(**creation_options)
    opts["transform"] = guard_transform(opts["transform"])

    out_dtype = out_dtype if out_dtype else opts["dtype"]
    opts["dtype"] = out_dtype

    args = {"ops_string": " ".join(operations), "out_dtype": out_dtype}
    # Just run this for validation this time
    # parsing will be run again within the worker
    # where its returned value will be used
    try:
        parse_operations(args["ops_string"])
    except ValueError as e:
        import sys

        sys.exit(1)
        print(e)

    jobs = check_jobs(jobs)

    if jobs > 1:
        with riomucho.RioMucho(
            [src_path],
                dst_path,
                color_worker,
                windows=windows,
                options=opts,
                global_args=args,
                mode="manual_read",
        ) as mucho:
            mucho.run(jobs)
    else:
        with rasterio.open(dst_path, "w", **opts) as dest:
            with rasterio.open(src_path) as src:
                rasters = [src]
                for window, ij in windows:
                    arr = color_worker(rasters, window, ij, args)
                    dest.write(arr, window=window)

                dest.colorinterp = src.colorinterp
Пример #6
0
    def post_process(
        self,
        in_range: Optional[Tuple[NumType, NumType]] = None,
        out_dtype: Union[str, numpy.number] = "uint8",
        color_formula: Optional[str] = None,
        **kwargs: Any,
    ) -> "ImageData":
        """Post-process image data.

        Args:
            in_range (tuple): input min/max bounds value to rescale from.
            out_dtype (str, optional): output datatype after rescaling. Defaults to `uint8`.
            color_formula (str, optional): rio-color formula (see: https://github.com/mapbox/rio-color).
            kwargs (optional): keyword arguments to forward to `rio_tiler.utils.linear_rescale`.

        Returns:
            ImageData: new ImageData object with the updated data.

        Examples:
            >>> img.post_process(in_range=(0, 16000))

            >>> img.post_process(color_formula="Gamma RGB 4.1")

        """
        data = self.data.copy()
        mask = self.mask.copy()

        if in_range:
            rescale_arr = tuple(_chunks(in_range, 2))
            if len(rescale_arr) != self.count:
                rescale_arr = ((rescale_arr[0]), ) * self.count

            for bdx in range(self.count):
                data[bdx] = numpy.where(
                    self.mask,
                    linear_rescale(
                        data[bdx],
                        in_range=rescale_arr[bdx],
                        **kwargs,
                    ),
                    0,
                )
            data = data.astype(out_dtype)

        if color_formula:
            data[data < 0] = 0
            for ops in parse_operations(color_formula):
                data = scale_dtype(ops(to_math_type(data)), numpy.uint8)

        return ImageData(
            data,
            mask,
            crs=self.crs,
            bounds=self.bounds,
            assets=self.assets,
            metadata=self.metadata,
        )
Пример #7
0
def test_parse_rgba(arr, arr_rgba):
    f = parse_operations("gamma rg 0.95")[0]
    rgb = f(arr)
    assert rgb.shape[0] == 3

    rgba = f(arr_rgba)
    assert rgba.shape[0] == 4
    # rgb bands are same
    assert np.allclose(rgba[0:3], rgb[0:3])
    # alpha unaltered
    assert np.array_equal(rgba[3], arr_rgba[3])
Пример #8
0
def test_saturation_rgba(arr, arr_rgba):
    f = parse_operations("saturation 1.25")[0]

    satrgb = f(arr)
    assert satrgb.shape[0] == 3

    satrgba = f(arr_rgba)
    # Still rgba
    assert satrgba.shape[0] == 4
    # alpha is unaltered
    assert np.array_equal(satrgba[3], arr_rgba[3])
    # first 3 bands are same b/t rgb and rgba
    assert np.allclose(satrgba[0:3], satrgb[0:3])
Пример #9
0
def read_tile(id, tile, scale=1, **kwargs):
    meta = get_metadata(id, **kwargs)
    maxzoom = int(meta['maxzoom'])
    minzoom = int(meta['minzoom'])

    if not minzoom <= tile.z <= maxzoom:
        raise InvalidTileRequest('Invalid zoom: {} outside [{}, {}]'.format(tile.z, minzoom, maxzoom))

    sw = mercantile.tile(*meta['bounds'][0:2], zoom=tile.z)
    ne = mercantile.tile(*meta['bounds'][2:4], zoom=tile.z)

    if not sw.x <= tile.x <= ne.x:
        raise InvalidTileRequest('Invalid x coordinate: {} outside [{}, {}]'.format(tile.x, sw.x, ne.x))

    if not ne.y <= tile.y <= sw.y:
        raise InvalidTileRequest('Invalid y coordinate: {} outside [{}, {}]'.format(tile.y, sw.y, ne.y))

    data = render_tile(meta, tile, scale=scale)

    # 8-bit per pixel
    target_dtype = np.uint8

    # default values from rio color atmo
    ops = meta['meta'].get('operations')
    if ops:
        # scale to (0..1)
        floats = (data * 1.0 / np.iinfo(data.dtype).max).astype(np.float32)

        for func in operations.parse_operations(ops):
            floats = func(floats)

        # scale back to uint8
        data = (floats * np.iinfo(target_dtype).max).astype(target_dtype)

    if data.dtype != target_dtype:
        # rescale
        try:
            data = (data * (np.iinfo(target_dtype).max / np.iinfo(data.dtype).max)).astype(target_dtype)
        except:
            raise Exception('Not enough information to rescale; source is "{}""'.format(data.dtype))

    imgarr = np.ma.transpose(data, [1, 2, 0])

    out = StringIO()
    im = Image.fromarray(imgarr, 'RGBA')
    im.save(out, 'png')

    return out.getvalue()
Пример #10
0
def _postprocess_tile(tile, mask, rescale=None, color_ops=None):
    """Tile data post-processing."""
    if rescale:
        rescale = (tuple(map(float, rescale.split(","))), ) * tile.shape[0]
        for bdx in range(tile.shape[0]):
            tile[bdx] = numpy.where(
                mask,
                linear_rescale(tile[bdx],
                               in_range=rescale[bdx],
                               out_range=[0, 255]),
                0,
            )
        tile = tile.astype(numpy.uint8)

    if color_ops:
        # make sure one last time we don't have
        # negative value before applying color formula
        tile[tile < 0] = 0
        for ops in parse_operations(color_ops):
            tile = scale_dtype(ops(to_math_type(tile)), numpy.uint8)

    return tile, mask
Пример #11
0
def _postprocess(tile, mask, tilesize, rescale=None, color_formula=None):
    if tile is None:
        # Return empty tile
        tile = numpy.zeros((1, tilesize, tilesize), dtype=numpy.uint8)
        mask = numpy.zeros((tilesize, tilesize), dtype=numpy.uint8)
    else:
        if rescale:
            rescale = (tuple(map(float, rescale.split(","))), ) * tile.shape[0]
            for bdx in range(tile.shape[0]):
                tile[bdx] = numpy.where(
                    mask,
                    linear_rescale(tile[bdx],
                                   in_range=rescale[bdx],
                                   out_range=[0, 255]),
                    0,
                )
            tile = tile.astype(numpy.uint8)

        if color_formula:
            for ops in parse_operations(color_formula):
                tile = scale_dtype(ops(to_math_type(tile)), numpy.uint8)

    return tile, mask
Пример #12
0
def test_parse_multi_saturation_first(arr):
    f1, f2 = parse_operations("saturation 1.25 gamma rgb 0.95")
    assert np.array_equal(f2(f1(arr)), gamma(saturation(arr, 1.25), g=0.95))
Пример #13
0
def test_parse_sigmoidal(arr):
    f = parse_operations("sigmoidal rgb 5 0.53")[0]
    assert np.array_equal(f(arr), sigmoidal(arr, contrast=5, bias=0.53))
Пример #14
0
def test_parse_gamma(arr):
    f = parse_operations("gamma rgb 0.95")[0]
    assert np.array_equal(f(arr), gamma(arr, 0.95))
Пример #15
0
def test_parse_comma(arr):
    # Commas are optional whitespace, treated like empty string
    f1, f2 = parse_operations("gamma r,g,b 0.95, sigmoidal r,g,b 35 0.13")
    assert np.array_equal(
        f2(f1(arr)), sigmoidal(gamma(arr, g=0.95), contrast=35, bias=0.13))
Пример #16
0
def test_parse_multi(arr):
    f1, f2 = parse_operations("gamma rgb 0.95 sigmoidal rgb 35 0.13")
    assert np.array_equal(
        f2(f1(arr)), sigmoidal(gamma(arr, g=0.95), contrast=35, bias=0.13))
Пример #17
0
 def apply_color(self, arr, state):
     """Apply color formula to an array."""
     ops = self.cmd(state)
     for func in parse_operations(ops):
         arr = func(arr)
     return arr
Пример #18
0
def color(ctx, jobs, out_dtype, src_path, dst_path, operations,
          creation_options):
    """Color correction

Operations will be applied to the src image in the specified order.

Available OPERATIONS include:

\b
    "gamma BANDS VALUE"
        Applies a gamma curve, brightening or darkening midtones.
        VALUE > 1 brightens the image.

\b
    "sigmoidal BANDS CONTRAST BIAS"
        Adjusts the contrast and brightness of midtones.
        BIAS > 0.5 darkens the image.

\b
    "saturation PROPORTION"
        Controls the saturation in LCH color space.
        PROPORTION = 0 results in a grayscale image
        PROPORTION = 1 results in an identical image
        PROPORTION = 2 is likely way too saturated

BANDS are specified as a single arg, no delimiters

\b
    `123` or `RGB` or `rgb` are all equivalent

Example:

\b
    rio color -d uint8 -j 4 input.tif output.tif \\
        gamma 3 0.95, sigmoidal rgb 35 0.13
    """
    with rasterio.open(src_path) as src:
        opts = src.profile.copy()
        windows = [(window, ij) for ij, window in src.block_windows()]

    opts.update(**creation_options)
    opts['transform'] = guard_transform(opts['transform'])

    out_dtype = out_dtype if out_dtype else opts['dtype']
    opts['dtype'] = out_dtype

    args = {
        'ops_string': ' '.join(operations),
        'out_dtype': out_dtype
    }
    # Just run this for validation this time
    # parsing will be run again within the worker
    # where its returned value will be used
    try:
        ops = parse_operations(args['ops_string'])
    except ValueError as e:
        raise click.UsageError(str(e))

    jobs = check_jobs(jobs)

    if jobs > 1:
        with riomucho.RioMucho(
            [src_path],
            dst_path,
            color_worker,
            windows=windows,
            options=opts,
            global_args=args,
            mode="manual_read"
        ) as mucho:
            mucho.run(jobs)
    else:
        with rasterio.open(dst_path, 'w', **opts) as dest:
            with rasterio.open(src_path) as src:
                rasters = [src]
                for window, ij in windows:
                    arr = color_worker(rasters, window, ij, args)
                    dest.write(arr, window=window)
Пример #19
0
def me(in_tif, out_tif, bands, s_expressions, ops, lut=None):

    bands = [band for band in bands if band]

    logging.info(len(bands))
    # read the input tif, it's the VRT
    ds = gdal.Open(in_tif)
    width = ds.RasterXSize
    height = ds.RasterYSize

    input_geotransform = ds.GetGeoTransform()
    input_georef = ds.GetProjectionRef()

    # the lenght of bands (same as ds.RasterCount) tells us how many inputs we have
    ctx = dict()

    ctx['v1'] = ds.GetRasterBand(1).ReadAsArray()

    if len(bands) > 1:

        ctx['v2'] = ds.GetRasterBand(2).ReadAsArray()

    if len(bands) == 3:

        ctx['v3'] = ds.GetRasterBand(3).ReadAsArray()

    # apply the expressions to the bands
    arr = np.stack(
        [snuggs.eval(s_expression, **ctx) for s_expression in s_expressions])

    # apply the color operations using rio color
    if ops is not None:

        assert arr.shape[0] == 3
        assert arr.min() >= 0
        assert arr.max() <= 1

        logging.info('Applying color operations: {}'.format(ops))

        for func in parse_operations(ops):

            arr = func(arr)

    if lut is not None:
        logging.info('Applying look-up table')
        temp_arr = cm.get_cmap(lut)(arr)

        arr = np.array(
            [temp_arr[0][:, :, 0], temp_arr[0][:, :, 1], temp_arr[0][:, :, 2]])

    # save
    driver = gdal.GetDriverByName('GTiff')

    output = driver.Create(out_tif, width, height, arr.shape[0], gdal.GDT_Byte)

    output.SetGeoTransform(input_geotransform)
    output.SetProjection(input_georef)

    for index in range(1, arr.shape[0] + 1):

        logging.info('Adding band {} of {}'.format(index, arr.shape[0]))

        band = output.GetRasterBand(index)

        band.WriteArray((arr[index - 1] * 255).astype(np.int))

        output.FlushCache()

        band = None

    output = None

    sleep(5)

    del (output)

    ds = None

    del (ds)

    return True
Пример #20
0
def test_parse_saturation_rgb(arr):
    f = parse_operations("saturation 1.25")[0]
    assert np.allclose(f(arr), saturation(arr, 1.25))
Пример #21
0
def test_simple_atmos_opstring(arr):
    x = simple_atmo(arr, 0.03, 10, 0.15)
    ops = simple_atmo_opstring(0.03, 10, 0.15)
    for op in parse_operations(ops):
        arr = op(arr)
    assert np.allclose(x, arr)
Пример #22
0
def color(ctx, jobs, out_dtype, src_path, dst_path, operations,
          creation_options):
    """Color correction

Operations will be applied to the src image in the specified order.

Available OPERATIONS include:

\b
    "gamma BANDS VALUE"
        Applies a gamma curve, brightening or darkening midtones.
        VALUE > 1 brightens the image.

\b
    "sigmoidal BANDS CONTRAST BIAS"
        Adjusts the contrast and brightness of midtones.
        BIAS > 0.5 darkens the image.

\b
    "saturation PROPORTION"
        Controls the saturation in LCH color space.
        PROPORTION = 0 results in a grayscale image
        PROPORTION = 1 results in an identical image
        PROPORTION = 2 is likely way too saturated

BANDS are specified as a single arg, no delimiters

\b
    `123` or `RGB` or `rgb` are all equivalent

Example:

\b
    rio color -d uint8 -j 4 input.tif output.tif \\
        gamma 3 0.95, sigmoidal rgb 35 0.13
    """
    with rasterio.open(src_path) as src:
        opts = src.profile.copy()
        windows = [(window, ij) for ij, window in src.block_windows()]

    opts.update(**creation_options)
    opts["transform"] = guard_transform(opts["transform"])

    out_dtype = out_dtype if out_dtype else opts["dtype"]
    opts["dtype"] = out_dtype

    args = {"ops_string": " ".join(operations), "out_dtype": out_dtype}
    # Just run this for validation this time
    # parsing will be run again within the worker
    # where its returned value will be used
    try:
        parse_operations(args["ops_string"])
    except ValueError as e:
        raise click.UsageError(str(e))

    jobs = check_jobs(jobs)

    if jobs > 1:
        with riomucho.RioMucho(
            [src_path],
                dst_path,
                color_worker,
                windows=windows,
                options=opts,
                global_args=args,
                mode="manual_read",
        ) as mucho:
            mucho.run(jobs)
    else:
        with rasterio.open(dst_path, "w", **opts) as dest:
            with rasterio.open(src_path) as src:
                rasters = [src]
                for window, ij in windows:
                    arr = color_worker(rasters, window, ij, args)
                    dest.write(arr, window=window)

                dest.colorinterp = src.colorinterp
Пример #23
0
def test_parse_bad_op():
    with pytest.raises(ValueError):
        parse_operations("foob 123")
Пример #24
0
    def _apply_color_operations(self, img, color_ops):
        for ops in parse_operations(color_ops):
            img = scale_dtype(ops(to_math_type(img)), numpy.uint8)

        return img
Пример #25
0
def test_parse_multi_name(arr):
    f1, f2 = parse_operations("saturation 1.25 gamma rgb 0.95")
    assert f1.__name__ == "saturation"
    assert f2.__name__ == "gamma"