Skip to content

Commit

Permalink
Merge pull request #145 from galileosteinberg/tab-navigation
Browse files Browse the repository at this point in the history
Web and REST service expansion
  • Loading branch information
bgyori authored Jul 23, 2024
2 parents dcfaca1 + ab3f9e0 commit 7677132
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 8 deletions.
69 changes: 67 additions & 2 deletions gilda/app/app.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import logging
from textwrap import dedent
from typing import Optional

Expand All @@ -11,6 +12,8 @@
# NOTE: the Flask REST-X API has to be declared here, below the home endpoint
# otherwise it reserves the / base path.

logger = logging.getLogger(__name__)

api = Api(title="Gilda",
description="A service for grounding entity strings",
version=version,
Expand Down Expand Up @@ -148,6 +151,39 @@
}
)

ner_result_model = api.model('NERResult', {
'text': fields.String(description='Matched text'),
'start': fields.Integer(description='Start index of the match'),
'end': fields.Integer(description='End index of the match'),
'matches': fields.List(fields.Nested(scored_match_model))
})

ner_input_model = api.model('NERInput', {
'text': fields.String(required=True, description='Text on which to perform'
' NER',
example='The EGF receptor binds EGF which is an interaction'
'important in cancer.'),
'organisms': fields.List(fields.String, example=['9606'],
description='An optional list of taxonomy '
'species IDs defining a priority list'
' in case an entity string can be '
'resolved to multiple'
'species-specific genes/proteins.',
required=False),
'namespaces': fields.List(fields.String,
description='A list of namespaces to pass to '
'the grounder to restrict the '
'matches to. By default, '
'no restriction is applied',
example=['HGNC', 'MESH'],
required=False),
'context_text': fields.String(required=False, description='Additional '
'context for '
'disambiguation',
example='The EGF receptor binds EGF which is an interaction'
'important in cancer.'),
})

names_model = fields.List(
fields.String,
example=['EGF receptor', 'EGFR', 'ERBB1', 'Proto-oncogene c-ErbB-1'])
Expand All @@ -162,8 +198,9 @@ class Ground(Resource):
@base_ns.response(200, "Grounding results", [scored_match_model])
@base_ns.expect(grounding_input_model)
def post(self):
"""Return a list of scored grounding matches for a given entity text.
"""Perform grounding on a given entity text.
Returns a list of scored grounding matches for the given entity text.
The returned value is a list with each entry being a scored match.
Each scored match contains a term which was matched, and each term
contains a db and id constituting a grounding. An empty list
Expand Down Expand Up @@ -248,6 +285,33 @@ def get(self):
return jsonify(grounder.get_models())


@base_ns.route('/annotate', methods=['POST'])
class Annotate(Resource):
@base_ns.response(200, "NER results", [ner_result_model])
@base_ns.expect(ner_input_model)
def post(self):
"""Perform named entity recognition on the given text.
This endpoint can be used to perform named entity recognition (NER)
using Gilda.
"""
from gilda.ner import annotate

if request.json is None:
abort(415, 'Missing application/json header.')

text = request.json.get('text')
context_text = request.json.get('context_text')
organisms = request.json.get('organisms')
namespaces = request.json.get('namespaces')

results = annotate(text, organisms=organisms if organisms else None,
namespaces=namespaces if namespaces else None,
context_text=context_text)
return jsonify([annotation.to_json() for annotation in results])



def get_app(terms: Optional[GrounderInput] = None, *, ui: bool = True) -> Flask:
app = Flask(__name__)
app.config['RESTX_MASK_SWAGGER'] = False
Expand Down Expand Up @@ -282,7 +346,8 @@ def get_app(terms: Optional[GrounderInput] = None, *, ui: bool = True) -> Flask:
)

from gilda.app.ui import ui_blueprint
except ImportError:
except ImportError as e:
logger.error('Could not import UI blueprint: %s', e)
_mount_home_redirect(app)
else:
Bootstrap(app)
Expand Down
28 changes: 25 additions & 3 deletions gilda/app/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
div.choices {
margin-bottom: 0;
}
.nav-tabs {
margin-bottom: 20px;
}
</style>
{% endblock %}

Expand All @@ -24,14 +27,33 @@ <h2 style="margin-top: 10px; margin-bottom: 10px;">Gilda Grounding Service</h2>
</div>
<div class="panel-body">
<p>
The Gilda grounding service (v{{ version }}) can be used to
identify structured identifiers for entity names.</p><p>
The Gilda service (v{{ version }}) can be used to
identify identifiers for entity names.
</p>
<p> There are two usage modes: <b>Grounding</b> and <b>Annotation</b>. Grounding is also called
named entity normalization or linking and it assumes that the input is a span
of text corresponding to a named entity. Annotation is also called named
entity recognition and works with a longer span of text as input that may contain
any number of named entities in it.
</p>
<p>
For programmatic access, see the <a href="apidocs">API documentation</a>
and the <a href="https://github.com/indralab/gilda/blob/master/README.md">user guide</a>
for more information.
</p>
</div>
</div>
<ul class="nav nav-tabs">
{% set navigation_bar = [
('/', 'home', 'Grounding'),
('/ner', 'view_ner', 'Annotation')
] %}
{% for href, id, caption in navigation_bar %}
<li class="{% if request.path == href %}active{% endif %}">
<a href="{{ url_for('ui.' + id) }}">{{ caption }}</a>
</li>
{% endfor %}
</ul>
{% block gcontent %}{% endblock %}
<div class="well">
<p>
Expand Down Expand Up @@ -133,7 +155,7 @@ <h4 class="modal-title">Context Text</h4>
{% macro render_ner_form(form) %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Gilda NER Input</h3>
<h3 class="panel-title">Gilda Annotation Input</h3>
</div>
<div class="panel-body">
<form class="form" method="POST" role="form">
Expand Down
2 changes: 1 addition & 1 deletion gilda/app/templates/ner_matches.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">NER Results</h3>
<h3 class="panel-title">Named Entity Recognition Results</h3>
</div>
<div class="panel-body">
<blockquote>
Expand Down
2 changes: 1 addition & 1 deletion gilda/app/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@

class GroundForm(FlaskForm):
text = StringField(
"Text",
"Entity text",
validators=[DataRequired()],
description="Input the entity text (e.g., <code>k-ras</code>) to ground.",
)
Expand Down
9 changes: 8 additions & 1 deletion gilda/grounder.py
Original file line number Diff line number Diff line change
Expand Up @@ -681,7 +681,14 @@ def __str__(self):
return (f"Annotation({self.text}, {self.matches}, {self.start}, "
f"{self.end})")


def to_json(self):
"""Convert the Annotation object to JSON."""
return {
'text': self.text,
'matches': [match.to_json() for match in self.matches],
'start': self.start,
'end': self.end
}


def load_entries_from_terms_file(terms_file: Union[str, Path]) -> Iterator[Term]:
Expand Down

0 comments on commit 7677132

Please sign in to comment.