Skip to content

Commit

Permalink
probe: Added truncated mean averaging capability
Browse files Browse the repository at this point in the history
Added a new 'samples_trunc_count' configuration attribute to the
'[probe]' section that will cause the 'samples_trunc_count' number of
highest and lowest samples to be removed and then the average of the
remaining samples is returned.

Signed-off-by: Nick Weedon <[email protected]>
  • Loading branch information
nickweedon committed Jan 4, 2025
1 parent 8a3d2af commit 4b78010
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 5 deletions.
14 changes: 14 additions & 0 deletions docs/Config_Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -1929,6 +1929,17 @@ z_offset:
# not obtained in the given number of retries then an error is
# reported. The default is zero which causes an error to be reported
# on the first sample that exceeds samples_tolerance.
#samples_trunc_count: 0
# Setting this to a non-zero value effectively enables a 'truncated mean' algorithm
# which removes 'samples_trunc_count' number of highest and lowest probe values
# before taking an average. The number which are removed is specified by this configruation value.
# This averaging option gives the best of both worlds between the median and the mean (average)
# since it is resistant to outliers but still averages the remaining results.
# This option is useful when you are taking 5 or more samples (using a
# samples_trunc_count of 2 for example).
# Note that if this option is configured such that there is less than 3 samples remaining
# after removing the truncated samples (i.e. samples_trunc_count) then this is considered a
# configuration error since this is the same as simply taking a median.
#activate_gcode:
# A list of G-Code commands to execute prior to each probe attempt.
# See docs/Command_Templates.md for G-Code format. This may be
Expand Down Expand Up @@ -1997,6 +2008,7 @@ control_pin:
#samples_result:
#samples_tolerance:
#samples_tolerance_retries:
#samples_trunc_count:
# See the "probe" section for information on these parameters.
```

Expand Down Expand Up @@ -2048,6 +2060,7 @@ z_offset:
#samples_result:
#samples_tolerance:
#samples_tolerance_retries:
#samples_trunc_count:
#activate_gcode:
#deactivate_gcode:
#deactivate_on_each_sample:
Expand Down Expand Up @@ -2088,6 +2101,7 @@ sensor_type: ldc1612
#samples_result:
#samples_tolerance:
#samples_tolerance_retries:
#samples_trunc_count:
# See the "probe" section for information on these parameters.
```

Expand Down
2 changes: 1 addition & 1 deletion docs/G-Codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -999,7 +999,7 @@ see the [probe calibrate guide](Probe_Calibrate.md)).
#### PROBE
`PROBE [PROBE_SPEED=<mm/s>] [LIFT_SPEED=<mm/s>] [SAMPLES=<count>]
[SAMPLE_RETRACT_DIST=<mm>] [SAMPLES_TOLERANCE=<mm>]
[SAMPLES_TOLERANCE_RETRIES=<count>] [SAMPLES_RESULT=median|average]`:
[SAMPLES_TOLERANCE_RETRIES=<count>] [SAMPLES_TRUNC_COUNT=<count>] [SAMPLES_RESULT=median|average]`:
Move the nozzle downwards until the probe triggers. If any of the
optional parameters are provided they override their equivalent
setting in the [probe config section](Config_Reference.md#probe).
Expand Down
48 changes: 44 additions & 4 deletions klippy/extras/probe.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging
import math
import pins
from . import manual_probe

Expand All @@ -14,12 +15,23 @@
"""

# Calculate the average Z from a set of positions
def calc_probe_z_average(positions, method='average'):
def calc_probe_z_average(positions, method='average', trunc_count=0):

if method != 'median':
# Use mean average
count = float(len(positions))
return [sum([pos[i] for pos in positions]) / count
count = len(positions)

# Perform truncated mean if required
if trunc_count > 0:
z_sorted = sorted(positions, key=(lambda p: p[2]))
start_index = trunc_count // 2
end_index = count - math.ceil(trunc_count)
return calc_probe_z_average(
z_sorted[start_index:end_index], 'average', 0)

return [sum([pos[i] for pos in positions]) / float(count)
for i in range(3)]

# Use median
z_sorted = sorted(positions, key=(lambda p: p[2]))
middle = len(positions) // 2
Expand Down Expand Up @@ -259,6 +271,19 @@ def __init__(self, config, mcu_probe):
minval=0.)
self.samples_retries = config.getint('samples_tolerance_retries', 0,
minval=0)
self.samples_trunc_count = config.getint('samples_trunc_count', 0,
minval=0)

if self.samples_trunc_count > 0:
if self.samples_result != 'average':
raise config.error("samples_trunc_count is only "
"valid with samples_result=average")

if not self.sample_count - self.samples_trunc_count > 2:
raise config.error(
"samples_trunc_count must be either zero or "
"such that: sample_count - samples_trunc_count > 2")

# Session state
self.multi_probe_pending = False
self.results = []
Expand Down Expand Up @@ -299,13 +324,27 @@ def get_probe_params(self, gcmd=None):
self.samples_tolerance, minval=0.)
samples_retries = gcmd.get_int("SAMPLES_TOLERANCE_RETRIES",
self.samples_retries, minval=0)
samples_trunc_count = gcmd.get_int("SAMPLES_TRUNC_COUNT",
self.samples_trunc_count, minval=0)
samples_result = gcmd.get("SAMPLES_RESULT", self.samples_result)

if samples_trunc_count > 0:
if samples_result != 'average':
raise gcmd.error("samples_trunc_count is only "
"valid with samples_result=average")

if not samples - samples_trunc_count > 2:
raise gcmd.error(
"samples_trunc_count must be either zero or "
"such that: sample_count - samples_trunc_count > 2")

return {'probe_speed': probe_speed,
'lift_speed': lift_speed,
'samples': samples,
'sample_retract_dist': sample_retract_dist,
'samples_tolerance': samples_tolerance,
'samples_tolerance_retries': samples_retries,
'samples_trunc_count': samples_trunc_count,
'samples_result': samples_result}
def _probe(self, speed):
toolhead = self.printer.lookup_object('toolhead')
Expand Down Expand Up @@ -355,7 +394,8 @@ def run_probe(self, gcmd):
probexy + [pos[2] + params['sample_retract_dist']],
params['lift_speed'])
# Calculate result
epos = calc_probe_z_average(positions, params['samples_result'])
epos = calc_probe_z_average(positions, params['samples_result'],
params['samples_trunc_count'])
self.results.append(epos)
def pull_probed_results(self):
res = self.results
Expand Down

0 comments on commit 4b78010

Please sign in to comment.