def test_select_annotations(config, image): windows = preprocess.compute_windows(image, config["patch_size"], config["patch_overlap"]) image_annotations = pd.read_csv("tests/data/OSBS_029.csv") selected_annotations = preprocess.select_annotations(image_annotations, windows, index=7) # Returns a 5 column matrix assert selected_annotations.shape[0] == 17 # image name should be name of image plus the index .tif assert selected_annotations.image_path.unique()[0] == "OSBS_029_7.png"
def test_select_annotations_tile(config, image): config["patch_size"] = 50 windows = preprocess.compute_windows(image, config["patch_size"], config["patch_overlap"]) image_annotations = pd.read_csv("tests/data/OSBS_029.csv") selected_annotations = preprocess.select_annotations(image_annotations, windows, index=10) # The largest box cannot be off the edge of the window assert selected_annotations.xmin.min() >= 0 assert selected_annotations.ymin.min() >= 0 assert selected_annotations.xmax.max() <= config["patch_size"] assert selected_annotations.ymax.max() <= config["patch_size"]
def test_compute_windows(config, image): windows = preprocess.compute_windows(image, config["patch_size"], config["patch_overlap"]) assert len(windows) == 9
def predict_tile(self, raster_path=None, numpy_image=None, patch_size=400, patch_overlap=0.05, iou_threshold=0.15, return_plot=False, show=False): """For images too large to input into the model, predict_tile cuts the image into overlapping windows, predicts trees on each window and reassambles into a single array. Args: raster_path: Path to image on disk numpy_image (array): Numpy image array in BGR channel order following openCV convention patch_size: patch size default400, patch_overlap: patch overlap default 0.15, iou_threshold: Minimum iou overlap among predictions between windows to be suppressed. Defaults to 0.5. Lower values suppress more boxes at edges. return_plot: Should the image be returned with the predictions drawn? show (bool): Plot the predicted image with bounding boxes. Ignored if return_plot=False Returns: boxes (array): if return_plot, an image. Otherwise a numpy array of predicted bounding boxes, scores and labels """ if numpy_image is not None: pass else: # Load raster as image raster = Image.open(raster_path) numpy_image = np.array(raster) # Compute sliding window index windows = preprocess.compute_windows(numpy_image, patch_size, patch_overlap) # Save images to tmpdir predicted_boxes = [] for index, window in enumerate(tqdm(windows)): # Crop window and predict crop = numpy_image[windows[index].indices()] # Crop is RGB channel order, change to BGR crop = crop[..., ::-1] boxes = self.predict_image(numpy_image=crop, return_plot=False, score_threshold=self.config["score_threshold"]) # transform coordinates to original system xmin, ymin, xmax, ymax = windows[index].getRect() boxes.xmin = boxes.xmin + xmin boxes.xmax = boxes.xmax + xmin boxes.ymin = boxes.ymin + ymin boxes.ymax = boxes.ymax + ymin predicted_boxes.append(boxes) predicted_boxes = pd.concat(predicted_boxes) # Non-max supression for overlapping boxes among window if patch_overlap == 0: mosaic_df = predicted_boxes else: with tf.Session() as sess: print( "{} predictions in overlapping windows, applying non-max supression". format(predicted_boxes.shape[0])) new_boxes, new_scores, new_labels = predict.non_max_suppression( sess, predicted_boxes[["xmin", "ymin", "xmax", "ymax"]].values, predicted_boxes.score.values, predicted_boxes.label.values, max_output_size=predicted_boxes.shape[0], iou_threshold=iou_threshold) # Recreate box dataframe image_detections = np.concatenate([ new_boxes, np.expand_dims(new_scores, axis=1), np.expand_dims(new_labels, axis=1) ], axis=1) mosaic_df = pd.DataFrame( image_detections, columns=["xmin", "ymin", "xmax", "ymax", "score", "label"]) if mosaic_df.shape[0] == 0: print("No predictions made, returning None") return None mosaic_df.label = mosaic_df.label.str.decode("utf-8") print("{} predictions kept after non-max suppression".format( mosaic_df.shape[0])) if return_plot: # Draw predictions for box in mosaic_df[["xmin", "ymin", "xmax", "ymax"]].values: draw_box(numpy_image, box, [0, 0, 255]) if show: plt.imshow(numpy_image[:, :, ::-1]) plt.show() # Mantain consistancy with predict_image return numpy_image else: return mosaic_df
def predict_tile(model, device, raster_path=None, image=None, patch_size=400, patch_overlap=0.05, iou_threshold=0.15, return_plot=False, mosaic=True, use_soft_nms=False, sigma=0.5, thresh=0.001, color=None, thickness=1): """For images too large to input into the model, predict_tile cuts the image into overlapping windows, predicts trees on each window and reassambles into a single array. Args: model: pytorch model device: pytorch device of 'cuda' or 'cpu' for gpu prediction. Set internally. numeric_to_label_dict: dictionary in which keys are numeric integers and values are character labels raster_path: Path to image on disk image (array): Numpy image array in BGR channel order following openCV convention patch_size: patch size default400, patch_overlap: patch overlap default 0.15, iou_threshold: Minimum iou overlap among predictions between windows to be suppressed. Defaults to 0.14. Lower values suppress more boxes at edges. return_plot: Should the image be returned with the predictions drawn? mosaic: If true, return a single annotations dataframe. If false, return a list of windows and predictions use_soft_nms: whether to perform Gaussian Soft NMS or not, if false, default perform NMS. sigma: variance of Gaussian function used in Gaussian Soft NMS thresh: the score thresh used to filter bboxes after soft-nms performed Returns: boxes (array): if return_plot, an image. windows (list): if mosaic = False, a two item tuple for each window of patch_size with predictions Otherwise a numpy array of predicted bounding boxes, scores and labels """ if image is not None: pass else: # load raster as image image = rio.open(raster_path).read() image = np.moveaxis(image, 0, 2) # Compute sliding window index windows = preprocess.compute_windows(image, patch_size, patch_overlap) predicted_boxes = [] crops = [] for index, window in enumerate(tqdm(windows)): # crop window and predict crop = image[windows[index].indices()] crop = crop.astype('float32') # crop is RGB channel order, change to BGR? boxes = predict_image(model=model, image=crop, return_plot=False, device=device) if boxes is not None: if mosaic: # transform the coordinates to original system xmin, ymin, xmax, ymax = windows[index].getRect() boxes.xmin = boxes.xmin + xmin boxes.xmax = boxes.xmax + xmin boxes.ymin = boxes.ymin + ymin boxes.ymax = boxes.ymax + ymin else: crops.append(crop) predicted_boxes.append(boxes) if len(predicted_boxes) == 0: print("No predictions made, returning None") return None if mosaic: predicted_boxes = pd.concat(predicted_boxes) # Non-max supression for overlapping boxes among window if patch_overlap == 0: mosaic_df = predicted_boxes else: print( f"{predicted_boxes.shape[0]} predictions in overlapping windows, applying non-max supression" ) # move prediciton to tensor boxes = torch.tensor( predicted_boxes[["xmin", "ymin", "xmax", "ymax"]].values, dtype=torch.float32) scores = torch.tensor(predicted_boxes.score.values, dtype=torch.float32) labels = predicted_boxes.label.values if not use_soft_nms: # Performs non-maximum suppression (NMS) on the boxes according to # their intersection-over-union (IoU). bbox_left_idx = nms(boxes=boxes, scores=scores, iou_threshold=iou_threshold) else: # Performs soft non-maximum suppression (soft-NMS) on the boxes. bbox_left_idx = soft_nms(boxes=boxes, scores=scores, sigma=sigma, thresh=thresh) bbox_left_idx = bbox_left_idx.numpy() new_boxes, new_labels, new_scores = boxes[bbox_left_idx].type( torch.int), labels[bbox_left_idx], scores[bbox_left_idx] # Recreate box dataframe image_detections = np.concatenate([ new_boxes, np.expand_dims(new_labels, axis=1), np.expand_dims(new_scores, axis=1) ], axis=1) mosaic_df = pd.DataFrame( image_detections, columns=["xmin", "ymin", "xmax", "ymax", "label", "score"]) print( f"{mosaic_df.shape[0]} predictions kept after non-max suppression" ) if return_plot: # Draw predictions on BGR image = image[:, :, ::-1] image = visualize.plot_predictions(image, mosaic_df, color=color, thickness=thickness) # Mantain consistancy with predict_image return image else: return mosaic_df else: return list(zip(predicted_boxes, crops))
import numpy as np from deepforest import preprocess from preProcess import pre_process_image_in_folder test_model = deepforest.deepforest() test_model.use_release() #predict image pre_process_image_in_folder("./data", "./processed") images = os.listdir('./processed') for image in images: image_path = os.path.join("./processed", image) raster = cv2.imread(image_path) numpy_image = np.array(raster) windows = preprocess.compute_windows(numpy_image, patch_size=400, patch_overlap=0.1) for window in windows: crop = numpy_image[window.indices()] img = test_model.predict_image(numpy_image=crop, return_plot=True, score_threshold=0.05) #Show image, matplotlib expects RGB channel order, but keras-retinanet predicts in BGR plt.imshow(img[..., ::-1]) plt.show() #raster = cv2.imread("./processed/IITK1.JPG") #numpy_image = np.array(raster) #windows = preprocess.compute_windows(numpy_image, patch_size=400,patch_overlap=0.1)