def test_random_transform(annotations): test_model = deepforest.deepforest() test_model.config["random_transform"] = True classes_file = utilities.create_classes(annotations) arg_list = utilities.format_args(annotations, classes_file, test_model.config) assert "--random-transform" in arg_list
def config(): print("Configuring tfrecord tests") config = {} config["patch_size"] = 200 config["patch_overlap"] = 0.05 config["annotations_xml"] = get_data("OSBS_029.xml") config["rgb_dir"] = "data" config["annotations_file"] = "tests/data/OSBS_029.csv" config["path_to_raster"] =get_data("OSBS_029.tif") config["image-min-side"] = 800 config["backbone"] = "resnet50" #Create a clean config test data annotations = utilities.xml_to_annotations(xml_path=config["annotations_xml"]) annotations.to_csv("tests/data/tfrecords_OSBS_029.csv",index=False) annotations_file = preprocess.split_raster(path_to_raster=config["path_to_raster"], annotations_file="tests/data/tfrecords_OSBS_029.csv", base_dir= "tests/data/", patch_size=config["patch_size"], patch_overlap=config["patch_overlap"]) annotations_file.to_csv("tests/data/testfile_tfrecords.csv", index=False,header=False) class_file = utilities.create_classes("tests/data/testfile_tfrecords.csv") return config
def evaluate_generator(self, annotations, comet_experiment=None, iou_threshold=0.5, max_detections=200): """ Evaluate prediction model using a csv fit_generator Args: annotations (str): Path to csv label file, labels are in the format -> path/to/image.png,x1,y1,x2,y2,class_name iou_threshold(float): IoU Threshold to count for a positive detection (defaults to 0.5) max_detections (int): Maximum number of bounding box predictions comet_experiment(object): A comet experiment class objects to track Return: mAP: Mean average precision of the evaluated data """ #Format args for CSV generator classes_file = utilities.create_classes(annotations) arg_list = utilities.format_args(annotations, classes_file, self.config) args = parse_args(arg_list) #create generator validation_generator = CSVGenerator( args.annotations, args.classes, image_min_side=args.image_min_side, image_max_side=args.image_max_side, config=args.config, shuffle_groups=False, ) average_precisions = evaluate(validation_generator, self.prediction_model, iou_threshold=iou_threshold, score_threshold=args.score_threshold, max_detections=max_detections, save_path=args.save_path, comet_experiment=comet_experiment) # print evaluation total_instances = [] precisions = [] for label, (average_precision, num_annotations) in average_precisions.items(): print('{:.0f} instances of class'.format(num_annotations), validation_generator.label_to_name(label), 'with average precision: {:.4f}'.format(average_precision)) total_instances.append(num_annotations) precisions.append(average_precision) if sum(total_instances) == 0: print('No test instances found.') return print('mAP using the weighted average of precisions among classes: {:.4f}'.format( sum([a * b for a, b in zip(total_instances, precisions)]) / sum(total_instances))) mAP = sum(precisions) / sum(x > 0 for x in total_instances) print('mAP: {:.4f}'.format(mAP)) return mAP
def from_retinanet(annotations_csv): model = deepforest.deepforest() model.use_release() """use the keras retinanet source to create detections""" ### Keras retinanet # Format args for CSV generator classes_file = utilities.create_classes(annotations_csv) arg_list = utilities.format_args(annotations_csv, classes_file, model.config) args = parse_args(arg_list) # create generator validation_generator = csv_generator.CSVGenerator( args.annotations, args.classes, image_min_side=args.image_min_side, image_max_side=args.image_max_side, config=args.config, shuffle_groups=False, ) all_detections = _get_detections(validation_generator, model.prediction_model, score_threshold=args.score_threshold, max_detections=100) all_annotations = _get_annotations(validation_generator) return all_detections, all_annotations, validation_generator
def test_format_args_steps(annotations, config): classes_file = utilities.create_classes(annotations) arg_list = utilities.format_args(annotations,classes_file, config, images_per_epoch=2) assert isinstance(arg_list, list) #A bit ugly, but since its a list, what is the argument after --steps to assert steps_position = np.where(["--steps" in x for x in arg_list])[0][0] + 1 assert arg_list[steps_position] == '2'
def test_retrain_release(annotations, release_model): release_model.config["epochs"] = 1 release_model.config["save-snapshot"] = False release_model.config["steps"] = 1 assert release_model.config["weights"] == release_model.weights #test that it gets passed to retinanet classes_file = utilities.create_classes(annotations) arg_list = utilities.format_args(annotations, classes_file, release_model.config, images_per_epoch=1) strs = ["--weights" == x for x in arg_list] index = np.where(strs)[0][0] + 1 assert arg_list[index] == release_model.weights
def train(self, annotations, input_type="fit_generator", list_of_tfrecords=None, comet_experiment=None, images_per_epoch=None): """Train a deep learning tree detection model using keras-retinanet. This is the main entry point for training a new model based on either existing weights or scratch. Args: annotations (str): Path to csv label file, labels are in the format -> path/to/image.png,x1,y1,x2,y2,class_name input_type: "fit_generator" or "tfrecord" list_of_tfrecords: Ignored if input_type != "tfrecord", list of tf records to process comet_experiment: A comet ml object to log images. Optional. images_per_epoch: number of images to override default config of images in annotations file / batch size. Useful for debug Returns: model (object): A trained keras model prediction model: with bbox nms trained model: without nms """ # Test if there is a new classes file in case # of classes has changed. self.classes_file = utilities.create_classes(annotations) self.read_classes() arg_list = utilities.format_args(annotations, self.classes_file, self.config, images_per_epoch) print("Training retinanet with the following args {}".format(arg_list)) # Train model self.model, self.prediction_model, self.training_model = retinanet_train( forest_object=self, args=arg_list, input_type=input_type, list_of_tfrecords=list_of_tfrecords, comet_experiment=comet_experiment)
def predict_generator(self, annotations, comet_experiment=None, iou_threshold=0.5, max_detections=200, return_plot=False, color=None): """Predict bounding boxes for a model using a csv fit_generator Args: annotations (str): Path to csv label file, labels are in the format -> path/to/image.png,x1,y1,x2,y2,class_name comet_experiment(object): A comet experiment class objects to track color: rgb color for the box annotations if return_plot is True e.g. (255,140,0) is orange. return_plot: Whether to return prediction boxes (False) or Images (True). If True, files will be written to current working directory if model.config["save_path"] is not defined. Return: boxes_output: If return_plot=False, a pandas dataframe of bounding boxes for each image in the annotations file None: If return_plot is True, images are written to save_dir as a side effect. """ # Format args for CSV generator classes_file = utilities.create_classes(annotations) arg_list = utilities.format_args(annotations, classes_file, self.config) args = parse_args(arg_list) # create generator generator = CSVGenerator( args.annotations, args.classes, image_min_side=args.image_min_side, image_max_side=args.image_max_side, config=args.config, shuffle_groups=False, ) if self.prediction_model: boxes_output = [] # For each image, gather predictions for i in range(generator.size()): # pass image as path plot_name = generator.image_names[i] image_path = os.path.join(generator.base_dir, plot_name) result = self.predict_image(image_path, return_plot=return_plot, score_threshold=args.score_threshold, color=color) if return_plot: if not self.config["save_path"]: print("model.config['save_path'] is None," "saving images to current working directory") save_path = "." else: save_path = self.config["save_path"] # Save image fname = os.path.join(save_path, plot_name) cv2.imwrite(fname, result) continue else: # Turn boxes to pandas frame and save output box_df = pd.DataFrame(result) # use only plot name, not extension box_df["plot_name"] = os.path.splitext(plot_name)[0] boxes_output.append(box_df) else: raise ValueError( "No prediction model loaded. Either load a retinanet from file, " "download the latest release or train a new model") if return_plot: return None else: # if boxes, name columns and return box data boxes_output = pd.concat(boxes_output) boxes_output.columns = [ "xmin", "ymin", "xmax", "ymax", "score", "label", "plot_name" ] boxes_output = boxes_output.reindex( columns=["plot_name", "xmin", "ymin", "xmax", "ymax", "score", "label"]) return boxes_output
def predict_generator(self, annotations, comet_experiment=None, iou_threshold=0.5, max_detections=200): """Predict bounding boxes for a model using a csv fit_generator Args: annotations (str): Path to csv label file, labels are in the format -> path/to/image.jpg,x1,y1,x2,y2,class_name iou_threshold(float): IoU Threshold to count for a positive detection (defaults to 0.5) max_detections (int): Maximum number of bounding box predictions comet_experiment(object): A comet experiment class objects to track Return: boxes_output: a pandas dataframe of bounding boxes for each image in the annotations file """ #Format args for CSV generator classes_file = utilities.create_classes(annotations) arg_list = utilities.format_args(annotations, classes_file, self.config) args = parse_args(arg_list) #create generator generator = CSVGenerator( args.annotations, args.classes, image_min_side=args.image_min_side, image_max_side=args.image_max_side, config=args.config, shuffle_groups=False, ) if self.prediction_model: boxes_output = [] #For each image, gather predictions for i in range(generator.size()): #pass image as path plot_name = generator.image_names[i] image_path = os.path.join(generator.base_dir, plot_name) boxes = self.predict_image( image_path, return_plot=False, score_threshold=args.score_threshold) #Turn to pandas frame and save output box_df = pd.DataFrame(boxes) #use only plot name, not extension box_df["plot_name"] = os.path.splitext(plot_name)[0] boxes_output.append(box_df) else: raise ValueError( "No prediction model loaded. Either load a retinanet from file, download the latest release or train a new model" ) #name columns and return box data boxes_output = pd.concat(boxes_output) boxes_output.columns = [ "xmin", "ymin", "xmax", "ymax", "score", "label", "plot_name" ] boxes_output = boxes_output.reindex(columns=[ "plot_name", "xmin", "ymin", "xmax", "ymax", "score", "label" ]) return boxes_output
def test_format_args(annotations, config): classes_file = utilities.create_classes(annotations) arg_list = utilities.format_args(annotations, classes_file, config) assert isinstance(arg_list, list)
def test_create_classes(annotations): classes_file = utilities.create_classes(annotations_file=annotations) assert os.path.exists(classes_file)
#Prepare data #Load annotations file annotations = pd.read_csv( BASE_PATH + "pretraining/crops/pretraining.csv", names=["image_path", "xmin", "ymin", "xmax", "ymax", "label"]) #Select a set of n image annotations = annotations[annotations.image_path == "2019_DELA_5_423000_3601000_image_0.jpg"].copy() #Generate tfrecords annotations_file = BASE_PATH + "pretraining/crops/test.csv" annotations.to_csv(annotations_file, header=False, index=False) class_file = utilities.create_classes(annotations_file) tfrecords_path = tfrecords.create_tfrecords(annotations_file, class_file, size=1) print("Created {} tfrecords: {}".format(len(tfrecords_path), tfrecords_path)) inputs, targets = tfrecords.create_tensors(tfrecords_path) #### Fit generator ## comet_experiment = Experiment(api_key="ypQZhYfs3nSyKzOfz13iuJpj2", project_name="deepforest", workspace="bw4sz") comet_experiment.log_parameter("Type", "testing") comet_experiment.log_parameter("input_type", "fit_generator")
def generate_hand_annotations(DEBUG, BASE_PATH, FILEPATH, SIZE, config, dask_client): #Generate tfrecords dirname = "hand_annotations/" annotations_file = BASE_PATH + dirname + "crops/hand_annotations.csv" class_file = utilities.create_classes(annotations_file) if DEBUG: tfrecords.create_tfrecords(annotations_file=annotations_file, class_file=class_file, image_min_side=config["image-min-side"], backbone_model=config["backbone"], size=SIZE, savedir=FILEPATH + dirname + "tfrecords/") else: #Collect annotation files for each tile annotations_file= BASE_PATH + dirname + "crops/hand_annotations.csv" df = pd.read_csv(annotations_file, names=["image_path","xmin","ymin","xmax","ymax","label"]) #enforce dtype, as there might be errors df.xmin = df.xmin.astype(pd.Int64Dtype()) df.ymin = df.ymin.astype(pd.Int64Dtype()) df.xmax = df.xmax.astype(pd.Int64Dtype()) df.ymax = df.ymax.astype(pd.Int64Dtype()) #Randomize rows df = df.sample(frac=1) #split pandas frame into chunks images = df.image_path.unique() indices = np.arange(len(images)) size = 500 chunk_list = [ ] #Split dataframe into chunks of images and write to file for i in range(ceil(len(indices) / size)): image_indices = indices[i * size:(i * size) + size] selected_images = images[image_indices] split_frame = df[df.image_path.isin(selected_images)] filename = BASE_PATH + dirname + "crops/hand_annotations{}.csv".format(i) split_frame.to_csv(filename, header=False,index=False) chunk_list.append(filename) print(" Created {} files to create tfrecords".format(len(chunk_list))) #Apply create tfrecords to each futures = dask_client.map( tfrecords.create_tfrecords, chunk_list, class_file=class_file, image_min_side=config["image-min-side"], backbone_model=config["backbone"], size=SIZE, savedir=FILEPATH + dirname + "tfrecords/") wait(futures) for future in futures: try: local_annotations = future.result() except Exception as e: print("future {} failed with {}".format(future, e))