-
Notifications
You must be signed in to change notification settings - Fork 61
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fixed plot bugs, added functionality, updated images #353
Changes from 1 commit
0ffa67b
c93f6f4
2c2d2f9
2072762
06fe374
60a89af
1bdb424
0ac6d02
f2917f8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,15 +17,14 @@ | |
from scipy.stats import gaussian_kde | ||
from tqdm import tqdm | ||
|
||
from awpy.plot.utils import is_position_on_lower_level, position_transform_axis | ||
from awpy.plot.utils import is_position_on_lower_level, game_to_pixel_axis | ||
|
||
|
||
def plot( # noqa: PLR0915 | ||
map_name: str, | ||
points: Optional[List[Tuple[float, float, float]]] = None, | ||
is_lower: Optional[bool] = False, | ||
point_settings: Optional[List[Dict]] = None, | ||
ignore_extreme_points: Optional[bool] = False, | ||
) -> Tuple[Figure, Axes]: | ||
"""Plot a Counter-Strike map with optional points. | ||
|
||
|
@@ -46,10 +45,6 @@ def plot( # noqa: PLR0915 | |
- 'armor': int (0-100) | ||
- 'direction': Tuple[float, float] (pitch, yaw in degrees) | ||
- 'label': str (optional) | ||
ignore_extreme_points (bool, optional): If set to True, will ignore | ||
points that are outside of the map graphic. If set to False, will | ||
draw those points, changing the resolution of the ouput image. | ||
Defaults to False. | ||
|
||
Raises: | ||
FileNotFoundError: Raises a FileNotFoundError if the map image is not found. | ||
|
@@ -86,16 +81,14 @@ def plot( # noqa: PLR0915 | |
|
||
# Plot each point | ||
for (x, y, z), settings in zip(points, point_settings): | ||
transformed_x = position_transform_axis(map_name, x, "x") | ||
transformed_y = position_transform_axis(map_name, y, "y") | ||
transformed_x = game_to_pixel_axis(map_name, x, "x") | ||
transformed_y = game_to_pixel_axis(map_name, y, "y") | ||
|
||
if (ignore_extreme_points | ||
and ( | ||
transformed_x < 0 | ||
or transformed_x > 1024 | ||
or transformed_y < 0 | ||
or transformed_y > 1024 | ||
) | ||
if ( | ||
transformed_x < 0 | ||
or transformed_x > 1024 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because these are constants, it would be helpful to have a comment above here stating that these are the maximum expected bounds |
||
or transformed_y < 0 | ||
or transformed_y > 1024 | ||
): | ||
continue | ||
# Default settings | ||
|
@@ -116,8 +109,8 @@ def plot( # noqa: PLR0915 | |
# if drawing lower-level map and point is top-level, don't draw | ||
alpha = 0 | ||
|
||
transformed_x = position_transform_axis(map_name, x, "x") | ||
transformed_y = position_transform_axis(map_name, y, "y") | ||
transformed_x = game_to_pixel_axis(map_name, x, "x") | ||
transformed_y = game_to_pixel_axis(map_name, y, "y") | ||
|
||
# Plot the marker | ||
axes.plot( | ||
|
@@ -240,7 +233,6 @@ def _generate_frame_plot( | |
map_name: str, | ||
frames_data: List[Dict], | ||
is_lower: Optional[bool] = False, | ||
ignore_extreme_points: Optional[bool] = False, | ||
) -> list[Image.Image]: | ||
"""Generate frames for the animation. | ||
|
||
|
@@ -251,10 +243,6 @@ def _generate_frame_plot( | |
is_lower (optional, bool): If set to False, will not draw lower-level | ||
points with alpha = 0.4. If True will draw only lower-level | ||
points on the lower-level minimap. Defaults to False. | ||
ignore_extreme_points (bool, optional): If set to True, will ignore | ||
points that are outside of the map graphic. If set to False, will | ||
draw those points, changing the resolution of the ouput image. | ||
Defaults to False. | ||
|
||
Returns: | ||
List[Image.Image]: List of PIL Image objects representing each frame. | ||
|
@@ -265,7 +253,6 @@ def _generate_frame_plot( | |
map_name, | ||
frame_data["points"], | ||
is_lower, frame_data["point_settings"], | ||
ignore_extreme_points, | ||
) | ||
|
||
# Convert the matplotlib figure to a PIL Image | ||
|
@@ -286,7 +273,6 @@ def gif( | |
output_filename: str, | ||
duration: int = 500, | ||
is_lower: Optional[bool] = False, | ||
ignore_extreme_points: Optional[bool] = False, | ||
) -> None: | ||
"""Create an animated gif from a list of frames. | ||
|
||
|
@@ -300,16 +286,11 @@ def gif( | |
is_lower (optional, bool): If set to False, will draw lower-level points | ||
with alpha = 0.4. If True will draw only lower-level points on the | ||
lower-level minimap. Defaults to False. | ||
ignore_extreme_points (bool, optional): If set to True, will ignore | ||
points that are outside of the map graphic. If set to False, will | ||
draw those points, changing the resolution of the ouput image. | ||
Defaults to False. | ||
""" | ||
frames = _generate_frame_plot( | ||
map_name, | ||
frames_data, | ||
is_lower, | ||
ignore_extreme_points, | ||
) | ||
frames[0].save( | ||
output_filename, | ||
|
@@ -325,13 +306,11 @@ def heatmap( | |
points: List[Tuple[float, float, float]], | ||
method: Literal["hex", "hist", "kde"], | ||
is_lower: Optional[bool] = False, | ||
ignore_extreme_points: Optional[bool] = False, | ||
size: int = 10, | ||
cmap: str = "RdYlGn", | ||
alpha: float = 0.5, | ||
*, | ||
vary_alpha: bool = False, | ||
vary_alpha_range: Optional[List[float]] = None, | ||
alpha_range: Optional[List[float]] = None, | ||
kde_lower_bound: float = 0.1, | ||
) -> tuple[Figure, Axes]: | ||
"""Create a heatmap of points on a Counter-Strike map. | ||
|
@@ -343,18 +322,13 @@ def heatmap( | |
is_lower (optional, bool): If set to False, will NOT draw lower-level | ||
points. If True will draw only lower-level points on the | ||
lower-level minimap. Defaults to False. | ||
ignore_extreme_points (bool, optional): If set to True, will ignore | ||
points that are outside of the map graphic. If set to False, will | ||
draw those points, changing the resolution of the ouput image. | ||
Defaults to False. | ||
size (int, optional): Size of the heatmap grid. Defaults to 10. | ||
cmap (str, optional): Colormap to use. Defaults to 'RdYlGn'. | ||
alpha (float, optional): Transparency of the heatmap. Defaults to 0.5. | ||
vary_alpha (bool, optional): Vary the alpha based on the density. Defaults | ||
to False. | ||
vary_alpha_range (List[float, float], optional): The min and max transparency | ||
variance of points (respectively). Both values should be between `0` | ||
and `1`. Defaults to `[]`, meaning min = `0` and max = `alpha`. | ||
alpha_range (List[float, float], optional): When value is provided | ||
here, points' transparency will vary based on the density, with | ||
min transparency of `alpha_range[0]` and max of `alpha_range[1]`. | ||
Defaults to `None`, meaning no variance of transparency. | ||
kde_lower_bound (float, optional): Lower bound for KDE density values. Defaults | ||
to 0.1. | ||
|
||
|
@@ -396,43 +370,39 @@ def heatmap( | |
|
||
x, y = [], [] | ||
for point in points: | ||
x_point = position_transform_axis(map_name, point[0], "x") | ||
y_point = position_transform_axis(map_name, point[1], "y") | ||
x_point = game_to_pixel_axis(map_name, point[0], "x") | ||
y_point = game_to_pixel_axis(map_name, point[1], "y") | ||
# Handle extreme points | ||
if (ignore_extreme_points | ||
and ( | ||
x_point < 0 | ||
or x_point > 1024 | ||
or y_point < 0 | ||
or y_point > 1024 | ||
) | ||
if ( | ||
x_point < 0 | ||
or x_point > 1024 | ||
or y_point < 0 | ||
or y_point > 1024 | ||
): | ||
continue | ||
|
||
x.append(x_point) | ||
y.append(y_point) | ||
|
||
# If user set vary_alpha to True, check and/or set vary_alpha_range | ||
# Check and/or set alpha_range | ||
min_alpha, max_alpha = 0, 1 | ||
if vary_alpha: | ||
if vary_alpha_range is None: | ||
vary_alpha_range = [0, alpha] | ||
if not isinstance(vary_alpha_range, list): | ||
raise ValueError("vary_alpha_range must be a list of length 2.") | ||
if len(vary_alpha_range) != 2: | ||
raise ValueError("vary_alpha_range must have exactly 2 elements.") | ||
min_temp, max_temp = vary_alpha_range[0], vary_alpha_range[1] | ||
if alpha_range is not None: | ||
if not isinstance(alpha_range, list): | ||
raise ValueError("alpha_range must be a list of length 2.") | ||
if len(alpha_range) != 2: | ||
raise ValueError("alpha_range must have exactly 2 elements.") | ||
min_temp, max_temp = alpha_range[0], alpha_range[1] | ||
if not (min_temp >= 0 and min_temp <= 1) or not ( | ||
max_temp >= 0 and max_temp <= 1 | ||
): | ||
raise ValueError( | ||
"vary_alpha_range must have both values as floats \ | ||
"alpha_range must have both values as floats \ | ||
between 0 and 1." | ||
) | ||
if min_temp > max_temp: | ||
raise ValueError( | ||
"vary_alpha_range[0] (min alpha) cannot be greater " | ||
"than vary_alpha[1] (max alpha)." | ||
"alpha_range[0] (min alpha) cannot be greater " | ||
"than alpha[1] (max alpha)." | ||
) | ||
min_alpha, max_alpha = min_temp, max_temp | ||
|
||
|
@@ -443,7 +413,7 @@ def heatmap( | |
# Get array of counts in each hexbin | ||
counts = heatmap.get_array() | ||
|
||
if vary_alpha: | ||
if alpha_range is not None: | ||
# Normalize counts to use as alpha values | ||
alphas = counts / counts.max() | ||
alphas = alphas * (max_alpha - min_alpha) + min_alpha | ||
|
@@ -461,7 +431,7 @@ def heatmap( | |
# Set counts of 0 to NaN to make them transparent | ||
hist[hist == 0] = np.nan | ||
|
||
if vary_alpha: | ||
if alpha_range is not None: | ||
# Normalize histogram values | ||
hist_norm = hist.T / hist.max() | ||
# Create a color array with variable alpha | ||
|
@@ -494,7 +464,7 @@ def heatmap( | |
threshold = zi.max() * kde_lower_bound # You can adjust this threshold | ||
zi[zi < threshold] = np.nan | ||
|
||
if vary_alpha: | ||
if alpha_range is not None: | ||
# Normalize KDE values | ||
zi_norm = zi / zi.max() | ||
# Create a color array with variable alpha | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,13 @@ | ||
"""Utilities for plotting and visualization.""" | ||
|
||
from typing import Literal | ||
import warnings | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should generally do
|
||
|
||
from awpy.data.map_data import MAP_DATA | ||
|
||
|
||
# Position function courtesy of PureSkill.gg | ||
def position_transform_axis( | ||
def game_to_pixel_axis( | ||
map_name: str, position: float, axis: Literal["x", "y"] | ||
) -> float: | ||
"""Transforms an X or Y-axis value. CS2 coordinate -> Minimap image pixel value. | ||
|
@@ -34,7 +35,7 @@ def position_transform_axis( | |
return (start - position) / scale | ||
|
||
|
||
def position_revert_axis( | ||
def pixel_to_game_axis( | ||
map_name: str, position: float, axis: Literal["x", "y"] | ||
) -> float: | ||
"""Reverts an X or Y-axis value. Minimap image pixel value -> CS2 coordinate. | ||
|
@@ -64,7 +65,7 @@ def position_revert_axis( | |
return start - position * scale | ||
|
||
|
||
def position_transform( | ||
def game_to_pixel( | ||
map_name: str, position: tuple[float, float, float] | ||
) -> tuple[float, float, float]: | ||
"""Transforms an single coordinate (X,Y,Z). CS2 coordinates -> Minimap image pixel values. | ||
|
@@ -77,13 +78,13 @@ def position_transform( | |
Tuple[float, float, float]: Transformed coordinates (X,Y,Z). | ||
""" | ||
return ( | ||
position_transform_axis(map_name, position[0], "x"), | ||
position_transform_axis(map_name, position[1], "y"), | ||
game_to_pixel_axis(map_name, position[0], "x"), | ||
game_to_pixel_axis(map_name, position[1], "y"), | ||
position[2], | ||
) | ||
|
||
|
||
def position_revert( | ||
def pixel_to_game( | ||
map_name: str, position: tuple[float, float, float] | ||
) -> tuple[float, float, float]: | ||
"""Transforms an single coordinate (X,Y,Z). Minimap image pixel values -> CS2 coordinates. | ||
|
@@ -96,8 +97,8 @@ def position_revert( | |
Tuple[float, float, float]: Transformed coordinates (X,Y,Z). | ||
""" | ||
return ( | ||
position_revert_axis(map_name, position[0], "x"), | ||
position_revert_axis(map_name, position[1], "y"), | ||
pixel_to_game_axis(map_name, position[0], "x"), | ||
pixel_to_game_axis(map_name, position[1], "y"), | ||
position[2], | ||
) | ||
|
||
|
@@ -115,6 +116,69 @@ def is_position_on_lower_level( | |
bool: True if the position on the lower level, False otherwise. | ||
""" | ||
metadata = MAP_DATA[map_name] | ||
if position[2] <= metadata["lower_level_max"]: | ||
if position[2] <= metadata["lower_level_max_units"]: | ||
return True | ||
return False | ||
|
||
|
||
# Old function names: | ||
def _renaming_warning(old: str, new: str): | ||
return f"""Deprecation warning: Function {old} has been renamed to {new}. | ||
Please update your code to avoid future deprecation. | ||
""" | ||
|
||
|
||
def position_transform_axis( | ||
map_name: str, position: float, axis: Literal["x", "y"] | ||
) -> float: | ||
"""Calls `game_to_pixel_axis` and sends warning. | ||
|
||
This is the old name of function `game_to_pixel_axis`. Please update | ||
your code to avoid future deprecation. | ||
""" | ||
warnings.warn( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great idea to add the deprecation warnings. I think we can drop the |
||
_renaming_warning("position_transform_axis()", "game_to_pixel_axis()"), | ||
DeprecationWarning) | ||
return game_to_pixel_axis(map_name, position, axis) | ||
|
||
|
||
def position_revert_axis( | ||
map_name: str, position: float, axis: Literal["x", "y"] | ||
) -> float: | ||
"""Calls `pixel_to_game_axis` and sends warning. | ||
|
||
This is the old name of function `pixel_to_game_axis`. Please update | ||
your code to avoid future deprecation. | ||
""" | ||
warnings.warn( | ||
_renaming_warning("position_revert_axis()", "pixel_to_game_axis()"), | ||
DeprecationWarning) | ||
return pixel_to_game_axis(map_name, position, axis) | ||
|
||
|
||
def position_transform( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I also think these functions would still live in |
||
map_name: str, position: tuple[float, float, float] | ||
) -> tuple[float, float, float]: | ||
"""Calls `game_to_pixel` and sends warning. | ||
|
||
This is the old name of function `game_to_pixel`. Please update | ||
your code to avoid future deprecation. | ||
""" | ||
warnings.warn( | ||
_renaming_warning("position_transform()", "game_to_pixel()"), | ||
DeprecationWarning) | ||
return game_to_pixel(map_name, position) | ||
|
||
|
||
def position_revert( | ||
map_name: str, position: tuple[float, float, float] | ||
) -> tuple[float, float, float]: | ||
"""Calls `pixel_to_game` and sends warning. | ||
|
||
This is the old name of function `pixel_to_game`. Please update | ||
your code to avoid future deprecation. | ||
""" | ||
warnings.warn( | ||
_renaming_warning("position_revert()", "pixel_to_game()"), | ||
DeprecationWarning) | ||
return pixel_to_game(map_name, position) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if this parameter were reworked into a "lower fraction"? As in, we assume that upper points are plotted with alpha = 1.0, and then lower are lower_frac*alpha ? This would allow for a more expressive form of what it appears you are going for.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which, we may also want to offer a param for the "dead player alpha" that we default to 0.15, too
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea!
The only thing to note is that when
is_lower = True
, it also changes the image to the lower part of the map, but then again, you can control this via themap_name
argument.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. Perhaps in the docstring we can say something like "if you want to plot a specific lower part of a map, use
*_lower
. However, then this could imply that a user may want to plot only the top part of the map, too. Perhaps we search for a "_lower" and "_upper" if we want to plot only those.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that I look at this, perhaps we want to just have a generic
game_to_pixel(tuple)
. That tuple can be an (x,y) pair. What do you think?