|
22 | 22 | LOCAL_EXPORT_FORMAT=['json', 'coco_json', 'csv', 'png'] |
23 | 23 | LOCAL_EXPORT_STATUS=['review', 'r_assigned','client_review', 'cr_assigned','accepted'] |
24 | 24 |
|
25 | | -## DATA TYPES: image, video, audio, document, text |
| 25 | +# DATA TYPES: image, video, audio, document, text |
26 | 26 | DATA_TYPES=('image', 'video', 'audio', 'document', 'text') |
27 | 27 | DATA_TYPE_FILE_EXT = { |
28 | 28 | 'image': ['.jpg','.jpeg', '.png', '.tiff'], |
@@ -440,7 +440,7 @@ def get_all_project_per_client_id(self,client_id): |
440 | 440 | 'request_id': unique_id |
441 | 441 | }) |
442 | 442 |
|
443 | | - print(response.text) |
| 443 | + # print(response.text) |
444 | 444 | return response.json() |
445 | 445 | except Exception as e: |
446 | 446 | logging.error(f"Failed to retrieve projects: {str(e)}") |
@@ -888,163 +888,164 @@ def create_local_export(self,project_id,client_id,export_config): |
888 | 888 |
|
889 | 889 | def create_project(self, project_name, data_type, client_id, dataset_id, annotation_template_id, rotation_config, created_by=None): |
890 | 890 | """ |
891 | | - Creates a project. |
892 | | -
|
893 | | - :param project_name: The name of the project. |
894 | | - :param data_type: The type of data. |
895 | | - :param client_id: The ID of the client. |
896 | | - :param created_by: The ID of the user who created the project. |
897 | | - :param rotation_config: The rotation configuration for the project. |
898 | | - :return: A dictionary containing the dataset ID, project ID, and project configuration. |
| 891 | + Creates a project with the given configuration. |
899 | 892 | """ |
900 | 893 | url = f"{constants.BASE_URL}/projects/create?client_id={client_id}" |
| 894 | + |
| 895 | + |
901 | 896 | payload = json.dumps({ |
902 | 897 | "project_name": project_name, |
903 | | - "dataset_id": dataset_id, |
| 898 | + "attached_datasets": [dataset_id], |
904 | 899 | "data_type": data_type, |
905 | 900 | "annotation_template_id": annotation_template_id, |
906 | 901 | "rotations": rotation_config, |
907 | 902 | "created_by": created_by |
908 | 903 | }) |
| 904 | + |
909 | 905 | headers = { |
910 | 906 | 'api_key': self.api_key, |
911 | 907 | 'api_secret': self.api_secret, |
912 | 908 | 'Origin': 'https://pro.labellerr.com', |
913 | 909 | 'Content-Type': 'application/json' |
914 | 910 | } |
| 911 | + |
| 912 | + # print(f"{payload}") |
| 913 | + |
915 | 914 | response = requests.post(url, headers=headers, data=payload) |
916 | | - return response.json() |
| 915 | + response_data = response.json() |
| 916 | + |
| 917 | + # print(f"{response_data}") |
| 918 | + |
| 919 | + |
| 920 | + if 'error' in response_data and response_data['error']: |
| 921 | + error_details = response_data['error'] |
| 922 | + error_msg = f"Validation Error: {response_data.get('message', 'Unknown error')}" |
| 923 | + for error in error_details: |
| 924 | + error_msg += f"\n- Field '{error['field']}': {error['message']}" |
| 925 | + raise LabellerrError(error_msg) |
| 926 | + |
| 927 | + return response_data |
917 | 928 |
|
918 | | - def initiate_create_project(self, payload): |
919 | 929 |
|
920 | | - # Creating an empty dataset by function call |
| 930 | + def initiate_create_project(self, payload): |
921 | 931 | """ |
922 | | - Creates an empty project. |
923 | | -
|
924 | | - :param payload: A dictionary containing the configuration for the project. |
925 | | - :return: A dictionary containing the dataset ID, project ID, and project configuration. |
| 932 | + Orchestrates project creation by handling dataset creation, annotation guidelines, |
| 933 | + and final project setup. |
926 | 934 | """ |
927 | | - |
928 | 935 | try: |
929 | | - result={} |
930 | | - # validate all the parameters |
931 | | - required_params = ['client_id', 'dataset_name', 'dataset_description', 'data_type', 'created_by', 'project_name','annotation_guide','autolabel'] |
| 936 | + |
| 937 | + required_params = ['client_id', 'dataset_name', 'dataset_description', 'data_type', |
| 938 | + 'created_by', 'project_name', 'annotation_guide', 'autolabel'] |
932 | 939 | for param in required_params: |
933 | 940 | if param not in payload: |
934 | 941 | raise LabellerrError(f"Required parameter {param} is missing") |
935 | | - if(param == 'client_id'): |
936 | | - # it should be an instance of string |
937 | | - if not isinstance(payload[param], str) or not payload[param].strip(): |
938 | | - raise LabellerrError(f"client_id must be a string") |
939 | | - if(param=='annotation_guide'): |
940 | | - annotation_guides=payload['annotation_guide'] |
941 | | - |
942 | | - # annotation_guides is an array and iterate |
943 | | - for annotation_guide in annotation_guides: |
944 | | - |
945 | | - if 'option_type' not in annotation_guide: |
946 | | - raise LabellerrError(f"option_type is required in annotation_guide") |
947 | | - else: |
948 | | - if annotation_guide['option_type'] not in OPTION_TYPE_LIST: |
949 | | - raise LabellerrError(f"option_type must be one of {OPTION_TYPE_LIST}") |
950 | | - |
951 | | - |
952 | 942 |
|
953 | | - if 'folder_to_upload' in payload or 'files_to_upload' in payload: |
954 | | - if 'folder_to_upload' in payload: |
955 | | - # make sure the folder path exist |
956 | | - if not os.path.exists(payload['folder_to_upload']): |
957 | | - raise LabellerrError(f"Folder {payload['folder_to_upload']} does not exist") |
958 | | - if not os.path.isdir(payload['folder_to_upload']): |
959 | | - raise LabellerrError(f"Folder {payload['folder_to_upload']} is not a directory") |
960 | | - elif 'files_to_upload' in payload: |
961 | | - # make sure the files exist in the array payload['files_to_upload'] |
962 | | - for file in payload['files_to_upload']: |
963 | | - if not os.path.exists(file): |
964 | | - raise LabellerrError(f"File {file} does not exist") |
965 | | - if not os.path.isfile(file): |
966 | | - raise LabellerrError(f"File {file} is not a file") |
967 | | - |
968 | | - if 'rotation_config' in payload: |
969 | | - self.validate_rotation_config(payload['rotation_config']) |
970 | | - print("Rotation configuration validated . . .") |
971 | | - else: |
| 943 | + |
| 944 | + if param == 'client_id' and not isinstance(payload[param], str): |
| 945 | + raise LabellerrError("client_id must be a non-empty string") |
| 946 | + |
| 947 | + if param == 'annotation_guide': |
| 948 | + for guide in payload['annotation_guide']: |
| 949 | + if 'option_type' not in guide: |
| 950 | + raise LabellerrError("option_type is required in annotation_guide") |
| 951 | + if guide['option_type'] not in OPTION_TYPE_LIST: |
| 952 | + raise LabellerrError(f"option_type must be one of {OPTION_TYPE_LIST}") |
| 953 | + |
| 954 | + |
| 955 | + if 'folder_to_upload' in payload and 'files_to_upload' in payload: |
| 956 | + raise LabellerrError("Cannot provide both files_to_upload and folder_to_upload") |
| 957 | + |
| 958 | + if 'folder_to_upload' not in payload and 'files_to_upload' not in payload: |
| 959 | + raise LabellerrError("Either files_to_upload or folder_to_upload must be provided") |
| 960 | + |
| 961 | + |
| 962 | + if 'rotation_config' not in payload: |
972 | 963 | payload['rotation_config'] = { |
973 | | - 'annotation_rotation_count':1, |
974 | | - 'review_rotation_count':1, |
975 | | - 'client_review_rotation_count':1 |
| 964 | + 'annotation_rotation_count': 1, |
| 965 | + 'review_rotation_count': 1, |
| 966 | + 'client_review_rotation_count': 1 |
976 | 967 | } |
| 968 | + self.validate_rotation_config(payload['rotation_config']) |
| 969 | + |
977 | 970 |
|
978 | 971 | if payload['data_type'] not in DATA_TYPES: |
979 | 972 | raise LabellerrError(f"Invalid data_type. Must be one of {DATA_TYPES}") |
980 | | - |
981 | | - if 'files_to_upload' in payload and 'folder_to_upload' in payload: |
982 | | - raise LabellerrError("Both files_to_upload and folder_to_upload cannot be provided at the same time.") |
983 | | - elif 'files_to_upload' not in payload and 'folder_to_upload' not in payload: |
984 | | - raise LabellerrError("Either files_to_upload or folder_to_upload must be provided.") |
985 | | - else: |
986 | | - if 'files_to_upload' in payload: |
987 | | - if payload['files_to_upload'] is None and len(payload['files_to_upload'])==0: |
988 | | - raise LabellerrError("files_to_upload must be a non-empty string.") |
989 | | - elif 'folder_to_upload' in payload: |
990 | | - if not isinstance(payload['folder_to_upload'], str) or not payload['folder_to_upload'].strip(): |
991 | | - raise LabellerrError("folder_to_upload must be a non-empty string.") |
992 | | - |
993 | | - |
994 | | - try: |
995 | | - print("creating dataset . . .") |
996 | | - response = self.create_dataset({ |
997 | | - 'client_id': payload['client_id'], |
998 | | - 'dataset_name': payload['project_name'], |
999 | | - 'data_type': payload['data_type'], |
1000 | | - 'dataset_description': payload['dataset_description'], |
1001 | | - }, files_to_upload=payload.get('files_to_upload', None), folder_to_upload=payload.get('folder_to_upload', None)) |
1002 | | - dataset_id = response['dataset_id'] |
1003 | | - gd = lambda: self.get_dataset(payload['client_id'], dataset_id) |
1004 | | - utils.poll(gd, lambda x: x['response']['status_code'] == 300, interval=5, timeout=300) |
1005 | | - |
1006 | | - except KeyError as e: |
1007 | | - # Preserve the original traceback while providing context |
1008 | | - raise LabellerrError(f"Missing required field in payload: {str(e)}") from e |
1009 | | - except Exception as e: |
1010 | | - # Log the original exception with traceback but preserve it in the raised exception |
1011 | | - logging.exception("Failed to create dataset during project creation") |
1012 | | - raise LabellerrError(f"Failed to create dataset: {str(e)}") from e |
1013 | | - |
1014 | | - try: |
1015 | | - annotation_template_id = self.create_annotation_guideline( |
1016 | | - payload['client_id'], |
1017 | | - payload['annotation_guide'], |
1018 | | - payload['project_name'], |
1019 | | - payload['data_type'] |
1020 | | - ) |
1021 | | - except Exception as e: |
1022 | | - # Log the original exception with traceback but preserve it in the raised exception |
1023 | | - logging.exception("Failed to create annotation guideline during project creation") |
1024 | | - raise LabellerrError(f"Failed to create annotation guideline: {str(e)}") from e |
1025 | | - |
1026 | | - try: |
1027 | | - return self.create_project( |
1028 | | - payload['project_name'], |
1029 | | - payload['data_type'], |
1030 | | - payload['client_id'], |
1031 | | - dataset_id, |
1032 | | - annotation_template_id, |
1033 | | - payload['rotation_config'], |
1034 | | - created_by=payload['created_by'] |
1035 | | - ) |
1036 | | - except Exception as e: |
1037 | | - # Log the original exception with traceback but preserve it in the raised exception |
1038 | | - logging.exception("Failed to create project after dataset and annotation template creation") |
1039 | | - raise LabellerrError(f"Failed to create project: {str(e)}") from e |
1040 | | - |
1041 | | - except LabellerrError: |
1042 | | - # Re-raise LabellerrError directly to preserve its context |
| 973 | + |
| 974 | + print("Rotation configuration validated . . .") |
| 975 | + |
| 976 | + |
| 977 | + print("Creating dataset . . .") |
| 978 | + dataset_response = self.create_dataset({ |
| 979 | + 'client_id': payload['client_id'], |
| 980 | + 'dataset_name': payload['dataset_name'], |
| 981 | + 'data_type': payload['data_type'], |
| 982 | + 'dataset_description': payload['dataset_description'], |
| 983 | + }, |
| 984 | + files_to_upload=payload.get('files_to_upload'), |
| 985 | + folder_to_upload=payload.get('folder_to_upload')) |
| 986 | + |
| 987 | + dataset_id = dataset_response['dataset_id'] |
| 988 | + |
| 989 | + |
| 990 | + def dataset_ready(): |
| 991 | + try: |
| 992 | + dataset_status = self.get_dataset(payload['client_id'], dataset_id) |
| 993 | + |
| 994 | + if isinstance(dataset_status, dict): |
| 995 | + |
| 996 | + if 'response' in dataset_status: |
| 997 | + return dataset_status['response'].get('status_code', 200) == 300 |
| 998 | + else: |
| 999 | + |
| 1000 | + return True |
| 1001 | + return False |
| 1002 | + except Exception as e: |
| 1003 | + print(f"Error checking dataset status: {e}") |
| 1004 | + return False |
| 1005 | + |
| 1006 | + |
| 1007 | + utils.poll( |
| 1008 | + function=dataset_ready, |
| 1009 | + condition=lambda x: x is True, |
| 1010 | + interval=5, |
| 1011 | + timeout=60 |
| 1012 | + ) |
| 1013 | + |
| 1014 | + print("Dataset created and ready for use") |
| 1015 | + |
| 1016 | + |
| 1017 | + annotation_template_id = self.create_annotation_guideline( |
| 1018 | + payload['client_id'], |
| 1019 | + payload['annotation_guide'], |
| 1020 | + payload['project_name'], |
| 1021 | + payload['data_type'] |
| 1022 | + ) |
| 1023 | + print("Annotation guidelines created") |
| 1024 | + |
| 1025 | + |
| 1026 | + project_response = self.create_project( |
| 1027 | + project_name=payload['project_name'], |
| 1028 | + data_type=payload['data_type'], |
| 1029 | + client_id=payload['client_id'], |
| 1030 | + dataset_id=dataset_id, |
| 1031 | + annotation_template_id=annotation_template_id, |
| 1032 | + rotation_config=payload['rotation_config'], |
| 1033 | + created_by=payload['created_by'] |
| 1034 | + ) |
| 1035 | + |
| 1036 | + |
| 1037 | + return { |
| 1038 | + 'status': 'success', |
| 1039 | + 'message': 'Project created successfully', |
| 1040 | + 'project_id': project_response |
| 1041 | + } |
| 1042 | + |
| 1043 | + except LabellerrError as e: |
| 1044 | + logging.error(f"Project creation failed: {str(e)}") |
1043 | 1045 | raise |
1044 | 1046 | except Exception as e: |
1045 | | - # For other exceptions, wrap with more context but preserve the original |
1046 | | - logging.exception("Unexpected error in initiate_create_project") |
1047 | | - raise LabellerrError(f"Failed to create project due to unexpected error: {str(e)}") from e |
| 1047 | + logging.exception("Unexpected error in project creation") |
| 1048 | + raise LabellerrError(f"Project creation failed: {str(e)}") from e |
1048 | 1049 |
|
1049 | 1050 | def upload_folder_files_to_dataset(self, data_config): |
1050 | 1051 | """ |
|
0 commit comments