mirror of
https://github.com/gsi-upm/senpy
synced 2025-09-17 12:02:21 +00:00
Compare commits
13 Commits
0.8.0-fix2
...
0.8.3
Author | SHA1 | Date | |
---|---|---|---|
|
efb305173e | ||
|
2288b04c92 | ||
|
7899cb4d33 | ||
|
62ddca79ac | ||
|
99403b3443 | ||
|
a0ff528a4b | ||
|
97bd245dfc | ||
|
d8b59d06a4 | ||
|
453b9f3257 | ||
|
5fb858f5fc | ||
|
bd984a1437 | ||
|
e741b565a1 | ||
|
668a803d89 |
@@ -6,7 +6,7 @@ image: gsiupm/dockermake:latest
|
|||||||
variables:
|
variables:
|
||||||
DOCKER_DRIVER: overlay
|
DOCKER_DRIVER: overlay
|
||||||
DOCKERFILE: Dockerfile
|
DOCKERFILE: Dockerfile
|
||||||
VERSION: $CI_BUILD_REF
|
IMAGENAME: $CI_REGISTRY_IMAGE
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- test
|
- test
|
||||||
@@ -43,32 +43,18 @@ push-3.5:
|
|||||||
<<: *image_definition
|
<<: *image_definition
|
||||||
variables:
|
variables:
|
||||||
PYTHON_VERSION: "3.5"
|
PYTHON_VERSION: "3.5"
|
||||||
VERSION: $CI_BUILD_TAG
|
|
||||||
IMAGENAME: $CI_REGISTRY_IMAGE
|
|
||||||
|
|
||||||
push-2.7:
|
push-2.7:
|
||||||
<<: *image_definition
|
<<: *image_definition
|
||||||
variables:
|
variables:
|
||||||
PYTHON_VERSION: "2.7"
|
PYTHON_VERSION: "2.7"
|
||||||
VERSION: $CI_BUILD_TAG
|
|
||||||
IMAGENAME: $CI_REGISTRY_IMAGE
|
|
||||||
|
|
||||||
push-main:
|
|
||||||
<<: *image_definition
|
|
||||||
variables:
|
|
||||||
PYTHON_VERSION: main
|
|
||||||
VERSION: $CI_BUILD_TAG
|
|
||||||
IMAGENAME: $CI_REGISTRY_IMAGE
|
|
||||||
|
|
||||||
push-latest:
|
push-latest:
|
||||||
<<: *image_definition
|
<<: *image_definition
|
||||||
variables:
|
variables:
|
||||||
PYTHON_VERSION: latest
|
PYTHON_VERSION: latest
|
||||||
VERSION: $CI_BUILD_TAG
|
|
||||||
IMAGENAME: $CI_REGISTRY_IMAGE
|
|
||||||
only:
|
only:
|
||||||
- master
|
- master
|
||||||
- tags
|
|
||||||
- triggers
|
- triggers
|
||||||
|
|
||||||
clean :
|
clean :
|
||||||
|
@@ -6,7 +6,7 @@ RUN mkdir /cache/ /senpy-plugins /data/
|
|||||||
|
|
||||||
VOLUME /data/
|
VOLUME /data/
|
||||||
|
|
||||||
ENV PIP_CACHE_DIR=/cache/
|
ENV PIP_CACHE_DIR=/cache/ SENPY_DATA=/data
|
||||||
|
|
||||||
ONBUILD COPY . /senpy-plugins/
|
ONBUILD COPY . /senpy-plugins/
|
||||||
ONBUILD RUN python -m senpy --only-install -f /senpy-plugins
|
ONBUILD RUN python -m senpy --only-install -f /senpy-plugins
|
||||||
|
14
Makefile
14
Makefile
@@ -41,12 +41,12 @@ build-%: version Dockerfile-%
|
|||||||
quick_test: $(addprefix test-,$(PYMAIN))
|
quick_test: $(addprefix test-,$(PYMAIN))
|
||||||
|
|
||||||
dev-%:
|
dev-%:
|
||||||
@docker start $(NAME)-dev || (\
|
@docker start $(NAME)-dev$* || (\
|
||||||
$(MAKE) build-$*; \
|
$(MAKE) build-$*; \
|
||||||
docker run -d -w /usr/src/app/ -v $$PWD:/usr/src/app --entrypoint=/bin/bash -p 5000:5000 -ti --name $(NAME)-dev '$(IMAGEWTAG)-python$*'; \
|
docker run -d -w /usr/src/app/ -v $$PWD:/usr/src/app --entrypoint=/bin/bash -ti --name $(NAME)-dev$* '$(IMAGEWTAG)-python$*'; \
|
||||||
)\
|
)\
|
||||||
|
|
||||||
docker exec -ti $(NAME)-dev bash
|
docker exec -ti $(NAME)-dev$* bash
|
||||||
|
|
||||||
dev: dev-$(PYMAIN)
|
dev: dev-$(PYMAIN)
|
||||||
|
|
||||||
@@ -95,15 +95,13 @@ run: run-$(PYMAIN)
|
|||||||
push-latest: build-$(PYMAIN)
|
push-latest: build-$(PYMAIN)
|
||||||
docker tag '$(IMAGEWTAG)-python$(PYMAIN)' '$(IMAGEWTAG)'
|
docker tag '$(IMAGEWTAG)-python$(PYMAIN)' '$(IMAGEWTAG)'
|
||||||
docker tag '$(IMAGEWTAG)-python$(PYMAIN)' '$(IMAGENAME)'
|
docker tag '$(IMAGEWTAG)-python$(PYMAIN)' '$(IMAGENAME)'
|
||||||
docker push '$(IMAGENAME)'
|
docker push '$(IMAGENAME):latest'
|
||||||
docker push '$(IMAGEWTAG)'
|
docker push '$(IMAGEWTAG)'
|
||||||
|
|
||||||
push-%: build-%
|
push-%: build-%
|
||||||
docker push $(IMAGE):$(VERSION)-python$*
|
docker push $(IMAGENAME):$(VERSION)-python$*
|
||||||
|
|
||||||
push-main: push-$(PYMAIN)
|
|
||||||
|
|
||||||
ci:
|
ci:
|
||||||
gitlab-runner exec docker --docker-volumes /var/run/docker.sock:/var/run/docker.sock --env CI_PROJECT_NAME=$(NAME) ${action}
|
gitlab-runner exec docker --docker-volumes /var/run/docker.sock:/var/run/docker.sock --env CI_PROJECT_NAME=$(NAME) ${action}
|
||||||
|
|
||||||
.PHONY: test test-% test-all build-% build test pip_test run yapf dev ci version .FORCE
|
.PHONY: test test-% test-all build-% build test pip_test run yapf push-main push-% dev ci version .FORCE
|
||||||
|
34
docs/api.rst
34
docs/api.rst
@@ -62,6 +62,7 @@ NIF API
|
|||||||
:query o outformat: one of `turtle` (default), `text`, `json-ld`
|
:query o outformat: one of `turtle` (default), `text`, `json-ld`
|
||||||
:query p prefix: prefix for the URIs
|
:query p prefix: prefix for the URIs
|
||||||
:query algo algorithm: algorithm/plugin to use for the analysis. For a list of options, see :http:get:`/api/plugins`. If not provided, the default plugin will be used (:http:get:`/api/plugins/default`).
|
:query algo algorithm: algorithm/plugin to use for the analysis. For a list of options, see :http:get:`/api/plugins`. If not provided, the default plugin will be used (:http:get:`/api/plugins/default`).
|
||||||
|
:query algo emotionModel: desired emotion model in the results. If the requested algorithm does not use that emotion model, there are conversion plugins specifically for this. If none of the plugins match, an error will be returned, which includes the results *as is*.
|
||||||
|
|
||||||
:reqheader Accept: the response content type depends on
|
:reqheader Accept: the response content type depends on
|
||||||
:mailheader:`Accept` header
|
:mailheader:`Accept` header
|
||||||
@@ -69,6 +70,7 @@ NIF API
|
|||||||
header of request
|
header of request
|
||||||
:statuscode 200: no error
|
:statuscode 200: no error
|
||||||
:statuscode 404: service not found
|
:statuscode 404: service not found
|
||||||
|
:statuscode 400: error while processing the request
|
||||||
|
|
||||||
.. http:post:: /api
|
.. http:post:: /api
|
||||||
|
|
||||||
@@ -94,7 +96,9 @@ NIF API
|
|||||||
"@context": {
|
"@context": {
|
||||||
...
|
...
|
||||||
},
|
},
|
||||||
"sentiment140": {
|
"@type": "plugins",
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
"name": "sentiment140",
|
"name": "sentiment140",
|
||||||
"is_activated": true,
|
"is_activated": true,
|
||||||
"version": "0.1",
|
"version": "0.1",
|
||||||
@@ -115,8 +119,7 @@ NIF API
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@id": "sentiment140_0.1"
|
"@id": "sentiment140_0.1"
|
||||||
},
|
}, {
|
||||||
"rand": {
|
|
||||||
"name": "rand",
|
"name": "rand",
|
||||||
"is_activated": true,
|
"is_activated": true,
|
||||||
"version": "0.1",
|
"version": "0.1",
|
||||||
@@ -138,6 +141,7 @@ NIF API
|
|||||||
},
|
},
|
||||||
"@id": "rand_0.1"
|
"@id": "rand_0.1"
|
||||||
}
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -148,7 +152,7 @@ NIF API
|
|||||||
|
|
||||||
.. sourcecode:: http
|
.. sourcecode:: http
|
||||||
|
|
||||||
GET /api/plugins/rand HTTP/1.1
|
GET /api/plugins/rand/ HTTP/1.1
|
||||||
Host: localhost
|
Host: localhost
|
||||||
Accept: application/json, text/javascript
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
@@ -159,6 +163,7 @@ NIF API
|
|||||||
|
|
||||||
{
|
{
|
||||||
"@id": "rand_0.1",
|
"@id": "rand_0.1",
|
||||||
|
"@type": "sentimentPlugin",
|
||||||
"extra_params": {
|
"extra_params": {
|
||||||
"@id": "extra_params_rand_0.1",
|
"@id": "extra_params_rand_0.1",
|
||||||
"language": {
|
"language": {
|
||||||
@@ -185,24 +190,3 @@ NIF API
|
|||||||
|
|
||||||
Return the information about the default plugin.
|
Return the information about the default plugin.
|
||||||
|
|
||||||
.. http:get:: /api/plugins/<pluginname>/{de}activate
|
|
||||||
|
|
||||||
{De}activate a plugin.
|
|
||||||
|
|
||||||
**Example request**:
|
|
||||||
|
|
||||||
.. sourcecode:: http
|
|
||||||
|
|
||||||
GET /api/plugins/rand/deactivate HTTP/1.1
|
|
||||||
Host: localhost
|
|
||||||
Accept: application/json, text/javascript
|
|
||||||
|
|
||||||
|
|
||||||
**Example response**:
|
|
||||||
|
|
||||||
.. sourcecode:: http
|
|
||||||
|
|
||||||
{
|
|
||||||
"@context": {},
|
|
||||||
"message": "Ok"
|
|
||||||
}
|
|
||||||
|
116
docs/conversion.rst
Normal file
116
docs/conversion.rst
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
Conversion
|
||||||
|
----------
|
||||||
|
|
||||||
|
Senpy includes experimental support for emotion/sentiment conversion plugins.
|
||||||
|
|
||||||
|
|
||||||
|
Use
|
||||||
|
===
|
||||||
|
|
||||||
|
Consider the original query: http://127.0.0.1:5000/api/?i=hello&algo=emoRand
|
||||||
|
|
||||||
|
The requested plugin (emoRand) returns emotions using Ekman's model (or big6 in EmotionML):
|
||||||
|
|
||||||
|
.. code:: json
|
||||||
|
|
||||||
|
|
||||||
|
... rest of the document ...
|
||||||
|
{
|
||||||
|
"@type": "emotionSet",
|
||||||
|
"onyx:hasEmotion": {
|
||||||
|
"@type": "emotion",
|
||||||
|
"onyx:hasEmotionCategory": "emoml:big6anger"
|
||||||
|
},
|
||||||
|
"prov:wasGeneratedBy": "plugins/emoRand_0.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
To get these emotions in VAD space (FSRE dimensions in EmotionML), we'd do this:
|
||||||
|
|
||||||
|
http://127.0.0.1:5000/api/?i=hello&algo=emoRand&emotionModel=emoml:fsre-dimensions
|
||||||
|
|
||||||
|
This call, provided there is a valid conversion plugin from Ekman's to VAD, would return something like this:
|
||||||
|
|
||||||
|
.. code:: json
|
||||||
|
|
||||||
|
|
||||||
|
... rest of the document ...
|
||||||
|
{
|
||||||
|
"@type": "emotionSet",
|
||||||
|
"onyx:hasEmotion": {
|
||||||
|
"@type": "emotion",
|
||||||
|
"onyx:hasEmotionCategory": "emoml:big6anger"
|
||||||
|
},
|
||||||
|
"prov:wasGeneratedBy": "plugins/emoRand_0.1"
|
||||||
|
}, {
|
||||||
|
"@type": "emotionSet",
|
||||||
|
"onyx:hasEmotion": {
|
||||||
|
"@type": "emotion",
|
||||||
|
"A": 7.22,
|
||||||
|
"D": 6.28,
|
||||||
|
"V": 8.6
|
||||||
|
},
|
||||||
|
"prov:wasGeneratedBy": "plugins/Ekman2VAD_0.1"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
That is called a *full* response, as it simply adds the converted emotion alongside.
|
||||||
|
It is also possible to get the original emotion nested within the new converted emotion, using the `conversion=nested` parameter:
|
||||||
|
|
||||||
|
.. code:: json
|
||||||
|
|
||||||
|
|
||||||
|
... rest of the document ...
|
||||||
|
{
|
||||||
|
"@type": "emotionSet",
|
||||||
|
"onyx:hasEmotion": {
|
||||||
|
"@type": "emotion",
|
||||||
|
"onyx:hasEmotionCategory": "emoml:big6anger"
|
||||||
|
},
|
||||||
|
"prov:wasGeneratedBy": "plugins/emoRand_0.1"
|
||||||
|
"onyx:wasDerivedFrom": {
|
||||||
|
"@type": "emotionSet",
|
||||||
|
"onyx:hasEmotion": {
|
||||||
|
"@type": "emotion",
|
||||||
|
"A": 7.22,
|
||||||
|
"D": 6.28,
|
||||||
|
"V": 8.6
|
||||||
|
},
|
||||||
|
"prov:wasGeneratedBy": "plugins/Ekman2VAD_0.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Lastly, `conversion=filtered` would only return the converted emotions.
|
||||||
|
|
||||||
|
Developing a conversion plugin
|
||||||
|
================================
|
||||||
|
|
||||||
|
Conversion plugins are discovered by the server just like any other plugin.
|
||||||
|
The difference is the slightly different API, and the need to specify the `source` and `target` of the conversion.
|
||||||
|
For instance, an emotion conversion plugin needs the following:
|
||||||
|
|
||||||
|
|
||||||
|
.. code:: yaml
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
onyx:doesConversion:
|
||||||
|
- onyx:conversionFrom: emoml:big6
|
||||||
|
onyx:conversionTo: emoml:fsre-dimensions
|
||||||
|
- onyx:conversionFrom: emoml:fsre-dimensions
|
||||||
|
onyx:conversionTo: emoml:big6
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
|
||||||
|
class MyConversion(EmotionConversionPlugin):
|
||||||
|
|
||||||
|
def convert(self, emotionSet, fromModel, toModel, params):
|
||||||
|
pass
|
@@ -1,8 +1,3 @@
|
|||||||
.. Senpy documentation master file, created by
|
|
||||||
sphinx-quickstart on Tue Feb 24 08:57:32 2015.
|
|
||||||
You can adapt this file completely to your liking, but it should at least
|
|
||||||
contain the root `toctree` directive.
|
|
||||||
|
|
||||||
Welcome to Senpy's documentation!
|
Welcome to Senpy's documentation!
|
||||||
=================================
|
=================================
|
||||||
|
|
||||||
@@ -15,5 +10,6 @@ Contents:
|
|||||||
api
|
api
|
||||||
schema
|
schema
|
||||||
plugins
|
plugins
|
||||||
|
conversion
|
||||||
demo
|
demo
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
Developing new plugins
|
Developing new plugins
|
||||||
----------------------
|
----------------------
|
||||||
|
This document describes how to develop a new analysis plugin. For an example of conversion plugins, see :doc:`conversion`.
|
||||||
|
|
||||||
Each plugin represents a different analysis process.There are two types of files that are needed by senpy for loading a plugin:
|
Each plugin represents a different analysis process.There are two types of files that are needed by senpy for loading a plugin:
|
||||||
|
|
||||||
- Definition file, has the ".senpy" extension.
|
- Definition file, has the ".senpy" extension.
|
||||||
|
@@ -48,8 +48,8 @@ Once the server is launched, there is a basic endpoint in the server, which prov
|
|||||||
|
|
||||||
In case you want to know the different endpoints of the server, there is more information available in the NIF API section_.
|
In case you want to know the different endpoints of the server, there is more information available in the NIF API section_.
|
||||||
|
|
||||||
Video example
|
CLI
|
||||||
=============
|
===
|
||||||
|
|
||||||
This video shows how to use senpy through command-line tool.
|
This video shows how to use senpy through command-line tool.
|
||||||
|
|
||||||
@@ -58,18 +58,23 @@ https://asciinema.org/a/9uwef1ghkjk062cw2t4mhzpyk
|
|||||||
Request example in python
|
Request example in python
|
||||||
=========================
|
=========================
|
||||||
|
|
||||||
This example shows how to make a request to a plugin.
|
This example shows how to make a request to the default plugin:
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
import requests
|
from senpy.client import Client
|
||||||
import json
|
|
||||||
|
|
||||||
r = requests.get('http://127.0.0.1:5000/api/?algo=rand&i=Testing')
|
c = Client('http://127.0.0.1:5000/api/')
|
||||||
response = r.content.decode('utf-8')
|
r = c.analyse('hello world')
|
||||||
response_json = json.loads(response)
|
|
||||||
|
for entry in r.entries:
|
||||||
|
print('{} -> {}'.format(entry.text, entry.emotions))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. _section: http://senpy.readthedocs.org/en/latest/api.html
|
.. _section: http://senpy.readthedocs.org/en/latest/api.html
|
||||||
|
|
||||||
|
|
||||||
|
Conversion
|
||||||
|
==========
|
||||||
|
See :doc:`conversion`
|
||||||
|
@@ -17,7 +17,6 @@
|
|||||||
"""
|
"""
|
||||||
Sentiment analysis server in Python
|
Sentiment analysis server in Python
|
||||||
"""
|
"""
|
||||||
from __future__ import print_function
|
|
||||||
from .version import __version__
|
from .version import __version__
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
@@ -18,7 +18,6 @@ class Client(object):
|
|||||||
try:
|
try:
|
||||||
resp = models.from_dict(response.json())
|
resp = models.from_dict(response.json())
|
||||||
resp.validate(resp)
|
resp.validate(resp)
|
||||||
return resp
|
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
logger.error(('There seems to be a problem with the response:\n'
|
logger.error(('There seems to be a problem with the response:\n'
|
||||||
'\tURL: {url}\n'
|
'\tURL: {url}\n'
|
||||||
@@ -33,3 +32,6 @@ class Client(object):
|
|||||||
code=response.status_code,
|
code=response.status_code,
|
||||||
content=response.content))
|
content=response.content))
|
||||||
raise ex
|
raise ex
|
||||||
|
if isinstance(resp, models.Error):
|
||||||
|
raise resp
|
||||||
|
return resp
|
||||||
|
@@ -13,10 +13,11 @@ from .api import API_PARAMS, NIF_PARAMS, parse_params
|
|||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import copy
|
||||||
import fnmatch
|
import fnmatch
|
||||||
import inspect
|
import inspect
|
||||||
import sys
|
import sys
|
||||||
import imp
|
import importlib
|
||||||
import logging
|
import logging
|
||||||
import traceback
|
import traceback
|
||||||
import yaml
|
import yaml
|
||||||
@@ -180,7 +181,7 @@ class Senpy(object):
|
|||||||
newentries = []
|
newentries = []
|
||||||
for i in resp.entries:
|
for i in resp.entries:
|
||||||
if output == "full":
|
if output == "full":
|
||||||
newemotions = i.emotions.copy()
|
newemotions = copy.deepcopy(i.emotions)
|
||||||
else:
|
else:
|
||||||
newemotions = []
|
newemotions = []
|
||||||
for j in i.emotions:
|
for j in i.emotions:
|
||||||
@@ -252,7 +253,7 @@ class Senpy(object):
|
|||||||
logger.error(msg)
|
logger.error(msg)
|
||||||
raise Error(msg)
|
raise Error(msg)
|
||||||
|
|
||||||
if sync:
|
if sync or 'async' in plugin and not plugin.async:
|
||||||
act()
|
act()
|
||||||
else:
|
else:
|
||||||
th = Thread(target=act)
|
th = Thread(target=act)
|
||||||
@@ -276,7 +277,7 @@ class Senpy(object):
|
|||||||
"Error deactivating plugin {}: {}".format(plugin.name, ex))
|
"Error deactivating plugin {}: {}".format(plugin.name, ex))
|
||||||
logger.error("Trace: {}".format(traceback.format_exc()))
|
logger.error("Trace: {}".format(traceback.format_exc()))
|
||||||
|
|
||||||
if sync:
|
if sync or 'async' in plugin and not plugin.async:
|
||||||
deact()
|
deact()
|
||||||
else:
|
else:
|
||||||
th = Thread(target=deact)
|
th = Thread(target=deact)
|
||||||
@@ -288,7 +289,7 @@ class Senpy(object):
|
|||||||
|
|
||||||
def install_deps(self):
|
def install_deps(self):
|
||||||
for i in self.plugins.values():
|
for i in self.plugins.values():
|
||||||
self._install_deps(i._info)
|
self._install_deps(i)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _install_deps(cls, info=None):
|
def _install_deps(cls, info=None):
|
||||||
@@ -302,6 +303,13 @@ class Senpy(object):
|
|||||||
logger.info('Installing requirements: ' + str(requirements))
|
logger.info('Installing requirements: ' + str(requirements))
|
||||||
pip.main(pip_args)
|
pip.main(pip_args)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _load_module(cls, name, root):
|
||||||
|
sys.path.append(root)
|
||||||
|
tmp = importlib.import_module(name)
|
||||||
|
sys.path.remove(root)
|
||||||
|
return tmp
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _load_plugin_from_info(cls, info, root):
|
def _load_plugin_from_info(cls, info, root):
|
||||||
if not cls.validate_info(info):
|
if not cls.validate_info(info):
|
||||||
@@ -309,11 +317,10 @@ class Senpy(object):
|
|||||||
return None, None
|
return None, None
|
||||||
module = info["module"]
|
module = info["module"]
|
||||||
name = info["name"]
|
name = info["name"]
|
||||||
sys.path.append(root)
|
|
||||||
(fp, pathname, desc) = imp.find_module(module, [root, ])
|
|
||||||
cls._install_deps(info)
|
cls._install_deps(info)
|
||||||
tmp = imp.load_module(module, fp, pathname, desc)
|
tmp = cls._load_module(module, root)
|
||||||
sys.path.remove(root)
|
|
||||||
candidate = None
|
candidate = None
|
||||||
for _, obj in inspect.getmembers(tmp):
|
for _, obj in inspect.getmembers(tmp):
|
||||||
if inspect.isclass(obj) and inspect.getmodule(obj) == tmp:
|
if inspect.isclass(obj) and inspect.getmodule(obj) == tmp:
|
||||||
|
@@ -3,11 +3,12 @@ standard_library.install_aliases()
|
|||||||
|
|
||||||
import inspect
|
import inspect
|
||||||
import os.path
|
import os.path
|
||||||
|
import os
|
||||||
import pickle
|
import pickle
|
||||||
import logging
|
import logging
|
||||||
import tempfile
|
import tempfile
|
||||||
import copy
|
import copy
|
||||||
from . import models
|
from .. import models
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -92,8 +93,8 @@ class ShelfMixin(object):
|
|||||||
@property
|
@property
|
||||||
def shelf_file(self):
|
def shelf_file(self):
|
||||||
if 'shelf_file' not in self or not self['shelf_file']:
|
if 'shelf_file' not in self or not self['shelf_file']:
|
||||||
self.shelf_file = os.path.join(tempfile.gettempdir(),
|
sd = os.environ.get('SENPY_DATA', tempfile.gettempdir())
|
||||||
self.name + '.p')
|
self.shelf_file = os.path.join(sd, self.name + '.p')
|
||||||
return self['shelf_file']
|
return self['shelf_file']
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
0
senpy/plugins/conversion/__init__.py
Normal file
0
senpy/plugins/conversion/__init__.py
Normal file
52
senpy/plugins/conversion/centroids.py
Normal file
52
senpy/plugins/conversion/centroids.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
from senpy.plugins import EmotionConversionPlugin
|
||||||
|
from senpy.models import EmotionSet, Emotion, Error
|
||||||
|
|
||||||
|
import logging
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class CentroidConversion(EmotionConversionPlugin):
|
||||||
|
|
||||||
|
def _forward_conversion(self, original):
|
||||||
|
"""Sum the VAD value of all categories found."""
|
||||||
|
res = Emotion()
|
||||||
|
for e in original.onyx__hasEmotion:
|
||||||
|
category = e.onyx__hasEmotionCategory
|
||||||
|
if category in self.centroids:
|
||||||
|
for dim, value in self.centroids[category].items():
|
||||||
|
try:
|
||||||
|
res[dim] += value
|
||||||
|
except Exception:
|
||||||
|
res[dim] = value
|
||||||
|
return res
|
||||||
|
|
||||||
|
def _backwards_conversion(self, original):
|
||||||
|
"""Find the closest category"""
|
||||||
|
dimensions = list(self.centroids.values())[0]
|
||||||
|
|
||||||
|
def distance(e1, e2):
|
||||||
|
return sum((e1[k] - e2.get(self.aliases[k], 0)) for k in dimensions)
|
||||||
|
|
||||||
|
emotion = ''
|
||||||
|
mindistance = 10000000000000000000000.0
|
||||||
|
for state in self.centroids:
|
||||||
|
d = distance(self.centroids[state], original)
|
||||||
|
if d < mindistance:
|
||||||
|
mindistance = d
|
||||||
|
emotion = state
|
||||||
|
result = Emotion(onyx__hasEmotionCategory=emotion)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def convert(self, emotionSet, fromModel, toModel, params):
|
||||||
|
|
||||||
|
cf, ct = self.centroids_direction
|
||||||
|
logger.debug('{}\n{}\n{}\n{}'.format(emotionSet, fromModel, toModel, params))
|
||||||
|
e = EmotionSet()
|
||||||
|
if fromModel == cf:
|
||||||
|
e.onyx__hasEmotion.append(self._forward_conversion(emotionSet))
|
||||||
|
elif fromModel == ct:
|
||||||
|
for i in emotionSet.onyx__hasEmotion:
|
||||||
|
e.onyx__hasEmotion.append(self._backwards_conversion(i))
|
||||||
|
else:
|
||||||
|
raise Error('EMOTION MODEL NOT KNOWN')
|
||||||
|
yield e
|
@@ -1,56 +0,0 @@
|
|||||||
from senpy.plugins import EmotionConversionPlugin
|
|
||||||
from senpy.models import EmotionSet, Emotion, Error
|
|
||||||
|
|
||||||
import logging
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
import math
|
|
||||||
|
|
||||||
|
|
||||||
class WNA2VAD(EmotionConversionPlugin):
|
|
||||||
|
|
||||||
def _ekman_to_vad(self, ekmanSet):
|
|
||||||
potency = 0
|
|
||||||
arousal = 0
|
|
||||||
dominance = 0
|
|
||||||
for e in ekmanSet.onyx__hasEmotion:
|
|
||||||
category = e.onyx__hasEmotionCategory
|
|
||||||
centroid = self.centroids[category]
|
|
||||||
potency += centroid['V']
|
|
||||||
arousal += centroid['A']
|
|
||||||
dominance += centroid['D']
|
|
||||||
e = Emotion({'emoml:potency': potency,
|
|
||||||
'emoml:arousal': arousal,
|
|
||||||
'emoml:dominance': dominance})
|
|
||||||
return e
|
|
||||||
|
|
||||||
def _vad_to_ekman(self, VADEmotion):
|
|
||||||
V = VADEmotion['emoml:valence']
|
|
||||||
A = VADEmotion['emoml:potency']
|
|
||||||
D = VADEmotion['emoml:dominance']
|
|
||||||
emotion = ''
|
|
||||||
value = 10000000000000000000000.0
|
|
||||||
for state in self.centroids:
|
|
||||||
valence = V - self.centroids[state]['V']
|
|
||||||
arousal = A - self.centroids[state]['A']
|
|
||||||
dominance = D - self.centroids[state]['D']
|
|
||||||
new_value = math.sqrt((valence**2) +
|
|
||||||
(arousal**2) +
|
|
||||||
(dominance**2))
|
|
||||||
if new_value < value:
|
|
||||||
value = new_value
|
|
||||||
emotion = state
|
|
||||||
result = Emotion(onyx__hasEmotionCategory=emotion)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def convert(self, emotionSet, fromModel, toModel, params):
|
|
||||||
logger.debug('{}\n{}\n{}\n{}'.format(emotionSet, fromModel, toModel, params))
|
|
||||||
e = EmotionSet()
|
|
||||||
if fromModel == 'emoml:big6':
|
|
||||||
e.onyx__hasEmotion.append(self._ekman_to_vad(emotionSet))
|
|
||||||
elif fromModel == 'emoml:fsre-dimensions':
|
|
||||||
for i in emotionSet.onyx__hasEmotion:
|
|
||||||
e.onyx__hasEmotion.append(self._vad_to_ekman(e))
|
|
||||||
else:
|
|
||||||
raise Error('EMOTION MODEL NOT KNOWN')
|
|
||||||
yield e
|
|
@@ -1,13 +1,13 @@
|
|||||||
---
|
---
|
||||||
name: Ekman2VAD
|
name: Ekman2VAD
|
||||||
module: ekman2vad
|
module: senpy.plugins.conversion.centroids
|
||||||
description: Plugin to convert emotion sets from Ekman to VAD
|
description: Plugin to convert emotion sets from Ekman to VAD
|
||||||
version: 0.1
|
version: 0.1
|
||||||
onyx:doesConversion:
|
onyx:doesConversion:
|
||||||
- onyx:conversionFrom: emoml:big6
|
- onyx:conversionFrom: emoml:big6
|
||||||
onyx:conversionTo: emoml:fsre-dimensions
|
onyx:conversionTo: emoml:fsre-dimensions
|
||||||
- onyx:conversionFrom: emoml:fsre-dimensions
|
- onyx:conversionFrom: emoml:fsre-dimensions
|
||||||
onyx:conversionTo: wna:WNAModel
|
onyx:conversionTo: emoml:big6
|
||||||
centroids:
|
centroids:
|
||||||
emoml:big6anger:
|
emoml:big6anger:
|
||||||
A: 6.95
|
A: 6.95
|
||||||
@@ -29,7 +29,10 @@ centroids:
|
|||||||
A: 5.21
|
A: 5.21
|
||||||
D: 2.82
|
D: 2.82
|
||||||
V: 2.21
|
V: 2.21
|
||||||
|
centroids_direction:
|
||||||
|
- emoml:big6
|
||||||
|
- emoml:fsre-dimensions
|
||||||
aliases:
|
aliases:
|
||||||
A: emoml:arousal
|
A: emoml:arousal
|
||||||
V: emoml:potency
|
V: emoml:valence
|
||||||
D: emoml:dominance
|
D: emoml:dominance
|
@@ -10,3 +10,5 @@ ignore = E402
|
|||||||
max-line-length = 100
|
max-line-length = 100
|
||||||
[bdist_wheel]
|
[bdist_wheel]
|
||||||
universal=1
|
universal=1
|
||||||
|
[tool:pytest]
|
||||||
|
addopts = --cov=senpy --cov-report term-missing
|
@@ -34,8 +34,11 @@ class ModelsTest(TestCase):
|
|||||||
url=endpoint + '/', method='GET', params={'input': 'hello'})
|
url=endpoint + '/', method='GET', params={'input': 'hello'})
|
||||||
error = Call(Error('Nothing'))
|
error = Call(Error('Nothing'))
|
||||||
with patch('requests.request', return_value=error) as patched:
|
with patch('requests.request', return_value=error) as patched:
|
||||||
resp = client.analyse(input='hello', algorithm='NONEXISTENT')
|
try:
|
||||||
assert isinstance(resp, Error)
|
client.analyse(input='hello', algorithm='NONEXISTENT')
|
||||||
|
raise Exception('Exceptions should be raised. This is not golang')
|
||||||
|
except Error:
|
||||||
|
pass
|
||||||
patched.assert_called_with(
|
patched.assert_called_with(
|
||||||
url=endpoint + '/',
|
url=endpoint + '/',
|
||||||
method='GET',
|
method='GET',
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import os
|
import os
|
||||||
|
from copy import deepcopy
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -9,7 +10,7 @@ except ImportError:
|
|||||||
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from senpy.extensions import Senpy
|
from senpy.extensions import Senpy
|
||||||
from senpy.models import Error
|
from senpy.models import Error, Results, Entry, EmotionSet, Emotion
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
|
|
||||||
@@ -52,6 +53,7 @@ class ExtensionsTest(TestCase):
|
|||||||
assert module
|
assert module
|
||||||
import noop
|
import noop
|
||||||
dir(noop)
|
dir(noop)
|
||||||
|
self.senpy.install_deps()
|
||||||
|
|
||||||
def test_installing(self):
|
def test_installing(self):
|
||||||
""" Enabling a plugin """
|
""" Enabling a plugin """
|
||||||
@@ -120,3 +122,42 @@ class ExtensionsTest(TestCase):
|
|||||||
def test_load_default_plugins(self):
|
def test_load_default_plugins(self):
|
||||||
senpy = Senpy(plugin_folder=self.dir, default_plugins=True)
|
senpy = Senpy(plugin_folder=self.dir, default_plugins=True)
|
||||||
assert len(senpy.plugins) > 1
|
assert len(senpy.plugins) > 1
|
||||||
|
|
||||||
|
def test_convert_emotions(self):
|
||||||
|
self.senpy.activate_all()
|
||||||
|
plugin = {
|
||||||
|
'id': 'imaginary',
|
||||||
|
'onyx:usesEmotionModel': 'emoml:fsre-dimensions'
|
||||||
|
}
|
||||||
|
eSet1 = EmotionSet()
|
||||||
|
eSet1['onyx:hasEmotion'].append(Emotion({
|
||||||
|
'emoml:arousal': 1,
|
||||||
|
'emoml:potency': 0,
|
||||||
|
'emoml:valence': 0
|
||||||
|
}))
|
||||||
|
response = Results({
|
||||||
|
'entries': [Entry({
|
||||||
|
'text': 'much ado about nothing',
|
||||||
|
'emotions': [eSet1]
|
||||||
|
})]
|
||||||
|
})
|
||||||
|
params = {'emotionModel': 'emoml:big6',
|
||||||
|
'conversion': 'full'}
|
||||||
|
r1 = deepcopy(response)
|
||||||
|
self.senpy.convert_emotions(r1,
|
||||||
|
plugin,
|
||||||
|
params)
|
||||||
|
assert len(r1.entries[0].emotions) == 2
|
||||||
|
params['conversion'] = 'nested'
|
||||||
|
r2 = deepcopy(response)
|
||||||
|
self.senpy.convert_emotions(r2,
|
||||||
|
plugin,
|
||||||
|
params)
|
||||||
|
assert len(r2.entries[0].emotions) == 1
|
||||||
|
assert r2.entries[0].emotions[0]['prov:wasDerivedFrom'] == eSet1
|
||||||
|
params['conversion'] = 'filtered'
|
||||||
|
r3 = deepcopy(response)
|
||||||
|
self.senpy.convert_emotions(r3,
|
||||||
|
plugin,
|
||||||
|
params)
|
||||||
|
assert len(r3.entries[0].emotions) == 1
|
||||||
|
@@ -143,7 +143,3 @@ class ModelsTest(TestCase):
|
|||||||
print(t)
|
print(t)
|
||||||
g = rdflib.Graph().parse(data=t, format='turtle')
|
g = rdflib.Graph().parse(data=t, format='turtle')
|
||||||
assert len(g) == len(triples)
|
assert len(g) == len(triples)
|
||||||
|
|
||||||
def test_convert_emotions(self):
|
|
||||||
"""It should be possible to convert between different emotion models"""
|
|
||||||
pass
|
|
||||||
|
Reference in New Issue
Block a user