예제 #1
0
    def __call__(self, sample):
        """
        Args:
            sample (ndarray): Image to be scaled.

        Returns:
            ndarray: Scaled image.
        """
        out_dtype = sample['metadata']['dtype']
        orig_range = self.range_values_raster(sample['sat_img'], out_dtype)
        sample['sat_img'] = minmax_scale(img=sample['sat_img'], orig_range=orig_range, scale_range=(self.sc_min, self.sc_max))

        return sample
예제 #2
0
def image_reader_as_array(input_image,
                          scale=None,
                          aux_vector_file=None,
                          aux_vector_attrib=None,
                          aux_vector_ids=None,
                          aux_vector_dist_maps=False,
                          aux_vector_dist_log=True,
                          aux_vector_scale=None):
    """Read an image from a file and return a 3d array (h,w,c)
    Args:
        input_image: Rasterio file handle holding the (already opened) input raster
        scale: optional scaling factor for the raw data
        aux_vector_file: optional vector file from which to extract auxiliary shapes
        aux_vector_attrib: optional vector file attribute name to parse in order to fetch ids
        aux_vector_ids: optional vector ids to target in the vector file above
        aux_vector_dist_maps: flag indicating whether aux vector bands should be distance maps or binary maps
        aux_vector_dist_log: flag indicating whether log distances should be used in distance maps or not
        aux_vector_scale: optional floating point scale factor to multiply to rasterized vector maps

    Return:
        numpy array of the image (possibly concatenated with auxiliary vector channels)
    """
    np_array = np.empty(
        [input_image.height, input_image.width, input_image.count],
        dtype=np.float32)
    for i in tqdm(
            range(input_image.count),
            position=1,
            leave=False,
            desc=f'Reading image bands: {Path(input_image.files[0]).stem}'):
        np_array[:, :, i] = input_image.read(
            i + 1
        )  # Bands starts at 1 in rasterio not 0  # TODO: reading a large image >10Gb is VERY slow. Is this line the culprit?

    # Guidelines for pre-processing: http://cs231n.github.io/neural-networks-2/#datapre
    # Scale array values from range [0,255] to values in config (e.g. [0,1])
    if scale:
        sc_min, sc_max = scale
        assert np.min(np_array) >= 0 and np.max(np_array) <= 255, f'Values in input image of shape {np_array.shape} ' \
                                                                  f'range from {np.min(np_array)} to {np.max(np_array)}.' \
                                                                  f'They should range from 0 to 255 (8bit).'
        np_array = minmax_scale(img=np_array,
                                orig_range=(0, 255),
                                scale_range=(sc_min, sc_max))

    # if requested, load vectors from external file, rasterize, and append distance maps to array
    if aux_vector_file is not None:
        vec_tensor = vector_to_raster(vector_file=aux_vector_file,
                                      input_image=input_image,
                                      attribute_name=aux_vector_attrib,
                                      fill=0,
                                      target_ids=aux_vector_ids,
                                      merge_all=False)
        if aux_vector_dist_maps:
            import cv2 as cv  # opencv becomes a project dependency only if we need to compute distance maps here
            vec_tensor = vec_tensor.astype(np.float32)
            for vec_band_idx in range(vec_tensor.shape[2]):
                mask = vec_tensor[:, :, vec_band_idx]
                mask = cv.dilate(
                    mask,
                    (3, 3))  # make points and linestring easier to work with
                #display_resize = cv.resize(np.where(mask, np.uint8(0), np.uint8(255)), (1000, 1000))
                #cv.imshow("mask", display_resize)
                dmap = cv.distanceTransform(
                    np.where(mask, np.uint8(0), np.uint8(255)), cv.DIST_L2,
                    cv.DIST_MASK_PRECISE)
                if aux_vector_dist_log:
                    dmap = np.log(dmap + 1)
                #display_resize = cv.resize(cv.normalize(dmap, None, 0, 1, cv.NORM_MINMAX, dtype=cv.CV_32F), (1000, 1000))
                #cv.imshow("dmap1", display_resize)
                dmap_inv = cv.distanceTransform(
                    np.where(mask, np.uint8(255), np.uint8(0)), cv.DIST_L2,
                    cv.DIST_MASK_PRECISE)
                if aux_vector_dist_log:
                    dmap_inv = np.log(dmap_inv + 1)
                #display_resize = cv.resize(cv.normalize(dmap_inv, None, 0, 1, cv.NORM_MINMAX, dtype=cv.CV_32F), (1000, 1000))
                #cv.imshow("dmap2", display_resize)
                vec_tensor[:, :,
                           vec_band_idx] = np.where(mask, -dmap_inv, dmap)
                #display = cv.normalize(vec_tensor[:, :, vec_band_idx], None, 0, 1, cv.NORM_MINMAX, dtype=cv.CV_32F)
                #display_resize = cv.resize(display, (1000, 1000))
                #cv.imshow("distmap", display_resize)
                #cv.waitKey(0)
        if aux_vector_scale:
            for vec_band_idx in vec_tensor.shape[2]:
                vec_tensor[:, :, vec_band_idx] *= aux_vector_scale
        np_array = np.concatenate([np_array, vec_tensor], axis=2)
    return np_array
def vis(params, input, output, vis_path, sample_num=0, label=None, dataset='', ep_num=0, inference_input_path=False, debug=False):
    '''saves input, output and label (if given) as .png in a grid or as individual pngs
    :param params: parameters from .yaml config file
    :param input: (tensor) input array as pytorch tensor, e.g. as returned by dataloader
    :param output: (tensor) output array as pytorch tensor before argmax, e.g. as returned by dataloader
    :param vis_path: path where visualisation images will be saved
    :param sample_num: index of sample if function is from for loop iterating through a batch or list of images.
    :param label: (tensor) label array as pytorch tensor, e.g. as returned by dataloader. Optional.
    :param dataset: (str) name of dataset arrays belong to. For file-naming purposes only.
    :param ep_num: (int) number of epoch arrays are inputted from. For file-naming purposes only.
    :param inference_input_path: (Path) path to input image on which inference is being performed. If given, turns «inference» bool to True below.
    :return: saves color images from input arrays as grid or as full scale .png
    '''
    inference = True if inference_input_path else False
    scale = get_key_def('scale_data', params['global'], None)
    colormap_file = get_key_def('colormap_file', params['visualization'], None)
    heatmaps = get_key_def('heatmaps', params['visualization'], False)
    grid = get_key_def('grid', params['visualization'], False)
    ignore_index = get_key_def('ignore_index', params['training'], -1)
    mean = get_key_def('mean', params['training']['normalization'])
    std = get_key_def('std', params['training']['normalization'])

    assert vis_path.parent.is_dir()
    vis_path.mkdir(exist_ok=True)

    if not inference:  # FIXME: function parameters should not come in as different types if inference or not.
        input = input.cpu().permute(1, 2, 0).numpy()  # channels last
        output = F.softmax(output, dim=0)  # Inference output is already softmax
        output = output.detach().cpu().permute(1, 2, 0).numpy()  # channels last
        if label is not None:
            label = label.cpu()
            if ignore_index < 0: # TODO: test when ignore_index is smaller than 1.
                warnings.warn('Choose 255 as ignore_index to visualize. Problems may occur otherwise...')
                new_ignore_index = 255
                label[label == ignore_index] = new_ignore_index  # Convert all pixels with ignore_index values to 255 to make sure it is last in order of values.

    if params['training']['normalization']['mean'] and params['training']['normalization']['std']:
        input = unnormalize(input_img=input, mean=mean, std=std)
    input = minmax_scale(img=input, orig_range=(scale[0], scale[1]), scale_range=(0, 255)) if scale else input
    if input.shape[2] == 2:
        input = input[:, :, :1]  # take first band (will become grayscale image)
    elif input.shape[2] > 3:
        input = input[:, :, :3]  # take three first bands assuming they are RGB in correct order
    mode = 'L' if input.shape[2] == 1 else 'RGB' # https://pillow.readthedocs.io/en/3.1.x/handbook/concepts.html#concept-modes
    input_PIL = Image.fromarray(input.astype(np.uint8), mode=mode) # TODO: test this with grayscale input.

    # Give value of class to band with highest value in final inference
    output_argmax = np.argmax(output, axis=2).astype(np.uint8) # Flatten along channels axis. Convert to 8bit

    # Define colormap and names of classes with respect to grayscale values
    classes, cmap = colormap_reader(output, colormap_file, default_colormap='Set1')

    heatmaps_dict = heatmaps_to_dict(output, classes, inference=inference, debug=debug)  # Prepare heatmaps from softmax output

    # Convert output and label, if provided, to RGB with matplotlib's colormap object
    output_argmax_color = cmap(output_argmax)
    output_argmax_PIL = Image.fromarray((output_argmax_color[:, :, :3] * 255).astype(np.uint8), mode='RGB')
    if not inference and label is not None:
        label_color = cmap(label)
        label_PIL = Image.fromarray((label_color[:, :, :3] * 255).astype(np.uint8), mode='RGB')
    else:
        label_PIL = None

    if inference:
        if debug and len(np.unique(output_argmax)) == 1:
            warnings.warn(f'Inference contains only {np.unique(output_argmax)} value. Make sure data scale '
                          f'{scale} is identical with scale used for training model.')
        output_name = vis_path.joinpath(f"{inference_input_path.stem}_inference.tif")
        create_new_raster_from_base(inference_input_path, output_name, output_argmax)
        if get_key_def('heatmaps', params['inference'], False):
            for key in heatmaps_dict.keys():
                heatmap = np.array(heatmaps_dict[key]['heatmap_PIL'])
                class_name = heatmaps_dict[key]['class_name']
                heatmap_name = vis_path.joinpath(f"{inference_input_path.stem}_inference_heatmap_{class_name}.tif")
                create_new_raster_from_base(inference_input_path, heatmap_name, heatmap)
    elif grid:  # SAVE PIL IMAGES AS GRID
        grid = grid_vis(input_PIL, output_argmax_PIL, heatmaps_dict, label=label_PIL, heatmaps=heatmaps)
        grid.savefig(vis_path.joinpath(f'{dataset}_{sample_num:03d}_ep{ep_num:03d}.png'))
        plt.close()
    else:  # SAVE PIL IMAGES DIRECTLY TO FILE
        if not vis_path.joinpath(f'{dataset}_{sample_num:03d}_satimg.jpg').is_file():
            input_PIL.save(vis_path.joinpath(f'{dataset}_{sample_num:03d}_satimg.jpg'))
            if not inference and label is not None:
                label_PIL.save(vis_path.joinpath(f'{dataset}_{sample_num:03d}_label.png')) # save label
        output_argmax_PIL.save(vis_path.joinpath(f'{dataset}_{sample_num:03d}_output_ep{ep_num:03d}.png'))
        if heatmaps: # TODO: test this.
            for key in heatmaps_dict.keys():
                heatmap = heatmaps_dict[key]['heatmap_PIL']
                class_name = heatmaps_dict[key]['class_name']
                heatmap.save(vis_path.joinpath(f"{dataset}_{sample_num:03d}_output_ep{ep_num:03d}_heatmap_{class_name}.png"))  # save heatmap