mirror of
https://github.com/Klipper3d/klipper.git
synced 2025-12-24 00:28:34 -07:00
probe_eddy_current: filter noisy calibration points
A misplaced sensor or a misconfigured one can return unreliable results. Assist with this by refusing to use the too noisy points. Filter noisy points by the frequency difference to noise ratio. Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
This commit is contained in:
parent
dc622f4ac3
commit
5fb59dbb26
1 changed files with 71 additions and 16 deletions
|
|
@ -134,16 +134,68 @@ class EddyCalibration:
|
|||
raise self.printer.command_error(
|
||||
"Failed calibration - incomplete sensor data")
|
||||
return cal
|
||||
|
||||
def _median(self, values):
|
||||
values = sorted(values)
|
||||
n = len(values)
|
||||
if n % 2 == 0:
|
||||
return (values[n//2 - 1] + values[n//2]) / 2.0
|
||||
return values[n // 2]
|
||||
def calc_freqs(self, meas):
|
||||
total_count = total_variance = 0
|
||||
positions = {}
|
||||
for pos, freqs in meas.items():
|
||||
count = len(freqs)
|
||||
freq_avg = float(sum(freqs)) / count
|
||||
positions[pos] = freq_avg
|
||||
total_count += count
|
||||
total_variance += sum([(f - freq_avg)**2 for f in freqs])
|
||||
return positions, math.sqrt(total_variance / total_count), total_count
|
||||
mads = [abs(f - freq_avg) for f in freqs]
|
||||
mad = self._median(mads)
|
||||
positions[pos] = (freq_avg, mad, count)
|
||||
return positions
|
||||
def validate_calibration_data(self, positions):
|
||||
last_freq = 40000000.
|
||||
last_pos = last_mad = .0
|
||||
gcode = self.printer.lookup_object("gcode")
|
||||
filtered = []
|
||||
mad_hz_total = .0
|
||||
mad_mm_total = .0
|
||||
samples_count = 0
|
||||
for pos, (freq_avg, mad_hz, count) in sorted(positions.items()):
|
||||
if freq_avg > last_freq:
|
||||
gcode.respond_info(
|
||||
"Frequency stops decreasing at step %.3f" % (pos))
|
||||
break
|
||||
diff_mad = math.sqrt(last_mad**2 + mad_hz**2)
|
||||
# Calculate if samples have a significant difference
|
||||
freq_diff = last_freq - freq_avg
|
||||
last_freq = freq_avg
|
||||
if freq_diff < 2.5 * diff_mad:
|
||||
gcode.respond_info(
|
||||
"Frequency too noisy at step %.3f -> %.3f" % (
|
||||
last_pos, pos))
|
||||
gcode.respond_info(
|
||||
"Frequency diff: %.3f, MAD_Hz: %.3f -> MAD_Hz: %.3f" % (
|
||||
freq_diff, last_mad, mad_hz
|
||||
))
|
||||
break
|
||||
last_mad = mad_hz
|
||||
delta_dist = pos - last_pos
|
||||
last_pos = pos
|
||||
# MAD is Median Absolute Deviation to Frequency avg ~ delta_hz_1
|
||||
# Signal is delta_hz_2 / delta_dist
|
||||
# SNR ~= delta_hz_1 / (delta_hz_2 / delta_mm) = d_1 * d_mm / d_2
|
||||
mad_mm = mad_hz * delta_dist / freq_diff
|
||||
filtered.append((pos, freq_avg, mad_hz, mad_mm))
|
||||
mad_hz_total += mad_hz
|
||||
mad_mm_total += mad_mm
|
||||
samples_count += count
|
||||
avg_mad = mad_hz_total / len(filtered)
|
||||
avg_mad_mm = mad_mm_total / len(filtered)
|
||||
gcode.respond_info(
|
||||
"probe_eddy_current: noise %.6fmm, MAD_Hz=%.3f in %d queries\n" % (
|
||||
avg_mad_mm, avg_mad, samples_count))
|
||||
freq_list = [freq for _, freq, _, _ in filtered]
|
||||
freq_diff = max(freq_list) - min(freq_list)
|
||||
gcode.respond_info("Total frequency range: %.3f Hz\n" % (freq_diff))
|
||||
return filtered
|
||||
def post_manual_probe(self, kin_pos):
|
||||
if kin_pos is None:
|
||||
# Manual Probe was aborted
|
||||
|
|
@ -166,24 +218,27 @@ class EddyCalibration:
|
|||
# Perform calibration movement and capture
|
||||
cal = self.do_calibration_moves(self.probe_speed)
|
||||
# Calculate each sample position average and variance
|
||||
positions, std, total = self.calc_freqs(cal)
|
||||
last_freq = 0.
|
||||
for pos, freq in reversed(sorted(positions.items())):
|
||||
if freq <= last_freq:
|
||||
raise self.printer.command_error(
|
||||
"Failed calibration - frequency not increasing each step")
|
||||
last_freq = freq
|
||||
_positions = self.calc_freqs(cal)
|
||||
# Fix Z position offset
|
||||
positions = {}
|
||||
for k in _positions:
|
||||
v = _positions[k]
|
||||
k = k - probe_calibrate_z
|
||||
positions[k] = v
|
||||
filtered = self.validate_calibration_data(positions)
|
||||
if len(filtered) <= 8:
|
||||
raise self.printer.command_error(
|
||||
"Failed calibration - No usable data")
|
||||
gcode = self.printer.lookup_object("gcode")
|
||||
gcode.respond_info(
|
||||
"probe_eddy_current: stddev=%.3f in %d queries\n"
|
||||
"The SAVE_CONFIG command will update the printer config file\n"
|
||||
"and restart the printer." % (std, total))
|
||||
"and restart the printer.")
|
||||
# Save results
|
||||
cal_contents = []
|
||||
for i, (pos, freq) in enumerate(sorted(positions.items())):
|
||||
for i, (pos, freq, _, _) in enumerate(filtered):
|
||||
if not i % 3:
|
||||
cal_contents.append('\n')
|
||||
cal_contents.append("%.6f:%.3f" % (pos - probe_calibrate_z, freq))
|
||||
cal_contents.append("%.6f:%.3f" % (pos, freq))
|
||||
cal_contents.append(',')
|
||||
cal_contents.pop()
|
||||
configfile = self.printer.lookup_object('configfile')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue