Пример #1
0
def main(args=None):
    # parse arguments
    if args is None:
        args = sys.argv[1:]
    args = parse_args(args)

    # Set modified tf session to avoid using the GPUs
    keras.backend.tensorflow_backend.set_session(get_session())

    # optionally load config parameters
    anchor_parameters = None
    if args.config:
        args.config = read_config_file(args.config)
        if 'anchor_parameters' in args.config:
            anchor_parameters = parse_anchor_parameters(args.config)

    # load the model
    model = models.load_model(args.model_in, backbone_name=args.backbone)

    # check if this is indeed a training model
    models.check_training_model(model)

    # convert the model
    model = models.convert_model(
        model,
        nms=args.nms,
        class_specific_filter=args.class_specific_filter,
        anchor_params=anchor_parameters)

    # save model
    model.save(args.model_out)
Пример #2
0
def main(args=None):
    # parse arguments
    if args is None:
        args = sys.argv[1:]
    args = parse_args(args)

    # optionally load config parameters
    if args.config:
        args.config = read_config_file(args, 'evaluation')
        #print("----------------------------------")
        #print("ARGUMENTS IN CONFIG FILE:")
        #for sec in args.config.sections():
        #print(sec, "=", dict(args.config.items(sec)))
        #print("----------------------------------")

    # for arg in vars(args):
    #     print(arg, "=", getattr(args, arg))
    # exit()

    # make sure keras is the minimum required version
    check_keras_version()

    # optionally choose specific GPU
    if args.gpu:
        os.environ['CUDA_VISIBLE_DEVICES'] = args.gpu
    keras.backend.tensorflow_backend.set_session(get_session())

    # make save path if it doesn't exist
    if args.save_path is not None and not os.path.exists(args.save_path):
        os.makedirs(args.save_path)

    # create the generator
    generator = create_generator(args)

    # optionally load anchor parameters
    anchor_params = None
    if args.config and 'anchor_parameters' in args.config:
        anchor_params = parse_anchor_parameters(args.config)

    # load the model
    print('Loading model, this may take a second...')
    model = models.load_model(args.model, backbone_name=args.backbone)

    # optionally convert the model
    if args.convert_model:
        model = models.convert_model(model, anchor_params=anchor_params)

    # print model summary
    # print(model.summary())

    # layer_outputs = []
    # layer_names = ['res2c_relu',                # C2
    #                'res3b3_relu',               # C3
    #                'res4b22_relu',              # C4
    #                'P2',                        # P2
    #                'P3',                        # P3
    #                'P4',                        # P4
    #                # 'regression_submodel',       # Subreg
    #                # 'classification_submodel',   # SubClas
    #                'regression',                # Regression
    #                'classification']            # Classification
    #
    # for layer in model.layers:
    #     if layer.name in layer_names:
    #         print('------------------------------------------------------------------------------------------------------------------')
    #         print('Layer found: ', layer.name)
    #         print('\tOutput:', layer.output)
    #         print('------------------------------------------------------------------------------------------------------------------')
    #         layer_outputs.append(layer.output)
    #
    # image = preprocess_image(generator.load_image(0))
    # image, scale = resize_image(image, args.image_min_side, args.image_max_side)
    #
    # activation_model = keras.Model(inputs=model.input, outputs=layer_outputs)
    # activations = activation_model.predict(np.expand_dims(image, axis=0))
    #
    # def display_activation(activations, col_size, row_size, act_index):
    #     activation = activations[act_index]
    #     activation_index=0
    #     fig, ax = plt.subplots(row_size, col_size, figsize=(row_size*2.5,col_size*1.5))
    #     for row in range(0,row_size):
    #         for col in range(0,col_size):
    #             ax[row][col].imshow(activation[0, :, :, activation_index], cmap='gray')
    #             activation_index += 1
    #             plt.savefig('layer_{}.png'.format(layer_names[act_index]))
    #
    # display_activation(activations, 8, 8, 0)
    # display_activation(activations, 8, 8, 1)
    # display_activation(activations, 8, 8, 2)
    # display_activation(activations, 8, 8, 3)
    # display_activation(activations, 8, 8, 4)
    # display_activation(activations, 8, 8, 5)
    #
    # exit()

    # start evaluation
    if args.dataset_type == 'coco':
        from ..utils.coco_eval import evaluate_coco
        evaluate_coco(generator, model, args.score_threshold)
    else:
        average_precisions = evaluate(generator,
                                      model,
                                      iou_threshold=args.iou_threshold,
                                      score_threshold=args.score_threshold,
                                      max_detections=args.max_detections,
                                      save_path=args.save_path,
                                      mask_base_path=args.mask_folder)

        # print evaluation
        total_instances = []
        precisions = []
        F1s = []
        for label, (recall, precision, F1, average_precision,
                    num_annotations) in average_precisions.items():
            print('{:.0f} instances of class'.format(num_annotations),
                  generator.label_to_name(label),
                  'with average precision: {:.4f}'.format(average_precision),
                  'precision: {:.4f}'.format(precision),
                  'recall: {:.4f}'.format(recall),
                  'and F1-score: {:.4f}'.format(F1))
            total_instances.append(num_annotations)
            precisions.append(average_precision)
            F1s.append(F1)

        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)))
        print('mAP: {:.4f}'.format(
            sum(precisions) / sum(x > 0 for x in total_instances)))
        print('mF1: {:.4f}'.format(
            sum(F1s) / sum(x > 0 for x in total_instances)))
    #     **common_args
    # )
    generator = PascalVocGenerator('datasets/VOC2007',
                                   'test',
                                   shuffle_groups=False,
                                   skip_truncated=False,
                                   skip_difficult=True,
                                   **common_args)
    model_path = 'snapshots/2019-08-25/resnet101_pascal_07_0.7352.h5'
    # load retinanet model
    import keras.backend as K
    K.clear_session()
    K.set_learning_phase(1)
    model = models.load_model(model_path, backbone_name='resnet101')
    # if the model is not converted to an inference model, use the line below
    # see: https://github.com/fizyr/keras-retinanet#converting-a-training-model-to-inference-model
    model = models.convert_model(model)
    average_precisions = evaluate(generator, model, epoch=0)
    # compute per class average precision
    total_instances = []
    precisions = []
    for label, (average_precision,
                num_annotations) in average_precisions.items():
        print('{:.0f} instances of class'.format(num_annotations),
              generator.label_to_name(label),
              'with average precision: {:.4f}'.format(average_precision))
        total_instances.append(num_annotations)
        precisions.append(average_precision)
    mean_ap = sum(precisions) / sum(x > 0 for x in total_instances)
    print('mAP: {:.4f}'.format(mean_ap))
