mirror of https://github.com/gsi-upm/senpy
Multiple changes in the API, schemas and UI
Check out the CHANGELOG.md file for more informationmaster
parent
4ba30304a4
commit
8a516d927e
@ -0,0 +1,53 @@
|
|||||||
|
# Changelog
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* Objects can control the keys that will be used in `serialize`/`jsonld`/`as_dict` by specifying a list of keys in `terse_keys`.
|
||||||
|
e.g.
|
||||||
|
```python
|
||||||
|
>>> class MyModel(senpy.models.BaseModel):
|
||||||
|
... _terse_keys = ['visible']
|
||||||
|
... invisible = 5
|
||||||
|
... visible = 1
|
||||||
|
...
|
||||||
|
>>> m = MyModel(id='testing')
|
||||||
|
>>> m.jsonld()
|
||||||
|
{'invisible': 5, 'visible': 1, '@id': 'testing'}
|
||||||
|
>>> m.jsonld(verbose=False)
|
||||||
|
{'visible': 1}
|
||||||
|
```
|
||||||
|
* Configurable logging format.
|
||||||
|
* Added default terse keys for the most common classes (entry, sentiment, emotion...).
|
||||||
|
* Flag parameters (boolean) are set to true even when no value is added (e.g. `&verbose` is the same as `&verbose=true`).
|
||||||
|
* Plugin and parameter descriptions are now formatted with (showdown)[https://github.com/showdownjs/showdown].
|
||||||
|
* The web UI requests extra_parameters from the server. This is useful for pipelines. See #52
|
||||||
|
* First batch of semantic tests (using SPARQL)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* `install_deps` now checks what requirements are already met before installing with pip.
|
||||||
|
* Help is now provided verbosely by default
|
||||||
|
* Other outputs are terse by default. This means some properties are now hidden unless verbose is set.
|
||||||
|
* `sentiments` and `emotions` are now `marl:hasOpinion` and `onyx:hasEmotionSet`, respectively.
|
||||||
|
* Nicer logging format
|
||||||
|
* Context aliases (e.g. `sentiments` and `emotions` properties) have been replaced with the original properties (e.g. `marl:hasOpinion` and `onyx:hasEmotionSet**), to use aliases, pass the `aliases** parameter.
|
||||||
|
* Several UI improvements
|
||||||
|
* Dedicated tab to show the list of plugins
|
||||||
|
* URLs in plugin descriptions are shown as links
|
||||||
|
* The format of the response is selected by clicking on a tab instead of selecting from a drop-down
|
||||||
|
* list of examples
|
||||||
|
* Bootstrap v4
|
||||||
|
* RandEmotion and RandSentiment are no longer included in the base set of plugins
|
||||||
|
* The `--plugin-folder` option can be used more than once, and every folder will be added to the app.
|
||||||
|
|
||||||
|
### Deprecated
|
||||||
|
### Removed
|
||||||
|
* Python 2.7 is no longer test or officially supported
|
||||||
|
### Fixed
|
||||||
|
* Plugin descriptions are now dedented when they are extracted from the docstring.
|
||||||
|
### Security
|
||||||
|
|
@ -1 +1 @@
|
|||||||
web: python -m senpy --host 0.0.0.0 --port $PORT --default-plugins
|
web: python -m senpy --host 0.0.0.0 --port $PORT
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
import os
|
|
||||||
|
|
||||||
SERVER_PORT = os.environ.get("SERVER_PORT", 5000)
|
|
||||||
DEBUG = os.environ.get("DEBUG", True)
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,317 +0,0 @@
|
|||||||
{
|
|
||||||
"cells": [
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {
|
|
||||||
"ExecuteTime": {
|
|
||||||
"end_time": "2017-04-10T17:05:31.465571Z",
|
|
||||||
"start_time": "2017-04-10T19:05:31.458282+02:00"
|
|
||||||
},
|
|
||||||
"deletable": true,
|
|
||||||
"editable": true
|
|
||||||
},
|
|
||||||
"source": [
|
|
||||||
"# Client"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {
|
|
||||||
"collapsed": true,
|
|
||||||
"deletable": true,
|
|
||||||
"editable": true
|
|
||||||
},
|
|
||||||
"source": [
|
|
||||||
"The built-in senpy client allows you to query any Senpy endpoint. We will illustrate how to use it with the public demo endpoint, and then show you how to spin up your own endpoint using docker."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {
|
|
||||||
"deletable": true,
|
|
||||||
"editable": true
|
|
||||||
},
|
|
||||||
"source": [
|
|
||||||
"Demo Endpoint\n",
|
|
||||||
"-------------"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {
|
|
||||||
"deletable": true,
|
|
||||||
"editable": true
|
|
||||||
},
|
|
||||||
"source": [
|
|
||||||
"To start using senpy, simply create a new Client and point it to your endpoint. In this case, the latest version of Senpy at GSI."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"metadata": {
|
|
||||||
"ExecuteTime": {
|
|
||||||
"end_time": "2017-04-10T17:29:12.827640Z",
|
|
||||||
"start_time": "2017-04-10T19:29:12.818617+02:00"
|
|
||||||
},
|
|
||||||
"collapsed": false,
|
|
||||||
"deletable": true,
|
|
||||||
"editable": true
|
|
||||||
},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"from senpy.client import Client\n",
|
|
||||||
"\n",
|
|
||||||
"c = Client('http://latest.senpy.cluster.gsi.dit.upm.es/api')\n"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {
|
|
||||||
"deletable": true,
|
|
||||||
"editable": true
|
|
||||||
},
|
|
||||||
"source": [
|
|
||||||
"Now, let's use that client analyse some queries:"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"metadata": {
|
|
||||||
"ExecuteTime": {
|
|
||||||
"end_time": "2017-04-10T17:29:14.011657Z",
|
|
||||||
"start_time": "2017-04-10T19:29:13.701808+02:00"
|
|
||||||
},
|
|
||||||
"collapsed": false,
|
|
||||||
"deletable": true,
|
|
||||||
"editable": true
|
|
||||||
},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"r = c.analyse('I like sugar!!', algorithm='sentiment140')\n",
|
|
||||||
"r"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {
|
|
||||||
"ExecuteTime": {
|
|
||||||
"end_time": "2017-04-10T17:08:19.616754Z",
|
|
||||||
"start_time": "2017-04-10T19:08:19.610767+02:00"
|
|
||||||
},
|
|
||||||
"deletable": true,
|
|
||||||
"editable": true
|
|
||||||
},
|
|
||||||
"source": [
|
|
||||||
"As you can see, that gave us the full JSON result. A more concise way to print it would be:"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"metadata": {
|
|
||||||
"ExecuteTime": {
|
|
||||||
"end_time": "2017-04-10T17:29:14.854213Z",
|
|
||||||
"start_time": "2017-04-10T19:29:14.842068+02:00"
|
|
||||||
},
|
|
||||||
"collapsed": false,
|
|
||||||
"deletable": true,
|
|
||||||
"editable": true
|
|
||||||
},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"for entry in r.entries:\n",
|
|
||||||
" print('{} -> {}'.format(entry['text'], entry['sentiments'][0]['marl:hasPolarity']))"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {
|
|
||||||
"deletable": true,
|
|
||||||
"editable": true
|
|
||||||
},
|
|
||||||
"source": [
|
|
||||||
"We can also obtain a list of available plugins with the client:"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"metadata": {
|
|
||||||
"ExecuteTime": {
|
|
||||||
"end_time": "2017-04-10T17:29:16.245198Z",
|
|
||||||
"start_time": "2017-04-10T19:29:16.056545+02:00"
|
|
||||||
},
|
|
||||||
"collapsed": false,
|
|
||||||
"deletable": true,
|
|
||||||
"editable": true
|
|
||||||
},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"c.plugins()"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {
|
|
||||||
"deletable": true,
|
|
||||||
"editable": true
|
|
||||||
},
|
|
||||||
"source": [
|
|
||||||
"Or, more concisely:"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"metadata": {
|
|
||||||
"ExecuteTime": {
|
|
||||||
"end_time": "2017-04-10T17:29:17.663275Z",
|
|
||||||
"start_time": "2017-04-10T19:29:17.484623+02:00"
|
|
||||||
},
|
|
||||||
"collapsed": false,
|
|
||||||
"deletable": true,
|
|
||||||
"editable": true
|
|
||||||
},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"c.plugins().keys()"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {
|
|
||||||
"deletable": true,
|
|
||||||
"editable": true
|
|
||||||
},
|
|
||||||
"source": [
|
|
||||||
"Local Endpoint\n",
|
|
||||||
"--------------"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {
|
|
||||||
"deletable": true,
|
|
||||||
"editable": true
|
|
||||||
},
|
|
||||||
"source": [
|
|
||||||
"To run your own instance of senpy, just create a docker container with the latest Senpy image. Using `--default-plugins` you will get some extra plugins to start playing with the API."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"metadata": {
|
|
||||||
"ExecuteTime": {
|
|
||||||
"end_time": "2017-04-10T17:29:20.637539Z",
|
|
||||||
"start_time": "2017-04-10T19:29:19.938322+02:00"
|
|
||||||
},
|
|
||||||
"collapsed": false,
|
|
||||||
"deletable": true,
|
|
||||||
"editable": true
|
|
||||||
},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"!docker run -ti --name 'SenpyEndpoint' -d -p 6000:5000 gsiupm/senpy:0.8.6 --host 0.0.0.0 --default-plugins"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {
|
|
||||||
"deletable": true,
|
|
||||||
"editable": true
|
|
||||||
},
|
|
||||||
"source": [
|
|
||||||
"To use this endpoint:"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"metadata": {
|
|
||||||
"ExecuteTime": {
|
|
||||||
"end_time": "2017-04-10T17:29:21.263976Z",
|
|
||||||
"start_time": "2017-04-10T19:29:21.260595+02:00"
|
|
||||||
},
|
|
||||||
"collapsed": false,
|
|
||||||
"deletable": true,
|
|
||||||
"editable": true
|
|
||||||
},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"c_local = Client('http://127.0.0.1:6000/api')"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {
|
|
||||||
"deletable": true,
|
|
||||||
"editable": true
|
|
||||||
},
|
|
||||||
"source": [
|
|
||||||
"That's all! After you are done with your analysis, stop the docker container:"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"metadata": {
|
|
||||||
"ExecuteTime": {
|
|
||||||
"end_time": "2017-04-10T17:29:33.226686Z",
|
|
||||||
"start_time": "2017-04-10T19:29:22.392121+02:00"
|
|
||||||
},
|
|
||||||
"collapsed": false,
|
|
||||||
"deletable": true,
|
|
||||||
"editable": true
|
|
||||||
},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"!docker stop SenpyEndpoint\n",
|
|
||||||
"!docker rm SenpyEndpoint"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"anaconda-cloud": {},
|
|
||||||
"kernelspec": {
|
|
||||||
"display_name": "Python 3",
|
|
||||||
"language": "python",
|
|
||||||
"name": "python3"
|
|
||||||
},
|
|
||||||
"language_info": {
|
|
||||||
"codemirror_mode": {
|
|
||||||
"name": "ipython",
|
|
||||||
"version": 3
|
|
||||||
},
|
|
||||||
"file_extension": ".py",
|
|
||||||
"mimetype": "text/x-python",
|
|
||||||
"name": "python",
|
|
||||||
"nbconvert_exporter": "python",
|
|
||||||
"pygments_lexer": "ipython3",
|
|
||||||
"version": "3.6.0"
|
|
||||||
},
|
|
||||||
"toc": {
|
|
||||||
"colors": {
|
|
||||||
"hover_highlight": "#DAA520",
|
|
||||||
"running_highlight": "#FF0000",
|
|
||||||
"selected_highlight": "#FFD700"
|
|
||||||
},
|
|
||||||
"moveMenuLeft": true,
|
|
||||||
"nav_menu": {
|
|
||||||
"height": "68px",
|
|
||||||
"width": "252px"
|
|
||||||
},
|
|
||||||
"navigate_menu": true,
|
|
||||||
"number_sections": true,
|
|
||||||
"sideBar": true,
|
|
||||||
"threshold": 4,
|
|
||||||
"toc_cell": false,
|
|
||||||
"toc_section_display": "block",
|
|
||||||
"toc_window_display": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nbformat": 4,
|
|
||||||
"nbformat_minor": 1
|
|
||||||
}
|
|
@ -1,106 +0,0 @@
|
|||||||
|
|
||||||
Client
|
|
||||||
======
|
|
||||||
|
|
||||||
Demo Endpoint
|
|
||||||
-------------
|
|
||||||
|
|
||||||
Import Client and send a request
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
from senpy.client import Client
|
|
||||||
|
|
||||||
c = Client('http://latest.senpy.cluster.gsi.dit.upm.es/api')
|
|
||||||
r = c.analyse('I like Pizza', algorithm='sentiment140')
|
|
||||||
|
|
||||||
Print response
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
for entry in r.entries:
|
|
||||||
print('{} -> {}'.format(entry['text'], entry['sentiments'][0]['marl:hasPolarity']))
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
I like Pizza -> marl:Positive
|
|
||||||
|
|
||||||
|
|
||||||
Obtain a list of available plugins
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
for plugin in c.request('/plugins')['plugins']:
|
|
||||||
print(plugin['name'])
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
emoRand
|
|
||||||
rand
|
|
||||||
sentiment140
|
|
||||||
|
|
||||||
|
|
||||||
Local Endpoint
|
|
||||||
--------------
|
|
||||||
|
|
||||||
Run a docker container with Senpy image and default plugins
|
|
||||||
|
|
||||||
.. code::
|
|
||||||
|
|
||||||
docker run -ti --name 'SenpyEndpoint' -d -p 5000:5000 gsiupm/senpy:0.8.6 --host 0.0.0.0 --default-plugins
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
a0157cd98057072388bfebeed78a830da7cf0a796f4f1a3fd9188f9f2e5fe562
|
|
||||||
|
|
||||||
|
|
||||||
Import client and send a request to localhost
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
c_local = Client('http://127.0.0.1:5000/api')
|
|
||||||
r = c_local.analyse('Hello world', algorithm='sentiment140')
|
|
||||||
|
|
||||||
Print response
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
for entry in r.entries:
|
|
||||||
print('{} -> {}'.format(entry['text'], entry['sentiments'][0]['marl:hasPolarity']))
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
Hello world -> marl:Neutral
|
|
||||||
|
|
||||||
|
|
||||||
Obtain a list of available plugins deployed locally
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
c_local.plugins().keys()
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
rand
|
|
||||||
sentiment140
|
|
||||||
emoRand
|
|
||||||
|
|
||||||
|
|
||||||
Stop the docker container
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
!docker stop SenpyEndpoint
|
|
||||||
!docker rm SenpyEndpoint
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
SenpyEndpoint
|
|
||||||
SenpyEndpoint
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
|||||||
About
|
|
||||||
--------
|
|
||||||
|
|
||||||
If you use Senpy in your research, please cite `Senpy: A Pragmatic Linked Sentiment Analysis Framework <http://gsi.dit.upm.es/index.php/es/investigacion/publicaciones?view=publication&task=show&id=417>`__ (`BibTex <http://gsi.dit.upm.es/index.php/es/investigacion/publicaciones?controller=publications&task=export&format=bibtex&id=417>`__):
|
|
||||||
|
|
||||||
.. code-block:: text
|
|
||||||
|
|
||||||
Sánchez-Rada, J. F., Iglesias, C. A., Corcuera, I., & Araque, Ó. (2016, October).
|
|
||||||
Senpy: A Pragmatic Linked Sentiment Analysis Framework.
|
|
||||||
In Data Science and Advanced Analytics (DSAA),
|
|
||||||
2016 IEEE International Conference on (pp. 735-742). IEEE.
|
|
@ -0,0 +1,9 @@
|
|||||||
|
Advanced usage
|
||||||
|
--------------
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
server-cli
|
||||||
|
conversion
|
||||||
|
commandline
|
@ -0,0 +1,27 @@
|
|||||||
|
Developing new services
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
Developing web services can be hard.
|
||||||
|
To illustrate it, the figure below summarizes the typical features in a text analysis service.
|
||||||
|
|
||||||
|
.. image:: senpy-framework.png
|
||||||
|
:width: 60%
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
Senpy implements all the common blocks, so developers can focus on what really matters: great analysis algorithms that solve real problems.
|
||||||
|
Among other things, Senpy takes care of these tasks:
|
||||||
|
|
||||||
|
* Interfacing with the user: parameter validation, error handling.
|
||||||
|
* Formatting: JSON-LD, Turtle/n-triples input and output, or simple text input
|
||||||
|
* Linked Data: senpy results are semantically annotated, using a series of well established vocabularies, and sane default URIs.
|
||||||
|
* User interface: a web UI where users can explore your service and test different settings
|
||||||
|
* A client to interact with the service. Currently only available in Python.
|
||||||
|
|
||||||
|
You only need to provide the algorithm to turn a piece of text into an annotation
|
||||||
|
Sharing your sentiment analysis with the world has never been easier!
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
plugins-quickstart
|
||||||
|
plugins-faq
|
Binary file not shown.
After Width: | Height: | Size: 76 KiB |
@ -0,0 +1,86 @@
|
|||||||
|
Quickstart for service developers
|
||||||
|
=================================
|
||||||
|
|
||||||
|
This document contains the minimum to get you started with developing new services using Senpy.
|
||||||
|
|
||||||
|
For an example of conversion plugins, see :doc:`conversion`.
|
||||||
|
For a description of definition files, see :doc:`plugins-definition`.
|
||||||
|
|
||||||
|
A more step-by-step tutorial with slides is available `here <https://lab.gsi.upm.es/senpy/senpy-tutorial>`__
|
||||||
|
|
||||||
|
.. contents:: :local:
|
||||||
|
|
||||||
|
Installation
|
||||||
|
############
|
||||||
|
|
||||||
|
First of all, you need to install the package.
|
||||||
|
See :doc:`installation` for instructions.
|
||||||
|
Once installed, the `senpy` command should be available.
|
||||||
|
|
||||||
|
Architecture
|
||||||
|
############
|
||||||
|
|
||||||
|
The main component of a sentiment analysis service is the algorithm itself. However, for the algorithm to work, it needs to get the appropriate parameters from the user, format the results according to the defined API, interact with the user whn errors occur or more information is needed, etc.
|
||||||
|
|
||||||
|
Senpy proposes a modular and dynamic architecture that allows:
|
||||||
|
|
||||||
|
* Implementing different algorithms in a extensible way, yet offering a common interface.
|
||||||
|
* Offering common services that facilitate development, so developers can focus on implementing new and better algorithms.
|
||||||
|
|
||||||
|
The framework consists of two main modules: Senpy core, which is the building block of the service, and Senpy plugins, which consist of the analysis algorithm. The next figure depicts a simplified version of the processes involved in an analysis with the Senpy framework.
|
||||||
|
|
||||||
|
.. image:: senpy-architecture.png
|
||||||
|
:width: 100%
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
|
||||||
|
What is a plugin?
|
||||||
|
#################
|
||||||
|
|
||||||
|
A plugin is a python object that can process entries. Given an entry, it will modify it, add annotations to it, or generate new entries.
|
||||||
|
|
||||||
|
|
||||||
|
What is an entry?
|
||||||
|
#################
|
||||||
|
|
||||||
|
Entries are objects that can be annotated.
|
||||||
|
In general, they will be a piece of text.
|
||||||
|
By default, entries are `NIF contexts <http://persistence.uni-leipzig.org/nlp2rdf/ontologies/nif-core/nif-core.html>`_ represented in JSON-LD format.
|
||||||
|
It is a dictionary/JSON object that looks like this:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
{
|
||||||
|
"@id": "<unique identifier or blank node name>",
|
||||||
|
"nif:isString": "input text",
|
||||||
|
"sentiments": [ {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
],
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
Annotations are added to the object like this:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
entry = Entry()
|
||||||
|
entry.vocabulary__annotationName = 'myvalue'
|
||||||
|
entry['vocabulary:annotationName'] = 'myvalue'
|
||||||
|
entry['annotationNameURI'] = 'myvalue'
|
||||||
|
|
||||||
|
Where vocabulary is one of the prefixes defined in the default senpy context, and annotationURI is a full URI.
|
||||||
|
The value may be any valid JSON-LD dictionary.
|
||||||
|
For simplicity, senpy includes a series of models by default in the ``senpy.models`` module.
|
||||||
|
|
||||||
|
Plugins Code
|
||||||
|
############
|
||||||
|
|
||||||
|
The basic methods in a plugin are:
|
||||||
|
|
||||||
|
* analyse_entry: called in every user requests. It takes two parameters: ``Entry``, the entry object, and ``params``, the parameters supplied by the user. It should yield one or more ``Entry`` objects.
|
||||||
|
* activate: used to load memory-hungry resources. For instance, to train a classifier.
|
||||||
|
* deactivate: used to free up resources when the plugin is no longer needed.
|
||||||
|
|
||||||
|
Plugins are loaded asynchronously, so don't worry if the activate method takes too long. The plugin will be marked as activated once it is finished executing the method.
|
||||||
|
|
@ -0,0 +1,46 @@
|
|||||||
|
Publications
|
||||||
|
============
|
||||||
|
|
||||||
|
|
||||||
|
If you use Senpy in your research, please cite `Senpy: A Pragmatic Linked Sentiment Analysis Framework <http://gsi.dit.upm.es/index.php/es/investigacion/publicaciones?view=publication&task=show&id=417>`__ (`BibTex <http://gsi.dit.upm.es/index.php/es/investigacion/publicaciones?controller=publications&task=export&format=bibtex&id=417>`__):
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
Sánchez-Rada, J. F., Iglesias, C. A., Corcuera, I., & Araque, Ó. (2016, October).
|
||||||
|
Senpy: A Pragmatic Linked Sentiment Analysis Framework.
|
||||||
|
In Data Science and Advanced Analytics (DSAA),
|
||||||
|
2016 IEEE International Conference on (pp. 735-742). IEEE.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Senpy uses Onyx for emotion representation, first introduced in:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
Sánchez-Rada, J. F., & Iglesias, C. A. (2016).
|
||||||
|
Onyx: A linked data approach to emotion representation.
|
||||||
|
Information Processing & Management, 52(1), 99-114.
|
||||||
|
|
||||||
|
Senpy uses Marl for sentiment representation, which was presented in:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
Westerski, A., Iglesias Fernandez, C. A., & Tapia Rico, F. (2011).
|
||||||
|
Linked opinions: Describing sentiments on the structured web of data.
|
||||||
|
|
||||||
|
|
||||||
|
Senpy has been used extensively in the toolbox of the MixedEmotions project:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
Buitelaar, P., Wood, I. D., Arcan, M., McCrae, J. P., Abele, A., Robin, C., … Tummarello, G. (2018).
|
||||||
|
MixedEmotions: An Open-Source Toolbox for Multi-Modal Emotion Analysis.
|
||||||
|
IEEE Transactions on Multimedia.
|
||||||
|
|
||||||
|
The representation models, formats and challenges are partially covered in a chapter of the book Sentiment Analysis in Social Networks:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
Iglesias, C. A., Sánchez-Rada, J. F., Vulcu, G., & Buitelaar, P. (2017).
|
||||||
|
Linked Data Models for Sentiment and Emotion Analysis in Social Networks.
|
||||||
|
In Sentiment Analysis in Social Networks (pp. 49-69).
|
@ -1,54 +1,27 @@
|
|||||||
What is Senpy?
|
What is Senpy?
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
Senpy is a framework for text analysis using Linked Data. There are three main applications of Senpy so far: sentiment and emotion analysis, user profiling and entity recoginition. Annotations and Services are compliant with NIF (NLP Interchange Format).
|
Senpy is a framework for sentiment and emotion analysis services.
|
||||||
|
Its goal is to produce analysis services that are interchangeable and fully interoperable.
|
||||||
|
|
||||||
Senpy aims at providing a framework where analysis modules can be integrated easily as plugins, and providing a core functionality for managing tasks such as data validation, user interaction, formatting, logging, translation to linked data, etc.
|
.. image:: senpy-architecture.png
|
||||||
|
:width: 100%
|
||||||
The figure below summarizes the typical features in a text analysis service.
|
|
||||||
Senpy implements all the common blocks, so developers can focus on what really matters: great analysis algorithms that solve real problems.
|
|
||||||
|
|
||||||
.. image:: senpy-framework.png
|
|
||||||
:width: 60%
|
|
||||||
:align: center
|
:align: center
|
||||||
|
|
||||||
|
|
||||||
Senpy for end users
|
|
||||||
===================
|
|
||||||
|
|
||||||
All services built using senpy share a common interface.
|
All services built using senpy share a common interface.
|
||||||
This allows users to use them (almost) interchangeably.
|
This allows users to use them (almost) interchangeably, with the same API and tools, simply by pointing to a different URL or changing a parameter.
|
||||||
Senpy comes with a :ref:`built-in client`.
|
The common schema also makes it easier to evaluate the performance of different algorithms and services.
|
||||||
|
In fact, Senpy has a built-in evaluation API you can use to compare results with different algorithms.
|
||||||
|
|
||||||
Senpy for service developers
|
|
||||||
============================
|
|
||||||
|
|
||||||
Senpy is a framework that turns your sentiment or emotion analysis algorithm into a full blown semantic service.
|
Services can also use the common interface to communicate with each other.
|
||||||
Senpy takes care of:
|
And higher level features can be built on top of these services, such as automatic fusion of results, emotion model conversion, and service discovery.
|
||||||
|
|
||||||
* Interfacing with the user: parameter validation, error handling.
|
These benefits are not limited to new services.
|
||||||
* Formatting: JSON-LD, Turtle/n-triples input and output, or simple text input
|
The community has developed wrappers for some proprietary and commercial services (such as sentiment140 and Meaning Cloud), so you can consult them as.
|
||||||
* Linked Data: senpy results are semantically annotated, using a series of well established vocabularies, and sane default URIs.
|
Senpy comes with a :ref:`built-in client`.
|
||||||
* User interface: a web UI where users can explore your service and test different settings
|
|
||||||
* A client to interact with the service. Currently only available in Python.
|
|
||||||
|
|
||||||
Sharing your sentiment analysis with the world has never been easier!
|
|
||||||
|
|
||||||
Check out the :doc:`plugins` if you have developed an analysis algorithm (e.g. sentiment analysis) and you want to publish it as a service.
|
|
||||||
|
|
||||||
Architecture
|
|
||||||
============
|
|
||||||
|
|
||||||
The main component of a sentiment analysis service is the algorithm itself. However, for the algorithm to work, it needs to get the appropriate parameters from the user, format the results according to the defined API, interact with the user whn errors occur or more information is needed, etc.
|
|
||||||
|
|
||||||
Senpy proposes a modular and dynamic architecture that allows:
|
|
||||||
|
|
||||||
* Implementing different algorithms in a extensible way, yet offering a common interface.
|
|
||||||
* Offering common services that facilitate development, so developers can focus on implementing new and better algorithms.
|
|
||||||
|
|
||||||
The framework consists of two main modules: Senpy core, which is the building block of the service, and Senpy plugins, which consist of the analysis algorithm. The next figure depicts a simplified version of the processes involved in an analysis with the Senpy framework.
|
To achieve this goal, Senpy uses a Linked Data principled approach, based on the NIF (NLP Interchange Format) specification, and open vocabularies such as Marl and Onyx.
|
||||||
|
You can learn more about this in :doc:`vocabularies`.
|
||||||
|
|
||||||
.. image:: senpy-architecture.png
|
Check out :doc:`plugins` if you have developed an analysis algorithm (e.g. sentiment analysis) and you want to publish it as a service.
|
||||||
:width: 100%
|
|
||||||
:align: center
|
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
Usage
|
|
||||||
-----
|
|
||||||
|
|
||||||
First of all, you need to install the package.
|
|
||||||
See :doc:`installation` for instructions.
|
|
||||||
Once installed, the `senpy` command should be available.
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 1
|
|
||||||
|
|
||||||
server
|
|
||||||
SenpyClientUse
|
|
||||||
commandline
|
|
||||||
|
|
||||||
|
|
@ -1 +1 @@
|
|||||||
gsitk
|
gsitk>0.1.9.1
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
import random
|
|
||||||
|
|
||||||
from senpy.plugins import EmotionPlugin
|
|
||||||
from senpy.models import EmotionSet, Emotion, Entry
|
|
||||||
|
|
||||||
|
|
||||||
class EmoRand(EmotionPlugin):
|
|
||||||
name = "emoRand"
|
|
||||||
description = 'A sample plugin that returns a random emotion annotation'
|
|
||||||
author = '@balkian'
|
|
||||||
version = '0.1'
|
|
||||||
url = "https://github.com/gsi-upm/senpy-plugins-community"
|
|
||||||
requirements = {}
|
|
||||||
onyx__usesEmotionModel = "emoml:big6"
|
|
||||||
|
|
||||||
def analyse_entry(self, entry, params):
|
|
||||||
category = "emoml:big6happiness"
|
|
||||||
number = max(-1, min(1, random.gauss(0, 0.5)))
|
|
||||||
if number > 0:
|
|
||||||
category = "emoml:big6anger"
|
|
||||||
emotionSet = EmotionSet()
|
|
||||||
emotion = Emotion({"onyx:hasEmotionCategory": category})
|
|
||||||
emotionSet.onyx__hasEmotion.append(emotion)
|
|
||||||
emotionSet.prov__wasGeneratedBy = self.id
|
|
||||||
entry.emotions.append(emotionSet)
|
|
||||||
yield entry
|
|
||||||
|
|
||||||
def test(self):
|
|
||||||
params = dict()
|
|
||||||
results = list()
|
|
||||||
for i in range(100):
|
|
||||||
res = next(self.analyse_entry(Entry(nif__isString="Hello"), params))
|
|
||||||
res.validate()
|
|
||||||
results.append(res.emotions[0]['onyx:hasEmotion'][0]['onyx:hasEmotionCategory'])
|
|
@ -1,347 +0,0 @@
|
|||||||
/*!
|
|
||||||
* Bootstrap v3.1.1 (http://getbootstrap.com)
|
|
||||||
* Copyright 2011-2014 Twitter, Inc.
|
|
||||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
||||||
*/
|
|
||||||
|
|
||||||
.btn-default,
|
|
||||||
.btn-primary,
|
|
||||||
.btn-success,
|
|
||||||
.btn-info,
|
|
||||||
.btn-warning,
|
|
||||||
.btn-danger {
|
|
||||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, .2);
|
|
||||||
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
|
|
||||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
|
|
||||||
}
|
|
||||||
.btn-default:active,
|
|
||||||
.btn-primary:active,
|
|
||||||
.btn-success:active,
|
|
||||||
.btn-info:active,
|
|
||||||
.btn-warning:active,
|
|
||||||
.btn-danger:active,
|
|
||||||
.btn-default.active,
|
|
||||||
.btn-primary.active,
|
|
||||||
.btn-success.active,
|
|
||||||
.btn-info.active,
|
|
||||||
.btn-warning.active,
|
|
||||||
.btn-danger.active {
|
|
||||||
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
|
|
||||||
box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
|
|
||||||
}
|
|
||||||
.btn:active,
|
|
||||||
.btn.active {
|
|
||||||
background-image: none;
|
|
||||||
}
|
|
||||||
.btn-default {
|
|
||||||
text-shadow: 0 1px 0 #fff;
|
|
||||||
background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%);
|
|
||||||
background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
border-color: #dbdbdb;
|
|
||||||
border-color: #ccc;
|
|
||||||
}
|
|
||||||
.btn-default:hover,
|
|
||||||
.btn-default:focus {
|
|
||||||
background-color: #e0e0e0;
|
|
||||||
background-position: 0 -15px;
|
|
||||||
}
|
|
||||||
.btn-default:active,
|
|
||||||
.btn-default.active {
|
|
||||||
background-color: #e0e0e0;
|
|
||||||
border-color: #dbdbdb;
|
|
||||||
}
|
|
||||||
.btn-primary {
|
|
||||||
background-image: -webkit-linear-gradient(top, #428bca 0%, #2d6ca2 100%);
|
|
||||||
background-image: linear-gradient(to bottom, #428bca 0%, #2d6ca2 100%);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
border-color: #2b669a;
|
|
||||||
}
|
|
||||||
.btn-primary:hover,
|
|
||||||
.btn-primary:focus {
|
|
||||||
background-color: #2d6ca2;
|
|
||||||
background-position: 0 -15px;
|
|
||||||
}
|
|
||||||
.btn-primary:active,
|
|
||||||
.btn-primary.active {
|
|
||||||
background-color: #2d6ca2;
|
|
||||||
border-color: #2b669a;
|
|
||||||
}
|
|
||||||
.btn-success {
|
|
||||||
background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);
|
|
||||||
background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
border-color: #3e8f3e;
|
|
||||||
}
|
|
||||||
.btn-success:hover,
|
|
||||||
.btn-success:focus {
|
|
||||||
background-color: #419641;
|
|
||||||
background-position: 0 -15px;
|
|
||||||
}
|
|
||||||
.btn-success:active,
|
|
||||||
.btn-success.active {
|
|
||||||
background-color: #419641;
|
|
||||||
border-color: #3e8f3e;
|
|
||||||
}
|
|
||||||
.btn-info {
|
|
||||||
background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
|
|
||||||
background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
border-color: #28a4c9;
|
|
||||||
}
|
|
||||||
.btn-info:hover,
|
|
||||||
.btn-info:focus {
|
|
||||||
background-color: #2aabd2;
|
|
||||||
background-position: 0 -15px;
|
|
||||||
}
|
|
||||||
.btn-info:active,
|
|
||||||
.btn-info.active {
|
|
||||||
background-color: #2aabd2;
|
|
||||||
border-color: #28a4c9;
|
|
||||||
}
|
|
||||||
.btn-warning {
|
|
||||||
background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
|
|
||||||
background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
border-color: #e38d13;
|
|
||||||
}
|
|
||||||
.btn-warning:hover,
|
|
||||||
.btn-warning:focus {
|
|
||||||
background-color: #eb9316;
|
|
||||||
background-position: 0 -15px;
|
|
||||||
}
|
|
||||||
.btn-warning:active,
|
|
||||||
.btn-warning.active {
|
|
||||||
background-color: #eb9316;
|
|
||||||
border-color: #e38d13;
|
|
||||||
}
|
|
||||||
.btn-danger {
|
|
||||||
background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
|
|
||||||
background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
border-color: #b92c28;
|
|
||||||
}
|
|
||||||
.btn-danger:hover,
|
|
||||||
.btn-danger:focus {
|
|
||||||
background-color: #c12e2a;
|
|
||||||
background-position: 0 -15px;
|
|
||||||
}
|
|
||||||
.btn-danger:active,
|
|
||||||
.btn-danger.active {
|
|
||||||
background-color: #c12e2a;
|
|
||||||
border-color: #b92c28;
|
|
||||||
}
|
|
||||||
.thumbnail,
|
|
||||||
.img-thumbnail {
|
|
||||||
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
|
|
||||||
box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
|
|
||||||
}
|
|
||||||
.dropdown-menu > li > a:hover,
|
|
||||||
.dropdown-menu > li > a:focus {
|
|
||||||
background-color: #e8e8e8;
|
|
||||||
background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
|
|
||||||
background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
}
|
|
||||||
.dropdown-menu > .active > a,
|
|
||||||
.dropdown-menu > .active > a:hover,
|
|
||||||
.dropdown-menu > .active > a:focus {
|
|
||||||
background-color: #357ebd;
|
|
||||||
background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%);
|
|
||||||
background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
}
|
|
||||||
.navbar-default {
|
|
||||||
background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%);
|
|
||||||
background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
border-radius: 4px;
|
|
||||||
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
|
|
||||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
|
|
||||||
}
|
|
||||||
.navbar-default .navbar-nav > .active > a {
|
|
||||||
background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f3f3f3 100%);
|
|
||||||
background-image: linear-gradient(to bottom, #ebebeb 0%, #f3f3f3 100%);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
-webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
|
|
||||||
box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
|
|
||||||
}
|
|
||||||
.navbar-brand,
|
|
||||||
.navbar-nav > li > a {
|
|
||||||
text-shadow: 0 1px 0 rgba(255, 255, 255, .25);
|
|
||||||
}
|
|
||||||
.navbar-inverse {
|
|
||||||
background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%);
|
|
||||||
background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
}
|
|
||||||
.navbar-inverse .navbar-nav > .active > a {
|
|
||||||
background-image: -webkit-linear-gradient(top, #222 0%, #282828 100%);
|
|
||||||
background-image: linear-gradient(to bottom, #222 0%, #282828 100%);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
-webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
|
|
||||||
box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
|
|
||||||
}
|
|
||||||
.navbar-inverse .navbar-brand,
|
|
||||||
.navbar-inverse .navbar-nav > li > a {
|
|
||||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, .25);
|
|
||||||
}
|
|
||||||
.navbar-static-top,
|
|
||||||
.navbar-fixed-top,
|
|
||||||
.navbar-fixed-bottom {
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
.alert {
|
|
||||||
text-shadow: 0 1px 0 rgba(255, 255, 255, .2);
|
|
||||||
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
|
|
||||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
|
|
||||||
}
|
|
||||||
.alert-success {
|
|
||||||
background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
|
|
||||||
background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
border-color: #b2dba1;
|
|
||||||
}
|
|
||||||
.alert-info {
|
|
||||||
background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
|
|
||||||
background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
border-color: #9acfea;
|
|
||||||
}
|
|
||||||
.alert-warning {
|
|
||||||
background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
|
|
||||||
background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
border-color: #f5e79e;
|
|
||||||
}
|
|
||||||
.alert-danger {
|
|
||||||
background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
|
|
||||||
background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
border-color: #dca7a7;
|
|
||||||
}
|
|
||||||
.progress {
|
|
||||||
background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
|
|
||||||
background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
}
|
|
||||||
.progress-bar {
|
|
||||||
background-image: -webkit-linear-gradient(top, #428bca 0%, #3071a9 100%);
|
|
||||||
background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
}
|
|
||||||
.progress-bar-success {
|
|
||||||
background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);
|
|
||||||
background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
}
|
|
||||||
.progress-bar-info {
|
|
||||||
background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
|
|
||||||
background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
}
|
|
||||||
.progress-bar-warning {
|
|
||||||
background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
|
|
||||||
background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
}
|
|
||||||
.progress-bar-danger {
|
|
||||||
background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);
|
|
||||||
background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
}
|
|
||||||
.list-group {
|
|
||||||
border-radius: 4px;
|
|
||||||
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
|
|
||||||
box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
|
|
||||||
}
|
|
||||||
.list-group-item.active,
|
|
||||||
.list-group-item.active:hover,
|
|
||||||
.list-group-item.active:focus {
|
|
||||||
text-shadow: 0 -1px 0 #3071a9;
|
|
||||||
background-image: -webkit-linear-gradient(top, #428bca 0%, #3278b3 100%);
|
|
||||||
background-image: linear-gradient(to bottom, #428bca 0%, #3278b3 100%);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
border-color: #3278b3;
|
|
||||||
}
|
|
||||||
.panel {
|
|
||||||
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
|
|
||||||
box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
|
|
||||||
}
|
|
||||||
.panel-default > .panel-heading {
|
|
||||||
background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
|
|
||||||
background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
}
|
|
||||||
.panel-primary > .panel-heading {
|
|
||||||
background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%);
|
|
||||||
background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
}
|
|
||||||
.panel-success > .panel-heading {
|
|
||||||
background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
|
|
||||||
background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
}
|
|
||||||
.panel-info > .panel-heading {
|
|
||||||
background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
|
|
||||||
background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
}
|
|
||||||
.panel-warning > .panel-heading {
|
|
||||||
background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
|
|
||||||
background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
}
|
|
||||||
.panel-danger > .panel-heading {
|
|
||||||
background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
|
|
||||||
background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
}
|
|
||||||
.well {
|
|
||||||
background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
|
|
||||||
background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
border-color: #dcdcdc;
|
|
||||||
-webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
|
|
||||||
box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
|
|
||||||
}
|
|
||||||
/*# sourceMappingURL=bootstrap-theme.css.map */
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
After Width: | Height: | Size: 5.1 KiB |
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,223 @@
|
|||||||
|
ns = {
|
||||||
|
'http://www.gsi.dit.upm.es/ontologies/marl/ns#': 'marl',
|
||||||
|
'http://www.gsi.dit.upm.es/ontologies/onyx/ns#': 'onyx',
|
||||||
|
'http://www.gsi.dit.upm.es/ontologies/senpy/ns#': 'onyx',
|
||||||
|
'http://www.gsi.upm.es/onto/senpy/ns#': 'senpy',
|
||||||
|
'http://www.w3.org/ns/prov#': 'prov',
|
||||||
|
'http://persistence.uni-leipzig.org/nlp2rdf/ontologies/nif-core#': 'nif'
|
||||||
|
}
|
||||||
|
|
||||||
|
mappings = {
|
||||||
|
'http://www.w3.org/1999/02/22-rdf-syntax-ns#type': 'a',
|
||||||
|
}
|
||||||
|
|
||||||
|
function load_graph(){
|
||||||
|
|
||||||
|
function filterNodesById(nodes,id){
|
||||||
|
return nodes.filter(function(n) { return n.id === id; });
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterNodesByType(nodes,value){
|
||||||
|
return nodes.filter(function(n) { return n.type === value; });
|
||||||
|
}
|
||||||
|
|
||||||
|
function triplesToGraph(triples){
|
||||||
|
|
||||||
|
svg.html("");
|
||||||
|
//Graph
|
||||||
|
var graph={nodes:[], links:[], triples: []};
|
||||||
|
|
||||||
|
triples = triples.filter(t=>t!=null).map(t => {
|
||||||
|
return t.map(e =>{
|
||||||
|
ids = e.match(/^\<(.*)\>/)
|
||||||
|
if (! ids ) {
|
||||||
|
return e
|
||||||
|
|
||||||
|
}
|
||||||
|
id = ids[1]
|
||||||
|
for (ix in ns) {
|
||||||
|
id = id.replace(ix, ns[ix] + ':')
|
||||||
|
}
|
||||||
|
if (! (id in mappings)) {
|
||||||
|
console.log(id, id in mappings)
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
return mappings[id]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
//Initial Graph from triples
|
||||||
|
triples.forEach(function(triple){
|
||||||
|
if (triple == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var subjId = triple[0];
|
||||||
|
var predId = triple[1];
|
||||||
|
var objId = triple[2];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var subjNode = filterNodesById(graph.nodes, subjId)[0];
|
||||||
|
var objNode = filterNodesById(graph.nodes, objId)[0];
|
||||||
|
|
||||||
|
if(subjNode==null){
|
||||||
|
subjNode = {id:subjId, label:subjId, weight:1, type:"node"};
|
||||||
|
graph.nodes.push(subjNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(objNode==null){
|
||||||
|
objNode = {id:objId, label:objId, weight:1, type:"node"};
|
||||||
|
graph.nodes.push(objNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
var predNode = {id:predId, label:predId, weight:1, type:"pred"} ;
|
||||||
|
graph.nodes.push(predNode);
|
||||||
|
|
||||||
|
var blankLabel = "";
|
||||||
|
|
||||||
|
graph.links.push({source:subjNode, target:predNode, predicate:blankLabel, weight:1});
|
||||||
|
graph.links.push({source:predNode, target:objNode, predicate:blankLabel, weight:1});
|
||||||
|
graph.triples.push({s:subjNode, p:predNode, o:objNode});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
return graph;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function update(graph){
|
||||||
|
|
||||||
|
|
||||||
|
// ==================== Add Marker ====================
|
||||||
|
svg.append("svg:defs").selectAll("marker")
|
||||||
|
.data(["end"])
|
||||||
|
.enter().append("svg:marker")
|
||||||
|
.attr("id", String)
|
||||||
|
.attr("viewBox", "0 -5 10 10")
|
||||||
|
.attr("refX", 30)
|
||||||
|
.attr("refY", -0.5)
|
||||||
|
.attr("markerWidth", 6)
|
||||||
|
.attr("markerHeight", 6)
|
||||||
|
.attr("orient", "auto")
|
||||||
|
.append("svg:polyline")
|
||||||
|
.attr("points", "0,-5 10,0 0,5")
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
|
// ==================== Add Links ====================
|
||||||
|
var links = svg.selectAll(".link")
|
||||||
|
.data(graph.triples)
|
||||||
|
.enter()
|
||||||
|
.append("path")
|
||||||
|
.attr("marker-end", "url(#end)")
|
||||||
|
.attr("class", "link")
|
||||||
|
;
|
||||||
|
;//links
|
||||||
|
|
||||||
|
// ==================== Add Link Names =====================
|
||||||
|
var linkTexts = svg.selectAll(".link-text")
|
||||||
|
.data(graph.triples)
|
||||||
|
.enter()
|
||||||
|
.append("text")
|
||||||
|
.attr("class", "link-text")
|
||||||
|
.text( function (d) { return d.p.label; })
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
|
//linkTexts.append("title")
|
||||||
|
// .text(function(d) { return d.predicate; });
|
||||||
|
|
||||||
|
// ==================== Add Link Names =====================
|
||||||
|
var nodeTexts = svg.selectAll(".node-text")
|
||||||
|
.data(filterNodesByType(graph.nodes, "node"))
|
||||||
|
.enter()
|
||||||
|
.append("text")
|
||||||
|
.attr("class", "node-text")
|
||||||
|
.text( function (d) { return d.label; })
|
||||||
|
;
|
||||||
|
//nodeTexts.append("title")
|
||||||
|
// .text(function(d) { return d.label; });
|
||||||
|
|
||||||
|
// ==================== Add Node =====================
|
||||||
|
var nodes = svg.selectAll(".node")
|
||||||
|
.data(filterNodesByType(graph.nodes, "node"))
|
||||||
|
.enter()
|
||||||
|
.append("circle")
|
||||||
|
.attr("class", "node")
|
||||||
|
.attr("r",8)
|
||||||
|
.call(force.drag)
|
||||||
|
;//nodes
|
||||||
|
|
||||||
|
// ==================== Add Predicate =====================
|
||||||
|
/*var preds = svg.selectAll(".node")
|
||||||
|
.data(graph.preds)
|
||||||
|
.enter()
|
||||||
|
.append("circle")
|
||||||
|
.attr("class", "node")
|
||||||
|
.attr("r",1)
|
||||||
|
//.call(force.drag)*/
|
||||||
|
;//nodes
|
||||||
|
|
||||||
|
// ==================== Force ====================
|
||||||
|
force.on("tick", function() {
|
||||||
|
nodes
|
||||||
|
.attr("cx", function(d){ return d.x; })
|
||||||
|
.attr("cy", function(d){ return d.y; })
|
||||||
|
;
|
||||||
|
|
||||||
|
links
|
||||||
|
.attr("d", function(d) {
|
||||||
|
return "M" + d.s.x + "," + d.s.y
|
||||||
|
+ "S" + d.p.x + "," + d.p.y
|
||||||
|
+ " " + d.o.x + "," + d.o.y;
|
||||||
|
})
|
||||||
|
;
|
||||||
|
|
||||||
|
nodeTexts
|
||||||
|
.attr("x", function(d) { return d.x + 12 ; })
|
||||||
|
.attr("y", function(d) { return d.y + 3; })
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
|
linkTexts
|
||||||
|
.attr("x", function(d) { return 4 + (d.s.x + d.p.x + d.o.x)/3 ; })
|
||||||
|
.attr("y", function(d) { return 4 + (d.s.y + d.p.y + d.o.y)/3 ; })
|
||||||
|
; });
|
||||||
|
|
||||||
|
// ==================== Run ====================
|
||||||
|
force
|
||||||
|
.nodes(graph.nodes)
|
||||||
|
.links(graph.links)
|
||||||
|
.charge(-500)
|
||||||
|
.linkDistance(50)
|
||||||
|
.start()
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
get_result('ntriples').done(resp => {
|
||||||
|
triples = resp.split('\n').map(line => line.match(/[^" ][^ ]*|\"[^"]+\"[^ ]*/g))
|
||||||
|
|
||||||
|
console.log(triples);
|
||||||
|
|
||||||
|
|
||||||
|
var graph = triplesToGraph(triples);
|
||||||
|
console.log(graph);
|
||||||
|
|
||||||
|
update(graph);
|
||||||
|
}).fail(resp => {
|
||||||
|
alert('Could not get a response.');
|
||||||
|
});
|
||||||
|
|
||||||
|
d3.select('#svg-body').selectAll("*").remove();
|
||||||
|
var svg = d3.select("#svg-body").append("svg")
|
||||||
|
.attr("width", width)
|
||||||
|
.attr("height", height)
|
||||||
|
;
|
||||||
|
|
||||||
|
var height = 600;
|
||||||
|
var width = $('.tab-content').width();
|
||||||
|
|
||||||
|
console.log('Graph with', width, height);
|
||||||
|
var force = d3.layout.force().size([width, height]);
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue