aboutsummaryrefslogtreecommitdiffstats
path: root/scripts/graph_mesh.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/graph_mesh.py')
-rwxr-xr-xscripts/graph_mesh.py133
1 files changed, 83 insertions, 50 deletions
diff --git a/scripts/graph_mesh.py b/scripts/graph_mesh.py
index 3a331e5d..a62f9df2 100755
--- a/scripts/graph_mesh.py
+++ b/scripts/graph_mesh.py
@@ -20,14 +20,14 @@ import matplotlib.cm as cm
import matplotlib.pyplot as plt
import matplotlib.animation as ani
-MESH_DUMP_REQUEST = json.dumps(
- {"id": 1, "method": "bed_mesh/dump_mesh"}
-)
+MESH_DUMP_REQUEST = json.dumps({"id": 1, "method": "bed_mesh/dump_mesh"})
+
def sock_error_exit(msg):
sys.stderr.write(msg + "\n")
sys.exit(-1)
+
def webhook_socket_create(uds_filename):
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
while 1:
@@ -45,6 +45,7 @@ def webhook_socket_create(uds_filename):
print("Connected")
return sock
+
def process_message(msg):
try:
resp = json.loads(msg)
@@ -54,16 +55,14 @@ def process_message(msg):
return None
if "error" in resp:
err = resp["error"].get("message", "Unknown")
- sock_error_exit(
- "Error requesting mesh dump: %s" % (err,)
- )
+ sock_error_exit("Error requesting mesh dump: %s" % (err,))
return resp["result"]
def request_from_unixsocket(unix_sock_name):
print("Connecting to Unix Socket File '%s'" % (unix_sock_name,))
whsock = webhook_socket_create(unix_sock_name)
- whsock.settimeout(1.)
+ whsock.settimeout(1.0)
# send mesh query
whsock.send(MESH_DUMP_REQUEST.encode() + b"\x03")
sock_data = b""
@@ -84,11 +83,12 @@ def request_from_unixsocket(unix_sock_name):
result = process_message(msg)
if result is not None:
return result
- time.sleep(.1)
+ time.sleep(0.1)
finally:
whsock.close()
sock_error_exit("Mesh dump request timed out")
+
def request_from_websocket(url):
print("Connecting to websocket url '%s'" % (url,))
try:
@@ -101,15 +101,16 @@ def request_from_websocket(url):
end_time = time.monotonic() + 20.0
while time.monotonic() < end_time:
try:
- msg = websocket.recv(10.)
+ msg = websocket.recv(10.0)
except TimeoutError:
continue
result = process_message(msg)
if result is not None:
return result
- time.sleep(.1)
+ time.sleep(0.1)
sock_error_exit("Mesh dump request timed out")
+
def request_mesh_data(input_name):
url_match = re.match(r"((?:https?)|(?:wss?))://(.+)", input_name.lower())
if url_match is None:
@@ -129,16 +130,21 @@ def request_mesh_data(input_name):
url = "%s://%s/klippysocket" % (scheme, host)
return request_from_websocket(url)
+
class PathAnimation:
instance = None
+
def __init__(self, artist, x_travel, y_travel):
self.travel_artist = artist
self.x_travel = x_travel
self.y_travel = y_travel
fig = plt.gcf()
self.animation = ani.FuncAnimation(
- fig=fig, func=self.update, frames=self.gen_path_position(),
- cache_frame_data=False, interval=60
+ fig=fig,
+ func=self.update,
+ frames=self.gen_path_position(),
+ cache_frame_data=False,
+ interval=60,
)
PathAnimation.instance = self
@@ -164,6 +170,7 @@ def _gen_mesh_coords(min_c, max_c, count):
dist = (max_c - min_c) / (count - 1)
return [min_c + i * dist for i in range(count)]
+
def _plot_path(travel_path, probed, diff, cmd_args):
x_travel, y_travel = np.array(travel_path).transpose()
x_probed, y_probed = np.array(probed).transpose()
@@ -183,6 +190,7 @@ def _plot_path(travel_path, probed, diff, cmd_args):
if cmd_args.animate and cmd_args.output is None:
PathAnimation(travel_line, x_travel, y_travel)
+
def _format_mesh_data(matrix, params):
min_pt = (params["min_x"], params["min_y"])
max_pt = (params["max_x"], params["max_y"])
@@ -192,6 +200,7 @@ def _format_mesh_data(matrix, params):
z = np.array(matrix)
return x, y, z
+
def _set_xy_limits(mesh_data, cmd_args):
if not cmd_args.scale_plot:
return
@@ -201,12 +210,14 @@ def _set_xy_limits(mesh_data, cmd_args):
ax.set_xlim((axis_min[0], axis_max[0]))
ax.set_ylim((axis_min[1], axis_max[1]))
+
def _plot_mesh(ax, matrix, params, cmap=cm.viridis, label=None):
x, y, z = _format_mesh_data(matrix, params)
surface = ax.plot_surface(x, y, z, cmap=cmap, label=label)
scale = max(abs(z.min()), abs(z.max())) * 3
return surface, scale
+
def plot_probe_points(mesh_data, cmd_args):
"""Plot original generated points"""
calibration = mesh_data["calibration"]
@@ -217,6 +228,7 @@ def plot_probe_points(mesh_data, cmd_args):
plt.plot(x, y, "b.")
_set_xy_limits(mesh_data, cmd_args)
+
def plot_probe_path(mesh_data, cmd_args):
"""Plot probe travel path"""
calibration = mesh_data["calibration"]
@@ -227,6 +239,7 @@ def plot_probe_path(mesh_data, cmd_args):
_plot_path(path_pts, path_pts[1:-1], diff, cmd_args)
_set_xy_limits(mesh_data, cmd_args)
+
def plot_rapid_path(mesh_data, cmd_args):
"""Plot rapid scan travel path"""
calibration = mesh_data["calibration"]
@@ -239,6 +252,7 @@ def plot_rapid_path(mesh_data, cmd_args):
_plot_path(rapid_path, probed, diff, cmd_args)
_set_xy_limits(mesh_data, cmd_args)
+
def plot_probed_matrix(mesh_data, cmd_args):
"""Plot probed Z values"""
ax = plt.subplot(projection="3d")
@@ -259,9 +273,10 @@ def plot_probed_matrix(mesh_data, cmd_args):
surface, scale = _plot_mesh(ax, matrix, params)
ax.set_title("Probed Mesh (%s)" % (name,))
ax.set(zlim=(-scale, scale))
- plt.gcf().colorbar(surface, shrink=.75)
+ plt.gcf().colorbar(surface, shrink=0.75)
_set_xy_limits(mesh_data, cmd_args)
+
def plot_mesh_matrix(mesh_data, cmd_args):
"""Plot mesh Z values"""
ax = plt.subplot(projection="3d")
@@ -274,9 +289,10 @@ def plot_mesh_matrix(mesh_data, cmd_args):
name = req_mesh["name"]
ax.set_title("Interpolated Mesh (%s)" % (name,))
ax.set(zlim=(-scale, scale))
- plt.gcf().colorbar(surface, shrink=.75)
+ plt.gcf().colorbar(surface, shrink=0.75)
_set_xy_limits(mesh_data, cmd_args)
+
def plot_overlay(mesh_data, cmd_args):
"""Plots the current probed mesh overlaid with a profile"""
ax = plt.subplot(projection="3d")
@@ -301,10 +317,11 @@ def plot_overlay(mesh_data, cmd_args):
ax.set_title("Probed Mesh Overlay")
scale = max(cur_scale, prof_scale)
ax.set(zlim=(-scale, scale))
- ax.legend(loc='best')
- plt.gcf().colorbar(prof_surf, shrink=.75)
+ ax.legend(loc="best")
+ plt.gcf().colorbar(prof_surf, shrink=0.75)
_set_xy_limits(mesh_data, cmd_args)
+
def plot_delta(mesh_data, cmd_args):
"""Plots the delta between current probed mesh and a profile"""
ax = plt.subplot(projection="3d")
@@ -327,9 +344,7 @@ def plot_delta(mesh_data, cmd_args):
pfields = ("x_count", "y_count", "min_x", "max_x", "min_y", "max_y")
for field in pfields:
if abs(prof_params[field] - cur_params[field]) >= 1e-6:
- raise Exception(
- "Values for field %s do not match, cant plot deviation"
- )
+ raise Exception("Values for field %s do not match, cant plot deviation")
delta = np.array(cur_matrix) - np.array(prof_matix)
surface, scale = _plot_mesh(ax, delta, cur_params)
ax.set(zlim=(-scale, scale))
@@ -347,12 +362,12 @@ PLOT_TYPES = {
"delta": plot_delta,
}
+
def print_types(cmd_args):
- typelist = [
- "%-10s%s" % (name, func.__doc__) for name, func in PLOT_TYPES.items()
- ]
+ typelist = ["%-10s%s" % (name, func.__doc__) for name, func in PLOT_TYPES.items()]
print("\n".join(typelist))
+
def plot_mesh_data(cmd_args):
mesh_data = request_mesh_data(cmd_args.input)
if cmd_args.output is not None:
@@ -368,6 +383,7 @@ def plot_mesh_data(cmd_args):
else:
fig.savefig(cmd_args.output)
+
def _check_path_unique(name, path):
path = np.array(path)
unique_pts, counts = np.unique(path, return_counts=True, axis=0)
@@ -380,6 +396,7 @@ def _check_path_unique(name, path):
% (name, coord)
)
+
def _analyze_mesh(name, mesh_axes):
print("\nAnalyzing Probed Mesh %s..." % (name,))
x, y, z = mesh_axes
@@ -389,8 +406,8 @@ def _analyze_mesh(name, mesh_axes):
print(
" Min Coord (%.2f, %.2f), Max Coord (%.2f, %.2f), "
- "Probe Count: (%d, %d)" %
- (x.min(), y.min(), x.max(), y.max(), len(z), len(z[0]))
+ "Probe Count: (%d, %d)"
+ % (x.min(), y.min(), x.max(), y.max(), len(z), len(z[0]))
)
print(
" Mesh range: min %.4f (%.2f, %.2f), max %.4f (%.2f, %.2f)"
@@ -398,6 +415,7 @@ def _analyze_mesh(name, mesh_axes):
)
print(" Mean: %.4f, Standard Deviation: %.4f" % (z.mean(), z.std()))
+
def _compare_mesh(name_a, name_b, mesh_a, mesh_b):
ax, ay, az = mesh_a
bx, by, bz = mesh_b
@@ -414,10 +432,21 @@ def _compare_mesh(name_a, name_b, mesh_a, mesh_b):
" Range: min %.4f (%.2f, %.2f), max %.4f (%.2f, %.2f)\n"
" Mean: %.6f, Standard Deviation: %.6f\n"
" Absolute Max: %.6f, Absolute Mean: %.6f"
- % (delta.min(), min_x, min_y, delta.max(), max_x, max_y,
- delta.mean(), delta.std(), abs_max, abs_mean)
+ % (
+ delta.min(),
+ min_x,
+ min_y,
+ delta.max(),
+ max_x,
+ max_y,
+ delta.mean(),
+ delta.std(),
+ abs_max,
+ abs_mean,
+ )
)
+
def analyze(cmd_args):
mesh_data = request_mesh_data(cmd_args.input)
print("Analyzing Travel Path...")
@@ -461,6 +490,7 @@ def analyze(cmd_args):
for prof_name, prof_axes in formatted_data.items():
_compare_mesh(name, prof_name, current_axes, prof_axes)
+
def dump_request(cmd_args):
mesh_data = request_mesh_data(cmd_args.input)
outfile = cmd_args.output
@@ -472,57 +502,60 @@ def dump_request(cmd_args):
with open(outfile, "w") as f:
f.write(json.dumps(mesh_data))
+
def main():
parser = argparse.ArgumentParser(description="Graph Bed Mesh Data")
sub_parsers = parser.add_subparsers()
- list_parser = sub_parsers.add_parser(
- "list", help="List available plot types"
- )
+ list_parser = sub_parsers.add_parser("list", help="List available plot types")
list_parser.set_defaults(func=print_types)
plot_parser = sub_parsers.add_parser("plot", help="Plot a specified type")
analyze_parser = sub_parsers.add_parser(
"analyze", help="Perform analysis on mesh data"
)
- dump_parser = sub_parsers.add_parser(
- "dump", help="Dump API response to json file"
- )
+ dump_parser = sub_parsers.add_parser("dump", help="Dump API response to json file")
plot_parser.add_argument(
- "-a", "--animate", action="store_true",
- help="Animate paths in live preview"
+ "-a", "--animate", action="store_true", help="Animate paths in live preview"
)
plot_parser.add_argument(
- "-s", "--scale-plot", action="store_true",
- help="Use axis limits reported by Klipper to scale plot X/Y"
+ "-s",
+ "--scale-plot",
+ action="store_true",
+ help="Use axis limits reported by Klipper to scale plot X/Y",
)
plot_parser.add_argument(
- "-p", "--profile-name", type=str, default=None,
- help="Optional name of a profile to plot for 'probedz'"
+ "-p",
+ "--profile-name",
+ type=str,
+ default=None,
+ help="Optional name of a profile to plot for 'probedz'",
)
plot_parser.add_argument(
- "-o", "--output", type=str, default=None,
- help="Output file path"
+ "-o", "--output", type=str, default=None, help="Output file path"
)
plot_parser.add_argument(
- "type", metavar="<plot type>", type=str, choices=PLOT_TYPES.keys(),
- help="Type of data to graph"
+ "type",
+ metavar="<plot type>",
+ type=str,
+ choices=PLOT_TYPES.keys(),
+ help="Type of data to graph",
)
plot_parser.add_argument(
- "input", metavar="<input>",
- help="Path/url to Klipper Socket or path to json file"
+ "input",
+ metavar="<input>",
+ help="Path/url to Klipper Socket or path to json file",
)
plot_parser.set_defaults(func=plot_mesh_data)
analyze_parser.add_argument(
- "input", metavar="<input>",
- help="Path/url to Klipper Socket or path to json file"
+ "input",
+ metavar="<input>",
+ help="Path/url to Klipper Socket or path to json file",
)
analyze_parser.set_defaults(func=analyze)
dump_parser.add_argument(
- "-o", "--output", type=str, default=None,
- help="Json output file path"
+ "-o", "--output", type=str, default=None, help="Json output file path"
)
dump_parser.add_argument(
- "input", metavar="<input>",
- help="Path or url to Klipper Socket"
+ "input", metavar="<input>", help="Path or url to Klipper Socket"
)
dump_parser.set_defaults(func=dump_request)
cmd_args = parser.parse_args()