Пример #4
0
def main(args=None):
    # parse arguments
    if args is None:
        args = sys.argv[1:]
    args = parse_args(args)

    # make sure keras is the minimum required version
    check_keras_version()

    # optionally choose specific GPU
    if args.gpu:
        os.environ['CUDA_VISIBLE_DEVICES'] = args.gpu
    keras.backend.tensorflow_backend.set_session(get_session())

    # make save path if it doesn't exist
    if args.save_path is not None and not os.path.exists(args.save_path):
        os.makedirs(args.save_path)

    # optionally load config parameters
    if args.config:
        args.config = read_config_file(args.config)

    # create the generator
    generator = create_generator(args)

    # optionally load anchor parameters
    anchor_params = None
    if args.config and 'anchor_parameters' in args.config:
        anchor_params = parse_anchor_parameters(args.config)

    # load the model
    print('Loading model, this may take a second...')
    model = models.load_model(args.model, backbone_name=args.backbone)

    # optionally convert the model
    if args.convert_model:
        model = models.convert_model(model, anchor_params=anchor_params)

    # print model summary
    # print(model.summary())

    # start evaluation
    if args.dataset_type == 'coco':
        from ..utils.coco_eval import evaluate_coco
        evaluate_coco(generator, model, args.score_threshold)
    else:
        average_precisions = evaluate(generator,
                                      model,
                                      iou_threshold=args.iou_threshold,
                                      score_threshold=args.score_threshold,
                                      max_detections=args.max_detections,
                                      save_path=args.save_path)

        # print evaluation
        total_instances = []
        precisions = []
        for label, (average_precision,
                    num_annotations) in average_precisions.items():
            print('{:.0f} instances of class'.format(num_annotations),
                  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)))
        print('mAP: {:.4f}'.format(
            sum(precisions) / sum(x > 0 for x in total_instances)))

        return precisions, total_instances
