Skip to content

Commit 6cb1cb3

Browse files
committed
Adding tacking data improvements
1 parent 609cd30 commit 6cb1cb3

7 files changed

+644
-118
lines changed

docks/requirements.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
requests>=2.20.0
2+
makefun>=1.10.0
3+
numpy>=1.18.4, <1.19.0
4+
pandas
5+
matplotlib>=3.5.0

setup.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,13 @@
1111
install_requires=[
1212
'requests>=2.20.0',
1313
'makefun>=1.10.0',
14-
'simplejson>=3.16.0'
14+
'numpy>=1.18.4, <1.19.0',
15+
'pandas',
16+
'matplotlib>=3.5.0',
1517
],
1618
extras_require={
1719
'test': 'mock==4.0.3',
18-
'release': ['Sphinx==4.2.0', 'sphinx-rtd-theme==1.0.0']
20+
'release': ['Sphinx==4.2.0', 'sphinx-rtd-theme==1.0.0'],
21+
'physical_visualisation': 'pandasgui',
1922
}
2023
)

skillcorner/client.py

Lines changed: 200 additions & 115 deletions
Large diffs are not rendered by default.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from pandasgui import show
2+
from skillcorner.client import SkillcornerClient
3+
4+
5+
skc_client = SkillcornerClient(username='PUT_YOUR_LOGIN_HERE', password='PUT_YOUR_PASSWORD_HERE')
6+
physical_data = skc_client.get_physical(params={'season': 6, 'competition': 1})
7+
show(physical_data)

skillcorner/tests/mocks/client_mock.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def __init__(self, *args, **kwargs):
3030
logger.debug(f'Creating Skillcorner mock client instance')
3131

3232
def _skillcorner_request(self, url, method, params, paginated_request, timeout, output_format=None,
33-
visualisation=None, json_data=None, pagination_limit=300):
33+
visualisation=None, json_data=None, pagination_limit=300, **kwargs):
3434
"""
3535
Mocked skillcorner_request method returning fake json response read from file.
3636

skillcorner/tracking_visualisation_example.ipynb

Lines changed: 154 additions & 0 deletions
Large diffs are not rendered by default.

skillcorner/visualisation_utils.py

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
import json
2+
3+
4+
def _get_json_list(response):
5+
data_bytes = response.content
6+
data_list = data_bytes.decode('utf-8').split("\n")
7+
data = []
8+
for line in data_list:
9+
if line:
10+
data.append(json.loads(line))
11+
return data
12+
13+
14+
def _convert_response_to_df(data, df_parameterization):
15+
import pandas as pd
16+
df = pd.DataFrame(data)
17+
18+
if not df_parameterization:
19+
return df
20+
21+
elif df_parameterization=='positions_per_frame':
22+
arrays = [[], []]
23+
24+
for frame in df.frame:
25+
if df.data[frame]:
26+
for i in range(len(df.data[frame])):
27+
trackable_object = df.data[frame][i]['trackable_object']
28+
if trackable_object not in arrays[0]:
29+
arrays[0] += [trackable_object]*2
30+
arrays[1] += ['x', 'y']
31+
32+
positions = _preprepare_positions(len(df.frame), len(arrays[0]))
33+
34+
for frame in df.frame:
35+
if df.data[frame]:
36+
for i in range(len(df.data[frame])):
37+
trackable_object = df.data[frame][i]['trackable_object']
38+
index = arrays[0].index(trackable_object)
39+
positions[frame][index] = df.data[frame][i]['x']
40+
positions[frame][index+1] = df.data[frame][i]['y']
41+
42+
names = ['trackable_object', 'position']
43+
arrays_2 = [[], []]
44+
arrays_2[0] = df.frame
45+
arrays_2[1] = df.timestamp
46+
tuples_2 = list(zip(*arrays_2))
47+
names_2 = ['frame', 'timestamp']
48+
index = pd.MultiIndex.from_tuples(tuples_2, names=names_2)
49+
50+
tuples = list(zip(*arrays))
51+
multi_index = pd.MultiIndex.from_tuples(tuples, names=names)
52+
data = pd.DataFrame(positions, columns=multi_index, index=index)
53+
54+
return data
55+
56+
57+
def _preprepare_positions(dim_1, dim_2):
58+
positions = []
59+
for i in range(dim_1):
60+
temp = []
61+
for j in range(dim_2):
62+
temp.append(0)
63+
positions.append(temp)
64+
return positions
65+
66+
67+
def create_visualisation_per_frame(self, match, frame):
68+
"""
69+
Function to plot extrapolated data visualisation at indicated frame.
70+
71+
:param match: df of extrapolated tracking data or int id of match which should be presented
72+
:param frame: int indicating which frame visualisation to be presented
73+
"""
74+
import matplotlib.pyplot as plt
75+
import pandas as pd
76+
77+
if not isinstance(match, pd.DataFrame):
78+
tracking_data = self.get_match_tracking_data(match)
79+
else:
80+
tracking_data = match
81+
82+
x = tracking_data.loc[frame, tracking_data.columns.get_level_values(1)=="x"]
83+
y = tracking_data.loc[frame, tracking_data.columns.get_level_values(1)=="y"]
84+
trackable_objects = tracking_data.columns.droplevel(1).unique()
85+
timestamp = tracking_data.index[frame][1]
86+
x[x==0] = float('nan')
87+
y[y==0] = float('nan')
88+
89+
fig, ax = plt.subplots()
90+
fig.suptitle(f'Tracking visualisation of frame: {frame}')
91+
fig.canvas.set_window_title('Skillcorner')
92+
_plot_field(ax)
93+
ax.get_xaxis().set_visible(False)
94+
ax.get_yaxis().set_visible(False)
95+
text = fig.text(0.15, 0.05, f"Timestamp: {timestamp}")
96+
scatter = ax.scatter(x, y)
97+
plt.show()
98+
99+
100+
def create_full_visualisation(self, match, valinit=100):
101+
"""
102+
Function to plot extrapolated data visualisation for the full game. Uses matplot Slider widget.
103+
104+
:param match: df of extrapolated tracking data or int id of match which should be presented
105+
:param valinit: int indicating frame of starting point for slider
106+
"""
107+
from matplotlib.widgets import Slider
108+
import matplotlib.pyplot as plt
109+
import pandas as pd
110+
import numpy as np
111+
112+
if not isinstance(match, pd.DataFrame):
113+
tracking_data = self.get_match_tracking_data(match)
114+
else:
115+
tracking_data = match
116+
117+
x = tracking_data.loc[valinit, tracking_data.columns.get_level_values(1)=="x"]
118+
y = tracking_data.loc[valinit, tracking_data.columns.get_level_values(1)=="y"]
119+
x[x==0] = float('nan')
120+
y[y==0] = float('nan')
121+
timestamp = tracking_data.index[valinit][1]
122+
123+
fig, ax = plt.subplots()
124+
fig.suptitle('Tracking visualisation')
125+
fig.canvas.set_window_title('Skillcorner')
126+
plt.subplots_adjust(bottom=0.25)
127+
_plot_field(ax)
128+
ax.get_xaxis().set_visible(False)
129+
ax.get_yaxis().set_visible(False)
130+
ax_slider = plt.axes([0.25, 0.1, 0.65, 0.03])
131+
ax_slider.get_xaxis().set_visible(False)
132+
ax_slider.get_yaxis().set_visible(False)
133+
text = fig.text(0.15, 0.05, f"Timestamp: {timestamp}")
134+
scatter = ax.scatter(x, y)
135+
slider = Slider(ax=ax_slider, label='Frames', valmin=0, valmax=len(tracking_data)-1, valinit=valinit, valstep=1)
136+
137+
def update(frame):
138+
x = tracking_data.loc[frame, tracking_data.columns.get_level_values(1)=="x"]
139+
y = tracking_data.loc[frame, tracking_data.columns.get_level_values(1)=="y"]
140+
x[x==0] = float('nan')
141+
y[y==0] = float('nan')
142+
timestamp = tracking_data.index[frame][1]
143+
xx = np.vstack((x, y))
144+
scatter.set_offsets(xx.T)
145+
text.set_text(f"Timestamp: {timestamp}")
146+
147+
slider.on_changed(update)
148+
149+
plt.show()
150+
151+
152+
def _plot_rectangle(x1, x2, y1, y2, ax):
153+
ax.plot([x1, x1], [y1, y2], color="white", zorder=8000)
154+
ax.plot([x2, x2], [y1, y2], color="white", zorder=8000)
155+
ax.plot([x1, x2], [y1, y1], color="white", zorder=8000)
156+
ax.plot([x1, x2], [y2, y2], color="white", zorder=8000)
157+
158+
159+
def _plot_field(ax):
160+
import matplotlib.pyplot as plt
161+
from matplotlib.patches import Arc
162+
# Pitch Outline & Centre Line
163+
origin_x1 = -52.5
164+
origin_x2 = 52.5
165+
origin_y1 = -34
166+
origin_y2 = 34
167+
168+
d = 2
169+
rectangle = plt.Rectangle(
170+
(origin_x1 - 2 * d, origin_y1 - 2 * d),
171+
105 + 4 * d,
172+
68 + 4 * d,
173+
fc="green",
174+
alpha=0.4,
175+
zorder = -5000,
176+
)
177+
ax.add_patch(rectangle)
178+
_plot_rectangle(origin_x1, origin_x2, origin_y1, origin_y2, ax)
179+
ax.plot([0, 0], [origin_y1, origin_y2], color="white", zorder=8000)
180+
181+
# Left Penalty Area
182+
penalty_box_length = 16.5
183+
penalty_box_width = 40.3
184+
185+
x1 = origin_x1
186+
x2 = origin_x1 + penalty_box_length
187+
y1 = -penalty_box_width / 2
188+
y2 = penalty_box_width / 2
189+
_plot_rectangle(x1, x2, y1, y2, ax)
190+
191+
# Right Penalty Area
192+
x1 = origin_x2 - penalty_box_length
193+
x2 = origin_x2
194+
y1 = -penalty_box_width / 2
195+
y2 = penalty_box_width / 2
196+
_plot_rectangle(x1, x2, y1, y2, ax)
197+
198+
# Left 6-yard Box
199+
six_yard_box_length = 5.5
200+
six_yard_box_width = 18.3
201+
202+
x1 = origin_x1
203+
x2 = origin_x1 + six_yard_box_length
204+
y1 = -six_yard_box_width / 2
205+
y2 = six_yard_box_width / 2
206+
_plot_rectangle(x1, x2, y1, y2, ax)
207+
208+
# Right 6-yard Box
209+
x1 = origin_x2 - six_yard_box_length
210+
x2 = origin_x2
211+
y1 = -six_yard_box_width / 2
212+
y2 = six_yard_box_width / 2
213+
_plot_rectangle(x1, x2, y1, y2, ax)
214+
215+
# Left Goal
216+
goal_width = 7.3
217+
goal_length = 2
218+
219+
x1 = origin_x1 - goal_length
220+
x2 = origin_x1
221+
y1 = -goal_width / 2
222+
y2 = goal_width / 2
223+
_plot_rectangle(x1, x2, y1, y2, ax)
224+
225+
# Right Goal
226+
x1 = origin_x2
227+
x2 = origin_x2 + goal_length
228+
y1 = -goal_width / 2
229+
y2 = goal_width / 2
230+
_plot_rectangle(x1, x2, y1, y2, ax)
231+
232+
# Prepare Circles
233+
circle_radius = 9.15
234+
penalty_spot_distance = 11
235+
centreCircle = plt.Circle((0, 0), circle_radius, color="white", fill=False, zorder=8000)
236+
centreSpot = plt.Circle((0, 0), 0.4, color="white", zorder=8000)
237+
lx = origin_x1 + penalty_spot_distance
238+
leftPenSpot = plt.Circle((lx, 0), 0.4, color="white", zorder=8000)
239+
rx = origin_x2 - penalty_spot_distance
240+
rightPenSpot = plt.Circle((rx, 0), 0.4, color="white", zorder=8000)
241+
242+
# Draw Circles
243+
ax.add_patch(centreCircle)
244+
ax.add_patch(centreSpot)
245+
ax.add_patch(leftPenSpot)
246+
ax.add_patch(rightPenSpot)
247+
248+
# Prepare Arcs
249+
r = circle_radius * 2
250+
leftArc = Arc(
251+
(lx, 0),
252+
height=r,
253+
width=r,
254+
angle=0,
255+
theta1=307,
256+
theta2=53,
257+
color="white",
258+
zorder=8000,
259+
)
260+
rightArc = Arc(
261+
(rx, 0),
262+
height=r,
263+
width=r,
264+
angle=0,
265+
theta1=127,
266+
theta2=233,
267+
color="white",
268+
zorder=8000,
269+
)
270+
# Draw Arcs
271+
ax.add_patch(leftArc)
272+
ax.add_patch(rightArc)

0 commit comments

Comments
 (0)