def snow_analysis(self, seg_model):
        """
        Complete snow analysis for each image of the directory and store the result in a text file.

        :param seg_model: segmentation model
        :type seg_model: keras.model
        """

        # Variable initialization
        self.snowy_list = []

        # Creation of directory to store results
        list_dir_path = os.path.join(self.img_dir, "Lists")
        list_dir_path = safe_folder_creation(list_dir_path)

        # Complete snow analysis for each image of the directory
        pbar = progressbar.ProgressBar()
        for img_filename in pbar(self.images_list):
            img_path = os.path.join(self.img_dir, img_filename)
            img = Ci.Image(img_path,
                           build_array=True,
                           build_snow_segmentation=True,
                           snow_seg_model=seg_model)

            img.get_global_key_from_filename()
            # img.show_image_snow_segmentation()

            if img.is_snowy():
                self.snowy_list.append(img_filename)

        # Save in to files
        write_list_to_textfile(
            self.snowy_list, os.path.join(list_dir_path, "21-Snowy_list.txt"))
def simple_training(data_left, data_right, data_label, model_function,
                    model_function_args, folder_path, val_split, epochs,
                    batch_size):
    """
    Train a siamese model and store results in a folder.

    :param data_left: left images array
    :type data_left: np.array
    :param data_right: right images array
    :type data_right: np.array
    :param data_label: labels array
    :type data_label: np.array
    :param model_function: function which build the model to train with k fold
    :type model_function: function
    :param model_function_args: arguments of the model building function
    :type model_function_args: list
    :param folder_path: path of the folder where results are stored
    :type folder_path: str
    :param val_split: set proportion dedicated to the validation set
    :type val_split: float
    :param epochs: number of training epochs
    :type epochs: int
    :param batch_size: batch size
    :type batch_size: int
    :return:
    :rtype:
    """
    # Create folder to store results
    folder_path = safe_folder_creation(folder_path)

    # Build model
    conv_model = model_function(*model_function_args)
    train_data = np.stack([data_left, data_right], 0)  #[data_left, data_right]

    # Train model
    model_fitted = conv_model.fit(train_data,
                                  data_label,
                                  epochs=epochs,
                                  batch_size=batch_size,
                                  validation_split=val_split)

    # Save model and plots
    model_path = os.path.join(folder_path, 'fitted_model.h5')
    conv_model.save(model_path)
    plot_validation_info(model_fitted, folder_path)

    # Save also weights and structure for backup
    weights_path = os.path.join(folder_path, 'weights.h5')
    conv_model.save_weights(weights_path)
    json_path = os.path.join(folder_path, 'structure.json')
    save_structure_in_json(conv_model, json_path)
Beispiel #3
0
    def create_images_from_mapillary_panos(self):
        """
        Create for 4 simple images for all the downloaded Mapillary panoramas.

        """
        # Creation of directory
        self.mapillary_dir_images_from_pano = safe_folder_creation(self.mapillary_dir_images_from_pano)

        # Display advancement of processing
        pbar = progressbar.ProgressBar()
        panos = os.listdir(self.mapillary_dir_pano)
        for pano in pbar(panos):
            # Create instance of panorama and its 4 images
            p = Panorama(os.path.join(self.mapillary_dir_pano, pano))
            p.create_4_images(self.mapillary_dir_images_from_pano)

        # Print results
        self.nb_mapillary_from_pano = 4 * len(panos)
        print("Number of images produced : {}".format(self.nb_mapillary_from_pano))
def save_hyperas_results(best_model, best_run, result_folder, data, model):
    """
    Create folder and file containing results of the Hyperas hyperparameters optimization.

    :param best_model: best model found
    :type best_model: keras.Model
    :param best_run: dictionary of parameters of the best model
    :type best_run: dict
    :param result_folder:
    :type result_folder:
    :param data: loading data function
    :type data: function
    :param model: creation model function
    :type model: function
    """

    # Create result folder
    result_folder = safe_folder_creation(result_folder)

    # Save best model
    best_model.save(os.path.join(result_folder, "best_model.h5"))

    # Save also weights and structure for backup
    weights_path = os.path.join(result_folder, 'weights.h5')
    best_model.save_weights(weights_path)
    json_path = os.path.join(result_folder, 'structure.json')
    save_structure_in_json(best_model, json_path)

    # Save model function an used data in a text file
    s = "Grid search parameters Hyperas \n"
    s += "\nData function code:\n\n" + inspect.getsource(data)
    s += "\nModel source code:\n\n" + inspect.getsource(model)
    s += "\nHyperas results:\n\n" + json.dumps(best_run)

    # Save in a file
    with open(os.path.join(result_folder, 'Hyperas_params.txt'), "w+") as f:
        f.write(s)
def k_fold(data_left, data_right, data_label, k, model_function, model_function_args, folder_path, epochs,
           batch_size):
    """
    Execute a K-fold cross validation for a model.

    :param data_left: left images array
    :type data_left: np.array
    :param data_right: right images array
    :type data_right: np.array
    :param data_label: labels array
    :type data_label: np.array
    :param k: number of fold
    :type k: int
    :param model_function: function which build the model to train with k fold
    :type model_function: function
    :param model_function_args: arguments of the model building function
    :type model_function_args: list
    :param folder_path: path of the folder where results are stored
    :type folder_path: str
    :param epochs: number of training epochs
    :type epochs: int
    :param batch_size: batch size
    :type batch_size: int
    """

    # Variable initialization
    nb_comp = len(data_label)
    num_val_samples = nb_comp // k
    all_scores = []

    # Create folder to store results
    folder_path = safe_folder_creation(folder_path)

    # Train model for each fold
    for i in range(k):
        print('Processing fold #', i)

        # Select validation set
        val_left = data_left[i * num_val_samples: (i + 1) * num_val_samples]
        val_right = data_right[i * num_val_samples: (i + 1) * num_val_samples]
        val_data = [val_left, val_right]
        val_label = data_label[i * num_val_samples: (i + 1) * num_val_samples]

        # Get the remaining data to create the training set
        partial_train_left = np.concatenate(
            [data_left[:i * num_val_samples], data_left[(i + 1) * num_val_samples:]])
        partial_train_right = np.concatenate(
            [data_right[:i * num_val_samples], data_right[(i + 1) * num_val_samples:]])
        partial_train_data = [partial_train_left, partial_train_right]
        partial_train_label = np.concatenate(
            [data_label[:i * num_val_samples], data_label[(i + 1) * num_val_samples:]])

        # Build model
        bk.clear_session()
        model = model_function(*model_function_args)

        # Train model
        model_fitted = model.fit(partial_train_data, partial_train_label, validation_data=[val_data, val_label],
                                 epochs=epochs, batch_size=batch_size, verbose=1)

        # Evaluate model and store result
        val_mse, val_mae = model.evaluate(val_data, val_label, verbose=1)
        all_scores.append(val_mae)

        # Save model and plots
        model_path = os.path.join(folder_path, 'fitted_model_k{}.h5'.format(i + 1))
        model.save(model_path)
        plot_validation_info_kfold(model_fitted, i + 1, folder_path)

        # Save also weights and structure for backup
        weights_path = os.path.join(folder_path, 'weights_k{}.h5'.format(i + 1))
        model.save_weights(weights_path)
        json_path = os.path.join(folder_path, 'structure_k{}.json'.format(i + 1))
        save_structure_in_json(model, json_path)

    # Save K-fold summary in a text file
    filename = os.path.join(folder_path, "k_fold_report.txt")
    summary = k_fold_summary(k, model_function, model_function_args, nb_comp, num_val_samples, all_scores, filename)
    print(summary)
    def green_analysis(self, green_seg_model, green_ratio, snow_seg_model):
        """
        Execute all the following analyses at the same time to gain computation time:

            - overexposure
            - exposure from histogram
            - blurriness
            - under green ratio
            - snow detection

        :param green_seg_model: segmentation model for green ratio analysis
        :type green_seg_model: keras.model
        :param green_ratio: threshold value for green ratio
        :type green_ratio: float
        :param snow_seg_model: segmentation model for snow detection
        :type snow_seg_model: keras.model
        """

        # Variable initialization
        self.green_ratio = green_ratio

        # Creation of directories to store results
        list_dir_path = os.path.join(self.img_dir, "Lists")
        list_dir_path = safe_folder_creation(list_dir_path)

        # Complete analysis for each image of the directory
        pbar = progressbar.ProgressBar()
        for img_filename in pbar(self.images_list):
            img_path = os.path.join(self.img_dir, img_filename)
            img = Ci.Image(img_path,
                           build_array=True,
                           build_green_segmentation=True,
                           green_seg_model=green_seg_model,
                           build_snow_segmentation=True,
                           snow_seg_model=snow_seg_model)

            if img.is_overexposed():
                self.overexpose_list.append(img_filename)

            if img.is_wrong_exposed():
                self.exposure_histogram_list.append(img_filename)

            if img.is_under_green_ratio(green_ratio):
                self.under_ratio_list.append(img_filename)

            if img.is_blurry():
                self.blurry_list.append(img_filename)

        # Produce all and bad lists
        self.bad_list = list_from_lists(self.overexpose_list,
                                        self.exposure_histogram_list,
                                        self.under_ratio_list,
                                        self.blurry_list)
        self.good_list = list(set(self.images_list) - set(self.bad_list))

        # Save in to files
        write_list_to_textfile(self.blurry_list,
                               os.path.join(list_dir_path, "01-Blurry.txt"))
        write_list_to_textfile(
            self.exposure_histogram_list,
            os.path.join(list_dir_path, "02-Exposure_histogram.txt"))
        write_list_to_textfile(
            self.overexpose_list,
            os.path.join(list_dir_path, "03-Overexposed_images.txt"))
        write_list_to_textfile(
            self.under_ratio_list,
            os.path.join(list_dir_path,
                         "04-Under_Ratio_{}.txt".format(green_ratio)))
        write_list_to_textfile(
            self.bad_list, os.path.join(list_dir_path, "10-All_discarded.txt"))
        write_list_to_textfile(self.good_list,
                               os.path.join(list_dir_path, "11-All_good.txt"))