Пример #5
0
    def produce(self,
                *,
                inputs: Inputs,
                timeout: float = None,
                iterations: int = None) -> CallResult[Outputs]:
        """
        Produce image detection predictions.

        Parameters
        ----------
            inputs  : numpy ndarray of size (n_images, dimension) containing the d3m Index, image name, 
                      and bounding box for each image.

        Returns
        -------
            outputs : A d3m dataframe container with the d3m index, image name, bounding boxes as 
                      a string (8 coordinate format), and confidence scores.
        """
        iou_threshold = 0.5  # Bounding box overlap threshold for false positive or true positive
        score_threshold = 0.05  # The score confidence threshold to use for detections
        max_detections = 100  # Maxmimum number of detections to use per image

        # create the generator
        generator = self._create_generator(self.annotations,
                                           self.classes,
                                           shuffle_groups=False)

        # Convert training model to inference model
        inference_model = models.convert_model(self.training_model)

        # Assemble output lists
        ## Generate predicted bounding boxes (8-coordinate format, list)
        boxes, scores = self._evaluate_model(generator, inference_model,
                                             iou_threshold, score_threshold,
                                             max_detections,
                                             self.hyperparams['output'])

        ## Convert predicted boxes from a list of arrays to a list of strings
        boxes = np.array(boxes).tolist()
        boxes = list(map(lambda x: ",".join(map(str, x)), boxes))

        ## Generate list of image names and d3m indices corresponding to predicted bounding boxes
        img_list = [
            os.path.basename(list)
            for list in self.annotations['img_file'].tolist()
        ]
        d3m_idx = inputs.d3mIndex.tolist()

        print(len(d3m_idx), file=sys.__stdout__)
        print(len(img_list), file=sys.__stdout__)
        print(len(boxes), file=sys.__stdout__)
        print(len(scores), file=sys.__stdout__)

        ## Assemble in a Pandas DataFrame
        results = pd.DataFrame({
            'd3mIndex': d3m_idx,
            'image': img_list,
            'bounding_box': boxes,
            'confidence': scores
        })

        # Convert to DataFrame container
        results_df = d3m_DataFrame(results)

        ## Assemble first output column ('d3mIndex)
        col_dict = dict(
            results_df.metadata.query((metadata_base.ALL_ELEMENTS, 0)))
        col_dict['structural_type'] = type("1")
        col_dict['name'] = 'd3mIndex'
        col_dict['semantic_types'] = (
            'http://schema.org/Integer',
            'https://metadata.datadrivendiscovery.org/types/PrimaryKey')
        results_df.metadata = results_df.metadata.update(
            (metadata_base.ALL_ELEMENTS, 0), col_dict)

        ## Assemble second output column ('image')
        col_dict = dict(
            results_df.metadata.query((metadata_base.ALL_ELEMENTS, 1)))
        col_dict['structural_type'] = type("1")
        col_dict['name'] = 'image'
        col_dict['semantic_types'] = (
            'http://schema.org/Text',
            'https://metadata.datadrivendiscovery.org/types/Attribute')
        results_df.metadata = results_df.metadata.update(
            (metadata_base.ALL_ELEMENTS, 1), col_dict)

        ## Assemble third output column ('bounding_box')
        col_dict = dict(
            results_df.metadata.query((metadata_base.ALL_ELEMENTS, 2)))
        col_dict['structural_type'] = type("1")
        col_dict['name'] = 'bounding_box'
        col_dict['semantic_types'] = (
            'http://schema.org/Text',
            'https://metadata.datadrivendiscovery.org/types/PredictedTarget',
            'https://metadata.datadrivendiscovery.org/types/BoundingPolygon')
        results_df.metadata = results_df.metadata.update(
            (metadata_base.ALL_ELEMENTS, 2), col_dict)

        ## Assemble fourth output column ('confidence')
        col_dict = dict(
            results_df.metadata.query((metadata_base.ALL_ELEMENTS, 3)))
        col_dict['structural_type'] = type("1")
        col_dict['name'] = 'confidence'
        col_dict['semantic_types'] = (
            'http://schema.org/Integer',
            'https://metadata.datadrivendiscovery.org/types/Score')
        results_df.metadata = results_df.metadata.update(
            (metadata_base.ALL_ELEMENTS, 3), col_dict)

        return CallResult(results_df)
