Skip to content

Commit

Permalink
Add more options to kloppy-query
Browse files Browse the repository at this point in the history
  • Loading branch information
koenvossen committed Jun 13, 2020
1 parent a13a226 commit ad24c90
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 26 deletions.
5 changes: 4 additions & 1 deletion CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
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
51 changes: 51 additions & 0 deletions examples/pattern_matching/repository/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,55 @@ cat ball_recovery.xml
</instance>
</ALL_INSTANCES>
</file>
```

## 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
```
88 changes: 64 additions & 24 deletions kloppy/cmdline.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import argparse
import sys
import logging
from collections import Counter
from dataclasses import dataclass
from typing import List

Expand Down Expand Up @@ -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:
Expand All @@ -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)
Expand All @@ -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))
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

setup(
name='kloppy',
version='0.5.0',
version='0.5.1',
author='Koen Vossen',
author_email='[email protected]',
url="https://github.com/PySport/kloppy",
Expand Down

0 comments on commit ad24c90

Please sign in to comment.