def create_input_from_comparisons_predictions(model_path, img_dir, nb_creation,
                                              save_folder):
    """
    Create new data input for ranking model by predicting them with the comparisons model.

    :param model_path: path of the trained comparisons model
    :type model_path: str
    :param img_dir: images directory path
    :type img_dir: str
    :param nb_creation: number of comparisons created
    :type nb_creation: int
    :param save_folder: path of the folder where the predictions dataset folder is stored
    :type save_folder: str
    :return: augmented dataset
    :rtype: tuple(np.array)
    """

    # Variable initialization
    images_path = glob.glob(img_dir + "/*jpg")
    nb_img = len(images_path) - 1
    train_left_pred = []
    train_right_pred = []
    train_label_pred = []
    train_label_score_pred = []

    # Create output folder
    pred_folder = safe_folder_creation(save_folder)

    # Load comparisons model
    comp_model = load_model(model_path)
    img_size = comp_model.input_shape[0][1]

    for _ in range(nb_creation):
        # Select randomly two images
        img_left = Ci.Image(images_path[random.randint(0, nb_img)],
                            build_array=True)
        img_right = Ci.Image(images_path[random.randint(0, nb_img)],
                             build_array=True)

        input_img_left = img_left.preprocess_image(img_size)
        input_img_right = img_right.preprocess_image(img_size)

        # Compute labels
        proba = comp_model.predict(
            [np.array([input_img_left]),
             np.array([input_img_right])])
        index_max = np.argmax(proba)
        jump_next_pred = False
        if index_max == 0:
            winner = "left"
        elif index_max == 1:
            winner = "right"
        else:
            jump_next_pred = True
            winner = "Draw"

        if not jump_next_pred:
            # Add labels to list
            train_label_pred.append(get_label_comparison_from_string(winner))
            train_label_score_pred.append(get_label_score_from_string(winner))

            # Add images to list
            train_left_pred.append(img_left.array)
            train_right_pred.append(img_right.array)

    # Convert to array
    train_left_pred = np.array(train_left_pred)
    train_right_pred = np.array(train_right_pred)
    train_label_pred = np.array(train_label_pred)
    train_label_score_pred = np.array(train_label_score_pred)
    train_data_pred = [train_left_pred, train_right_pred]

    # Save augmented training dataset as npy
    np.save(os.path.join(pred_folder, "train_left_{}".format(img_size)),
            train_left_pred)
    np.save(os.path.join(pred_folder, "train_right_{}".format(img_size)),
            train_right_pred)
    np.save(os.path.join(pred_folder, "train_labels{}".format(img_size)),
            train_label_pred)
    np.save(os.path.join(pred_folder, "train_labels_score{}".format(img_size)),
            train_label_score_pred)

    return train_data_pred, train_label_pred, train_label_score_pred