def main():
    cfg, args = _parse_args()
    torch.manual_seed(args.seed)

    output_base = cfg.OUTPUT_DIR if len(cfg.OUTPUT_DIR) > 0 else './output'
    exp_name = '-'.join([
        datetime.now().strftime("%Y%m%d-%H%M%S"), cfg.MODEL.ARCHITECTURE,
        str(cfg.INPUT.IMG_SIZE)
    ])
    output_dir = get_outdir(output_base, exp_name)
    with open(os.path.join(output_dir, 'config.yaml'), 'w',
              encoding='utf-8') as file_writer:
        # cfg.dump(stream=file_writer, default_flow_style=False, indent=2, allow_unicode=True)
        file_writer.write(pyaml.dump(cfg))
    logger = setup_logger(file_name=os.path.join(output_dir, 'train.log'),
                          control_log=False,
                          log_level='INFO')

    # create model
    model = create_model(cfg.MODEL.ARCHITECTURE,
                         num_classes=cfg.MODEL.NUM_CLASSES,
                         pretrained=True,
                         in_chans=cfg.INPUT.IN_CHANNELS,
                         drop_rate=cfg.MODEL.DROP_RATE,
                         drop_connect_rate=cfg.MODEL.DROP_CONNECT,
                         global_pool=cfg.MODEL.GLOBAL_POOL)

    os.environ['CUDA_VISIBLE_DEVICES'] = args.gpu
    gpu_list = list(map(int, args.gpu.split(',')))
    device = 'cuda'
    if len(gpu_list) == 1:
        model.cuda()
        torch.backends.cudnn.benchmark = True
    elif len(gpu_list) > 1:
        model = nn.DataParallel(model, device_ids=gpu_list)
        model = convert_model(model).cuda()
        torch.backends.cudnn.benchmark = True
    else:
        device = 'cpu'
    logger.info('device: {}, gpu_list: {}'.format(device, gpu_list))

    optimizer = create_optimizer(cfg, model)

    # optionally initialize from a checkpoint
    if args.initial_checkpoint and os.path.isfile(args.initial_checkpoint):
        load_checkpoint(model, args.initial_checkpoint)

    # optionally resume from a checkpoint
    resume_state = None
    resume_epoch = None
    if args.resume and os.path.isfile(args.resume):
        resume_state, resume_epoch = resume_checkpoint(model, args.resume)
    if resume_state and not args.no_resume_opt:
        if 'optimizer' in resume_state:
            optimizer.load_state_dict(resume_state['optimizer'])
            logger.info('Restoring optimizer state from [{}]'.format(
                args.resume))

    start_epoch = 0
    if args.start_epoch is not None:
        start_epoch = args.start_epoch
    elif resume_epoch is not None:
        start_epoch = resume_epoch

    model_ema = None
    if cfg.SOLVER.EMA:
        # Important to create EMA model after cuda()
        model_ema = ModelEma(model,
                             decay=cfg.SOLVER.EMA_DECAY,
                             device=device,
                             resume=args.resume)

    lr_scheduler, num_epochs = create_scheduler(cfg, optimizer)
    if lr_scheduler is not None and start_epoch > 0:
        lr_scheduler.step(start_epoch)

    # summary
    print('=' * 60)
    print(cfg)
    print('=' * 60)
    print(model)
    print('=' * 60)
    summary(model, (3, cfg.INPUT.IMG_SIZE, cfg.INPUT.IMG_SIZE))

    # dataset
    dataset_train = Dataset(cfg.DATASETS.TRAIN)
    dataset_valid = Dataset(cfg.DATASETS.TEST)
    train_loader = create_loader(dataset_train, cfg, is_training=True)
    valid_loader = create_loader(dataset_valid, cfg, is_training=False)

    # loss function
    if cfg.SOLVER.LABEL_SMOOTHING > 0:
        train_loss_fn = LabelSmoothingCrossEntropy(
            smoothing=cfg.SOLVER.LABEL_SMOOTHING).to(device)
        validate_loss_fn = nn.CrossEntropyLoss().to(device)
    else:
        train_loss_fn = nn.CrossEntropyLoss().to(device)
        validate_loss_fn = train_loss_fn

    eval_metric = cfg.SOLVER.EVAL_METRIC
    best_metric = None
    best_epoch = None
    saver = CheckpointSaver(
        checkpoint_dir=output_dir,
        recovery_dir=output_dir,
        decreasing=True if eval_metric == 'loss' else False)
    try:
        for epoch in range(start_epoch, num_epochs):
            train_metrics = train_epoch(epoch,
                                        model,
                                        train_loader,
                                        optimizer,
                                        train_loss_fn,
                                        cfg,
                                        logger,
                                        lr_scheduler=lr_scheduler,
                                        saver=saver,
                                        device=device,
                                        model_ema=model_ema)

            eval_metrics = validate(epoch, model, valid_loader,
                                    validate_loss_fn, cfg, logger)

            if model_ema is not None:
                ema_eval_metrics = validate(epoch, model_ema.ema, valid_loader,
                                            validate_loss_fn, cfg, logger)
                eval_metrics = ema_eval_metrics

            if lr_scheduler is not None:
                # step LR for next epoch
                lr_scheduler.step(epoch + 1, eval_metrics[eval_metric])

            update_summary(epoch,
                           train_metrics,
                           eval_metrics,
                           os.path.join(output_dir, 'summary.csv'),
                           write_header=best_metric is None)

            if saver is not None:
                # save proper checkpoint with eval metric
                save_metric = eval_metrics[eval_metric]
                best_metric, best_epoch = saver.save_checkpoint(
                    model,
                    optimizer,
                    cfg,
                    epoch=epoch,
                    model_ema=model_ema,
                    metric=save_metric)

    except KeyboardInterrupt:
        pass
    if best_metric is not None:
        logger.info('*** Best metric: {0} (epoch {1})'.format(
            best_metric, best_epoch))
