22import json
33import mimetypes
44import os
5- import re
65import sys
76import time
87import warnings
1211import requests
1312
1413from roboflow .adapters import rfapi
14+ from roboflow .adapters .rfapi import ImageUploadError
1515from roboflow .config import API_URL , DEMO_KEYS
1616from roboflow .core .version import Version
1717from roboflow .util .general import Retry
@@ -465,6 +465,76 @@ def upload(
465465 print ("[ " + path + " ] was skipped." )
466466 continue
467467
468+ def upload_image (
469+ self ,
470+ image_path = None ,
471+ hosted_image = False ,
472+ split = "train" ,
473+ num_retry_uploads = 0 ,
474+ batch_name = None ,
475+ tag_names = [],
476+ sequence_number = None ,
477+ sequence_size = None ,
478+ ** kwargs ,
479+ ):
480+ project_url = self .id .rsplit ("/" )[1 ]
481+
482+ t0 = time .time ()
483+ upload_retry_attempts = 0
484+ retry = Retry (num_retry_uploads , ImageUploadError )
485+
486+ try :
487+ image = retry (
488+ rfapi .upload_image ,
489+ self .__api_key ,
490+ project_url ,
491+ image_path ,
492+ hosted_image = hosted_image ,
493+ split = split ,
494+ batch_name = batch_name ,
495+ tag_names = tag_names ,
496+ sequence_number = sequence_number ,
497+ sequence_size = sequence_size ,
498+ ** kwargs ,
499+ )
500+ upload_retry_attempts = retry .retries
501+ except ImageUploadError as e :
502+ e .retries = upload_retry_attempts
503+ raise e
504+
505+ upload_time = time .time () - t0
506+
507+ return image , upload_time , upload_retry_attempts
508+
509+ def save_annotation (
510+ self ,
511+ annotation_path = None ,
512+ annotation_labelmap = None ,
513+ image_id = None ,
514+ job_name = None ,
515+ is_prediction : bool = False ,
516+ annotation_overwrite = False ,
517+ ):
518+ project_url = self .id .rsplit ("/" )[1 ]
519+ annotation_name , annotation_str = self ._annotation_params (annotation_path )
520+ t0 = time .time ()
521+
522+ annotation = rfapi .save_annotation (
523+ self .__api_key ,
524+ project_url ,
525+ annotation_name , # type: ignore[type-var]
526+ annotation_str , # type: ignore[type-var]
527+ image_id ,
528+ job_name = job_name , # type: ignore[type-var]
529+ is_prediction = is_prediction ,
530+ annotation_labelmap = annotation_labelmap ,
531+ overwrite = annotation_overwrite ,
532+ )
533+
534+ upload_time = time .time () - t0
535+
536+ return annotation , upload_time
537+
468538 def single_upload (
469539 self ,
470540 image_path = None ,
@@ -482,64 +552,41 @@ def single_upload(
482552 sequence_size = None ,
483553 ** kwargs ,
484554 ):
485- project_url = self .id .rsplit ("/" )[1 ]
486555 if image_path and image_id :
487556 raise Exception ("You can't pass both image_id and image_path" )
488557 if not (image_path or image_id ):
489558 raise Exception ("You need to pass image_path or image_id" )
490559 if isinstance (annotation_labelmap , str ):
491560 annotation_labelmap = load_labelmap (annotation_labelmap )
561+
492562 uploaded_image , uploaded_annotation = None , None
493- upload_time = None
563+ upload_time , annotation_time = None , None
494564 upload_retry_attempts = 0
565+
495566 if image_path :
496- t0 = time .time ()
497- try :
498- retry = Retry (num_retry_uploads , Exception )
499- uploaded_image = retry (
500- rfapi .upload_image ,
501- self .__api_key ,
502- project_url ,
503- image_path ,
504- hosted_image = hosted_image ,
505- split = split ,
506- batch_name = batch_name ,
507- tag_names = tag_names ,
508- sequence_number = sequence_number ,
509- sequence_size = sequence_size ,
510- ** kwargs ,
511- )
512- image_id = uploaded_image ["id" ] # type: ignore[index]
513- upload_retry_attempts = retry .retries
514- except rfapi .UploadError as e :
515- raise RuntimeError (f"Error uploading image: { self ._parse_upload_error (e )} " )
516- except BaseException as e :
517- uploaded_image = {"error" : e }
518- finally :
519- upload_time = time .time () - t0
520-
521- annotation_time = None
567+ uploaded_image , upload_time , upload_retry_attempts = self .upload_image (
568+ image_path ,
569+ hosted_image ,
570+ split ,
571+ num_retry_uploads ,
572+ batch_name ,
573+ tag_names ,
574+ sequence_number ,
575+ sequence_size ,
576+ ** kwargs ,
577+ )
578+ image_id = uploaded_image ["id" ] # type: ignore[index]
579+
522580 if annotation_path and image_id :
523- annotation_name , annotation_str = self ._annotation_params (annotation_path )
524- try :
525- t0 = time .time ()
526- uploaded_annotation = rfapi .save_annotation (
527- self .__api_key ,
528- project_url ,
529- annotation_name , # type: ignore[type-var]
530- annotation_str , # type: ignore[type-var]
531- image_id ,
532- job_name = batch_name , # type: ignore[type-var]
533- is_prediction = is_prediction ,
534- annotation_labelmap = annotation_labelmap ,
535- overwrite = annotation_overwrite ,
536- )
537- except rfapi .UploadError as e :
538- raise RuntimeError (f"Error uploading annotation: { self ._parse_upload_error (e )} " )
539- except BaseException as e :
540- uploaded_annotation = {"error" : e }
541- finally :
542- annotation_time = time .time () - t0
581+ uploaded_annotation , annotation_time = self .save_annotation (
582+ annotation_path ,
583+ annotation_labelmap ,
584+ image_id ,
585+ batch_name ,
586+ is_prediction ,
587+ annotation_overwrite ,
588+ )
589+
543590 return {
544591 "image" : uploaded_image ,
545592 "annotation" : uploaded_annotation ,
@@ -568,20 +615,6 @@ def _annotation_params(self, annotation_path):
568615 )
569616 return annotation_name , annotation_string
570617
571- def _parse_upload_error (self , error : rfapi .UploadError ) -> str :
572- dict_part = str (error ).split (": " , 2 )[2 ]
573- dict_part = dict_part .replace ("True" , "true" )
574- dict_part = dict_part .replace ("False" , "false" )
575- dict_part = dict_part .replace ("None" , "null" )
576- if re .search (r"'\w+':" , dict_part ):
577- temp_str = dict_part .replace (r"\'" , "<PLACEHOLDER>" )
578- temp_str = temp_str .replace ('"' , r"\"" )
579- temp_str = temp_str .replace ("'" , '"' )
580- dict_part = temp_str .replace ("<PLACEHOLDER>" , "'" )
581- parsed_dict : dict = json .loads (dict_part )
582- message = parsed_dict .get ("message" )
583- return message or str (parsed_dict )
584-
585618 def search (
586619 self ,
587620 like_image : Optional [str ] = None ,
0 commit comments