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
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