Exemple #1
0
def merge_n(output,
            inputs,
            offsets,
            averaging='average_if_close',
            threshold=1):
    """
    Merge n images of equal sizes by taking the median/mean/min/max pixelwise.

    Args:
        inputs: list of paths to the input images
        output: path to the output image
        averaging: string containing the name of a function that accepts
            1D arrays. It is applied to 1D slices of the stack of images along
            the last axis. Possible values are, for instance np.min, np.max,
            np.mean, np.median and their nanproof counterparts, ie np.nanmin,
            np.nanmax, np.nanmean, np.nanmedian
    """
    assert (len(inputs) == len(offsets))

    # get input images size
    if inputs:
        with rasterio.open(inputs[0], 'r') as f:
            h, w = f.shape

    # read input images and apply offsets
    x = np.empty((h, w, len(inputs)))
    for i, img in enumerate(inputs):
        with rasterio.open(img, 'r') as f:
            x[:, :, i] = f.read(1) - offsets[i]
        if cfg['debug']:
            common.rasterio_write(
                '{}_registered.tif'.format(os.path.splitext(img)[0]),
                x[:, :, i] + np.mean(offsets))

    # apply the averaging operator
    if averaging.startswith(('np.', 'numpy.')):
        avg = np.apply_along_axis(getattr(sys.modules['numpy'],
                                          averaging.split('.')[1]),
                                  axis=2,
                                  arr=x)
    elif averaging == 'average_if_close':
        avg = np.apply_along_axis(average_if_close, 2, x, threshold)

    # add the mean offset
    avg += np.mean(offsets)

    # write the average to output
    if inputs:
        shutil.copy(inputs[0],
                    output)  # copy an input file to get the metadata
        with rasterio.open(output, 'r+') as f:
            f.write(np.asarray(
                [avg]).astype('float32'))  # update the output file content
Exemple #2
0
def plys_to_dsm(tile):
    """
    Generates DSM from plyfiles (cloud.ply)

    Args:
        tile: a dictionary that provides all you need to process a tile
    """
    out_dsm  = os.path.join(tile['dir'], 'dsm.tif')
    out_conf = os.path.join(tile['dir'], 'confidence.tif')

    res = cfg['dsm_resolution']
    if 'utm_bbx' in cfg:
        bbx = cfg['utm_bbx']
        global_xoff = bbx[0]
        global_yoff = bbx[3]
    else:
        global_xoff = 0  # arbitrary reference
        global_yoff = 0

    xmin, xmax, ymin, ymax = np.loadtxt(os.path.join(tile['dir'], "plyextrema.txt"))

    if not all(np.isfinite([xmin, xmax, ymin, ymax])):  # then the ply is empty
        return

    # compute xoff, yoff, xsize, ysize considering final dsm
    xoff = global_xoff + np.floor((xmin - global_xoff) / res) * res
    xsize = int(1 + np.floor((xmax - xoff) / res))

    yoff = global_yoff + np.ceil((ymax - global_yoff) / res) * res
    ysize = int(1 - np.floor((ymin - yoff) / res))

    roi = xoff, yoff, xsize, ysize

    clouds = [os.path.join(tile['dir'], n_dir, 'cloud.ply') for n_dir in tile['neighborhood_dirs']]
    raster, profile = rasterization.plyflatten_from_plyfiles_list(clouds,
                                                                  resolution=res,
                                                                  roi=roi,
                                                                  radius=cfg['dsm_radius'],
                                                                  sigma=cfg['dsm_sigma'])

    # save output image with utm georeferencing
    common.rasterio_write(out_dsm, raster[:, :, 0], profile=profile)

    # export confidence (optional)
    if raster.shape[-1] == 5:
        common.rasterio_write(out_conf, raster[:, :, 4], profile=profile)
