diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c5fb802..7a6be97 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -84,7 +84,15 @@ deploy: only: - master -clean_docker : +push-github: + stage: deploy + script: + - make -e push-github + only: + - master + - triggers + +clean : stage: clean script: - make -e clean diff --git a/Makefile b/Makefile index 00756d1..fc5d7ae 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,7 @@ DEVPORT=5000 TARNAME=$(NAME)-$(VERSION).tar.gz action="test-${PYMAIN}" +GITHUB_REPO=git@github.com:gsi-upm/senpy.git KUBE_CA_PEM_FILE="" KUBE_URL="" diff --git a/senpy/plugins/conversion/emotion/__init__.py b/senpy/plugins/conversion/emotion/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/senpy/plugins/conversion/centroids.py b/senpy/plugins/conversion/emotion/centroids.py similarity index 52% rename from senpy/plugins/conversion/centroids.py rename to senpy/plugins/conversion/emotion/centroids.py index 2dd1c97..fe400cd 100644 --- a/senpy/plugins/conversion/centroids.py +++ b/senpy/plugins/conversion/emotion/centroids.py @@ -32,36 +32,58 @@ class CentroidConversion(EmotionConversionPlugin): nv1[aliases.get(k2, k2)] = v2 ncentroids[aliases.get(k1, k1)] = nv1 info['centroids'] = ncentroids + super(CentroidConversion, self).__init__(info) + self.dimensions = set() + for c in self.centroids.values(): + self.dimensions.update(c.keys()) + self.neutralPoints = self.get("neutralPoints", dict()) + if not self.neutralPoints: + for i in self.dimensions: + self.neutralPoints[i] = self.get("neutralValue", 0) + def _forward_conversion(self, original): - """Sum the VAD value of all categories found.""" + """Sum the VAD value of all categories found weighted by intensity. + Intensities are scaled by onyx:maxIntensityValue if it is present, else maxIntensityValue + is assumed to be one. Emotion entries that do not have onxy:hasEmotionIntensity specified + are assumed to have maxIntensityValue. Emotion entries that do not have + onyx:hasEmotionCategory specified are ignored.""" res = Emotion() + maxIntensity = float(original.get("onyx:maxIntensityValue", 1)) 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 + category = e.get("onyx:hasEmotionCategory", None) + if not category: + continue + intensity = e.get("onyx:hasEmotionIntensity", maxIntensity) / maxIntensity + if not intensity: + continue + centroid = self.centroids.get(category, None) + if centroid: + for dim, value in centroid.items(): + neutral = self.neutralPoints[dim] + if dim not in res: + res[dim] = 0 + res[dim] += (value - neutral) * intensity + neutral return res def _backwards_conversion(self, original): """Find the closest category""" - dimensions = list(self.centroids.values())[0] + centroids = self.centroids + neutralPoints = self.neutralPoints + dimensions = self.dimensions + + def distance_k(centroid, original, k): + # k component of the distance between the value and a given centroid + return (centroid.get(k, neutralPoints[k]) - original.get(k, neutralPoints[k]))**2 + + def distance(centroid): + return sum(distance_k(centroid, original, k) for k in dimensions) - def distance(e1, e2): - return sum((e1[k] - e2.get(k, 0)) for k in dimensions) + emotion = min(centroids, key=lambda x: distance(centroids[x])) - 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) + result.onyx__algorithmConfidence = distance(centroids[emotion]) return result def convert(self, emotionSet, fromModel, toModel, params): diff --git a/senpy/plugins/conversion/emotion/ekman2fsre.senpy b/senpy/plugins/conversion/emotion/ekman2fsre.senpy index 3cc0c17..0606183 100644 --- a/senpy/plugins/conversion/emotion/ekman2fsre.senpy +++ b/senpy/plugins/conversion/emotion/ekman2fsre.senpy @@ -1,6 +1,6 @@ --- name: Ekman2FSRE -module: senpy.plugins.conversion.centroids +module: senpy.plugins.conversion.emotion.centroids description: Plugin to convert emotion sets from Ekman to VAD version: 0.1 # No need to specify onyx:doesConversion because centroids.py adds it automatically from centroids_direction diff --git a/senpy/plugins/conversion/emotion/ekman2vad.senpy b/senpy/plugins/conversion/emotion/ekman2vad.senpy index a1f21f0..9af84ba 100644 --- a/senpy/plugins/conversion/emotion/ekman2vad.senpy +++ b/senpy/plugins/conversion/emotion/ekman2vad.senpy @@ -1,9 +1,14 @@ --- name: Ekman2PAD -module: senpy.plugins.conversion.centroids +module: senpy.plugins.conversion.emotion.centroids description: Plugin to convert emotion sets from Ekman to VAD version: 0.1 # No need to specify onyx:doesConversion because centroids.py adds it automatically from centroids_direction +origin: + # Point in VAD space with no emotion (aka Neutral) + A: 5.0 + D: 5.0 + V: 5.0 centroids: anger: A: 6.95 @@ -36,4 +41,4 @@ aliases: # These are aliases for any key in the centroid, to avoid repeating a l disgust: emoml:big6disgust fear: emoml:big6fear happiness: emoml:big6happiness - sadness: emoml:big6sadness \ No newline at end of file + sadness: emoml:big6sadness diff --git a/senpy/version.py b/senpy/version.py index fa3d933..c517050 100644 --- a/senpy/version.py +++ b/senpy/version.py @@ -11,7 +11,7 @@ def read_version(versionfile=DEFAULT_FILE): try: with open(versionfile) as f: return f.read().strip() - except IOError: + except IOError: # pragma: no cover logger.error('Running an unknown version of senpy. Be careful!.') return '0.0' diff --git a/tests/test_plugins.py b/tests/test_plugins.py index dbfe60d..fe8ee1c 100644 --- a/tests/test_plugins.py +++ b/tests/test_plugins.py @@ -6,8 +6,9 @@ import shutil import tempfile from unittest import TestCase -from senpy.models import Results, Entry +from senpy.models import Results, Entry, EmotionSet, Emotion from senpy.plugins import SentimentPlugin, ShelfMixin +from senpy.plugins.conversion.emotion.centroids import CentroidConversion class ShelfDummyPlugin(SentimentPlugin, ShelfMixin): @@ -152,3 +153,52 @@ class PluginsTest(TestCase): } }) assert 'example' in a.extra_params + + def test_conversion_centroids(self): + info = { + "name": "CentroidTest", + "description": "Centroid test", + "version": 0, + "centroids": { + "c1": {"V1": 0.5, + "V2": 0.5}, + "c2": {"V1": -0.5, + "V2": 0.5}, + "c3": {"V1": -0.5, + "V2": -0.5}, + "c4": {"V1": 0.5, + "V2": -0.5}}, + "aliases": { + "V1": "X-dimension", + "V2": "Y-dimension" + }, + "centroids_direction": ["emoml:big6", "emoml:fsre-dimensions"] + } + c = CentroidConversion(info) + + es1 = EmotionSet() + e1 = Emotion() + e1.onyx__hasEmotionCategory = "c1" + es1.onyx__hasEmotion.append(e1) + res = c._forward_conversion(es1) + assert res["X-dimension"] == 0.5 + assert res["Y-dimension"] == 0.5 + + e2 = Emotion() + e2.onyx__hasEmotionCategory = "c2" + es1.onyx__hasEmotion.append(e2) + res = c._forward_conversion(es1) + assert res["X-dimension"] == 0 + assert res["Y-dimension"] == 1 + + e = Emotion() + e["X-dimension"] = -0.2 + e["Y-dimension"] = -0.3 + res = c._backwards_conversion(e) + assert res["onyx:hasEmotionCategory"] == "c3" + + e = Emotion() + e["X-dimension"] = -0.2 + e["Y-dimension"] = 0.3 + res = c._backwards_conversion(e) + assert res["onyx:hasEmotionCategory"] == "c2"