Example #1
0
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)
Example #2
0
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
Example #3
0
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
Example #4
0
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}
Example #5
0
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)
Example #6
0
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)
Example #7
0
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
Example #8
0
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
Example #9
0
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
Example #10
0
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)