Пример #7
0
def main(args=None):

    iou_thres_vec = []
    if sys.argv[1:] == []:
        print('Please give at least one value for IoU threshold.')
        exit(0)
    for v in sys.argv[1:]:
        iou_thres_vec.append(float(v))
    print('Doing job for IoU values:', iou_thres_vec)

    # make sure keras is the minimum required version
    check_keras_version()

    # define base paths
    model_base_path = '/path/snapshots/'
    data_base_path = '/path/dataset/'
    out_imgs_base_path = '/path/out_imgs/'

    # define number of experiments and k-folds
    experiments = ['1']  #,'2','3','4']
    kfolds = ['1']  #,'2','3','4','5']

    # loop for each experiment and k-fold
    for exp in experiments:
        # set experiment name
        if exp == '1': data_name = 'cns_stratified'
        elif exp == '2': data_name = 'cns_unseen_split'
        elif exp == '3': data_name = 'all_stratified'
        elif exp == '4': data_name = 'all_unseen_split'

        for kf in kfolds:
            # set names
            model_name = 'resnet101_fpn4_1000_sc4_ar3_cycLRexp-8_allDataAugm_350-450_exp' + exp + '_k' + kf
            model_path = model_base_path + model_name + '/resnet101_ivm.h5'
            data_path = data_base_path + 'image_sets/' + data_name + '/k_' + kf
            save_path = out_imgs_base_path + model_name

            # copy data image_set to data_base_path
            files = os.listdir(data_path)
            for f in files:
                if f.endswith('.txt'):
                    f = data_path + '/' + f
                    dst = data_base_path + 'image_sets/'
                    # print('File copied\n\tFROM:', f, '\n\tTO:', dst)
                    shutil.copy(f, dst)

            # optionally choose specific GPU
            os.environ['CUDA_VISIBLE_DEVICES'] = '0'
            keras.backend.tensorflow_backend.set_session(get_session())

            # load the model
            print('Loading model, this may take a second...')
            model = models.load_model(model_path, backbone_name='resnet101')

            # convert the model
            model = models.convert_model(model)
            print('Loaded.')

            # create the generator
            generator = IVMGenerator(data_base_path,
                                     'val',
                                     image_min_side=1000,
                                     image_max_side=1400)

            #
            # Grid search
            #
            md = 500
            nms_thres_vec = np.arange(.05, 1., .05)
            score_thres_vec = np.arange(.05, 1., .05)

            # output values
            outputs = []

            # varying params
            for it in iou_thres_vec:
                for nt in nms_thres_vec:
                    for st in score_thres_vec:
                        evaluate_model(generator=generator,
                                       model=model,
                                       nt=nt,
                                       it=it,
                                       st=st,
                                       md=md,
                                       save_path=None,
                                       mask='/path/dataset/all/masks',
                                       output=outputs)

            # save in data frame format
            df = pd.DataFrame(data=outputs)

            # ensure directory created first and save file
            makedirs(save_path)
            out_path = save_path + '/params_search.csv'
            df.to_csv(out_path, index=None, header=True)

            #
            # Get best set of parameters
            #
            # compute measure
            avg_measure = (df['f1_score'] + df['average_precision']) / 2
            df['avg_measure'] = avg_measure

            # set best param values
            best = df.iloc[df['avg_measure'].idxmax()]
            md = best['max_detections'].astype(int)
            it = best['iou_threshold']
            nt = best['nms_threshold']
            st = best['score_threshold']

            # create the generator for test dataset
            generator = IVMGenerator(data_base_path,
                                     'test',
                                     image_min_side=1000,
                                     image_max_side=1400)

            # perform inference in test dataset
            outputs = []

            # varying params
            evaluate_model(generator=generator,
                           model=model,
                           nt=nt,
                           it=it,
                           st=st,
                           md=md,
                           save_path=save_path,
                           mask='/path/dataset/all/masks',
                           output=outputs)

            # save in data frame format
            df = pd.DataFrame(data=outputs)
            out_path = save_path + '/best_output.csv'
            df.to_csv(out_path, index=None, header=True)
    def produce(self, *, inputs: Inputs, timeout: float = None, iterations: int = None) -> CallResult[Outputs]:
        """
        Produce image detection predictions.

        Parameters
        ----------
            inputs  : numpy ndarray of size (n_images, dimension) containing the d3m Index, image name, 
                      and bounding box for each image.

        Returns
        -------
            outputs : A d3m dataframe container with the d3m index, image name, bounding boxes as 
                      a string (8 coordinate format), and confidence scores.
        """
        iou_threshold = 0.5     # Bounding box overlap threshold for false positive or true positive
        score_threshold = 0.05  # The score confidence threshold to use for detections
        max_detections = 100    # Maxmimum number of detections to use per image

        # Convert training model to inference model
        inference_model = models.convert_model(self.training_model)

        # Generate image paths
        image_cols = inputs.metadata.get_columns_with_semantic_type('https://metadata.datadrivendiscovery.org/types/FileName')
        self.base_dir = [inputs.metadata.query((metadata_base.ALL_ELEMENTS, t))['location_base_uris'][0].replace('file:///', '/') for t in image_cols]
        self.image_paths = np.array([[os.path.join(self.base_dir, filename) for filename in inputs.iloc[:,col]] for self.base_dir, col in zip(self.base_dir, image_cols)]).flatten()
        self.image_paths = pd.Series(self.image_paths)

        # Initialize output objects
        box_list = []
        score_list = []
        image_name_list = []

        # Predict bounding boxes and confidence scores for each image
        image_list = [x for i, x in enumerate(self.image_paths.tolist()) if self.image_paths.tolist().index(x) == i]

        start_time = time.time()
        print('Starting testing...', file = sys.__stdout__)

        for i in image_list:
            image = read_image_bgr(i)

            # preprocess image for network
            image = preprocess_image(image)
            image, scale = resize_image(image)

            boxes, scores, labels = inference_model.predict_on_batch(np.expand_dims(image, axis = 0))

            # correct for image scale
            boxes /= scale

            for box, score in zip(boxes[0], scores[0]):
                if score < 0.5:
                    break
    
                b = box.astype(int)
                box_list.append(b)
                score_list.append(score)
                image_name_list.append(i * len(b))

        print(f'Testing complete. Testing took {time.time()-start_time} seconds.', file = sys.__stdout__)
        
        ## Convert predicted boxes from a list of arrays to a list of strings
        boxes = np.array(box_list).tolist()
        boxes = list(map(lambda x : [x[0], x[1], x[0], x[3], x[2], x[3], x[2], x[1]], boxes))  # Convert to 8 coordinate format for D3M            
        boxes = list(map(lambda x : ",".join(map(str, x)), boxes))

        # Create mapping between image names and D3M index
        input_df = pd.DataFrame({
            'd3mIndex': inputs.d3mIndex,
            'image': [os.path.basename(list) for list in self.image_paths]
        })

        d3mIdx_image_mapping = input_df.set_index('image').T.to_dict('list')

        # Extract values for image name keys and get missing image predictions (if they exist)
        image_name_list = [os.path.basename(list) for list in image_name_list]
        d3mIdx = [d3mIdx_image_mapping.get(key) for key in image_name_list]
        empty_predictions_image_names = [k for k,v in d3mIdx_image_mapping.items() if v not in d3mIdx]
        d3mIdx = [item for sublist in d3mIdx for item in sublist]   # Flatten list of lists

        ## Assemble in a Pandas DataFrame
        results = pd.DataFrame({
            'd3mIndex': d3mIdx,
            'bounding_box': boxes,
            'confidence': score_list
        })

        # D3M metrics evaluator needs at least one prediction per image. If RetinaNet does not return 
        # predictions for an image, create a dummy empty prediction row to add to results_df for that
        # missing image.
        if len(empty_predictions_image_names) != 0:
            # Create data frame of empty predictions for missing each image and concat with results.
            # Sort results_df.
            empty_predictions_df = self._fill_empty_predictions(empty_predictions_image_names, d3mIdx_image_mapping)
            results_df = pd.concat([results, empty_predictions_df]).sort_values('d3mIndex')

        # Convert to DataFrame container
        results_df = d3m_DataFrame(results_df)
        
        ## Assemble first output column ('d3mIndex)
        col_dict = dict(results_df.metadata.query((metadata_base.ALL_ELEMENTS, 0)))
        col_dict['structural_type'] = type("1")
        col_dict['name'] = 'd3mIndex'
        col_dict['semantic_types'] = ('http://schema.org/Integer', 
                                      'https://metadata.datadrivendiscovery.org/types/PrimaryKey')
        results_df.metadata = results_df.metadata.update((metadata_base.ALL_ELEMENTS, 0), col_dict)

        ## Assemble second output column ('bounding_box')
        col_dict = dict(results_df.metadata.query((metadata_base.ALL_ELEMENTS, 1)))
        col_dict['structural_type'] = type("1")
        col_dict['name'] = 'bounding_box'
        col_dict['semantic_types'] = ('http://schema.org/Text', 
                                      'https://metadata.datadrivendiscovery.org/types/PredictedTarget', 
                                      'https://metadata.datadrivendiscovery.org/types/BoundingPolygon')
        results_df.metadata = results_df.metadata.update((metadata_base.ALL_ELEMENTS, 1), col_dict)

        ## Assemble third output column ('confidence')
        col_dict = dict(results_df.metadata.query((metadata_base.ALL_ELEMENTS, 2)))
        col_dict['structural_type'] = type("1")
        col_dict['name'] = 'confidence'
        col_dict['semantic_types'] = ('http://schema.org/Integer', 
                                      'https://metadata.datadrivendiscovery.org/types/Score')
        results_df.metadata = results_df.metadata.update((metadata_base.ALL_ELEMENTS, 2), col_dict) 
        
        return CallResult(results_df)