avoid returning wrong axes_map if it wasn't determined correctly

This commit is contained in:
Félix Boisselier
2024-06-16 18:31:52 +02:00
parent 8b0862a96a
commit fb8e1ce98f
3 changed files with 109 additions and 77 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 501 KiB

After

Width:  |  Height:  |  Size: 490 KiB

View File

@@ -36,9 +36,9 @@ axes_map: -z,y,x
This plot shows the acceleration data over time for the X, Y, and Z axes after removing the gravity offset. Look for patterns in the acceleration data for each axis: you should have exactly 2 spikes for each subplot (for the start and stop of the motion) that break away from the global noise. This can help identify any anomalies or inconsistencies in your accelerometer behavior. This plot shows the acceleration data over time for the X, Y, and Z axes after removing the gravity offset. Look for patterns in the acceleration data for each axis: you should have exactly 2 spikes for each subplot (for the start and stop of the motion) that break away from the global noise. This can help identify any anomalies or inconsistencies in your accelerometer behavior.
The detected gravity offset is printed in the legend to give some context to the readings and their scale: if it's too far from the standard 9.8-10 m/s², this means that your accelerometer is not working properly and should be fixed or calibrated. The dynamic noise and background vibrations measured by the accelerometer are extracted from the signal (using wavelet transform decomposition) and printed in the legend. **Usually values below about 500mm/s² are ok**, but Shake&Tune will automatically add a note if too much noise is recorded. **Be careful because this value is very different from Klipper's `MEASURE_AXES_NOISE` command, as Shake&Tune measures everything during the motion**, such as accelerometer noise, but also vibrations and motor noise, axis and toolhead oscillations, etc. If you want to record your axes_map correctly, you may need to use about 10 times this value in the `ACCEL` parameter to get a good signal-to-noise ratio and allow Shake&Tune to correctly detect the toolhead acceleration and deceleration phases.
The average noise in the accelerometer measurement is calculated (using wavelet transform decomposition) and displayed at the top of the image. Usually values <500mm/s² are ok, but a note is automatically added by Shake&Tune in case your accelerometer has too much noise. The detected gravity offset is printed in the legend to give some context to the readings and their scale: if it's too far from the standard 9.8-10 m/s², this means that your accelerometer is not working properly and should be fixed or calibrated.
### Estimated 3D movement path ### Estimated 3D movement path

View File