Exemple #3
0
def disparity_to_height(tile, i):
    """
    Compute a height map from the disparity map of a pair of image tiles.

    Args:
        tile: dictionary containing the information needed to process a tile.
        i: index of the processed pair.
    """
    out_dir = os.path.join(tile['dir'], 'pair_{}'.format(i))
    x, y, w, h = tile['coordinates']

    print('triangulating tile {} {} pair {}...'.format(x, y, i))
    rpc1 = cfg['images'][0]['rpcm']
    rpc2 = cfg['images'][i]['rpcm']
    H_ref = np.loadtxt(os.path.join(out_dir, 'H_ref.txt'))
    H_sec = np.loadtxt(os.path.join(out_dir, 'H_sec.txt'))
    disp = os.path.join(out_dir, 'rectified_disp.tif')
    mask = os.path.join(out_dir, 'rectified_mask.png')
    pointing = os.path.join(cfg['out_dir'],
                            'global_pointing_pair_{}.txt'.format(i))

    with rasterio.open(disp, 'r') as f:
        disp_img = f.read().squeeze()
    with rasterio.open(mask, 'r') as f:
        mask_rect_img = f.read().squeeze()
    height_map = triangulation.height_map(x,
                                          y,
                                          w,
                                          h,
                                          rpc1,
                                          rpc2,
                                          H_ref,
                                          H_sec,
                                          disp_img,
                                          mask_rect_img,
                                          A=np.loadtxt(pointing))

    # write height map to a file
    common.rasterio_write(os.path.join(out_dir, 'height_map.tif'), height_map)

    if cfg['clean_intermediate']:
        common.remove(H_ref)
        common.remove(H_sec)
        common.remove(disp)
        common.remove(mask)
Exemple #4
0
def plys_to_dsm(tile):
    """
    Generates DSM from plyfiles (cloud.ply)

    Args:
        tile: a dictionary that provides all you need to process a tile
    """
    out_dsm = os.path.join(tile['dir'], 'dsm.tif')
    out_conf = os.path.join(tile['dir'], 'confidence.tif')
    r = cfg['dsm_resolution']
    xmin, xmax, ymin, ymax = np.loadtxt(
        os.path.join(tile['dir'], "plyextrema.txt"))

    if not all(np.isfinite([xmin, xmax, ymin, ymax])):  # then the ply is empty
        return

    # compute xoff, yoff, xsize, ysize on a grid of unit r
    xoff = np.floor(xmin / r) * r
    xsize = int(1 + np.floor((xmax - xoff) / r))

    yoff = np.ceil(ymax / r) * r
    ysize = int(1 - np.floor((ymin - yoff) / r))

    roi = xoff, yoff, xsize, ysize

    clouds = [
        os.path.join(tile['dir'], n_dir, 'cloud.ply')
        for n_dir in tile['neighborhood_dirs']
    ]
    raster, profile = plyflatten_from_plyfiles_list(clouds,
                                                    resolution=r,
                                                    roi=roi,
                                                    radius=cfg['dsm_radius'],
                                                    sigma=cfg['dsm_sigma'])

    # save output image with utm georeferencing
    common.rasterio_write(out_dsm, raster[:, :, 0], profile=profile)

    # export confidence (optional)
    # note that the plys are assumed to contain the fields:
    # [x(float32), y(float32), z(float32), r(uint8), g(uint8), b(uint8), confidence(optional, float32)]
    # so the raster has 4 or 5 columns: [z, r, g, b, confidence (optional)]
    if raster.shape[-1] == 5:
        common.rasterio_write(out_conf, raster[:, :, 4], profile=profile)
Exemple #5
0
def plot_matches_low_level(im1, im2, matches):
    """
    Displays two images side by side with matches highlighted

    Args:
        im1, im2: paths to the two input images
        matches: 2D numpy array of size 4xN containing a list of matches (a
            list of pairs of points, each pair being represented by x1, y1, x2,
            y2)

    Returns:
        path to the resulting image, to be displayed
    """
    # load images
    with rasterio.open(im1, 'r') as f:
        img1 = f.read().squeeze()
    with rasterio.open(im2, 'r') as f:
        img2 = f.read().squeeze()

    # transform single channel to 3-channels
    if img1.ndim < 3:
        img1 = np.dstack([img1] * 3)
    if img2.ndim < 3:
        img2 = np.dstack([img2] * 3)

    # if images have more than 3 channels, keep only the first 3
    if img1.shape[2] > 3:
        img1 = img1[:, :, 0:3]
    if img2.shape[2] > 3:
        img2 = img2[:, :, 0:3]

    # build the output image
    h1, w1 = img1.shape[:2]
    h2, w2 = img2.shape[:2]
    w = w1 + w2
    h = max(h1, h2)
    out = np.zeros((h, w, 3), np.uint8)
    out[:h1, :w1] = img1
    out[:h2, w1:w] = img2

    # define colors, according to min/max intensity values
    out_min = min(np.nanmin(img1), np.nanmin(img2))
    out_max = max(np.nanmax(img1), np.nanmax(img2))
    green = [out_min, out_max, out_min]
    blue = [out_min, out_min, out_max]

    # plot the matches
    for i in range(len(matches)):
        x1 = matches[i, 0]
        y1 = matches[i, 1]
        x2 = matches[i, 2] + w1
        y2 = matches[i, 3]
        # convert endpoints to int (nn interpolation)
        x1, y1, x2, y2 = list(map(int, np.round([x1, y1, x2, y2])))
        plot_line(out, x1, y1, x2, y2, blue)
        try:
            out[y1, x1] = green
            out[y2, x2] = green
        except IndexError:
            pass
    # save the output image, and return its path
    outfile = common.tmpfile('.png')
    common.rasterio_write(outfile, out)
    return outfile