def data_aug(train_left, train_right, train_label, train_label_score, nb,
             save_folder):
    """
    Create nb augmented comparisons from one comparison of the set. Save results in a folder.

    :param train_left: left images array
    :type train_left: np.array
    :param train_right: right images array
    :type train_right: np.array
    :param train_label: comparisons labels array
    :type train_label: np.array
    :param train_label_score: ranking labels array
    :type train_label_score: np.array
    :param nb: number of augmented comparisons created for each original one
    :type nb: int
    :param save_folder: path of the folder where the augmented dataset folder is stored
    :type save_folder: str
    :return: augmented dataset
    :rtype: tuple(np.array)
    """

    # Create saving folder
    aug_folder = safe_folder_creation(os.path.join(save_folder))

    # Specify data generator parameters
    datagenargs = {
        'rotation_range': 2,
        'width_shift_range': 0.2,
        'height_shift_range': 0.2,
        'shear_range': 0.1,
        'zoom_range': 0.25,
        'horizontal_flip': True,
        'fill_mode': 'nearest'
    }

    #  Create generators
    left_datagen = ImageDataGenerator(**datagenargs)
    right_datagen = ImageDataGenerator(**datagenargs)

    # Initialization of data
    train_left_aug = list(train_left)
    train_right_aug = list(train_right)
    train_label_aug = list(train_label)
    train_label_score_aug = list(train_label_score)
    img_size = train_left[0].shape[0]

    # Display processing advancement
    print("Creating new inputs...")
    pbar = progressbar.ProgressBar()
    # Create nb augmented images from an original one
    for duel in pbar(range(len(train_label_score))):
        for _ in range(nb):
            # Create one augmented image from the left one
            ori_left_img = train_left[duel]
            left_img = ori_left_img.reshape((1, ) + ori_left_img.shape)
            aug_img = left_datagen.flow(left_img, batch_size=1)
            left_aug_img = aug_img[0].reshape(ori_left_img.shape)

            # Create one augmented image from the right one
            ori_right_img = train_right[duel]
            right_img = ori_right_img.reshape((1, ) + ori_right_img.shape)
            aug_img = right_datagen.flow(right_img, batch_size=1)
            right_aug_img = aug_img[0].reshape(ori_right_img.shape)

            # Add to list
            train_left_aug.append(left_aug_img)
            train_right_aug.append(right_aug_img)
            train_label_aug.append(train_label[duel])
            train_label_score_aug.append(train_label_score[duel])

    # Convert to array
    train_left_aug = np.array(train_left_aug)
    train_right_aug = np.array(train_right_aug)
    train_label_aug = np.array(train_label_aug)
    train_label_score_aug = np.array(train_label_score_aug)
    train_data_aug = [train_left_aug, train_right_aug]

    print("Done\nSaving data ...")
    # Save augmented training dataset as npy
    np.save(os.path.join(aug_folder, "train_left_{}".format(img_size)),
            np.array(train_left_aug))
    np.save(os.path.join(aug_folder, "train_right_{}".format(img_size)),
            np.array(train_right_aug))
    np.save(os.path.join(aug_folder, "train_labels{}".format(img_size)),
            np.array(train_label_aug))
    np.save(os.path.join(aug_folder, "train_labels_score{}".format(img_size)),
            np.array(train_label_score_aug))
    print("Done")

    return train_data_aug, train_label_aug, train_label_score_aug