@@ -7,7 +7,6 @@
# Description: Implements the axes map detection script for Shake&Tune, including # Description: Implements the axes map detection script for Shake&Tune, including
# calibration tools and graph creation for 3D printer vibration analysis. # calibration tools and graph creation for 3D printer vibration analysis.
import optparse import optparse
import os import os
from datetime import datetime from datetime import datetime
@@ -194,35 +193,39 @@ def linear_regression_direction(
def plot_compare_frequency( def plot_compare_frequency(
ax: plt.Axes, time: np.ndarray, accel_x: np.ndarray, accel_y: np.ndarray, accel_z: np.ndarray, offset: float, i: int ax: plt.Axes,
time_data: List[np.ndarray],
accel_data: List[Tuple[np.ndarray, np.ndarray, np.ndarray]],
offset: float,
noise_level: str,
) -> None: ) -> None:
# Plot acceleration data # Plot acceleration data
ax.plot( for i, (time, (accel_x, accel_y, accel_z)) in enumerate(zip(time_data, accel_data)):
time, ax.plot(
accel_x, time,
label='X' if i == 0 else '', accel_x,
color=KLIPPAIN_COLORS['purple'], label='X' if i == 0 else '',
linewidth=0.5, color=KLIPPAIN_COLORS['purple'],
zorder=50 if i == 0 else 10, linewidth=0.5,
) zorder=50 if i == 0 else 10,
ax.plot( )
time, ax.plot(
accel_y, time,
label='Y' if i == 0 else '', accel_y,
color=KLIPPAIN_COLORS['orange'], label='Y' if i == 0 else '',
linewidth=0.5, color=KLIPPAIN_COLORS['orange'],
zorder=50 if i == 1 else 10, linewidth=0.5,
) zorder=50 if i == 1 else 10,
ax.plot( )
time, ax.plot(
accel_z, time,
label='Z' if i == 0 else '', accel_z,
color=KLIPPAIN_COLORS['red_pink'], label='Z' if i == 0 else '',
linewidth=0.5, color=KLIPPAIN_COLORS['red_pink'],
zorder=50 if i == 2 else 10, linewidth=0.5,
) zorder=50 if i == 2 else 10,
)
# Setting axis parameters, grid and graph title
ax.set_xlabel('Time (s)') ax.set_xlabel('Time (s)')
ax.set_ylabel('Acceleration (mm/s²)') ax.set_ylabel('Acceleration (mm/s²)')
@@ -242,53 +245,52 @@ def plot_compare_frequency(
ax.legend(loc='upper left', prop=fontP) ax.legend(loc='upper left', prop=fontP)
# Add gravity offset to the graph # Add the gravity and noise level to the graph legend
if i == 0: ax2 = ax.twinx()
ax2 = ax.twinx() # To split the legends in two box ax2.yaxis.set_visible(False)
ax2.yaxis.set_visible(False) ax2.plot([], [], ' ', label=noise_level)
ax2.plot([], [], ' ', label=f'Measured gravity: {offset / 1000:0.3f} m/s²') ax2.plot([], [], ' ', label=f'Measured gravity: {offset / 1000:0.3f} m/s²')
ax2.legend(loc='upper right', prop=fontP) ax2.legend(loc='upper right', prop=fontP)
def plot_3d_path( def plot_3d_path(
ax: plt.Axes, ax: plt.Axes,
i: int, position_data: List[Tuple[np.ndarray, np.ndarray, np.ndarray]],
position_x: np.ndarray, direction_vectors: List[np.ndarray],
position_y: np.ndarray, angle_errors: List[float],
position_z: np.ndarray,
average_direction_vector: np.ndarray,
angle_error: float,
) -> None: ) -> None:
ax.plot(position_x, position_y, position_z, color=KLIPPAIN_COLORS['orange'], linestyle=':', linewidth=2) # Plot the 3D path of the movement
ax.scatter(position_x[0], position_y[0], position_z[0], color=KLIPPAIN_COLORS['red_pink'], zorder=10) for i, ((position_x, position_y, position_z), average_direction_vector, angle_error) in enumerate(
ax.text( zip(position_data, direction_vectors, angle_errors)
position_x[0] + 1, ):
position_y[0], ax.plot(position_x, position_y, position_z, color=KLIPPAIN_COLORS['orange'], linestyle=':', linewidth=2)
position_z[0], ax.scatter(position_x[0], position_y[0], position_z[0], color=KLIPPAIN_COLORS['red_pink'], zorder=10)
str(i + 1), ax.text(
color='black', position_x[0] + 1,
fontsize=16, position_y[0],
fontweight='bold', position_z[0],
zorder=20, str(i + 1),
) color='black',
fontsize=16,
fontweight='bold',
zorder=20,
)
# Plot the average direction vector # Plot the average direction vector
start_position = np.array([position_x[0], position_y[0], position_z[0]]) start_position = np.array([position_x[0], position_y[0], position_z[0]])
end_position = start_position + average_direction_vector * np.linalg.norm( end_position = start_position + average_direction_vector * np.linalg.norm(
[position_x[-1] - position_x[0], position_y[-1] - position_y[0], position_z[-1] - position_z[0]] [position_x[-1] - position_x[0], position_y[-1] - position_y[0], position_z[-1] - position_z[0]]
) )
axes = ['X', 'Y', 'Z'] ax.plot(
ax.plot( [start_position[0], end_position[0]],
[start_position[0], end_position[0]], [start_position[1], end_position[1]],
[start_position[1], end_position[1]], [start_position[2], end_position[2]],
[start_position[2], end_position[2]], label=f'{["X", "Y", "Z"][i]} angle: {angle_error:0.2f}°',
label=f'{axes[i]} angle: {angle_error:0.2f}°', color=KLIPPAIN_COLORS['purple'],
color=KLIPPAIN_COLORS['purple'], linestyle='-',
linestyle='-', linewidth=2,
linewidth=2, )
)
# Setting axis parameters, grid and graph title
ax.set_xlabel('X Position (mm)') ax.set_xlabel('X Position (mm)')
ax.set_ylabel('Y Position (mm)') ax.set_ylabel('Y Position (mm)')
ax.set_zlabel('Z Position (mm)') ax.set_zlabel('Z Position (mm)')
@@ -311,14 +313,24 @@ def plot_3d_path(
def format_direction_vector(vectors: List[np.ndarray]) -> str: def format_direction_vector(vectors: List[np.ndarray]) -> str:
formatted_vector = [] formatted_vector = []
axes_count = {'x': 0, 'y': 0, 'z': 0}
for vector in vectors: for vector in vectors:
for i in range(len(vector)): for i in range(len(vector)):
if vector[i] > 0: if vector[i] > 0:
formatted_vector.append(MACHINE_AXES[i]) formatted_vector.append(MACHINE_AXES[i])
axes_count[MACHINE_AXES[i]] += 1
break break
elif vector[i] < 0: elif vector[i] < 0:
formatted_vector.append(f'-{MACHINE_AXES[i]}') formatted_vector.append(f'-{MACHINE_AXES[i]}')
axes_count[MACHINE_AXES[i]] += 1
break break
# Check if all axes are present in the axes_map and return an error message if not
for _, count in axes_count.items():
if count != 1:
return 'unable to determine it correctly!'
return ', '.join(formatted_vector) return ', '.join(formatted_vector)
@@ -360,8 +372,12 @@ def axesmap_calibration(
cumulative_start_position = np.array([0, 0, 0]) cumulative_start_position = np.array([0, 0, 0])
direction_vectors = [] direction_vectors = []
angle_errors = []
total_noise_intensity = 0.0 total_noise_intensity = 0.0
for i, machine_axis in enumerate(MACHINE_AXES): acceleration_data = []
position_data = []
gravities = []
for _, machine_axis in enumerate(MACHINE_AXES):
if machine_axis not in raw_datas: if machine_axis not in raw_datas:
raise ValueError(f'Missing CSV file for axis {machine_axis}') raise ValueError(f'Missing CSV file for axis {machine_axis}')
@@ -388,15 +404,19 @@ def axesmap_calibration(
f'Machine axis {machine_axis.upper()} -> nearest accelerometer direction vector: {direction_vector} (angle error: {angle_error:.2f}°)' f'Machine axis {machine_axis.upper()} -> nearest accelerometer direction vector: {direction_vector} (angle error: {angle_error:.2f}°)'
) )
direction_vectors.append(direction_vector) direction_vectors.append(direction_vector)
angle_errors.append(angle_error)
total_noise_intensity += noise_intensity total_noise_intensity += noise_intensity
plot_compare_frequency(ax1, time, accel_x, accel_y, accel_z, gravity, i) acceleration_data.append((time, (accel_x, accel_y, accel_z)))
plot_3d_path(ax2, i, position_x, position_y, position_z, average_direction_vector, angle_error) position_data.append((position_x, position_y, position_z))
gravities.append(gravity)
# Update the cumulative start position for the next segment # Update the cumulative start position for the next segment
cumulative_start_position = np.array([position_x[-1], position_y[-1], position_z[-1]]) cumulative_start_position = np.array([position_x[-1], position_y[-1], position_z[-1]])
gravity = np.mean(gravities)
average_noise_intensity = total_noise_intensity / len(raw_datas) average_noise_intensity = total_noise_intensity / len(raw_datas)
if average_noise_intensity <= 350: if average_noise_intensity <= 350:
average_noise_intensity_text = '-> OK' average_noise_intensity_text = '-> OK'
@@ -405,11 +425,25 @@ def axesmap_calibration(
else: else:
average_noise_intensity_text = '-> ERROR: accelerometer noise is too high!' average_noise_intensity_text = '-> ERROR: accelerometer noise is too high!'
average_noise_intensity_label = (
f'Dynamic noise level: {average_noise_intensity:.2f} mm/s² {average_noise_intensity_text}'
)
ConsoleOutput.print(average_noise_intensity_label)
ConsoleOutput.print(f'--> Detected gravity: {gravity / 1000 :.2f} m/s²')
formatted_direction_vector = format_direction_vector(direction_vectors) formatted_direction_vector = format_direction_vector(direction_vectors)
ConsoleOutput.print(f'--> Detected axes_map: {formatted_direction_vector}') ConsoleOutput.print(f'--> Detected axes_map: {formatted_direction_vector}')
ConsoleOutput.print(
f'Average accelerometer noise level: {average_noise_intensity:.2f} mm/s² {average_noise_intensity_text}' # Plot the differents graphs
plot_compare_frequency(
ax1,
[d[0] for d in acceleration_data],
[d[1] for d in acceleration_data],
gravity,
average_noise_intensity_label,
) )
plot_3d_path(ax2, position_data, direction_vectors, angle_errors)
# Add title # Add title
title_line1 = 'AXES MAP CALIBRATION TOOL' title_line1 = 'AXES MAP CALIBRATION TOOL'
@@ -430,9 +464,7 @@ def axesmap_calibration(
fig.text(0.060, 0.939, title_line2, ha='left', va='top', fontsize=16, color=KLIPPAIN_COLORS['dark_purple']) fig.text(0.060, 0.939, title_line2, ha='left', va='top', fontsize=16, color=KLIPPAIN_COLORS['dark_purple'])
title_line3 = f'| Detected axes_map: {formatted_direction_vector}' title_line3 = f'| Detected axes_map: {formatted_direction_vector}'
title_line4 = f'| Accelerometer noise level: {average_noise_intensity:.2f} mm/s² {average_noise_intensity_text}' fig.text(0.50, 0.985, title_line3, ha='left', va='top', fontsize=16, color=KLIPPAIN_COLORS['dark_purple'])
fig.text(0.50, 0.985, title_line3, ha='left', va='top', fontsize=14, color=KLIPPAIN_COLORS['dark_purple'])
fig.text(0.50, 0.950, title_line4, ha='left', va='top', fontsize=11, color=KLIPPAIN_COLORS['dark_purple'])
# Adding a small Klippain logo to the top left corner of the figure # Adding a small Klippain logo to the top left corner of the figure
ax_logo = fig.add_axes([0.001, 0.894, 0.105, 0.105], anchor='NW') ax_logo = fig.add_axes([0.001, 0.894, 0.105, 0.105], anchor='NW')