Exemple #1
0
def main(req: func.HttpRequest) -> func.HttpResponse:
    logging.info(
        'ML Professoar HTTP trigger function AddLabeledDataClient processed a request.'
    )

    try:
        # retrieve the parameters from the multi-part form http request
        image_url = req.form.get('ImageUrl')
        image_labeling_json = req.form.get('DataLabels')
    except:
        return func.HttpResponse(
            "Please pass JSON containing the labeled regions associated with this image on the query string or in the request body.",
            status_code=400)

    # validate both parameters were passed into the function
    if image_url and image_labeling_json:
        labels = []
        count_of_labels_applied_to_image = 0

        endpoint = os.environ['ClientEndpoint']

        # Get Cognitive Services Environment Variables
        project_id = os.environ["ProjectID"]
        training_key = os.environ['TrainingKey']

        # load labeled image regions passed in request into dictionary
        image_labeling_data = json.loads(image_labeling_json)

        # instanciate custom vision client
        trainer = CustomVisionTrainingClient(training_key, endpoint=endpoint)

        # retrieve tags from project and loop through them to find tag ids for tags that need to be applied to the image
        tags = trainer.get_tags(project_id)
        image_label = image_labeling_data["label"]
        for tag in tags:
            if tag.name == image_label:
                labels.append(tag.id)
                count_of_labels_applied_to_image = count_of_labels_applied_to_image + 1
                break

        # create the image from a url and attach the appropriate tags as labels.
        upload_result = trainer.create_images_from_urls(
            project_id, [ImageUrlCreateEntry(url=image_url, tag_ids=labels)])
        if upload_result.is_batch_successful:
            return func.HttpResponse(
                str(count_of_labels_applied_to_image) +
                " Tag(s) applied to image at url: " + image_url)
        else:
            return func.HttpResponse("Upload of " + image_url + " failed.")

    else:
        return func.HttpResponse(
            "Please pass a URL to a blob file containing the image to be added to training in this request on the query string.",
            status_code=400)
Exemple #2
0
def main(req: func.HttpRequest) -> func.HttpResponse:
    logging.info('Python HTTP trigger function processed a request.')

    try:
        data = req.get_json()
        farmid, runid = data['farmid'], data['runid']
        feedback_list = data['feedback']

        #Decide on the correct split
        for feedback in feedback_list:
            feedback["split"] = "train" if random.random() < 0.5 else "dev"

        #Get Project ID and CustomVision Trainer
        logging.info("Get Project with Project ID : " +
                     CVTrainConstants.PROJECT_ID)
        credentials = ApiKeyCredentials(
            in_headers={"Training-key": CVTrainConstants.TRAINING_KEY})
        trainer = CustomVisionTrainingClient(CVTrainConstants.ENDPOINT,
                                             credentials)

        #Get list of tags in the current project
        tag_dict = {}
        for tag in trainer.get_tags(
                CVTrainConstants.PROJECT_ID):  ##Can modify dictionary later
            tag_dict[tag.name] = tag.id

        train_image_list = add_train_dev_images(farmid, runid, feedback_list,
                                                tag_dict)
        #train_image_list = add_train_dev_images("1", "2", [{"grid_id": "3", "label": "Charlock", "split": "dev"}, \
        #{"grid_id": "7", "label": "Fat Hen", "split": "train"}], tag_dict)

        #Upload to Custom Vision with new tags from train
        if len(train_image_list) != 0:  # IF not empty
            upload_result = trainer.create_images_from_urls(
                CVTrainConstants.PROJECT_ID, images=train_image_list)
            if not upload_result.is_batch_successful:
                logging.error("Image batch upload failed.")
            else:
                logging.info("Image batch upload Success")

        best_iteration(trainer)

    except Exception as e:
        logging.error(
            "Exception while trying to get data from HTTP Request: " + str(e))
        return func.HttpResponse(f"Failure !")

    return func.HttpResponse(f"Trained Sucessfully")
print("Adding images...")
url_list = []

for blob in ambulance_container.list_blobs():
    blob_name = blob.name
    blob_url = f"{base_image_url}ambulance/{blob_name}"
    url_list.append(
        ImageUrlCreateEntry(url=blob_url, tag_ids=[ambulance_tag.id]))

for blob in bench_container.list_blobs():
    blob_name = blob.name
    blob_url = f"{base_image_url}bench/{blob_name}"
    url_list.append(ImageUrlCreateEntry(url=blob_url, tag_ids=[bench_tag.id]))

for url_chunk in chunks(url_list, 64):
    upload_result = trainer.create_images_from_urls(project.id,
                                                    images=url_chunk)
    if not upload_result.is_batch_successful:
        print("Image batch upload failed.")
        for image in upload_result.images:
            if image.status != "OKDUPLICATE":
                print(image.source_url)
                print(image)
                print("Image status: ", image.status)

        nfailed = len([i for i in upload_result.images if i.status != "OK"])

print("Training...")

iteration = trainer.train_project(project.id)
while iteration.status != "Completed":
    iteration = trainer.get_iteration(project.id, iteration.id)
def main(req: func.HttpRequest) -> func.HttpResponse:
    logging.info(
        'ML Professoar HTTP trigger function AddLabeledDataClient processed a request.'
    )

    dataBlobUrl = req.params.get('dataBlobUrl')
    if not dataBlobUrl:
        return func.HttpResponse(
            "Please pass a URL to a blob containing the image to be added to training in this request on the query string.",
            status_code=400)

    if dataBlobUrl:
        ImageLabelsJson = req.params.get('imageLabels')
        if not ImageLabelsJson:
            try:
                ImageLabelsJson = req.get_json()
            except:
                return func.HttpResponse(
                    "Please pass JSON containing the labels associated with this image on the query string or in the request body.",
                    status_code=400)

        if ImageLabelsJson:
            # https://pypi.org/project/custom_vision_client/
            # https://github.com/Azure-Samples/cognitive-services-python-sdk-samples/blob/master/samples/vision/custom_vision_training_samples.py

            Labels = []
            CountOfTagsAppliedToTimage = 0
            Endpoint = os.environ['clientEndpoint']

            # Get Cognitive Services Environment Variables
            ProjectID = os.environ["projectID"]
            TrainingKey = os.environ['trainingKey']

            # strip out list of Image Labels passed in request
            ImageLabels = json.loads(ImageLabelsJson)

            # retrieve tags from project and loop through them to find tag ids for tags that need to be applied to the image
            Trainer = CustomVisionTrainingClient(TrainingKey,
                                                 endpoint=Endpoint)
            Tags = Trainer.get_tags(ProjectID)
            for ImageLabel in ImageLabels:
                for Tag in Tags:
                    if Tag.name == ImageLabel:
                        Labels.append(Tag.id)
                        CountOfTagsAppliedToTimage = CountOfTagsAppliedToTimage + 1
                        break

            # create the image from a url and attach the appropriate tags as labels.
            upload_result = Trainer.create_images_from_urls(
                ProjectID,
                [ImageUrlCreateEntry(url=dataBlobUrl, tag_ids=Labels)])
            if upload_result.is_batch_successful:
                return func.HttpResponse(
                    str(CountOfTagsAppliedToTimage) +
                    " Tag(s) applied to image at url: " + dataBlobUrl)
            else:
                return func.HttpResponse("Upload of " + dataBlobUrl +
                                         " failed.")

    else:
        return func.HttpResponse(
            "Please pass a URL to a blob file containing the image to be added to training in this request on the query string.",
            status_code=400)
Exemple #5
0
        print("attempting blob: {}".format(blob_name))
        now = datetime.utcnow()
        permission = BlobPermissions(read=True)
        sas_token = bbs.generate_blob_shared_access_signature(
            container_name=args.storage_container,
            blob_name=blob_name,
            start=now,
            expiry=now + timedelta(minutes=15),
            permission=permission)
        sas_url = bbs.make_blob_url(container_name=args.storage_container,
                                    blob_name=blob_name,
                                    sas_token=sas_token)
        print(sas_url)
        image_entry = ImageUrlCreateEntry(url=sas_url)
        image_block.append(image_entry)
        counter += 1
    else:
        print("attempting upload...")
        upload_result = train_client.create_images_from_urls(
            project_id=args.cv_project_id, images=image_block)
        check_print_failure(upload_result)
        image_block = []
        counter = 0
        print("COMPLETED BLOCK")

print(len(image_block))
upload_result = train_client.create_images_from_urls(
    project_id=args.cv_project_id, images=image_block)
check_print_failure(upload_result)
print("DONE!!!")
# ## Create the images with regions on destination project

# In[52]:


print(len(tagged_images))
print(len(tagged_images_with_tags))

print(len(dest_tags_ids))


# In[53]:


limit = 64 # this is a limit imposed on the API, so we need to batch the creation process
count_of_images = len(tagged_images_with_tags)

for i in range(0,count_of_images,limit):
    begin=i
    end=limit+i
    if(end > count_of_images ): end = count_of_images
    dest_trainer.create_images_from_urls(dest_Project.id, images=tagged_images_with_tags[begin:end])


# In[57]:


print("Count of Tagged images on origin project: " + str(trainer.get_tagged_image_count(Project.id)))
print("Count of Tagged images on destination project: " + str(dest_trainer.get_tagged_image_count(dest_Project.id)))
print(prediction_resource_id)
def main(req: func.HttpRequest) -> func.HttpResponse:
    logging.info(
        'MLP Pbject Detection HTTP trigger function AddLabeledData processed a request.'
    )

    try:
        image_url = req.form.get('ImageUrl')
        image_labeling_json = req.form.get('DataLabels')
    except:
        return func.HttpResponse(
            "Please pass JSON containing the labeled regions associated with this image on the query string or in the request body.",
            status_code=400)

    if image_url and image_labeling_json:
        labels = []
        labeled_images_with_regions = []
        count_of_regions_applied_to_image = 0
        count_of_labels_applied_to_region = 0

        endpoint = os.environ['ClientEndpoint']

        # Get Cognitive Services Environment Variables
        project_id = os.environ["ProjectID"]
        training_key = os.environ['TrainingKey']

        # load labeled image regions passed in request into dictionary
        image_labeling_data = json.loads(image_labeling_json)

        # instanciate custom vision client
        trainer = CustomVisionTrainingClient(training_key, endpoint=endpoint)

        # get list of valid tags for this model
        tags = trainer.get_tags(project_id)

        # get the height and width of the image
        image_width = image_labeling_data['asset']['size']['width']
        image_height = image_labeling_data['asset']['size']['height']

        # for each labeled region in this asset get the map lables to tag ids and map boundaries formats
        # from vott to azure cognitive services then upload to the cognitive services project.
        for labeled_region in image_labeling_data['regions']:
            for label in labeled_region['tags']:
                for tag in tags:
                    if tag.name == label:
                        labels.append(tag.id)
                        count_of_labels_applied_to_region = count_of_labels_applied_to_region + 1
                        break
            if count_of_labels_applied_to_region > 0:
                count_of_regions_applied_to_image = count_of_regions_applied_to_image + 1
            else:
                return func.HttpResponse(
                    "This Azure Cognitive Services Object Detection project does not contain any labeling tags.  Please add tags to the project before attempting to add labeled data into the project.",
                    status_code=400)

            top_left_x = labeled_region['points'][0]['x']
            top_left_y = labeled_region['points'][0]['y']
            top_right_x = labeled_region['points'][1]['x']
            top_right_y = labeled_region['points'][1]['y']
            bottom_right_x = labeled_region['points'][2]['x']
            bottom_right_y = labeled_region['points'][2]['y']
            bottom_left_x = labeled_region['points'][3]['x']
            bottom_left_y = labeled_region['points'][3]['y']

            # calculate normalized coordinates.
            normalized_left = top_left_x / image_width
            normalized_top = top_left_y / image_height
            normalized_width = (top_right_x - top_left_x) / image_width
            normalized_height = (bottom_left_y - top_left_y) / image_height

            regions = [
                Region(tag_id=labels[0],
                       left=normalized_left,
                       top=normalized_top,
                       width=normalized_width,
                       height=normalized_height)
            ]

        labeled_images_with_regions.append(
            ImageUrlCreateEntry(url=image_url, regions=regions))
        upload_result = trainer.create_images_from_urls(
            project_id, images=labeled_images_with_regions)

        result = ""
        if upload_result.is_batch_successful:
            for image in upload_result.images:
                result = result + "Image " + image.source_url + " Status: " + image.status + ", "
            return func.HttpResponse(
                "Images successfully uploaded with " +
                str(count_of_regions_applied_to_image) + " regions and " +
                str(count_of_labels_applied_to_region) +
                " label(s) to project " + project_id + "with result: " +
                result,
                status_code=200)
        else:
            success = True
            for image in upload_result.images:
                result = result + "Image " + image.source_url + " Status: " + image.status + ", "
                if not "ok" in image.status.lower():
                    success = False

            if success:
                return func.HttpResponse(
                    "Image batch upload succeeded with result: " + result,
                    status_code=200)
            else:
                return func.HttpResponse(
                    "Image batch upload failed with result: " + result,
                    status_code=400)

    else:
        return func.HttpResponse(
            "Please pass valid a valid image url and labels in the request body using the parameter names: ImageUrl and DataLabels.",
            status_code=400)
Exemple #8
0
        arcpy.AddMessage("Creating Model {}...".format(name))
        project = trainer.create_project(name)

        #Negative Tag Name
        negTagname = "Not_{}".format(name)

        #Make two tags in the new project
        positive_tag = trainer.create_tag(project.id, name)
        negative_tag = trainer.create_tag(project.id, negTagname)

        imageEntryList = [ImageUrlCreateEntry(url=image_url, tag_ids=[positive_tag.id]) for image_url in imageList(name)]
        negEntryList = [ImageUrlCreateEntry(url=image_url, tag_ids=[negative_tag.id]) for image_url in imageList(negTagname)]

        arcpy.AddMessage("Loading training photos into model...")
        for imgChunk in imageListChunks(imageEntryList, 63):
            trainer.create_images_from_urls(project.id,imgChunk)
        for imgChunk in imageListChunks(negEntryList, 63):
            trainer.create_images_from_urls(project.id,imgChunk)
        arcpy.AddMessage("Training Model...")
        iteration = trainer.train_project(project.id)
        while iteration.status == "Training":
            iteration = trainer.get_iteration(project.id, iteration.id)
            time.sleep(3)

        iteration_name = name + "classifyModel"
        # The iteration is now trained. Make it the default project endpoint
        #trainer.update_iteration(project.id, iteration.id, is_default=True)
        trainer.publish_iteration(project.id,iteration.id,iteration_name, resource_id)
        
        arcpy.AddMessage("Done!")
    else:
Exemple #9
0
class Classifier:
    """
        Class for interacting with Custom Vision. Contatins three key methods:
            - predict_imgage() / predicts a an image
            - upload_images() / reads image URLs from Blob Storage and uploads to Custom Vision
            - train() / trains a model
    """
    def __init__(self) -> None:
        """
            Reads configuration file
            Initializes connection to Azure Custom Vision predictor and training resources.

            Parameters:
            blob_service_client: Azure Blob Service interaction client

            Returns:
            None
        """
        self.ENDPOINT = Keys.get("CV_ENDPOINT")
        self.project_id = Keys.get("CV_PROJECT_ID")
        self.prediction_key = Keys.get("CV_PREDICTION_KEY")
        self.training_key = Keys.get("CV_TRAINING_KEY")
        self.base_img_url = Keys.get("BASE_BLOB_URL")
        self.prediction_resource_id = Keys.get("CV_PREDICTION_RESOURCE_ID")

        self.prediction_credentials = ApiKeyCredentials(
            in_headers={"Prediction-key": self.prediction_key})
        self.predictor = CustomVisionPredictionClient(
            self.ENDPOINT, self.prediction_credentials)
        self.training_credentials = ApiKeyCredentials(
            in_headers={"Training-key": self.training_key})
        self.trainer = CustomVisionTrainingClient(self.ENDPOINT,
                                                  self.training_credentials)
        connect_str = Keys.get("BLOB_CONNECTION_STRING")
        self.blob_service_client = BlobServiceClient.from_connection_string(
            connect_str)
        try:
            # get all project iterations
            iterations = self.trainer.get_iterations(self.project_id)
            # find published iterations
            puplished_iterations = [
                iteration for iteration in iterations
                if iteration.publish_name != None
            ]
            # get the latest published iteration
            puplished_iterations.sort(key=lambda i: i.created)
            self.iteration_name = puplished_iterations[-1].publish_name

            with api.app.app_context():
                models.update_iteration_name(self.iteration_name)
        except Exception as e:
            logging.info(e)
            self.iteration_name = "iteration1"

    def predict_image_url(self, img_url: str) -> Dict[str, float]:
        """
            Predicts label(s) of Image read from URL.

            Parameters:
            img_url: Image URL

            Returns:
            (prediction (dict[str,float]): labels and assosiated probabilities,
            best_guess: (str): name of the label with highest probability)
        """
        with api.app.app_context():
            self.iteration_name = models.get_iteration_name()
        res = self.predictor.classify_image_url(self.project_id,
                                                self.iteration_name, img_url)
        pred_kv = dict([(i.tag_name, i.probability) for i in res.predictions])
        best_guess = max(pred_kv, key=pred_kv.get)

        return pred_kv, best_guess

    def predict_image(self, img) -> Dict[str, float]:
        """
            Predicts label(s) of Image read from URL.
            ASSUMES:
            -image of type .png
            -image size less than 4MB
            -image resolution at least 256x256 pixels

            Parameters:
            img_url: .png file

            Returns:
            (prediction (dict[str,float]): labels and assosiated probabilities,
            best_guess: (str): name of the label with highest probability)
        """
        with api.app.app_context():
            self.iteration_name = models.get_iteration_name()
        res = self.predictor.classify_image_with_no_store(
            self.project_id, self.iteration_name, img)
        # reset the file head such that it does not affect the state of the file handle
        img.seek(0)
        pred_kv = dict([(i.tag_name, i.probability) for i in res.predictions])
        best_guess = max(pred_kv, key=pred_kv.get)
        return pred_kv, best_guess

    def predict_image_by_post(self, img) -> Dict[str, float]:
        """
            Predicts label(s) of Image read from URL.
            ASSUMES:
            -image of type .png
            -image size less than 4MB
            -image resolution at least 256x256 pixels

            Parameters:
            img_url: .png file

            Returns:
            (prediction (dict[str,float]): labels and assosiated probabilities,
            best_guess: (str): name of the label with highest probability)
        """

        headers = {
            'content-type': 'application/octet-stream',
            "prediction-key": self.prediction_key
        }
        res = requests.post(Keys.get("CV_PREDICTION_ENDPOINT"),
                            img.read(),
                            headers=headers).json()
        img.seek(0)
        pred_kv = dict([(i["tagName"], i["probability"])
                        for i in res["predictions"]])
        best_guess = max(pred_kv, key=pred_kv.get)
        return pred_kv, best_guess

    def __chunks(self, lst, n):
        """
            Helper method used by upload_images() to upload URL chunks of 64, which is maximum chunk size in Azure Custom Vision.
        """
        for i in range(0, len(lst), n):
            yield lst[i:i + n]

    def upload_images(self, labels: List, container_name) -> None:
        """
            Takes as input a list of labels, uploads all assosiated images to Azure Custom Vision project.
            If label in input already exists in Custom Vision project, all images are uploaded directly.
            If label in input does not exist in Custom Vision project, new label (Tag object in Custom Vision) is created before uploading images

            Parameters:
            labels (str[]): List of labels

            Returns:
            None
        """
        url_list = []
        existing_tags = list(self.trainer.get_tags(self.project_id))

        try:
            container = self.blob_service_client.get_container_client(
                container_name)
        except Exception as e:
            print(
                "could not find container with CONTAINER_NAME name error: ",
                str(e),
            )

        for label in labels:
            # check if input has correct type
            if not isinstance(label, str):
                raise Exception("label " + str(label) + " must be a string")

            tag = [t for t in existing_tags if t.name == label]
            # check if tag already exists
            if len(tag) == 0:
                try:
                    tag = self.trainer.create_tag(self.project_id, label)
                    print("Created new label in project: " + label)
                except Exception as e:
                    print(e)
                    continue
            else:
                tag = tag[0]

            blob_prefix = f"{label}/"
            blob_list = container.list_blobs(name_starts_with=blob_prefix)

            if not blob_list:
                raise AttributeError("no images for this label")

            # build correct URLs and append to URL list
            for blob in blob_list:
                blob_url = f"{self.base_img_url}/{container_name}/{blob.name}"
                url_list.append(
                    ImageUrlCreateEntry(url=blob_url, tag_ids=[tag.id]))

        # upload URLs in chunks of 64
        print("Uploading images from blob to CV")
        img_f = 0
        img_s = 0
        img_d = 0
        itr_img = 0
        chunks = self.__chunks(url_list, setup.CV_MAX_IMAGES)
        num_imgs = len(url_list)
        error_messages = set()
        for url_chunk in chunks:
            upload_result = self.trainer.create_images_from_urls(
                self.project_id, images=url_chunk)
            if not upload_result.is_batch_successful:
                for image in upload_result.images:
                    if image.status == "OK":
                        img_s += 1
                    elif image.status == "OKDuplicate":
                        img_d += 1
                    else:
                        error_messages.add(image.status)
                        img_f += 1

                    itr_img += 1
            else:
                batch_size = len(upload_result.images)
                img_s += batch_size
                itr_img += batch_size

            prc = itr_img / num_imgs
            print(
                f"\t succesfull: \033[92m {img_s:5d} \033]92m \033[0m",
                f"\t duplicates: \033[33m {img_d:5d} \033]33m \033[0m",
                f"\t failed: \033[91m {img_f:5d} \033]91m \033[0m",
                f"\t [{prc:03.2%}]",
                sep="",
                end="\r",
                flush=True,
            )

        print()
        if len(error_messages) > 0:
            print("Error messages:")
            for error_message in error_messages:
                print(f"\t {error_message}")

    def get_iteration(self):
        iterations = self.trainer.get_iterations(self.project_id)
        iterations.sort(key=(lambda i: i.created))
        newest_iteration = iterations[-1]
        return newest_iteration

    def delete_iteration(self) -> None:
        """
            Deletes the oldest iteration in Custom Vision if there are 11 iterations.
            Custom Vision allows maximum 10 iterations in the free version.
        """
        iterations = self.trainer.get_iterations(self.project_id)
        if len(iterations) >= setup.CV_MAX_ITERATIONS:
            iterations.sort(key=lambda i: i.created)
            oldest_iteration = iterations[0].id
            self.trainer.unpublish_iteration(self.project_id, oldest_iteration)
            self.trainer.delete_iteration(self.project_id, oldest_iteration)

    def train(self, labels: list) -> None:
        """
            Trains model on all labels specified in input list, exeption is raised by self.trainer.train_projec() is asked to train on non existent labels.
            Generates unique iteration name, publishes model and sets self.iteration_name if successful.
            Parameters:
            labels (str[]): List of labels
        """
        try:
            email = Keys.get("EMAIL")
        except Exception:
            print("No email found, setting to empty")
            email = ""

        self.delete_iteration()
        print("Training...")
        iteration = self.trainer.train_project(
            self.project_id,
            reserved_budget_in_hours=1,
            notification_email_address=email,
        )
        # Wait for training to complete
        start = time.time()
        while iteration.status != "Completed":
            iteration = self.trainer.get_iteration(self.project_id,
                                                   iteration.id)
            minutes, seconds = divmod(time.time() - start, 60)
            print(
                f"Training status: {iteration.status}",
                f"\t[{minutes:02.0f}m:{seconds:02.0f}s]",
                end="\r",
            )
            time.sleep(1)

        print()

        # The iteration is now trained. Publish it to the project endpoint
        iteration_name = uuid.uuid4()
        self.trainer.publish_iteration(
            self.project_id,
            iteration.id,
            iteration_name,
            self.prediction_resource_id,
        )
        with api.app.app_context():
            self.iteration_name = models.update_iteration_name(iteration_name)

    def delete_all_images(self) -> None:
        """
            Function for deleting uploaded images in Customv Vision.
        """
        try:
            self.trainer.delete_images(self.project_id,
                                       all_images=True,
                                       all_iterations=True)
        except Exception as e:
            raise Exception("Could not delete all images: " + str(e))

    def retrain(self):
        """
            Train model on all labels and update iteration.
        """
        with api.app.app_context():
            labels = models.get_all_labels()

        self.upload_images(labels, setup.CONTAINER_NAME_NEW)
        try:
            self.train(labels)
        except CustomVisionErrorException as e:
            msg = "No changes since last training"
            print(e, "exiting...")
            raise excp.BadRequest(msg)

    def hard_reset_retrain(self):
        """
            Train model on all labels and update iteration.
            This method sleeps for 60 seconds to make sure all
            old images are deleted from custom vision before
            uploading original dataset.
        """
        with api.app.app_context():
            labels = models.get_all_labels()

        # Wait 60 seconds to make sure all images are deleted in custom vision
        time.sleep(60)
        self.upload_images(labels, setup.CONTAINER_NAME_ORIGINAL)
        try:
            self.train(labels)
        except CustomVisionErrorException as e:
            msg = "No changes since last training"
            print(e, "exiting...")
            raise excp.BadRequest(msg)
    # going through a small portion of url list as can only upload 64 at a time
    for batch_number in range(math.ceil(len(image_url_list) / 67)):
        image_list = []
        endIndex = (batch_number + 1) * 67
        if endIndex > len(image_url_list):
            endIndex = len(image_url_list)
        for url in image_url_list[batch_number * 67:endIndex - 3]:
            image_list.append(ImageUrlCreateEntry(url=url, tag_ids=[tag.id]))
        # every 67 items, add 3 to the testing images, along with their actual species
        for i in range(endIndex - 3, endIndex):
            if i >= 0 and i < len(image_url_list):
                test_list.append([image_url_list[i], species])

        # check that there are some images to train on, then upload these
        if len(image_list) > 0:
            upload_result = trainer.create_images_from_urls(
                project.id, ImageUrlCreateBatch(images=image_list))

            # gives error when there is a duplicate (which is possible with the ALA data) so code below is to ignore
            # duplicate error.
            while not upload_result.is_batch_successful:
                image_list = []
                for image in upload_result.images:
                    if image.status == 'OK':
                        # add to new new image_list with corresponding url and tag
                        image_list.append(
                            ImageUrlCreateEntry(url=image.source_url,
                                                tag_ids=[tag.id]))
                    elif image.status != 'OKDuplicate':
                        print("Image batch upload failed.")
                        print("Image status: ", image.status)
                        print("Image url: ", image.source_url)