Exemple #6
0
    for i in range(len(matches)):
        x1 = matches[i, 0]
        y1 = matches[i, 1]
        x2 = matches[i, 2] + w1
        y2 = matches[i, 3]
        # convert endpoints to int (nn interpolation)
        x1, y1, x2, y2 = list(map(int, np.round([x1, y1, x2, y2])))
        plot_line(out, x1, y1, x2, y2, blue)
        try:
            out[y1, x1] = green
            out[y2, x2] = green
        except IndexError:
            pass
    # save the output image, and return its path
    outfile = common.tmpfile('.png')
    common.rasterio_write(outfile, out)
    return outfile


def plot_matches(im1, im2, rpc1, rpc2, matches, x=None, y=None, w=None, h=None,
                 outfile=None):
    """
    Plot matches on Pleiades images

    Args:
        im1, im2: paths to full Pleiades images
        rpc1, rpc2: two  instances of the RPCModel class, or paths to xml files
            containing the rpc coefficients
        matches: 2D numpy array of size 4xN containing a list of matches (a
            list of pairs of points, each pair being represented by x1, y1, x2,
            y2). The coordinates are given in the frame of the full images.
Exemple #7
0
def tiles_full_info(tw, th, tiles_txt, create_masks=False):
    """
    List the tiles to process and prepare their output directories structures.

    Most of the time is spent discarding tiles that are masked by water
    (according to exogenous dem).

    Returns:
        a list of dictionaries. Each dictionary contains the image coordinates
        and the output directory path of a tile.
    """
    rpc = cfg['images'][0]['rpcm']
    roi_msk = cfg['images'][0]['roi']
    cld_msk = cfg['images'][0]['cld']
    wat_msk = cfg['images'][0]['wat']
    rx = cfg['roi']['x']
    ry = cfg['roi']['y']
    rw = cfg['roi']['w']
    rh = cfg['roi']['h']

    # list of dictionaries (one for each non-masked tile)
    tiles = []

    # list tiles coordinates
    tiles_coords, neighborhood_coords_dict = compute_tiles_coordinates(
        rx, ry, rw, rh, tw, th)

    if create_masks or not os.path.exists(tiles_txt):
        print('\ndiscarding masked tiles...')
        images_sizes = []
        for img in cfg['images']:
            with rasterio.open(img['img'], 'r') as f:
                images_sizes.append(f.shape)

        # compute all masks in parallel as numpy arrays
        tiles_usefulnesses = parallel.launch_calls(is_this_tile_useful,
                                                   tiles_coords,
                                                   cfg['max_processes'],
                                                   images_sizes,
                                                   tilewise=False,
                                                   timeout=cfg['timeout'])

        # discard useless tiles from neighborhood_coords_dict
        discarded_tiles = set(
            x for x, (b, _) in zip(tiles_coords, tiles_usefulnesses) if not b)
        for k, v in neighborhood_coords_dict.items():
            neighborhood_coords_dict[k] = list(set(v) - discarded_tiles)

        for coords, usefulness in zip(tiles_coords, tiles_usefulnesses):

            useful, mask = usefulness
            if not useful:
                continue

            tile = create_tile(coords, neighborhood_coords_dict)
            tiles.append(tile)

            # make tiles directories and store json configuration dumps
            common.mkdir_p(tile['dir'])
            for i in range(1, len(cfg['images'])):
                common.mkdir_p(os.path.join(tile['dir'], 'pair_{}'.format(i)))

            # save a json dump of the tile configuration
            tile_cfg = copy.deepcopy(cfg)
            x, y, w, h = tile['coordinates']
            for img in tile_cfg['images']:
                img.pop('rpcm', None)
            tile_cfg['roi'] = {'x': x, 'y': y, 'w': w, 'h': h}
            tile_cfg['full_img'] = False
            tile_cfg['max_processes'] = 1
            tile_cfg['neighborhood_dirs'] = tile['neighborhood_dirs']
            tile_cfg['out_dir'] = '../../..'

            with open(os.path.join(cfg['out_dir'], tile['json']), 'w') as f:
                json.dump(tile_cfg, f, indent=2, default=workaround_json_int64)

            # save the mask
            common.rasterio_write(os.path.join(tile['dir'], 'mask.png'),
                                  mask.astype(np.uint8))
    else:
        if len(tiles_coords) == 1:
            tiles.append(create_tile(tiles_coords[0],
                                     neighborhood_coords_dict))
        else:
            with open(tiles_txt, 'r') as f_tiles:
                for config_json in f_tiles:
                    tile = {}
                    with open(
                            os.path.join(cfg['out_dir'],
                                         config_json.rstrip(os.linesep)),
                            'r') as f_config:
                        tile_cfg = json.load(f_config)
                        roi = tile_cfg['roi']
                        coords = roi['x'], roi['y'], roi['w'], roi['h']
                        tiles.append(
                            create_tile(coords, neighborhood_coords_dict))

    return tiles
Exemple #8
0
def tiles_full_info(tw, th, tiles_txt, create_masks=False):
    """
    List the tiles to process and prepare their output directories structures.

    Most of the time is spent discarding tiles that are masked by water
    (according to exogenous dem).

    Returns:
        a list of dictionaries. Each dictionary contains the image coordinates
        and the output directory path of a tile.
    """
    rpc = cfg['images'][0]['rpc']
    roi_msk = cfg['images'][0]['roi']
    cld_msk = cfg['images'][0]['cld']
    wat_msk = cfg['images'][0]['wat']
    rx = cfg['roi']['x']
    ry = cfg['roi']['y']
    rw = cfg['roi']['w']
    rh = cfg['roi']['h']

    # build a tile dictionary for all non-masked tiles and store them in a list
    tiles = []
    # list tiles coordinates
    tiles_coords, neighborhood_coords_dict = compute_tiles_coordinates(
        rx, ry, rw, rh, tw, th)

    if os.path.exists(tiles_txt) is False or create_masks is True:
        print('\ndiscarding masked tiles...')
        # compute all masks in parallel as numpy arrays
        tiles_masks = parallel.launch_calls_simple(
            masking.cloud_water_image_domain, tiles_coords,
            cfg['max_processes'], rpc, roi_msk, cld_msk, wat_msk)

        for coords, mask in zip(tiles_coords, tiles_masks):
            if mask.any():  # there's at least one non-masked pixel in the tile
                tile = create_tile(coords, neighborhood_coords_dict)
                tiles.append(tile)

                # make tiles directories and store json configuration dumps
                common.mkdir_p(tile['dir'])
                for i in range(1, len(cfg['images'])):
                    common.mkdir_p(
                        os.path.join(tile['dir'], 'pair_{}'.format(i)))

                # save a json dump of the tile configuration
                tile_cfg = copy.deepcopy(cfg)
                x, y, w, h = tile['coordinates']
                tile_cfg['roi'] = {'x': x, 'y': y, 'w': w, 'h': h}
                tile_cfg['full_img'] = False
                tile_cfg['max_processes'] = 1
                tile_cfg['neighborhood_dirs'] = tile['neighborhood_dirs']
                tile_cfg['out_dir'] = '../../..'

                with open(os.path.join(cfg['out_dir'], tile['json']),
                          'w') as f:
                    json.dump(tile_cfg,
                              f,
                              indent=2,
                              default=workaround_json_int64)

                # save the mask
                common.rasterio_write(
                    os.path.join(tile['dir'],
                                 'cloud_water_image_domain_mask.png'),
                    mask.astype(np.uint8))
    else:
        if len(tiles_coords) == 1:
            tiles.append(create_tile(tiles_coords[0],
                                     neighborhood_coords_dict))
        else:
            with open(tiles_txt, 'r') as f_tiles:
                for config_json in f_tiles:
                    tile = {}
                    with open(
                            os.path.join(cfg['out_dir'],
                                         config_json.rstrip(os.linesep)),
                            'r') as f_config:
                        tile_cfg = json.load(f_config)
                        roi = tile_cfg['roi']
                        coords = roi['x'], roi['y'], roi['w'], roi['h']
                        tiles.append(
                            create_tile(coords, neighborhood_coords_dict))

    return tiles