add locomotion-action analyzer

simu_flags
agp8x 2017-08-07 12:54:24 +02:00
parent 1482537b66
commit 97f5d380a4
8 changed files with 244 additions and 20 deletions

View File

@ -1,2 +1,3 @@
from .analyzer import *
from .biogames import *
from .locomotion_action import *

View File

@ -1,5 +1,5 @@
import json
from collections import defaultdict
from collections import defaultdict, Iterable
from log_analyzer import LogSettings
@ -11,21 +11,31 @@ class Analyzer:
def process(self, entry: dict) -> bool:
raise NotImplementedError()
def result(self) -> object:
def result(self) -> Iterable:
raise NotImplementedError()
def name(self):
return self.__name__
class LocationAnalyzer(Analyzer):
"""
store spatial log entries
"""
entries = []
__name__ = "Location"
class Formats:
geojson = 0
def __init__(self, settings: LogSettings):
super().__init__(settings)
def result(self) -> object:
def result(self) -> list:
return self.entries
def render(self, format="geojson"):
if format is "geojson":
def render(self, format: int = Formats.geojson):
if format is self.Formats.geojson:
return json.dumps([entry['location']['coordinates'] for entry in self.entries])
raise NotImplementedError()
@ -36,7 +46,12 @@ class LocationAnalyzer(Analyzer):
class LogEntryCountAnalyzer(Analyzer):
def result(self) -> object:
"""
count occurrences of log entry types
"""
__name__ = "LogEntryCount"
def result(self) -> defaultdict:
return self.store
def process(self, entry: dict) -> bool:
@ -48,3 +63,34 @@ class LogEntryCountAnalyzer(Analyzer):
self.store = defaultdict(lambda: 0)
class LogEntrySequenceAnalyzer(Analyzer):
"""
store sequence of all log entry types
"""
__name__ = "LogEntrySequence"
def result(self) -> list:
return self.store
def process(self, entry: dict) -> bool:
entry_type = entry[self.settings.entry_type]
self.store.append(entry_type)
return False
def __init__(self, settings: LogSettings):
super().__init__(settings)
self.store = []
class ActionSequenceAnalyzer(LogEntrySequenceAnalyzer):
"""
find sequence of non-spatial log entry types
"""
__name__ = "ActionSequenceAnalyzer"
def process(self, entry: dict) -> bool:
entry_type = entry[self.settings.entry_type]
if entry_type in self.settings.spatials:
return False
self.store.append(entry_type)
return False

View File

@ -6,13 +6,41 @@ from .analyzer import Analyzer
class BoardDurationAnalyzer(Analyzer):
def result(self) -> object:
return self.store
"""
calculate display duration of boards
"""
__name__ = "BoardDuration"
def render(self) -> str:
return "\n".join(["{}\t{}".format(entry["active"], entry["id"]) for entry in self.result()])
def result(self) -> list:
result = []
last_timestamp = None
last_board = None
for board in self.store:
board_id, timestamp = board["id"], board["timestamp"]
if not last_timestamp is None:
result.append(self.save_entry(last_board, last_timestamp, timestamp - last_timestamp))
last_timestamp = timestamp
last_board = board_id
# TODO: last board?
return result
def process(self, entry: dict) -> bool:
self.store[entry[self.settings.entry_type]] += 1
entry_type = entry[self.settings.entry_type]
if entry_type in self.settings.boards:
self.store.append(self.save_entry(entry["board_id"], entry["timestamp"])) # TODO: make configurable
return False
def save_entry(self, board_id: str, timestamp: int, active: int = None):
entry = {"id": board_id, "timestamp": timestamp}
if not active is None:
entry["active"] = active
return entry
def __init__(self, settings: LogSettings):
super().__init__(settings)
self.store = defaultdict(lambda: 0)
self.store = []
self.last = {}

View File

@ -0,0 +1,93 @@
from collections import defaultdict
from log_analyzer import LogSettings
from .analyzer import Analyzer
from util import combinate
def init_filter(settings: LogSettings, state: str) -> callable:
# this implies OR for lists; AND for dicts
if type(settings.sequences[state]) in (str, list):
return lambda entry: entry[settings.entry_type] in settings.sequences[state]
else:
return lambda entry: combinate(settings.sequences[state], entry)
class LocomotionActionAnalyzer(Analyzer): # TODO
"""
calculate locomation/action times and ratio
Anything between LogEntryCache and CacheEnableAction
is counted as ActionTime, the rest as LocomotionTime.
"""
__name__ = "LocomotionAction"
def process(self, entry: dict) -> bool:
self.last_timestamp = entry["timestamp"]
if self.instance_start is None:
self.instance_start = self.last_timestamp
self.last = self.last_timestamp
if self.cache_time is None:
self.cache_time = self.last_timestamp
offset = self.last_timestamp - self.cache_time
if self.current_cache is None and self.filter_start(entry):
if entry['cache'] is None:
return False
self.current_cache = entry['cache']['@id']
self.cache_time = self.last_timestamp
self.locomotion.append(offset)
self.last = None
elif self.current_cache and self.filter_end(entry):
self.actions.append(offset)
self.cache_time = self.last_timestamp
self.current_cache = None
self.last = None
def result(self) -> dict:
if self.last is not None:
if self.current_cache is None:
self.locomotion.append(self.last - self.cache_time)
else:
self.actions.append(self.last - self.cache_time)
locomotion = sum(self.locomotion)
action = sum(self.actions)
return {
'loco_sum': locomotion,
'action_sum': action,
'loco': self.locomotion,
'act': self.actions,
'dur': (self.last_timestamp - self.instance_start)
}
def __init__(self, settings: LogSettings):
super().__init__(settings)
self.filter_start = init_filter(settings, "start")
self.filter_end = init_filter(settings, "end")
self.current_cache = None
self.cache_time = None
self.locomotion = []
self.actions = []
self.instance_start = None
self.last_timestamp = None
self.last = None
class CacheSequenceAnalyzer(Analyzer): # TODO
__name__ = "CacheSequence"
def process(self, entry: dict) -> bool:
if self.filter(entry):
if entry['cache']:
self.store.append((entry['timestamp'],entry['cache']['@id']))
else:
self.store.append((entry['timestamp'],entry['cache']))
def result(self) -> list:
return self.store
def __init__(self, settings: LogSettings):
super().__init__(settings)
self.store = []
self.filter = init_filter(settings, "start")

View File

@ -1,14 +1,36 @@
{
"logFormat": "sqlite",
"entryType": "@class",
"spatials":["de.findevielfalt.games.game2.instance.log.entry.LogEntryLocation"],
"actions":["...QuestionAnswerEvent", "...SimuAnswerEvent"],
"spatials": [
"de.findevielfalt.games.game2.instance.log.entry.LogEntryLocation"
],
"actions": [
"...QuestionAnswerEvent",
"...SimuAnswerEvent"
],
"boards": [
"de.findevielfalt.games.game2.instance.log.entry.ShowBoardLogEntry"
],
"analyzers": {
"analyzer": [
"LocationAnalyzer"
],
"LocationAnalyzer",
"LogEntryCountAnalyzer",
"LogEntrySequenceAnalyzer",
"ActionSequenceAnalyzer"
],
"analyzer.biogames": [
"BoardDurationAnalyzer"
],
"analyzer.locomotion_action": [
"LocomotionActionAnalyzer",
"CacheSequenceAnalyzer"
]
},
"sequences": {
"start": "de.findevielfalt.games.game2.instance.log.entry.LogEntryCache",
"end": {
"@class": "de.findevielfalt.games.game2.instance.log.entry.LogEntryInstanceAction",
"action.@class": "de.findevielfalt.games.game2.instance.action.CacheEnableAction"
}
}
}

View File

@ -10,15 +10,19 @@ class LogSettings:
spatials = None
actions = None
analyzers = []
boards = None
sequences = None
def __init__(self, json_dict):
self.log_format = json_dict['logFormat']
self.entry_type = json_dict['entryType']
self.spatials = json_dict['spatials']
self.actions = json_dict['actions']
self.boards = json_dict['boards']
for mod in json_dict['analyzers']:
for name in json_dict['analyzers'][mod]:
self.analyzers.append(getattr(sys.modules[mod], name))
self.sequences = json_dict['sequences']
def __repr__(self):
return str({
@ -26,11 +30,13 @@ class LogSettings:
"entryType": self.entry_type,
"spatials": self.spatials,
"actions": self.actions,
"analyzers": self.analyzers
"analyzers": self.analyzers,
"boards": self.boards,
"sequences": self.sequences,
})
def load_settings(file:str) -> LogSettings:
def load_settings(file: str) -> LogSettings:
return LogSettings(json.load(open(file)))
@ -51,8 +57,17 @@ if __name__ == '__main__':
if analyzer.process(entry):
break
for analyzer in analyzers:
print("* Result for " + str(type(analyzer)))
print("* Result for " + analyzer.name())
print(analyzer.result())
coords = analyzers[0].render()
with open("test.js", "w") as out:
out.write("coords = "+coords)
#for analyzer in analyzers:
# if analyzer.name() in ["LogEntryCount", "ActionSequenceAnalyzer"]:
# print(json.dumps(analyzer.result(), indent=2))
#for analyzer in analyzers:
# if analyzer.name() in ["BoardDuration"]:
# print(json.dumps(analyzer.result(), indent=2))
# print(analyzer.render())
# coords = analyzers[1].render()
# with open("test.js", "w") as out:
# out.write("coords = "+coords)

1
util/__init__.py Normal file
View File

@ -0,0 +1 @@
from .iter import *

18
util/iter.py Normal file
View File

@ -0,0 +1,18 @@
def json_path(obj: dict, key: str):
"""Query a nested dict with a dot-separated path"""
if not type(obj) is dict:
return None
if "." not in key:
return obj[key]
child_key = key.split(".")
if child_key[0] not in obj:
return None
return json_path(obj[child_key[0]], ".".join(child_key[1:]))
def combinate(settings: dict, entry:dict)-> bool:
"""combine all settings {<key>: <expected>} with entry using AND"""
result = True
for key, value in settings.items():
result = result and json_path(entry, key) == value
return result