diff --git a/src/software/thunderscope/constants.py b/src/software/thunderscope/constants.py index eb5bc0bb00..6bb71de55b 100644 --- a/src/software/thunderscope/constants.py +++ b/src/software/thunderscope/constants.py @@ -402,6 +402,9 @@ class RuntimeManagerConstants: EXTERNAL_RUNTIMES_PATH = "/opt/tbotspython/external_runtimes" RUNTIME_CONFIG_PATH = f"{EXTERNAL_RUNTIMES_PATH}/runtime_config.toml" + RUNTIME_EVENTS_DIRECTORY_PATH = "/tmp/tbots/stats" + RUNTIME_EVENTS_FILE = "game_events.csv" + RUNTIME_STATS_DIRECTORY_PATH = "/tmp/tbots/stats" RUNTIME_FRIENDLY_STATS_FILE = "blue.toml" RUNTIME_ENEMY_FROM_FRIENDLY_STATS_FILE = "yellow_from_blue.toml" diff --git a/src/software/thunderscope/log/stats/BUILD b/src/software/thunderscope/log/stats/BUILD index 20f800a11d..178d7c8c1b 100644 --- a/src/software/thunderscope/log/stats/BUILD +++ b/src/software/thunderscope/log/stats/BUILD @@ -1,3 +1,5 @@ +load("@thunderscope_deps//:requirements.bzl", "requirement") + package(default_visibility = ["//visibility:public"]) py_library( @@ -16,3 +18,15 @@ py_library( "//software/thunderscope:thread_safe_buffer", ], ) + +py_binary( + name = "analysis", + srcs = ["analysis.py"], + data = [ + "//software:py_constants.so", + ], + deps = [ + "//software/thunderscope", + requirement("pandas"), + ], +) diff --git a/src/software/thunderscope/log/stats/analysis.py b/src/software/thunderscope/log/stats/analysis.py new file mode 100644 index 0000000000..eb9ca4bdb3 --- /dev/null +++ b/src/software/thunderscope/log/stats/analysis.py @@ -0,0 +1,204 @@ +import numpy as np +import pandas as pd +import pyqtgraph as pg +from pyqtgraph.exporters import ImageExporter + +from software.py_constants import DIV_B_NUM_ROBOTS +from software.thunderscope.constants import RuntimeManagerConstants + +CSV_PATH = f"{RuntimeManagerConstants.RUNTIME_EVENTS_DIRECTORY_PATH}/{RuntimeManagerConstants.RUNTIME_EVENTS_FILE}" +OUTPUT_DIR = RuntimeManagerConstants.RUNTIME_EVENTS_DIRECTORY_PATH + +COLUMNS = { + "ball": ["ball_x", "ball_y", "ball_vx", "ball_vy"], + "robot_attrs": ["x", "y", "orientation", "vx", "vy", "angular_velocity"], + "event_type": ["event_type"], + "timestamp": ["timestamp"], + "teams": ["from", "to"], +} + +NUM_ROBOTS_PER_TEAM = DIV_B_NUM_ROBOTS + + +def load_raw_data(path: str) -> pd.DataFrame: + """Load raw CSV data and assign column names. + + :param path: Path to the CSV file. + :return: DataFrame with named columns for event_type, timestamp, ball state, + and robot state (6 robots per team, zero-indexed). + """ + cols = ( + COLUMNS["timestamp"] + + COLUMNS["event_type"] + + COLUMNS["teams"] + + COLUMNS["ball"] + + [f"friendly_{i // 6}_{COLUMNS['robot_attrs'][i % 6]}" for i in range(36)] + + [f"enemy_{i // 6}_{COLUMNS['robot_attrs'][i % 6]}" for i in range(36)] + ) + df = pd.read_csv(path, header=None) + df.columns = cols + return df + + +def extract_events(df: pd.DataFrame) -> pd.DataFrame: + """Extract event data with ball state. + + :param df: Raw DataFrame from load_raw_data(). + :return: DataFrame with columns: event_type, timestamp, ball_x, ball_y, + ball_vx, ball_vy. + """ + return df[COLUMNS["event_type"] + COLUMNS["timestamp"] + COLUMNS["ball"]].copy() + + +def extract_robots(df: pd.DataFrame) -> pd.DataFrame: + """Extract robot state data into long format. + + :param df: Raw DataFrame from load_raw_data(). + :return: DataFrame with columns: timestamp, robot_index (0-5), team, x, y, + orientation, vx, vy, angular_velocity. Each row represents one robot + at one timestamp. + """ + records = [] + for team in ["friendly", "enemy"]: + for robot_idx in range(NUM_ROBOTS_PER_TEAM): + cols = [f"{team}_{robot_idx}_{attr}" for attr in COLUMNS["robot_attrs"]] + records.append( + pd.DataFrame( + { + "timestamp": df["timestamp"], + "robot_index": robot_idx, + "team": team, + **{ + attr: df[col] + for attr, col in zip(COLUMNS["robot_attrs"], cols) + }, + } + ) + ) + return pd.concat(records, ignore_index=True) + + +def plot_shots(events_df: pd.DataFrame, output_path: str) -> None: + """Plot cumulative shots on goal over time. + + :param events_df: DataFrame from extract_events(). + :param output_path: Path to save the PNG plot. + """ + shots = events_df[events_df["event_type"] == "shot_on_goal"].copy() + shots = shots.sort_values("timestamp") + shots["time_relative_ms"] = shots["timestamp"] - shots["timestamp"].min() + shots["cumulative"] = range(1, len(shots) + 1) + + pg.setConfigOption("antialias", True) + plot = pg.plot() + + plot.plot( + shots["time_relative_ms"].values / 1000, + shots["cumulative"].values, + pen={"color": "g", "width": 2}, + symbol="o", + symbolSize=8, + symbolBrush=pg.mkBrush("g"), + ) + + plot.setLabel("bottom", "Time (seconds)") + plot.setLabel("left", "Total Shots") + plot.setTitle("Cumulative Shots on Goal") + + ImageExporter(plot.plotItem).export(output_path) + print(f"Saved plot to {output_path}") + + +def plot_robot_heatmap(robots_df: pd.DataFrame, output_path: str) -> None: + """Plot a heatmap of robot positions on the field. + + SSL Division B field dimensions: + - Field: 9.0m x 6.0m + """ + x = robots_df["x"].to_numpy() + y = robots_df["y"].to_numpy() + + field_x_min, field_x_max = -4.5, 4.5 + field_y_min, field_y_max = -3.0, 3.0 + + bins = 80 + + heatmap, xedges, yedges = np.histogram2d( + x, + y, + bins=bins, + range=[[field_x_min, field_x_max], [field_y_min, field_y_max]], + ) + + pg.setConfigOption("antialias", True) + + plot_widget = pg.PlotWidget() + plot_item = plot_widget.getPlotItem() + + img = pg.ImageItem(heatmap.T) + + img.setRect( + field_x_min, + field_y_min, + field_x_max - field_x_min, + field_y_max - field_y_min, + ) + + # Apply colormap + cmap = pg.colormap.get("viridis") + img.setColorMap(cmap) + + plot_item.addItem(img) + + plot_item.setLabel("bottom", "X (meters)") + plot_item.setLabel("left", "Y (meters)") + plot_item.setTitle("Robot Position Heatmap") + + plot_item.setXRange(field_x_min, field_x_max) + plot_item.setYRange(field_y_min, field_y_max) + + plot_item.showGrid(x=True, y=True) + + # ---- sanity check point ---- + sanity_x = 1 + sanity_y = 0 + + plot_item.plot( + [sanity_x], + [sanity_y], + pen=None, + symbol="o", + symbolSize=10, + symbolBrush="red", + ) + + # label it so you know what you're looking at + text = pg.TextItem("(1,0)", anchor=(0, 1)) + text.setPos(sanity_x, sanity_y) + plot_item.addItem(text) + # ---------------------------- + + exporter = ImageExporter(plot_item) + exporter.export(output_path) + + print(f"Saved heatmap to {output_path}") + + +def main() -> None: + """Run exploratory analysis on game events data.""" + df = load_raw_data(CSV_PATH) + events_df = extract_events(df) + robots_df = extract_robots(df) + + print() + print("Events summary:") + print() + print(events_df["event_type"].value_counts().to_string()) + print() + + plot_shots(events_df, f"{OUTPUT_DIR}/shots_over_time.png") + plot_robot_heatmap(robots_df, f"{OUTPUT_DIR}/robot_heatmap.png") + + +if __name__ == "__main__": + main() diff --git a/src/software/thunderscope/requirements.in b/src/software/thunderscope/requirements.in index 18c98bc70f..a4fc67fb0d 100644 --- a/src/software/thunderscope/requirements.in +++ b/src/software/thunderscope/requirements.in @@ -2,6 +2,7 @@ colorama==0.4.6 netifaces==0.11.0 evdev==1.7.0; sys_platform == "linux" numpy==1.26.4 +pandas==3.0.2 protobuf==6.31.1 pyqtgraph==0.13.7 pyqtdarktheme-fork==2.3.2 diff --git a/src/software/thunderscope/requirements_lock.darwin.txt b/src/software/thunderscope/requirements_lock.darwin.txt index 87532ad2ba..4d271b95c0 100644 --- a/src/software/thunderscope/requirements_lock.darwin.txt +++ b/src/software/thunderscope/requirements_lock.darwin.txt @@ -83,11 +83,62 @@ numpy==1.26.4 \ --hash=sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f # via # -r software/thunderscope/requirements.in + # pandas # pyqtgraph packaging==24.2 \ --hash=sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759 \ --hash=sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f # via qtpy +pandas==3.0.2 \ + --hash=sha256:01f31a546acd5574ef77fe199bc90b55527c225c20ccda6601cf6b0fd5ed597c \ + --hash=sha256:0555c5882688a39317179ab4a0ed41d3ebc8812ab14c69364bbee8fb7a3f6288 \ + --hash=sha256:07a10f5c36512eead51bc578eb3354ad17578b22c013d89a796ab5eee90cd991 \ + --hash=sha256:08504503f7101300107ecdc8df73658e4347586db5cfdadabc1592e9d7e7a0fd \ + --hash=sha256:0f48afd9bb13300ffb5a3316973324c787054ba6665cda0da3fbd67f451995db \ + --hash=sha256:140f0cffb1fa2524e874dde5b477d9defe10780d8e9e220d259b2c0874c89d9d \ + --hash=sha256:232a70ebb568c0c4d2db4584f338c1577d81e3af63292208d615907b698a0f18 \ + --hash=sha256:32cc41f310ebd4a296d93515fcac312216adfedb1894e879303987b8f1e2b97d \ + --hash=sha256:339dda302bd8369dedeae979cb750e484d549b563c3f54f3922cb8ff4978c5eb \ + --hash=sha256:4544c7a54920de8eeacaa1466a6b7268ecfbc9bc64ab4dbb89c6bbe94d5e0660 \ + --hash=sha256:4d888a5c678a419a5bb41a2a93818e8ed9fd3172246555c0b37b7cc27027effd \ + --hash=sha256:5371b72c2d4d415d08765f32d689217a43227484e81b2305b52076e328f6f482 \ + --hash=sha256:57a07209bebcbcf768d2d13c9b78b852f9a15978dac41b9e6421a81ad4cdd276 \ + --hash=sha256:5880314e69e763d4c8b27937090de570f1fb8d027059a7ada3f7f8e98bdcb677 \ + --hash=sha256:5d3cfe227c725b1f3dff4278b43d8c784656a42a9325b63af6b1492a8232209e \ + --hash=sha256:5fdbfa05931071aba28b408e59226186b01eb5e92bea2ab78b65863ca3228d84 \ + --hash=sha256:60a80bb4feacbef5e1447a3f82c33209c8b7e07f28d805cfd1fb951e5cb443aa \ + --hash=sha256:61c2fd96d72b983a9891b2598f286befd4ad262161a609c92dc1652544b46b76 \ + --hash=sha256:63d141b56ef686f7f0d714cfb8de4e320475b86bf4b620aa0b7da89af8cbdbbb \ + --hash=sha256:6c4d8458b97a35717b62469a4ea0e85abd5ed8687277f5ccfc67f8a5126f8c53 \ + --hash=sha256:710246ba0616e86891b58ab95f2495143bb2bc83ab6b06747c74216f583a6ac9 \ + --hash=sha256:734be7551687c00fbd760dc0522ed974f82ad230d4a10f54bf51b80d44a08702 \ + --hash=sha256:7cadd7e9a44ec13b621aec60f9150e744cfc7a3dd32924a7e2f45edff31823b0 \ + --hash=sha256:81526c4afd31971f8b62671442a4b2b51e0aa9acc3819c9f0f12a28b6fcf85f1 \ + --hash=sha256:970762605cff1ca0d3f71ed4f3a769ea8f85fc8e6348f6e110b8fea7e6eb5a14 \ + --hash=sha256:a3096110bf9eac0070b7208465f2740e2d8a670d5cb6530b5bb884eca495fd39 \ + --hash=sha256:a4785e1d6547d8427c5208b748ae2efb64659a21bd82bf440d4262d02bfa02a4 \ + --hash=sha256:a727a73cbdba2f7458dc82449e2315899d5140b449015d822f515749a46cbbe0 \ + --hash=sha256:ae37e833ff4fed0ba352f6bdd8b73ba3ab3256a85e54edfd1ab51ae40cca0af8 \ + --hash=sha256:aff4e6f4d722e0652707d7bcb190c445fe58428500c6d16005b02401764b1b3d \ + --hash=sha256:b35d14bb5d8285d9494fe93815a9e9307c0876e10f1e8e89ac5b88f728ec8dcf \ + --hash=sha256:b444dc64c079e84df91baa8bf613d58405645461cabca929d9178f2cd392398d \ + --hash=sha256:b5329e26898896f06035241a626d7c335daa479b9bbc82be7c2742d048e41172 \ + --hash=sha256:b5918ba197c951dec132b0c5929a00c0bf05d5942f590d3c10a807f6e15a57d3 \ + --hash=sha256:b75c347eff42497452116ce05ef461822d97ce5b9ff8df6edacb8076092c855d \ + --hash=sha256:c3b723df9087a9a9a840e263ebd9f88b64a12075d1bf2ea401a5a42f254f084d \ + --hash=sha256:c934008c733b8bbea273ea308b73b3156f0181e5b72960790b09c18a2794fe1e \ + --hash=sha256:d1478075142e83a5571782ad007fb201ed074bdeac7ebcc8890c71442e96adf7 \ + --hash=sha256:d606a041c89c0a474a4702d532ab7e73a14fe35c8d427b972a625c8e46373668 \ + --hash=sha256:db0dbfd2a6cdf3770aa60464d50333d8f3d9165b2f2671bcc299b72de5a6677b \ + --hash=sha256:dbbd4aa20ca51e63b53bbde6a0fa4254b1aaabb74d2f542df7a7959feb1d760c \ + --hash=sha256:dbc20dea3b9e27d0e66d74c42b2d0c1bed9c2ffe92adea33633e3bedeb5ac235 \ + --hash=sha256:deeca1b5a931fdf0c2212c8a659ade6d3b1edc21f0914ce71ef24456ca7a6535 \ + --hash=sha256:ed72cb3f45190874eb579c64fa92d9df74e98fd63e2be7f62bce5ace0ade61df \ + --hash=sha256:ef8b27695c3d3dc78403c9a7d5e59a62d5464a7e1123b4e0042763f7104dc74f \ + --hash=sha256:f12b1a9e332c01e09510586f8ca9b108fd631fd656af82e452d7315ef6df5f9f \ + --hash=sha256:f4753e73e34c8d83221ba58f232433fca2748be8b18dbca02d242ed153945043 \ + --hash=sha256:f8d68083e49e16b84734eb1a4dcae4259a75c90fb6e2251ab9a00b61120c06ab + # via -r software/thunderscope/requirements.in protobuf==6.31.1 \ --hash=sha256:0414e3aa5a5f3ff423828e1e6a6e907d6c65c1d5b7e6e975793d5590bdeecc16 \ --hash=sha256:426f59d2964864a1a366254fa703b8632dcec0790d8862d30034d8245e1cd447 \ @@ -120,7 +171,21 @@ pyqtgraph==0.13.7 \ --hash=sha256:64f84f1935c6996d0e09b1ee66fe478a7771e3ca6f3aaa05f00f6e068321d9e3 \ --hash=sha256:7754edbefb6c367fa0dfb176e2d0610da3ada20aa7a5318516c74af5fb72bf7a # via -r software/thunderscope/requirements.in +python-dateutil==2.9.0.post0 \ + --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ + --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 + # via pandas +qtawesome==1.4.0 \ + --hash=sha256:783e414d1317f3e978bf67ea8e8a1b1498bad9dbd305dec814027e3b50521be6 \ + --hash=sha256:a4d689fa071c595aa6184171ce1f0f847677cb8d2db45382c43129f1d72a3d93 + # via -r software/thunderscope/requirements.in qtpy==2.4.2 \ --hash=sha256:5a696b1dd7a354cb330657da1d17c20c2190c72d4888ba923f8461da67aa1a1c \ --hash=sha256:9d6ec91a587cc1495eaebd23130f7619afa5cdd34a277acb87735b4ad7c65156 - # via pyqt-toast-notification + # via + # pyqt-toast-notification + # qtawesome +six==1.17.0 \ + --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \ + --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81 + # via python-dateutil diff --git a/src/software/thunderscope/requirements_lock.txt b/src/software/thunderscope/requirements_lock.txt index 1b4d0632ee..e29b7a14a3 100644 --- a/src/software/thunderscope/requirements_lock.txt +++ b/src/software/thunderscope/requirements_lock.txt @@ -86,11 +86,62 @@ numpy==1.26.4 \ --hash=sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f # via # -r software/thunderscope/requirements.in + # pandas # pyqtgraph packaging==24.2 \ --hash=sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759 \ --hash=sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f # via qtpy +pandas==3.0.2 \ + --hash=sha256:01f31a546acd5574ef77fe199bc90b55527c225c20ccda6601cf6b0fd5ed597c \ + --hash=sha256:0555c5882688a39317179ab4a0ed41d3ebc8812ab14c69364bbee8fb7a3f6288 \ + --hash=sha256:07a10f5c36512eead51bc578eb3354ad17578b22c013d89a796ab5eee90cd991 \ + --hash=sha256:08504503f7101300107ecdc8df73658e4347586db5cfdadabc1592e9d7e7a0fd \ + --hash=sha256:0f48afd9bb13300ffb5a3316973324c787054ba6665cda0da3fbd67f451995db \ + --hash=sha256:140f0cffb1fa2524e874dde5b477d9defe10780d8e9e220d259b2c0874c89d9d \ + --hash=sha256:232a70ebb568c0c4d2db4584f338c1577d81e3af63292208d615907b698a0f18 \ + --hash=sha256:32cc41f310ebd4a296d93515fcac312216adfedb1894e879303987b8f1e2b97d \ + --hash=sha256:339dda302bd8369dedeae979cb750e484d549b563c3f54f3922cb8ff4978c5eb \ + --hash=sha256:4544c7a54920de8eeacaa1466a6b7268ecfbc9bc64ab4dbb89c6bbe94d5e0660 \ + --hash=sha256:4d888a5c678a419a5bb41a2a93818e8ed9fd3172246555c0b37b7cc27027effd \ + --hash=sha256:5371b72c2d4d415d08765f32d689217a43227484e81b2305b52076e328f6f482 \ + --hash=sha256:57a07209bebcbcf768d2d13c9b78b852f9a15978dac41b9e6421a81ad4cdd276 \ + --hash=sha256:5880314e69e763d4c8b27937090de570f1fb8d027059a7ada3f7f8e98bdcb677 \ + --hash=sha256:5d3cfe227c725b1f3dff4278b43d8c784656a42a9325b63af6b1492a8232209e \ + --hash=sha256:5fdbfa05931071aba28b408e59226186b01eb5e92bea2ab78b65863ca3228d84 \ + --hash=sha256:60a80bb4feacbef5e1447a3f82c33209c8b7e07f28d805cfd1fb951e5cb443aa \ + --hash=sha256:61c2fd96d72b983a9891b2598f286befd4ad262161a609c92dc1652544b46b76 \ + --hash=sha256:63d141b56ef686f7f0d714cfb8de4e320475b86bf4b620aa0b7da89af8cbdbbb \ + --hash=sha256:6c4d8458b97a35717b62469a4ea0e85abd5ed8687277f5ccfc67f8a5126f8c53 \ + --hash=sha256:710246ba0616e86891b58ab95f2495143bb2bc83ab6b06747c74216f583a6ac9 \ + --hash=sha256:734be7551687c00fbd760dc0522ed974f82ad230d4a10f54bf51b80d44a08702 \ + --hash=sha256:7cadd7e9a44ec13b621aec60f9150e744cfc7a3dd32924a7e2f45edff31823b0 \ + --hash=sha256:81526c4afd31971f8b62671442a4b2b51e0aa9acc3819c9f0f12a28b6fcf85f1 \ + --hash=sha256:970762605cff1ca0d3f71ed4f3a769ea8f85fc8e6348f6e110b8fea7e6eb5a14 \ + --hash=sha256:a3096110bf9eac0070b7208465f2740e2d8a670d5cb6530b5bb884eca495fd39 \ + --hash=sha256:a4785e1d6547d8427c5208b748ae2efb64659a21bd82bf440d4262d02bfa02a4 \ + --hash=sha256:a727a73cbdba2f7458dc82449e2315899d5140b449015d822f515749a46cbbe0 \ + --hash=sha256:ae37e833ff4fed0ba352f6bdd8b73ba3ab3256a85e54edfd1ab51ae40cca0af8 \ + --hash=sha256:aff4e6f4d722e0652707d7bcb190c445fe58428500c6d16005b02401764b1b3d \ + --hash=sha256:b35d14bb5d8285d9494fe93815a9e9307c0876e10f1e8e89ac5b88f728ec8dcf \ + --hash=sha256:b444dc64c079e84df91baa8bf613d58405645461cabca929d9178f2cd392398d \ + --hash=sha256:b5329e26898896f06035241a626d7c335daa479b9bbc82be7c2742d048e41172 \ + --hash=sha256:b5918ba197c951dec132b0c5929a00c0bf05d5942f590d3c10a807f6e15a57d3 \ + --hash=sha256:b75c347eff42497452116ce05ef461822d97ce5b9ff8df6edacb8076092c855d \ + --hash=sha256:c3b723df9087a9a9a840e263ebd9f88b64a12075d1bf2ea401a5a42f254f084d \ + --hash=sha256:c934008c733b8bbea273ea308b73b3156f0181e5b72960790b09c18a2794fe1e \ + --hash=sha256:d1478075142e83a5571782ad007fb201ed074bdeac7ebcc8890c71442e96adf7 \ + --hash=sha256:d606a041c89c0a474a4702d532ab7e73a14fe35c8d427b972a625c8e46373668 \ + --hash=sha256:db0dbfd2a6cdf3770aa60464d50333d8f3d9165b2f2671bcc299b72de5a6677b \ + --hash=sha256:dbbd4aa20ca51e63b53bbde6a0fa4254b1aaabb74d2f542df7a7959feb1d760c \ + --hash=sha256:dbc20dea3b9e27d0e66d74c42b2d0c1bed9c2ffe92adea33633e3bedeb5ac235 \ + --hash=sha256:deeca1b5a931fdf0c2212c8a659ade6d3b1edc21f0914ce71ef24456ca7a6535 \ + --hash=sha256:ed72cb3f45190874eb579c64fa92d9df74e98fd63e2be7f62bce5ace0ade61df \ + --hash=sha256:ef8b27695c3d3dc78403c9a7d5e59a62d5464a7e1123b4e0042763f7104dc74f \ + --hash=sha256:f12b1a9e332c01e09510586f8ca9b108fd631fd656af82e452d7315ef6df5f9f \ + --hash=sha256:f4753e73e34c8d83221ba58f232433fca2748be8b18dbca02d242ed153945043 \ + --hash=sha256:f8d68083e49e16b84734eb1a4dcae4259a75c90fb6e2251ab9a00b61120c06ab + # via -r software/thunderscope/requirements.in protobuf==6.31.1 \ --hash=sha256:0414e3aa5a5f3ff423828e1e6a6e907d6c65c1d5b7e6e975793d5590bdeecc16 \ --hash=sha256:426f59d2964864a1a366254fa703b8632dcec0790d8862d30034d8245e1cd447 \ @@ -123,6 +174,10 @@ pyqtgraph==0.13.7 \ --hash=sha256:64f84f1935c6996d0e09b1ee66fe478a7771e3ca6f3aaa05f00f6e068321d9e3 \ --hash=sha256:7754edbefb6c367fa0dfb176e2d0610da3ada20aa7a5318516c74af5fb72bf7a # via -r software/thunderscope/requirements.in +python-dateutil==2.9.0.post0 \ + --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ + --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 + # via pandas qtawesome==1.4.0 \ --hash=sha256:783e414d1317f3e978bf67ea8e8a1b1498bad9dbd305dec814027e3b50521be6 \ --hash=sha256:a4d689fa071c595aa6184171ce1f0f847677cb8d2db45382c43129f1d72a3d93 @@ -133,3 +188,7 @@ qtpy==2.4.2 \ # via # pyqt-toast-notification # qtawesome +six==1.17.0 \ + --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \ + --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81 + # via python-dateutil