def preprocessing_duels(csv_path, img_size, image_folder, save_folder, test):
    """
    Create the inputs of the comparison network from the comparisons csv and save them as npy

    A csv line has the following format:
        image_key_1, image_key_2, winner, ip_address
        the image keys are 22 character-long string
        winner is one of the 3 following string : left, right, equivalent
    :param csv_path: path of the comparisons csv
    :type csv_path: str
    :param img_size: input image size
    :type img_size: int
    :param image_folder: path of the image folder
    :type image_folder: str
    :param save_folder: path of the folder where npy are saved
    :type save_folder: str
    :param test: value to determine the size of the data dedicated to the test set.
                It can either be a proportion inside [0,1] or the number of comparisons kept aside.
    :type test: float
    :return:
    :rtype:
    """

    # List initialization
    left_images = []
    right_images = []
    labels = []
    labels_score = []
    print(csv_path)
    # Get data from csv
    with open(csv_path, 'r') as csvfileReader:
        reader = csv.reader(csvfileReader, delimiter=',')
        print("Creating inputs from csv ...")
        pbar = progressbar.ProgressBar()
        #for r in reader:
        #    print(r)

        for line in reader:  #pbar(reader):
            # Do not include No preference comparisons
            if line != [] and line[2] != 'No preference':
                # Create Image instances
                #print(line)
                left_image_path = get_filename_from_key(line[0], image_folder)
                right_image_path = get_filename_from_key(line[1], image_folder)
                #print(left_image_path)
                left_img = Ci.Image(left_image_path)
                right_img = Ci.Image(right_image_path)

                # Add images to list
                left_images.append(left_img.preprocess_image(img_size))
                right_images.append(right_img.preprocess_image(img_size))

                # Add labels to list
                labels.append(get_label_comparison_from_string(line[2]))
                labels_score.append(get_label_score_from_string(line[2]))

    # Compute number of comparisons kept for test set
    if len(labels) > test > 1:
        nb_test = int(test)
    elif 0 <= test <= 1:
        nb_test = int(test * len(labels))
    else:
        raise ValueError

    print("Done\nSaving test set ...")
    # Create test dataset
    test_left = np.array(left_images[:nb_test])
    test_right = np.array(right_images[:nb_test])
    test_labels = np.array(labels[:nb_test])
    test_labels_score = np.array(labels_score[:nb_test])

    # Save testing dataset as npy
    test_folder = os.path.join(save_folder, "test")
    test_folder = safe_folder_creation(test_folder)
    np.save(os.path.join(test_folder, "test_left_{}".format(img_size)),
            np.array(test_left))
    np.save(os.path.join(test_folder, "test_right_{}".format(img_size)),
            np.array(test_right))
    np.save(os.path.join(test_folder, "test_labels_{}".format(img_size)),
            np.array(test_labels))
    np.save(os.path.join(test_folder, "test_labels_score_{}".format(img_size)),
            np.array(test_labels_score))

    del test_left, test_right, test_labels, test_labels_score

    print("Done\nSaving train set ...")
    # Create training dataset
    train_left = np.array(left_images[nb_test:])
    train_right = np.array(right_images[nb_test:])
    train_labels = np.array(labels[nb_test:])
    train_labels_score = np.array(labels_score[nb_test:])

    # Save training dataset as npy
    train_folder = os.path.join(save_folder, "train")
    train_folder = safe_folder_creation(train_folder)
    np.save(os.path.join(train_folder, "train_left_{}".format(img_size)),
            np.array(train_left))
    del train_left
    np.save(os.path.join(train_folder, "train_right_{}".format(img_size)),
            np.array(train_right))
    del train_right
    np.save(os.path.join(train_folder, "train_labels_{}".format(img_size)),
            np.array(train_labels))
    np.save(
        os.path.join(train_folder, "train_labels_score_{}".format(img_size)),
        np.array(train_labels_score))
    print("Done")
Beispiel #10
0
    def download_gsv(self, gsv_key, start=0, end=-1):
        """
        Downloads Google Street View (GSV) images points in Ottawa from a shapefile layer.

        WARNING: the filename format is currently not compatible with the class Image definition.

        :param gsv_key: GSV API key
        :type gsv_key: str
        :param start: index of the feature of the layer from where download starts
        :type start: int
        :param end: index of the feature of the layer from where download ends
        :type end: int 
        """

        # Creation of directory
        self.gsv_dir = safe_folder_creation(self.gsv_dir)

        #  Convert layer file
        ds = ogr.Open(self.layer_path)
        layer = ds.GetLayer()

        # Determine the number of locations to download
        loc_max = len(layer)
        if start < end < len(layer):
            stop = end
        else:
            stop = loc_max
        n_loc = stop - start

        # Display advancement of downloading
        pbar = progressbar.ProgressBar()
        for i in pbar(range(start, stop)):
            # Get location
            feature = layer[i]
            lon = feature.GetGeometryRef().GetX()
            lat = feature.GetGeometryRef().GetY()

            # Get the closest panoramas from the location
            pano_id = streetview.panoids(lat, lon, closest=True)

            # Check if there is a pano
            if len(pano_id):
                # Create filename
                image_key = pano_id[0]["panoid"]
                if pano_id[0]["month"] < 10:
                    image_date = str(pano_id[0]["year"]) + "-0" + str(pano_id[0]["month"]) + "-01T00-00-00"
                else:
                    image_date = str(pano_id[0]["year"]) + "-" + str(pano_id[0]["month"]) + "-01T00-00-00"
                image_lon = "{0:.6f}".format(lon)
                image_lat = "{0:.6f}".format(lat)
                image_filename = '{}_{}_{}_{}'.format(image_lon, image_lat, image_key, image_date)
                # Download one image
                try:
                    streetview.api_download(image_key, 90, self.gsv_dir, gsv_key, fov=80, pitch=0,
                                            fname=image_filename)
                    self.nb_gsv += 1
                except Exception as err:
                    print(err)
                    print("Error on feature {}, lat = {}, lon = {} ".format(i, lat, lon))
                    continue

        # Display information
        print("Number of locations         : {}".format(n_loc))
        print("Number of images downloaded : {}".format(self.nb_gsv))
        print("Ratio : {}%".format((self.nb_gsv / n_loc) * 100))
Beispiel #11
0
    def download_mapillary(self, mapillary_key, start=0, end=-1):
        """
        Downloads Mapillary images of points from a ArcGis layer

        :param mapillary_key: Mapillary API key
        :type mapillary_key: str
        :param start: index of the feature of the layer from where download starts
        :type start: int
        :param end: index of the feature of the layer where download ends
        :type end: int
        """
        # Initialization of variables
        self.nb_mapillary_jpg = 0
        self.nb_mapillary_panos = 0
        cmp_err = 0
        err_list = []

        # Creation of directory
        self.mapillary_dir = safe_folder_creation(self.mapillary_dir)
        self.mapillary_dir_pano = safe_folder_creation(self.mapillary_dir_pano)

        # Convert layer file
        ds = ogr.Open(self.layer_path)
        layer = ds.GetLayer()

        # Determine the number of locations to download
        loc_max = len(layer)
        if start < end < len(layer):
            stop = end
        else:
            stop = loc_max
        n_loc = stop - start

        # Create a Mapillary Object
        mapillary = Mapillary(mapillary_key)

        # Display advancement of downloading
        pbar = progressbar.ProgressBar()
        for i in pbar(range(start, stop)):
            # Get location
            feature = layer[i]
            lon = feature.GetGeometryRef().GetX()
            lat = feature.GetGeometryRef().GetY()
            close_to = "{},{}".format(lon, lat)

            # Catch metadata search errors
            try:
                raw_json = mapillary.search_images(closeto=close_to,
                                                   per_page=1,
                                                   radius=10)
                raw_json_pano = mapillary.search_images(closeto=close_to,
                                                        per_page=1,
                                                        radius=10,
                                                        pano="true")
            except Exception as err:
                print(err)
                print("Error during metadata search on feature {}, lat = {}, lon = {} ".format(i, lat, lon))
                cmp_err += 1
                err_list.append(i)
                continue

            # Check if there is a image at this location and download it
            try:
                if len(raw_json['features']):
                    image_key = raw_json['features'][0]['properties']['key']
                    image_date = raw_json['features'][0]['properties']['captured_at'][:-5]
                    image_date = image_date.replace(":", "-")
                    image_lon = raw_json['features'][0]['geometry']['coordinates'][0]
                    image_lat = raw_json['features'][0]['geometry']['coordinates'][1]
                    image_lon = "{0:.6f}".format(image_lon)
                    image_lat = "{0:.6f}".format(image_lat)

                    image_filename = '{}_{}_{}_000_{}.jpg'.format(image_lon, image_lat, image_key, image_date)
                    image_path = os.path.join(self.mapillary_dir, image_filename)
                    download_image_by_key(image_key, 640, image_path)
                    self.nb_mapillary_jpg += 1
            except Exception as err:
                print(err)
                print("Error when downloading image on feature {}, lat = {}, lon = {} ".format(i, lat, lon))
                cmp_err += 1
                err_list.append(i)
                continue

            # Check if there is a pano at this location and download it
            try:
                if len(raw_json_pano['features']):
                    pano_key = raw_json_pano['features'][0]['properties']['key']
                    pano_date = raw_json_pano['features'][0]['properties']['captured_at'][:-5]
                    pano_date = pano_date.replace(":", "-")
                    pano_lon = raw_json_pano['features'][0]['geometry']['coordinates'][0]
                    pano_lat = raw_json_pano['features'][0]['geometry']['coordinates'][1]
                    pano_lon = "{0:.6f}".format(pano_lon)
                    pano_lat = "{0:.6f}".format(pano_lat)

                    pano_filename = '{}_{}_{}_999_{}.jpg'.format(pano_lon, pano_lat, pano_key, pano_date)
                    pano_path = os.path.join(self.mapillary_dir_pano, pano_filename)
                    download_image_by_key(pano_key, 2048, pano_path)
                    self.nb_mapillary_panos += 1
            except Exception as err:
                print(err)
                print("Error when downloading panorama on feature {}, lat = {}, lon = {} ".format(i, lat, lon))
                cmp_err += 1
                err_list.append(i)
                continue

        # Display information
        print("Number of locations            : {}".format(n_loc))
        print("Number of images downloaded    : {}".format(self.nb_mapillary_jpg))
        print("Number of panoramas downloaded : {}".format(self.nb_mapillary_panos))
        print("Number of errors caught : {}".format(cmp_err))
        print("List of features index with errors : {}".format(err_list))