def microsoft_name_add(ibs, alias, *args, **kwargs): r""" Add a Name to the system and return the UUID --- parameters: - name: alias in: formData description: a user-defined string to specify an alias for this name, useful for forward facing interfaces required: false type: string consumes: - multipart/form-data produces: - application/json responses: 200: description: Returns an Name model with a UUID schema: $ref: "#/definitions/Name" 400: description: Invalid input parameter """ try: parameter = 'alias' assert isinstance(alias, str), 'Alias must be a string' assert len(alias) > 0, 'Alias cannot be empty' except AssertionError as ex: raise controller_inject.WebInvalidInput(str(ex), parameter) nid = ibs.add_names(alias) return _name(ibs, nid)
def _ensure_general(ibs, models, tag, rowid_from_uuid_func, unpack=True, *args, **kwargs): if isinstance(models, dict): models = [models] length = len(models) single = length == 1 if length == 0: return [] rowid_list = [] for index, model in enumerate(models): try: if single: parameter = 'models' else: parameter = 'models:%d' % (index,) assert 'uuid' in model, '%s Model provided is invalid, missing UUID key' % ( tag, ) uuid_ = uuid.UUID(model['uuid']) assert uuid_ is not None, "%s Model's UUID is invalid" % (tag,) rowid = rowid_from_uuid_func(uuid_) assert rowid is not None, '%s Model is unrecognized, please upload' % (tag,) except AssertionError as ex: raise controller_inject.WebInvalidInput(str(ex), parameter) rowid_list.append(rowid) if single and unpack: return rowid_list[0] else: return rowid_list
def microsoft_task_result(ibs, task): r""" Retrieve the result of a completed asynchronous Task --- parameters: - name: task in: body description: A Task model required: true schema: $ref: "#/definitions/Task" responses: 200: description: Returns the result of the provided Task 400: description: Invalid input parameter """ ibs = current_app.ibs # Input argument validation try: parameter = 'task' assert 'uuid' in task, 'Task Model provided is invalid, missing UUID key' except AssertionError as ex: raise controller_inject.WebInvalidInput(str(ex), parameter) uuid_ = task.get('uuid', None) assert uuid_ is not None result = ibs.get_job_result(uuid_) result = result.get('json_result', None) return result
def microsoft_task_status(ibs, task): r""" Check the status of an asynchronous Task. A Task is an asynchronous task that was launched as a background process with an optional callback. The status of a given Task with a UUID can be checked with this call. The status of the call depends on where in the execution queue the Task is currently, which will be processed in a first-come-first-serve list and only one Task at a time to present atomicity of the API. The status can be one of the following: - received -> The Task request was received but has not passed any input validation. - accepted -> The Task request has passed basic input validation and will be queued soon for execution. - queued -> The Task is queued in the execution list and will be processed in order and one at a time. - working -> The Task is being processed, awaiting completed results or an error exception - publishing -> The Task is publishing the results of the background API call. - completed -> One of two end states: the Task is complete, completed results available for downloading with the REST API. - exception -> One of two end states: the Task has encountered an error, an error message can be received using the results REST API. - unknown -> The Task you asked for is not known, indicating that the either UUID is not recognized (i.e. a Task with that ID was never currently created) or the server has been restarted. **Important: when the API server is restarted, all queued and running background Tasks are killed and all Task requests and cached results are deleted.** --- parameters: - name: task in: body description: A Task model required: true schema: $ref: "#/definitions/Task" responses: 200: description: Returns the status of the provided Task schema: type: object properties: status: type: string enum: - received - accepted - queued - working - publishing - completed - exception - unknown 400: description: Invalid input parameter """ ibs = current_app.ibs # Input argument validation try: parameter = 'task' assert 'uuid' in task, 'Task Model provided is invalid, missing UUID key' except AssertionError as ex: raise controller_inject.WebInvalidInput(str(ex), parameter) uuid_ = task.get('uuid', None) assert uuid_ is not None status = ibs.get_job_status(uuid_) status = status.get('jobstatus', None) return {'status': status}
def microsoft_image_upload(ibs, *args, **kwargs): r""" Upload an Image to the system and return the UUID --- parameters: - name: image in: formData description: The image to upload. required: true type: file enum: - image/png - image/jpg - image/tiff consumes: - multipart/form-data produces: - application/json responses: 200: description: Returns an Image model with a UUID schema: $ref: "#/definitions/Image" 400: description: Invalid input parameter 415: description: Unsupported media type in the request body. Currently only image/png, image/jpeg, image/tiff are supported. """ from wbia.web.apis import image_upload try: gid = image_upload(cleanup=True, **kwargs) assert gid is not None except controller_inject.WebException: raise except Exception: raise controller_inject.WebInvalidInput( 'Uploaded image is corrupted or is an unsupported file format (supported: image/png, image/jpeg, image/tiff)', 'image', image=True, ) return _image(ibs, gid)
def microsoft_detect_input_validation(model, score_threshold, use_nms, nms_threshold): from wbia.algo.detect.lightnet import CONFIG_URL_DICT try: parameter = 'model' assert model in CONFIG_URL_DICT, 'Specified model is not supported' parameter = 'score_threshold' assert isinstance(score_threshold, float), 'Score threshold must be a float' assert ( 0.0 <= score_threshold and score_threshold <= 1.0 ), 'Score threshold is invalid, must be in range [0.0, 1.0]' parameter = 'use_nms' assert isinstance(use_nms, bool), 'NMS flag must be a boolean' parameter = 'nms_threshold' assert isinstance(nms_threshold, float), 'NMS threshold must be a float' assert ( 0.0 <= nms_threshold and nms_threshold <= 1.0 ), 'NMS threshold is invalid, must be in range [0.0, 1.0]' except AssertionError as ex: raise controller_inject.WebInvalidInput(str(ex), parameter)
def start_identify_annots_query( ibs, query_annot_uuid_list=None, # query_annot_name_uuid_list=None, query_annot_name_list=None, database_annot_uuid_list=None, # database_annot_name_uuid_list=None, database_annot_name_list=None, matching_state_list=[], query_config_dict={}, echo_query_params=True, include_qaid_in_daids=True, callback_url=None, callback_method=None, lane='slow', ): r""" REST: Method: GET URL: /api/engine/query/graph/ Args: query_annot_uuid_list (list) : specifies the query annotations to identify. query_annot_name_list (list) : specifies the query annotation names database_annot_uuid_list (list) : specifies the annotations that the algorithm is allowed to use for identification. If not specified all annotations are used. (default=None) database_annot_name_list (list) : specifies the database annotation names (default=None) matching_state_list (list of tuple) : the list of matching state 3-tuples corresponding to the query_annot_uuid_list (default=None) query_config_dict (dict) : dictionary of algorithmic configuration arguments. (default=None) echo_query_params (bool) : flag for if to return the original query parameters with the result CommandLine: # Normal mode python -m wbia.web.apis_engine start_identify_annots_query # Split mode wbia --web python -m wbia.web.apis_engine start_identify_annots_query --show --domain=localhost Example: >>> # DISABLE_DOCTEST >>> # xdoctest: +REQUIRES(--job-engine-tests) >>> from wbia.web.apis_engine import * # NOQA >>> import wbia >>> #domain = 'localhost' >>> domain = None >>> with wbia.opendb_bg_web('testdb1', domain=domain, managed=True) as web_ibs: # , domain='http://52.33.105.88') ... aids = web_ibs.send_wbia_request('/api/annot/', 'get')[0:3] ... uuid_list = web_ibs.send_wbia_request('/api/annot/uuid/', type_='get', aid_list=aids) ... quuid_list = ut.get_argval('--quuids', type_=list, default=uuid_list)[0:1] ... duuid_list = ut.get_argval('--duuids', type_=list, default=uuid_list) ... query_config_dict = { ... #'pipeline_root' : 'BC_DTW' ... } ... data = dict( ... query_annot_uuid_list=quuid_list, database_annot_uuid_list=duuid_list, ... query_config_dict=query_config_dict, ... ) ... jobid = web_ibs.send_wbia_request('/api/engine/query/graph/', **data) ... print('jobid = %r' % (jobid,)) ... status_response = web_ibs.wait_for_results(jobid) ... result_response = web_ibs.read_engine_results(jobid) ... print('result_response = %s' % (ut.repr3(result_response),)) ... inference_result = result_response['json_result'] ... if isinstance(inference_result, six.string_types): ... print(inference_result) ... cm_dict = inference_result['cm_dict'] ... quuid = quuid_list[0] ... cm = cm_dict[str(quuid)] """ valid_states = { 'match': ['matched'], # ['match', 'matched'], 'nomatch': [ 'notmatched', 'nonmatch', ], # ['nomatch', 'notmatched', 'nonmatched', 'notmatch', 'non-match', 'not-match'], 'notcomp': ['notcomparable'], } prefered_states = ut.take_column(valid_states.values(), 0) flat_states = ut.flatten(valid_states.values()) def sanitize(state): state = state.strip().lower() state = ''.join(state.split()) assert ( state in flat_states ), 'matching_state_list has unrecognized states. Should be one of %r' % ( prefered_states, ) return state # HACK # if query_annot_uuid_list is None: # if True: # query_annot_uuid_list = [] # else: # query_annot_uuid_list = ibs.get_annot_uuids(ibs.get_valid_aids()[0:1]) dname_list = database_annot_name_list qname_list = query_annot_name_list # Check inputs assert (len(query_annot_uuid_list) == 1 ), 'Can only identify one query annotation at a time. Got %d ' % ( len(query_annot_uuid_list), ) assert ( database_annot_uuid_list is None or len(database_annot_uuid_list) > 0 ), 'Cannot specify an empty database_annot_uuid_list (e.g. []), specify or use None instead' if qname_list is not None: assert len(query_annot_uuid_list) == len(qname_list) if database_annot_uuid_list is not None and dname_list is not None: assert len(database_annot_uuid_list) == len(dname_list) proot = query_config_dict.get('pipeline_root', 'vsmany') proot = query_config_dict.get('proot', proot) if proot.lower() in ( 'curvrankdorsal', 'curvrankfinfindrhybriddorsal', 'curvrankfluke', 'curvranktwodorsal', 'curvranktwofluke', ): curvrank_daily_tag = query_config_dict.get('curvrank_daily_tag', '') if len(curvrank_daily_tag) > 144: message = 'The curvrank_daily_tag cannot have more than 128 characters, please shorten and try again.' key = 'query_config_dict["curvrank_daily_tag"]' value = curvrank_daily_tag raise controller_inject.WebInvalidInput(message, key, value) else: logger.info(ut.repr3(query_config_dict)) pop_key_list = ['curvrank_daily_tag'] for pop_key in pop_key_list: logger.info('Popping irrelevant key for config: %r' % (pop_key, )) query_config_dict.pop(pop_key, None) # Check UUIDs if dname_list is not None: vals = web_check_annot_uuids_with_names(database_annot_uuid_list, dname_list) database_annot_uuid_list, dname_list = vals ibs.web_check_uuids([], query_annot_uuid_list, database_annot_uuid_list) # import wbia # from wbia.web import apis_engine # ibs.load_plugin_module(apis_engine) qannot_uuid_list = ensure_uuid_list(query_annot_uuid_list) dannot_uuid_list = ensure_uuid_list(database_annot_uuid_list) # Ensure annotations qaid_list = ibs.get_annot_aids_from_uuid(qannot_uuid_list) if dannot_uuid_list is None or (len(dannot_uuid_list) == 1 and dannot_uuid_list[0] is None): qaid = qaid_list[0] species = ibs.get_annot_species_texts(qaid) assert ( species != const.UNKNOWN ), 'Cannot query an annotation with an empty (unset) species against an unspecified database_annot_uuid_list' daid_list = ibs.get_valid_aids() daid_list = ibs.filter_annotation_set(daid_list, species=species) else: daid_list = ibs.get_annot_aids_from_uuid(dannot_uuid_list) # Ensure that the daid_list is non-empty after filtering out qaid_list daid_remaining_set = set(daid_list) - set(qaid_list) if len(daid_remaining_set) == 0: raise controller_inject.WebInvalidMatchException(qaid_list, daid_list) # Ensure names # FIXME: THE QREQ STRUCTURE SHOULD HANDLE NAMES. if qname_list is not None: # Set names for query annotations qnid_list = ibs.add_names(qname_list) ibs.set_annot_name_rowids(qaid_list, qnid_list) if dname_list is not None: # Set names for database annotations dnid_list = ibs.add_names(dname_list) ibs.set_annot_name_rowids(daid_list, dnid_list) # For caching reasons, let's add the qaids to the daids if include_qaid_in_daids: daid_list = daid_list + qaid_list daid_list = sorted(daid_list) # Convert annot UUIDs to aids for matching_state_list into user_feedback for query state_list = map(sanitize, ut.take_column(matching_state_list, 2)) # {'aid1': [1], 'aid2': [2], 'p_match': [1.0], 'p_nomatch': [0.0], 'p_notcomp': [0.0]} user_feedback = { 'aid1': ibs.get_annot_aids_from_uuid(ut.take_column(matching_state_list, 0)), 'aid2': ibs.get_annot_aids_from_uuid(ut.take_column(matching_state_list, 1)), 'p_match': [ 1.0 if state in valid_states['match'] else 0.0 for state in state_list ], 'p_nomatch': [ 1.0 if state in valid_states['nomatch'] else 0.0 for state in state_list ], 'p_notcomp': [ 1.0 if state in valid_states['notcomp'] else 0.0 for state in state_list ], } ibs.assert_valid_aids(qaid_list, msg='error in start_identify qaids', auuid_list=qannot_uuid_list) ibs.assert_valid_aids(daid_list, msg='error in start_identify daids', auuid_list=dannot_uuid_list) args = (qaid_list, daid_list, user_feedback, query_config_dict, echo_query_params) jobid = ibs.job_manager.jobiface.queue_job('query_chips_graph', callback_url, callback_method, lane, *args) return jobid
def microsoft_detect_batch( ibs, images, model, score_threshold=0.0, use_nms=True, nms_threshold=0.4, asynchronous=True, callback_url=None, callback_method=None, *args, **kwargs, ): r""" The asynchronous variant of POST 'detect' that takes in a list of Image models and returns a task ID. It may be more ideal for a particular application to upload many images at one time and perform processing later in a large batch. This type of batch detection is certainly much more efficient because the detection on GPU can process more images in parallel. However, if you intend to run the detector on an upload as quickly as possible, please use the on-demand, non-batched API. --- parameters: - name: images in: body description: A JSON list of Image models for which detection will be processed. required: true type: array - name: model in: formData description: The model name to use with detection. Available models can be queried using the API $PREFIX/detect/model/ required: true type: string - name: score_threshold in: formData description: A score threshold to filter the detection results. Default 0.0. Must be in range [0, 1], inclusive required: false type: number format: float - name: use_nms in: formData description: Flag to turn on/off Non-Maximum Suppression (NMS). Default True. required: false type: boolean - name: nms_threshold in: formData description: A Intersection-over-Union (IoU) threshold to suppress detection results with NMS. Default 0.0. Must be in range [0, 1], inclusive required: false type: number format: float - name: callback_url in: formData description: The URL of where to callback when the task is completed, must be a fully resolvable address and accessible. The callback will include a 'body' parameter called `task` which will provide a Task model required: false type: string format: url - name: callback_method in: formData description: The HTTP method for which to make the callback. Default POST. required: false type: string enum: - get - post - put - delete consumes: - multipart/form-data responses: 200: description: Returns a Task model schema: $ref: "#/definitions/Task" 400: description: Invalid input parameter x-task-response: description: The task returns an array of arrays of Detection models, in parallel lists with the provided Image models schema: type: array items: schema: type: array items: $ref: "#/definitions/Detection" """ ibs = current_app.ibs # Input argument validation for index, image in enumerate(images): try: parameter = 'images:%d' % (index,) assert 'uuid' in image, 'Image Model provided is invalid, missing UUID key' except AssertionError as ex: raise controller_inject.WebInvalidInput(str(ex), parameter) microsoft_detect_input_validation(model, score_threshold, use_nms, nms_threshold) try: parameter = 'asynchronous' assert isinstance(asynchronous, bool), 'Asynchronous flag must be a boolean' parameter = 'callback_url' assert callback_url is None or isinstance( callback_url, str ), 'Callback URL must be a string' if callback_url is not None: assert callback_url.startswith('http://') or callback_url.startswith( 'https://' ), 'Callback URL must start with http:// or https://' parameter = 'callback_method' assert callback_method is None or isinstance( callback_method, str ), 'Callback URL must be a string' if callback_method is not None: callback_method = callback_method.lower() assert callback_method in [ 'get', 'post', 'put', 'delete', ], 'Unsupported callback method, must be one of ("get", "post", "put", "delete")' except AssertionError as ex: raise controller_inject.WebInvalidInput(str(ex), parameter) args = ( images, model, ) kwargs = { 'score_threshold': score_threshold, 'use_nms': use_nms, 'nms_threshold': nms_threshold, } if asynchronous: taskid = ibs.job_manager.jobiface.queue_job( 'microsoft_detect', callback_url, callback_method, *args, **kwargs ) response = _task(ibs, taskid) else: response = ibs.microsoft_detect(*args, **kwargs) return response
def microsoft_annotation_add( ibs, image, bbox, theta=None, species=None, viewpoint=None, name=None, *args, **kwargs, ): r""" Add an Annotation to the system and return the UUID --- parameters: - name: image in: body description: A Image model for which the annotation will be added as a child required: true schema: $ref: "#/definitions/Image" - name: bbox in: formData description: a 4-tuple of coordinates that defines a rectangle in the format (x-axis top left corner, y-axis top left corner, width, height) in pixels. These values are expected to be bounded by the size of the parent image. required: true type: array items: type: number format: float - name: theta in: formData description: a rotation around the center of the annotation, in radians. Default is 0.0 required: false type: number - name: species in: formData description: a user-defined string to specify the species of the annotation (e.g. 'zebra' or 'massai_giraffe'). This value is used to filter matches and run-time models for ID. Default is None. required: false type: string - name: viewpoint in: formData description: a user-defined string to specify the viewpoint of the annotation (e.g. 'right' or 'front_left'). This value is used to filter matches and run-time models for ID. Default is None. required: false type: string - name: name in: body description: a Name model to assign to the created Annotation required: false schema: $ref: "#/definitions/Name" consumes: - multipart/form-data produces: - application/json responses: 200: description: Returns an Annotations model with a UUID schema: $ref: "#/definitions/Annotation" 400: description: Invalid input parameter """ try: parameter = 'bbox' assert isinstance(bbox, (tuple, list)), 'Bounding box must be a list' assert len(bbox) == 4, 'Bounding Box must have 4 yntegers' assert isinstance(bbox[0], int), 'Bounding box xtl (index 0) must be an integer' assert isinstance(bbox[1], int), 'Bounding box ytl (index 1) must be an integer' assert isinstance(bbox[2], int), 'Bounding box width (index 2) must be an integer' assert isinstance( bbox[3], int ), 'Bounding box height (index 3) must be an integer' if theta is not None: assert isinstance(theta, float), 'Theta must be a float' if species is not None: assert isinstance(species, str), 'Species must be a string' assert len(species) > 0, 'Species cannot be empty' if viewpoint is not None: assert isinstance(viewpoint, str), 'Viewpoint must be a string' assert len(viewpoint) > 0, 'Viewpoint cannot be empty' assert ( viewpoint in const.YAWALIAS ), 'Invalid viewpoint provided. Must be one of: %s' % ( list(const.YAWALIAS.keys()), ) if name is not None: nid = _ensure_names(ibs, name) else: nid = None except AssertionError as ex: raise controller_inject.WebInvalidInput(str(ex), parameter) gid = _ensure_images(ibs, image) gid_list = [gid] bbox_list = [bbox] theta_list = None if theta is None else [theta] species_list = None if species is None else [species] viewpoint_list = None if viewpoint is None else [viewpoint] nid_list = None if nid is None else [nid] aid_list = ibs.add_annots( gid_list, bbox_list=bbox_list, theta_list=theta_list, species_list=species_list, viewpoint_list=viewpoint_list, nid_list=nid_list, ) assert len(aid_list) == 1 aid = aid_list[0] response = _annotation(ibs, aid) logger.info(response) return response
def microsoft_identify( ibs, query_annotation, database_annotations, algorithm, callback_url=None, callback_method=None, *args, **kwargs, ): r""" The asynchronous call to identify a list of pre-uploaded query annotations against a database of annotations. Returns a task ID. This call expects all of the data has been already uploaded and the appropriate metadata set. For example, this call expects any known ground-truth nameshave already been made and assigned to the appropriate Annotation models. Conversely, we also provide a method for marking individual reviews between two Annotation models. The decision between a given pair should always be one of ['match', 'nomatch', or 'notcomp']. The full docuemntation for this call can be seen in the POST 'decision' API. --- parameters: - name: query_annotation in: formData description: An Annotation models to query for an identification required: true schema: $ref: "#/definitions/Annotation" - name: database_annotations in: formData description: A JSON list of Annotation models to compare the query Annotation against required: true type: array items: $ref: "#/definitions/Annotation" - name: algorithm in: formData description: The algorithm you with to run ID with. Must be one of "HotSpotter", "CurvRank", "CurvRankTwo", "Finfindr", or "Deepsense" required: true type: string - name: callback_url in: formData description: The URL of where to callback when the task is completed, must be a fully resolvable address and accessible. The callback will include a 'body' parameter called `task` which will provide a Task model required: false type: string format: url - name: callback_method in: formData description: The HTTP method for which to make the callback. Default POST. required: false type: string enum: - get - post - put - delete consumes: - multipart/form-data responses: 200: description: Returns a Task model schema: $ref: "#/definitions/Task" 400: description: Invalid input parameter """ qaid = _ensure_annotations(ibs, query_annotation) daid_list = _ensure_annotations(ibs, database_annotations) try: parameter = 'database_annotations' assert ( len(daid_list) > 0 ), 'Cannot specify an empty list of database Annotations to compare against' parameter = 'algorithm' assert isinstance(algorithm, str), 'Must specify the algorithm as a string' algorithm = algorithm.lower() assert algorithm in [ 'hotspotter', 'curvrank', 'curvrank_v2', 'curvrankv2', 'deepsense', 'finfindr', 'kaggle7', 'kaggleseven', ], 'Must specify the algorithm for ID as HotSpotter, CurvRank, CurvRankTwo, Deepsense, Finfindr, Kaggle7' parameter = 'callback_url' assert callback_url is None or isinstance( callback_url, str ), 'Callback URL must be a string' if callback_url is not None: assert callback_url.startswith('http://') or callback_url.startswith( 'https://' ), 'Callback URL must start with http:// or https://' parameter = 'callback_method' assert callback_method is None or isinstance( callback_method, str ), 'Callback URL must be a string' if callback_method is not None: callback_method = callback_method.lower() assert callback_method in [ 'get', 'post', 'put', 'delete', ], 'Unsupported callback method, must be one of ("get", "post", "put", "delete")' except AssertionError as ex: raise controller_inject.WebInvalidInput(str(ex), parameter) if algorithm in ['hotspotter']: query_config_dict = {} elif algorithm in ['curvrank']: query_config_dict = { 'pipeline_root': 'CurvRankFluke', } elif algorithm in ['curvrank_v2', 'curvrankv2']: query_config_dict = { 'pipeline_root': 'CurvRankTwoFluke', } elif algorithm in ['deepsense']: query_config_dict = { 'pipeline_root': 'Deepsense', } elif algorithm in ['finfindr']: query_config_dict = { 'pipeline_root': 'Finfindr', } elif algorithm in ['kaggle7', 'kaggleseven']: query_config_dict = { 'pipeline_root': 'KaggleSeven', } user_feedback = { 'aid1': [], 'aid2': [], 'p_match': [], 'p_nomatch': [], 'p_notcomp': [], } echo_query_params = False args = ( [qaid], daid_list, user_feedback, query_config_dict, echo_query_params, ) kwargs = { 'endpoint': url_for('microsoft_identify_visualize'), 'n': 5, } taskid = ibs.job_manager.jobiface.queue_job( 'query_chips_graph_microsoft', callback_url, callback_method, *args, **kwargs ) return _task(ibs, taskid)