From ad24c90bb4d575c1b2a9cf701c96f8bdb025afe6 Mon Sep 17 00:00:00 2001 From: Koen Vossen Date: Sat, 13 Jun 2020 19:42:58 +0200 Subject: [PATCH] Add more options to kloppy-query --- CHANGES.txt | 5 +- .../pattern_matching/repository/README.md | 51 +++++++++++ kloppy/cmdline.py | 88 ++++++++++++++----- setup.py | 2 +- 4 files changed, 120 insertions(+), 26 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 3670b6aa..cf976444 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -14,4 +14,7 @@ v0.4.0, 2020-06-02 -- Add StatsBomb event Serializer Minor fixes to datasets loader v0.4.1, 2020-06-05 -- Fix for StatsBomb Serializer when location contains z coordinate v0.5.0, 2020-06-13 -- Add pattern matching based on regular expressions - Add kloppy-query: command line tool to search for patterns \ No newline at end of file + Add kloppy-query: command line tool to search for patterns +v0.5.1, 2020-06-13 -- Add stats in json/text format to kloppy-query + Add show-events to kloppy-query to print all events in the matches + Change kloppy-query to make it possible to run without an output file \ No newline at end of file diff --git a/examples/pattern_matching/repository/README.md b/examples/pattern_matching/repository/README.md index 9c9ebce7..8bc6aeb2 100644 --- a/examples/pattern_matching/repository/README.md +++ b/examples/pattern_matching/repository/README.md @@ -51,4 +51,55 @@ cat ball_recovery.xml +``` + +## Without output file +It's possible to only show stats and don't write a XML file. + +```shell script +$ kloppy-query --input-statsbomb=events.json,lineup.json --query-file=ball_recovery.py --stats=text 2>/dev/null +Home: + total count: 73 + success: 22 (30%) + no success: 51 (70%) + +Away: + total count: 86 + success: 7 (8%) + no success: 79 (92%) + + +# or in json format +$ kloppy-query --input-statsbomb=events.json,lineup.json --query-file=ball_recovery.py --stats=json 2>/dev/null +{ + "away_total": 86, + "away_success": 7, + "home_total": 73, + "home_success": 22 +} + +# or print matches +$ kloppy-query --input-statsbomb=events.json,lineup.json --query-file=ball_recovery.py --show-events + +Match 2: away no-success +126756d5-bfe3-44a7-a2c3-d36fb4d0a548 PASS INCOMPLETE / 1: 24.687 / away 5 / 35.5x68.5 +df65f591-1131-4565-ad3c-7295ccdf3f26 PASS COMPLETE / 1: 30.008 / home 1 / 13.5x27.5 +9a0bd516-551c-4e12-832e-a85b92dffcff PASS COMPLETE / 1: 34.738 / home 3 / 34.5x53.5 +d0c15c32-4a22-442a-82e6-916e54266de3 PASS COMPLETE / 1: 37.467 / home 2 / 49.5x71.5 +e4a69750-20f6-401f-bd10-4f1b4cd25b7a PASS COMPLETE / 1: 38.184 / home 20 / 55.5x62.5 +f8030bfa-a45c-4a41-80e8-def34021d09d PASS COMPLETE / 1: 39.870 / home 2 / 47.5x68.5 +b7c78e80-bb42-4b8f-94a6-999e88b4e32e PASS COMPLETE / 1: 44.029 / home 23 / 42.5x24.5 +43ba8e37-a6b8-4c61-964f-b7c2f8ab1863 PASS COMPLETE / 1: 46.653 / home 18 / 54.5x4.5 +2b0b8ea1-4b03-40f6-aadf-d2b71e3bf6b4 PASS COMPLETE / 1: 49.171 / home 4 / 51.5x15.5 +bb173a3c-9289-4040-b547-1e1d9b136e27 PASS COMPLETE / 1: 54.192 / home 3 / 56.5x51.5 +d2d49535-0037-4b01-9d4c-7715dbb58665 PASS COMPLETE / 1: 59.988 / home 23 / 62.5x25.5 +8bd96804-d1c5-4657-a081-0e8eb0bf3881 PASS COMPLETE / 1: 63.663 / home 3 / 63.5x52.5 +029b5f5d-0220-4f56-873d-e3db9cdb7c7e PASS INCOMPLETE / 1: 66.228 / home 10 / 76.5x77.5 + +Match 3: home SUCCESS +029b5f5d-0220-4f56-873d-e3db9cdb7c7e PASS INCOMPLETE / 1: 66.228 / home 10 / 76.5x77.5 +4667e094-bbd7-40a5-8ba3-82ba220558d1 PASS COMPLETE / 1: 67.685 / away 6 / 21.5x13.5 +90b4252c-a9ad-4671-b158-9b3be1e51629 PASS COMPLETE / 1: 72.116 / away 3 / 1.5x3.5 +53e333dc-7cb9-472b-b4b9-c6a773b2d919 PASS COMPLETE / 1: 76.219 / home 5 / 76.5x76.5 +410ff4d0-1aa5-4856-bcc4-4288880285f4 PASS COMPLETE / 1: 80.809 / home 23 / 64.5x33.5 ``` \ No newline at end of file diff --git a/kloppy/cmdline.py b/kloppy/cmdline.py index 12aef4c4..d4aa1ed2 100644 --- a/kloppy/cmdline.py +++ b/kloppy/cmdline.py @@ -1,6 +1,7 @@ import argparse import sys import logging +from collections import Counter from dataclasses import dataclass from typing import List @@ -46,6 +47,13 @@ def write_to_xml(video_fragments: List[VideoFragment], filename): method="xml") +def print_match(id_: int, match, success: bool, label): + print(f"Match {id_}: {label} {'SUCCESS' if success else 'no-success'}") + for event in match.events: + print(f"{event.event_id} {event.event_type} {str(event.result).ljust(10)} / {event.period.id}: {event.timestamp:.3f} / {event.team} {str(event.player_jersey_no).rjust(2)} / {event.position.x}x{event.position.y}") + print("") + + def load_query(query_file: str) -> pm.Query: locals_dict = {} with open(query_file, "rb") as fp: @@ -59,14 +67,16 @@ def load_query(query_file: str) -> pm.Query: def run_query(argv=sys.argv[1:]): parser = argparse.ArgumentParser(description="Run query on event data") parser.add_argument('--input-statsbomb', help="StatsBomb event input files (events.json,lineup.json)") - parser.add_argument('--output-xml', help="Output file", required=True) + parser.add_argument('--output-xml', help="Output file") parser.add_argument('--with-success', default=True, help="Input existence of success capture in output") parser.add_argument('--prepend-time', default=7, help="Seconds to prepend to match") parser.add_argument('--append-time', default=5, help="Seconds to append to match") parser.add_argument('--query-file', help="File containing the query", required=True) + parser.add_argument('--stats', default="none", help="Show matches stats", choices=["text", "json", "none"]) + parser.add_argument('--show-events', default=False, help="Show events for each match", action="store_true") logger = logging.getLogger("run_query") - logging.basicConfig(stream=sys.stdout, level=logging.INFO, + logging.basicConfig(stream=sys.stderr, level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") opts = parser.parse_args(argv) @@ -92,32 +102,62 @@ def run_query(argv=sys.argv[1:]): matches = pm.search(dataset, query.pattern) video_fragments = [] + counter = Counter() for i, match in enumerate(matches): + team = match.events[0].team success = 'success' in match.captures - label = str(match.events[0].team) - if opts.with_success and success: - label += " success" - - start_timestamp = ( - match.events[0].timestamp + - match.events[0].period.start_timestamp - - opts.prepend_time - ) - end_timestamp = ( - match.events[-1].timestamp + - match.events[-1].period.start_timestamp + - opts.append_time - ) - - video_fragments.append( - VideoFragment( - id_=str(i), - start=start_timestamp, - end=end_timestamp, - label=label + + counter.update({ + f"{team}_total": 1, + f"{team}_success": 1 if success else 0 + }) + + if opts.show_events: + print_match(i, match, success, str(team)) + + if opts.output_xml: + label = str(team) + if opts.with_success and success: + label += " success" + + start_timestamp = ( + match.events[0].timestamp + + match.events[0].period.start_timestamp - + opts.prepend_time + ) + end_timestamp = ( + match.events[-1].timestamp + + match.events[-1].period.start_timestamp + + opts.append_time + ) + + video_fragments.append( + VideoFragment( + id_=str(i), + start=start_timestamp, + end=end_timestamp, + label=label + ) ) - ) if opts.output_xml: write_to_xml(video_fragments, opts.output_xml) logger.info(f"Wrote {len(video_fragments)} video fragments to file") + + if opts.stats == "text": + print("Home:") + print(f" total count: {counter['home_total']}") + print( + f" success: {counter['home_success']} ({counter['home_success'] / counter['home_total'] * 100:.0f}%)") + print( + f" no success: {counter['home_total'] - counter['home_success']} ({(counter['home_total'] - counter['home_success']) / counter['home_total'] * 100:.0f}%)") + print("") + print("Away:") + print(f" total count: {counter['away_total']}") + print( + f" success: {counter['away_success']} ({counter['away_success'] / counter['away_total'] * 100:.0f}%)") + print( + f" no success: {counter['away_total'] - counter['away_success']} ({(counter['away_total'] - counter['away_success']) / counter['away_total'] * 100:.0f}%)") + elif opts.stats == "json": + import json + print(json.dumps(counter, indent=4)) diff --git a/setup.py b/setup.py index eeb9f1a1..29d0dbf6 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name='kloppy', - version='0.5.0', + version='0.5.1', author='Koen Vossen', author_email='info@koenvossen.nl', url="https://github.com/PySport/kloppy",