Commit 41aa142c authored by J. Fernando Sánchez's avatar J. Fernando Sánchez

Refactored conversion and postprocessing

parent b4873013
...@@ -3,10 +3,8 @@ from .models import Error, Results, Entry, from_string ...@@ -3,10 +3,8 @@ from .models import Error, Results, Entry, from_string
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
boolean = [True, False] boolean = [True, False]
API_PARAMS = { API_PARAMS = {
"algorithm": { "algorithm": {
"aliases": ["algorithms", "a", "algo"], "aliases": ["algorithms", "a", "algo"],
...@@ -140,6 +138,15 @@ NIF_PARAMS = { ...@@ -140,6 +138,15 @@ NIF_PARAMS = {
} }
} }
BUILTIN_PARAMS = {}
for d in [
NIF_PARAMS, CLI_PARAMS, WEB_PARAMS, PLUGINS_PARAMS, EVAL_PARAMS,
API_PARAMS
]:
for k, v in d.items():
BUILTIN_PARAMS[k] = v
def parse_params(indict, *specs): def parse_params(indict, *specs):
if not specs: if not specs:
...@@ -164,7 +171,7 @@ def parse_params(indict, *specs): ...@@ -164,7 +171,7 @@ def parse_params(indict, *specs):
continue continue
if "options" in options: if "options" in options:
if options["options"] == boolean: if options["options"] == boolean:
outdict[param] = outdict[param] in [None, True, 'true', '1'] outdict[param] = str(outdict[param]).lower() in ['true', '1']
elif outdict[param] not in options["options"]: elif outdict[param] not in options["options"]:
wrong_params[param] = spec[param] wrong_params[param] = spec[param]
if wrong_params: if wrong_params:
...@@ -180,11 +187,19 @@ def parse_params(indict, *specs): ...@@ -180,11 +187,19 @@ def parse_params(indict, *specs):
return outdict return outdict
def parse_extra_params(request, plugin=None): def parse_extra_params(request, plugins=None):
plugins = plugins or []
params = request.parameters.copy() params = request.parameters.copy()
if plugin: for plugin in plugins:
extra_params = parse_params(params, plugin.get('extra_params', {})) if plugin:
params.update(extra_params) extra_params = parse_params(params, plugin.get('extra_params', {}))
for k, v in extra_params.items():
if k not in BUILTIN_PARAMS:
if k in params: # Set by another plugin
del params[k]
else:
params[k] = v
params['{}.{}'.format(plugin.name, k)] = v
return params return params
...@@ -194,12 +209,12 @@ def parse_call(params): ...@@ -194,12 +209,12 @@ def parse_call(params):
params = parse_params(params, NIF_PARAMS) params = parse_params(params, NIF_PARAMS)
if params['informat'] == 'text': if params['informat'] == 'text':
results = Results() results = Results()
entry = Entry(nif__isString=params['input'], entry = Entry(nif__isString=params['input'], id='#') # Use @base
id='#') # Use @base
results.entries.append(entry) results.entries.append(entry)
elif params['informat'] == 'json-ld': elif params['informat'] == 'json-ld':
results = from_string(params['input'], cls=Results) results = from_string(params['input'], cls=Results)
else: # pragma: no cover else: # pragma: no cover
raise NotImplementedError('Informat {} is not implemented'.format(params['informat'])) raise NotImplementedError('Informat {} is not implemented'.format(
params['informat']))
results.parameters = params results.parameters = params
return results return results
...@@ -197,7 +197,9 @@ def api_root(plugin): ...@@ -197,7 +197,9 @@ def api_root(plugin):
plugin = plugin.replace('+', '/') plugin = plugin.replace('+', '/')
plugin = plugin.split('/') plugin = plugin.split('/')
req.parameters['algorithm'] = tuple(plugin) req.parameters['algorithm'] = tuple(plugin)
return current_app.senpy.analyse(req) results = current_app.senpy.analyse(req)
results.analysis = set(i.id for i in results.analysis)
return results
@api_blueprint.route('/evaluate/', methods=['POST', 'GET']) @api_blueprint.route('/evaluate/', methods=['POST', 'GET'])
......
This diff is collapsed.
This diff is collapsed.
--- ---
name: Ekman2FSRE name: Ekman2FSRE
module: senpy.plugins.conversion.emotion.centroids module: senpy.plugins.postprocessing.emotion.centroids
description: Plugin to convert emotion sets from Ekman to VAD description: Plugin to convert emotion sets from Ekman to VAD
version: 0.2 version: 0.2
# No need to specify onyx:doesConversion because centroids.py adds it automatically from centroids_direction # No need to specify onyx:doesConversion because centroids.py adds it automatically from centroids_direction
......
--- ---
name: Ekman2PAD name: Ekman2PAD
module: senpy.plugins.conversion.emotion.centroids module: senpy.plugins.postprocessing.emotion.centroids
description: Plugin to convert emotion sets from Ekman to VAD description: Plugin to convert emotion sets from Ekman to VAD
version: 0.2 version: 0.2
# No need to specify onyx:doesConversion because centroids.py adds it automatically from centroids_direction # No need to specify onyx:doesConversion because centroids.py adds it automatically from centroids_direction
......
from senpy import PostProcessing, easy_test
class MaxEmotion(PostProcessing):
'''Plugin to extract the emotion with highest value from an EmotionSet'''
author = '@dsuarezsouto'
version = '0.1'
def process_entry(self, entry, params):
if len(entry.emotions) < 1:
yield entry
return
set_emotions = entry.emotions[0]['onyx:hasEmotion']
# If there is only one emotion, do not modify it
if len(set_emotions) < 2:
yield entry
return
max_emotion = set_emotions[0]
# Extract max emotion from the set emotions (emotion with highest intensity)
for tmp_emotion in set_emotions:
if tmp_emotion['onyx:hasEmotionIntensity'] > max_emotion[
'onyx:hasEmotionIntensity']:
max_emotion = tmp_emotion
if max_emotion['onyx:hasEmotionIntensity'] == 0:
max_emotion['onyx:hasEmotionCategory'] = "neutral"
max_emotion['onyx:hasEmotionIntensity'] = 1.0
entry.emotions[0]['onyx:hasEmotion'] = [max_emotion]
entry.emotions[0]['prov:wasGeneratedBy'] = "maxSentiment"
yield entry
def check(self, request, plugins):
return 'maxemotion' in request.parameters and self not in plugins
# Test Cases:
# 1 Normal Situation.
# 2 Case to return a Neutral Emotion.
test_cases = [
{
"name":
"If there are several emotions within an emotion set, reduce it to one.",
"entry": {
"@type":
"entry",
"emotions": [
{
"@id":
"Emotions0",
"@type":
"emotionSet",
"onyx:hasEmotion": [
{
"@id": "_:Emotion_1538121033.74",
"@type": "emotion",
"onyx:hasEmotionCategory": "anger",
"onyx:hasEmotionIntensity": 0
},
{
"@id": "_:Emotion_1538121033.74",
"@type": "emotion",
"onyx:hasEmotionCategory": "joy",
"onyx:hasEmotionIntensity": 0.3333333333333333
},
{
"@id": "_:Emotion_1538121033.74",
"@type": "emotion",
"onyx:hasEmotionCategory": "negative-fear",
"onyx:hasEmotionIntensity": 0
},
{
"@id": "_:Emotion_1538121033.74",
"@type": "emotion",
"onyx:hasEmotionCategory": "sadness",
"onyx:hasEmotionIntensity": 0
},
{
"@id": "_:Emotion_1538121033.74",
"@type": "emotion",
"onyx:hasEmotionCategory": "disgust",
"onyx:hasEmotionIntensity": 0
}
]
}
],
"nif:isString":
"Test"
},
'expected': {
"@type":
"entry",
"emotions": [
{
"@id":
"Emotions0",
"@type":
"emotionSet",
"onyx:hasEmotion": [
{
"@id": "_:Emotion_1538121033.74",
"@type": "emotion",
"onyx:hasEmotionCategory": "joy",
"onyx:hasEmotionIntensity": 0.3333333333333333
}
],
"prov:wasGeneratedBy":
'maxSentiment'
}
],
"nif:isString":
"Test"
}
},
{
"name":
"If the maximum emotion has an intensity of 0, return a neutral emotion.",
"entry": {
"@type":
"entry",
"emotions": [{
"@id":
"Emotions0",
"@type":
"emotionSet",
"onyx:hasEmotion": [
{
"@id": "_:Emotion_1538121033.74",
"@type": "emotion",
"onyx:hasEmotionCategory": "anger",
"onyx:hasEmotionIntensity": 0
},
{
"@id": "_:Emotion_1538121033.74",
"@type": "emotion",
"onyx:hasEmotionCategory": "joy",
"onyx:hasEmotionIntensity": 0
},
{
"@id":
"_:Emotion_1538121033.74",
"@type":
"emotion",
"onyx:hasEmotionCategory":
"negative-fear",
"onyx:hasEmotionIntensity":
0
},
{
"@id": "_:Emotion_1538121033.74",
"@type": "emotion",
"onyx:hasEmotionCategory":
"sadness",
"onyx:hasEmotionIntensity": 0
},
{
"@id": "_:Emotion_1538121033.74",
"@type": "emotion",
"onyx:hasEmotionCategory":
"disgust",
"onyx:hasEmotionIntensity": 0
}]
}],
"nif:isString":
"Test"
},
'expected': {
"@type":
"entry",
"emotions": [{
"@id":
"Emotions0",
"@type":
"emotionSet",
"onyx:hasEmotion": [{
"@id": "_:Emotion_1538121033.74",
"@type": "emotion",
"onyx:hasEmotionCategory": "neutral",
"onyx:hasEmotionIntensity": 1
}],
"prov:wasGeneratedBy":
'maxSentiment'
}],
"nif:isString":
"Test"
}
}
]
if __name__ == '__main__':
easy_test()
...@@ -138,14 +138,14 @@ class BlueprintsTest(TestCase): ...@@ -138,14 +138,14 @@ class BlueprintsTest(TestCase):
# Calling dummy twice, should return the same string # Calling dummy twice, should return the same string
self.assertCode(resp, 200) self.assertCode(resp, 200)
js = parse_resp(resp) js = parse_resp(resp)
assert len(js['analysis']) == 2 assert len(js['analysis']) == 1
assert js['entries'][0]['nif:isString'] == 'My aloha mohame' assert js['entries'][0]['nif:isString'] == 'My aloha mohame'
resp = self.client.get("/api/Dummy+Dummy?i=My aloha mohame") resp = self.client.get("/api/Dummy+Dummy?i=My aloha mohame")
# Same with pluses instead of slashes # Same with pluses instead of slashes
self.assertCode(resp, 200) self.assertCode(resp, 200)
js = parse_resp(resp) js = parse_resp(resp)
assert len(js['analysis']) == 2 assert len(js['analysis']) == 1
assert js['entries'][0]['nif:isString'] == 'My aloha mohame' assert js['entries'][0]['nif:isString'] == 'My aloha mohame'
def test_error(self): def test_error(self):
......
...@@ -121,8 +121,8 @@ class ExtensionsTest(TestCase): ...@@ -121,8 +121,8 @@ class ExtensionsTest(TestCase):
# Leaf (defaultdict with __setattr__ and __getattr__. # Leaf (defaultdict with __setattr__ and __getattr__.
r1 = analyse(self.senpy, algorithm="Dummy", input="tupni", output="tuptuo") r1 = analyse(self.senpy, algorithm="Dummy", input="tupni", output="tuptuo")
r2 = analyse(self.senpy, input="tupni", output="tuptuo") r2 = analyse(self.senpy, input="tupni", output="tuptuo")
assert r1.analysis[0] == "endpoint:plugins/Dummy_0.1" assert r1.analysis[0].id == "endpoint:plugins/Dummy_0.1"
assert r2.analysis[0] == "endpoint:plugins/Dummy_0.1" assert r2.analysis[0].id == "endpoint:plugins/Dummy_0.1"
assert r1.entries[0]['nif:isString'] == 'input' assert r1.entries[0]['nif:isString'] == 'input'
def test_analyse_empty(self): def test_analyse_empty(self):
...@@ -156,8 +156,8 @@ class ExtensionsTest(TestCase): ...@@ -156,8 +156,8 @@ class ExtensionsTest(TestCase):
r2 = analyse(self.senpy, r2 = analyse(self.senpy,
input="tupni", input="tupni",
output="tuptuo") output="tuptuo")
assert r1.analysis[0] == "endpoint:plugins/Dummy_0.1" assert r1.analysis[0].id == "endpoint:plugins/Dummy_0.1"
assert r2.analysis[0] == "endpoint:plugins/Dummy_0.1" assert r2.analysis[0].id == "endpoint:plugins/Dummy_0.1"
assert r1.entries[0]['nif:isString'] == 'input' assert r1.entries[0]['nif:isString'] == 'input'
def test_analyse_error(self): def test_analyse_error(self):
...@@ -165,7 +165,7 @@ class ExtensionsTest(TestCase): ...@@ -165,7 +165,7 @@ class ExtensionsTest(TestCase):
mm.id = 'magic_mock' mm.id = 'magic_mock'
mm.name = 'mock' mm.name = 'mock'
mm.is_activated = True mm.is_activated = True
mm.analyse_entries.side_effect = Error('error in analysis', status=500) mm.process.side_effect = Error('error in analysis', status=500)
self.senpy.add_plugin(mm) self.senpy.add_plugin(mm)
try: try:
analyse(self.senpy, input='nothing', algorithm='MOCK') analyse(self.senpy, input='nothing', algorithm='MOCK')
...@@ -175,8 +175,7 @@ class ExtensionsTest(TestCase): ...@@ -175,8 +175,7 @@ class ExtensionsTest(TestCase):
assert ex['status'] == 500 assert ex['status'] == 500
ex = Exception('generic exception on analysis') ex = Exception('generic exception on analysis')
mm.analyse.side_effect = ex mm.process.side_effect = ex
mm.analyse_entries.side_effect = ex
try: try:
analyse(self.senpy, input='nothing', algorithm='MOCK') analyse(self.senpy, input='nothing', algorithm='MOCK')
...@@ -211,27 +210,28 @@ class ExtensionsTest(TestCase): ...@@ -211,27 +210,28 @@ class ExtensionsTest(TestCase):
'emoml:valence': 0 'emoml:valence': 0
})) }))
response = Results({ response = Results({
'analysis': [{'plugin': plugin}], 'analysis': [plugin],
'entries': [Entry({ 'entries': [Entry({
'nif:isString': 'much ado about nothing', 'nif:isString': 'much ado about nothing',
'emotions': [eSet1] 'emotions': [eSet1]
})] })]
}) })
params = {'emotionModel': 'emoml:big6', params = {'emotionModel': 'emoml:big6',
'algorithm': ['conversion'],
'conversion': 'full'} 'conversion': 'full'}
r1 = deepcopy(response) r1 = deepcopy(response)
r1.parameters = params r1.parameters = params
self.senpy.convert_emotions(r1) self.senpy.analyse(r1)
assert len(r1.entries[0].emotions) == 2 assert len(r1.entries[0].emotions) == 2
params['conversion'] = 'nested' params['conversion'] = 'nested'
r2 = deepcopy(response) r2 = deepcopy(response)
r2.parameters = params r2.parameters = params
self.senpy.convert_emotions(r2) self.senpy.analyse(r2)
assert len(r2.entries[0].emotions) == 1 assert len(r2.entries[0].emotions) == 1
assert r2.entries[0].emotions[0]['prov:wasDerivedFrom'] == eSet1 assert r2.entries[0].emotions[0]['prov:wasDerivedFrom'] == eSet1
params['conversion'] = 'filtered' params['conversion'] = 'filtered'
r3 = deepcopy(response) r3 = deepcopy(response)
r3.parameters = params r3.parameters = params
self.senpy.convert_emotions(r3) self.senpy.analyse(r3)
assert len(r3.entries[0].emotions) == 1 assert len(r3.entries[0].emotions) == 1
r3.jsonld() r3.jsonld()
...@@ -8,7 +8,7 @@ import tempfile ...@@ -8,7 +8,7 @@ import tempfile
from unittest import TestCase, skipIf from unittest import TestCase, skipIf
from senpy.models import Results, Entry, EmotionSet, Emotion, Plugins from senpy.models import Results, Entry, EmotionSet, Emotion, Plugins
from senpy import plugins from senpy import plugins
from senpy.plugins.conversion.emotion.centroids import CentroidConversion from senpy.plugins.postprocessing.emotion.centroids import CentroidConversion
from senpy.gsitk_compat import GSITK_AVAILABLE from senpy.gsitk_compat import GSITK_AVAILABLE
import pandas as pd import pandas as pd
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment