1111import requests
1212
1313from roboflow .adapters import rfapi
14- from roboflow .adapters .rfapi import ImageUploadError
14+ from roboflow .adapters .rfapi import AnnotationSaveError , ImageUploadError
1515from roboflow .config import API_URL , DEMO_KEYS
1616from roboflow .core .version import Version
1717from roboflow .util .general import Retry
@@ -515,26 +515,34 @@ def save_annotation(
515515 job_name = None ,
516516 is_prediction : bool = False ,
517517 annotation_overwrite = False ,
518+ num_retry_uploads = 0 ,
518519 ):
519520 project_url = self .id .rsplit ("/" )[1 ]
520521 annotation_name , annotation_str = self ._annotation_params (annotation_path )
521522 t0 = time .time ()
523+ upload_retry_attempts = 0
524+ retry = Retry (num_retry_uploads , AnnotationSaveError )
522525
523- annotation = rfapi .save_annotation (
524- self .__api_key ,
525- project_url ,
526- annotation_name , # type: ignore[type-var]
527- annotation_str , # type: ignore[type-var]
528- image_id ,
529- job_name = job_name , # type: ignore[type-var]
530- is_prediction = is_prediction ,
531- annotation_labelmap = annotation_labelmap ,
532- overwrite = annotation_overwrite ,
533- )
526+ try :
527+ annotation = rfapi .save_annotation (
528+ self .__api_key ,
529+ project_url ,
530+ annotation_name , # type: ignore[type-var]
531+ annotation_str , # type: ignore[type-var]
532+ image_id ,
533+ job_name = job_name , # type: ignore[type-var]
534+ is_prediction = is_prediction ,
535+ annotation_labelmap = annotation_labelmap ,
536+ overwrite = annotation_overwrite ,
537+ )
538+ upload_retry_attempts = retry .retries
539+ except AnnotationSaveError as e :
540+ e .retries = upload_retry_attempts
541+ raise
534542
535543 upload_time = time .time () - t0
536544
537- return annotation , upload_time
545+ return annotation , upload_time , upload_retry_attempts
538546
539547 def single_upload (
540548 self ,
@@ -563,6 +571,7 @@ def single_upload(
563571 uploaded_image , uploaded_annotation = None , None
564572 upload_time , annotation_time = None , None
565573 upload_retry_attempts = 0
574+ annotation_upload_retry_attempts = 0
566575
567576 if image_path :
568577 uploaded_image , upload_time , upload_retry_attempts = self .upload_image (
@@ -579,13 +588,14 @@ def single_upload(
579588 image_id = uploaded_image ["id" ] # type: ignore[index]
580589
581590 if annotation_path and image_id :
582- uploaded_annotation , annotation_time = self .save_annotation (
591+ uploaded_annotation , annotation_time , annotation_upload_retry_attempts = self .save_annotation (
583592 annotation_path ,
584593 annotation_labelmap ,
585594 image_id ,
586595 batch_name ,
587596 is_prediction ,
588597 annotation_overwrite ,
598+ num_retry_uploads = num_retry_uploads ,
589599 )
590600
591601 return {
@@ -594,6 +604,7 @@ def single_upload(
594604 "upload_time" : upload_time ,
595605 "annotation_time" : annotation_time ,
596606 "upload_retry_attempts" : upload_retry_attempts ,
607+ "annotation_upload_retry_attempts" : annotation_upload_retry_attempts ,
597608 }
598609
599610 def _annotation_params (self , annotation_path ):
@@ -801,3 +812,57 @@ def image(self, image_id: str) -> Dict:
801812 image_details = data ["image" ]
802813
803814 return image_details
815+
816+ def create_annotation_job (
817+ self , name : str , batch_id : str , num_images : int , labeler_email : str , reviewer_email : str
818+ ) -> Dict :
819+ """
820+ Create a new annotation job in the project.
821+
822+ Args:
823+ name (str): The name of the annotation job
824+ batch_id (str): The ID of the batch that contains the images to annotate
825+ num_images (int): The number of images to include in the job
826+ labeler_email (str): The email of the user who will label the images
827+ reviewer_email (str): The email of the user who will review the annotations
828+
829+ Returns:
830+ Dict: A dictionary containing the created job details
831+
832+ Example:
833+ >>> import roboflow
834+
835+ >>> rf = roboflow.Roboflow(api_key="YOUR_API_KEY")
836+
837+ >>> project = rf.workspace().project("PROJECT_ID")
838+
839+ >>> job = project.create_annotation_job(
840+ ... name="Job created by API",
841+ ... batch_id="batch123",
842+ ... num_images=10,
843+ ... labeler_email="[email protected] ", 844+ ... reviewer_email="[email protected] " 845+ ... )
846+ """
847+ url = f"{ API_URL } /{ self .__workspace } /{ self .__project_name } /jobs?api_key={ self .__api_key } "
848+
849+ payload = {
850+ "name" : name ,
851+ "batch" : batch_id ,
852+ "num_images" : num_images ,
853+ "labelerEmail" : labeler_email ,
854+ "reviewerEmail" : reviewer_email ,
855+ }
856+
857+ response = requests .post (url , headers = {"Content-Type" : "application/json" }, json = payload )
858+
859+ if response .status_code != 200 :
860+ try :
861+ error_data = response .json ()
862+ if "error" in error_data :
863+ raise RuntimeError (error_data ["error" ])
864+ raise RuntimeError (response .text )
865+ except ValueError :
866+ raise RuntimeError (f"Failed to create annotation job: { response .text } " )
867+
868+ return response .json ()
0 commit comments