This commit is contained in:
Sgr A* VMT
2024-03-31 23:14:02 +08:00
parent 944c7668d7
commit f48673f555
3 changed files with 125 additions and 103 deletions

219
idm.py
View File

@@ -566,11 +566,18 @@ class IDMProbe:
self._enrich_sample_time(sample) self._enrich_sample_time(sample)
self._data_filter.update(sample["time"], sample["data"]) self._data_filter.update(sample["time"], sample["data"])
self._enrich_sample_freq(sample) self._enrich_sample_freq(sample)
self._enrich_sample(sample)
if len(self._stream_callbacks) > 0: if len(self._stream_callbacks) > 0:
self._enrich_sample(sample)
for cb in list(self._stream_callbacks.values()): for cb in list(self._stream_callbacks.values()):
cb(sample) cb(sample)
last = sample
if last is not None:
last = last.copy()
dist = last["dist"]
if dist is None or np.isinf(dist) or np.isnan(dist):
del last["dist"]
self.last_received_sample = last
except queue.Empty: except queue.Empty:
return return
@@ -596,20 +603,12 @@ class IDMProbe:
self._stream_flush_schedule() self._stream_flush_schedule()
def _get_trapq_position(self, print_time): def _get_trapq_position(self, print_time):
move = None ffi_main, ffi_lib = chelper.get_ffi()
if self._last_trapq_move: data = ffi_main.new("struct pull_move[1]")
last = self._last_trapq_move[0] count = ffi_lib.trapq_extract_old(self.trapq, data, 1, 0.0, print_time)
last_end = last.print_time + last.move_t if not count:
if last.print_time <= print_time < last_end: return None, None
move = last move = data[0]
if move is None:
ffi_main, ffi_lib = chelper.get_ffi()
data = ffi_main.new("struct pull_move[1]")
count = ffi_lib.trapq_extract_old(self.trapq, data, 1, 0.0, print_time)
if not count:
return None, None
self._last_trapq_move = data
move = data[0]
move_time = max(0.0, min(move.move_t, print_time - move.print_time)) move_time = max(0.0, min(move.move_t, print_time - move.print_time))
dist = (move.start_v + .5 * move.accel * move_time) * move_time dist = (move.start_v + .5 * move.accel * move_time) * move_time
pos = (move.start_x + move.x_r * dist, move.start_y + move.y_r * dist, pos = (move.start_x + move.x_r * dist, move.start_y + move.y_r * dist,
@@ -618,8 +617,7 @@ class IDMProbe:
return pos, velocity return pos, velocity
def _sample_printtime_sync(self, skip=0, count=1): def _sample_printtime_sync(self, skip=0, count=1):
toolhead = self.printer.lookup_object("toolhead") move_time = self.toolhead.get_last_move_time()
move_time = toolhead.get_last_move_time()
settle_clock = self._mcu.print_time_to_clock(move_time) settle_clock = self._mcu.print_time_to_clock(move_time)
samples = [] samples = []
total = skip + count total = skip + count
@@ -681,6 +679,7 @@ class IDMProbe:
model = self.model.name model = self.model.name
return { return {
"last_sample": self.last_sample, "last_sample": self.last_sample,
"last_received_sample": self.last_received_sample,
"model": model, "model": model,
} }
@@ -775,7 +774,7 @@ class IDMProbe:
"time": sample["time"], "time": sample["time"],
"value": last_value, "value": last_value,
"temp": temp, "temp": temp,
"dist": dist, "dist": None if np.isinf(dist) or np.isnan(dist) else dist,
} }
if dist is None: if dist is None:
gcmd.respond_info("Last reading: %.2fHz, %.2fC, no model" % gcmd.respond_info("Last reading: %.2fHz, %.2fC, no model" %
@@ -1410,6 +1409,7 @@ class IDMMeshHelper:
def __init__(self, idm, config, mesh_config): def __init__(self, idm, config, mesh_config):
self.idm = idm self.idm = idm
self.scipy = None
self.mesh_config = mesh_config self.mesh_config = mesh_config
self.bm = self.idm.printer.load_object(mesh_config, "bed_mesh") self.bm = self.idm.printer.load_object(mesh_config, "bed_mesh")
@@ -1725,6 +1725,16 @@ class IDMMeshHelper:
def _is_valid_position(self, x, y): def _is_valid_position(self, x, y):
return self.min_x <= x <= self.max_x and self.min_y <= y <= self.min_y return self.min_x <= x <= self.max_x and self.min_y <= y <= self.min_y
def _is_faulty_coordinate(self, x, y, add_offsets=False):
if add_offsets:
xo, yo = self.idm.x_offset, self.idm.y_offset
x += xo
y += yo
for r in self.faulty_regions:
if r.is_point_within(x, y):
return True
return False
def _sample_mesh(self, gcmd, path, speed, runs): def _sample_mesh(self, gcmd, path, speed, runs):
cs = gcmd.get_float("CLUSTER_SIZE", self.cluster_size, minval=0.0) cs = gcmd.get_float("CLUSTER_SIZE", self.cluster_size, minval=0.0)
zcs = self.zero_ref_pos_cluster_size zcs = self.zero_ref_pos_cluster_size
@@ -1753,6 +1763,8 @@ class IDMMeshHelper:
# Calculate coordinate of the cluster we are in # Calculate coordinate of the cluster we are in
xi = int(round((x - min_x) / self.step_x)) xi = int(round((x - min_x) / self.step_x))
yi = int(round((y - min_y) / self.step_y)) yi = int(round((y - min_y) / self.step_y))
if xi < 0 or self.res_x <= xi or yi < 0 or self.res_y <= yi:
return
# If there's a cluster size limit, apply it here # If there's a cluster size limit, apply it here
if cs > 0: if cs > 0:
@@ -1792,10 +1804,11 @@ class IDMMeshHelper:
def _process_clusters(self, raw_clusters, gcmd): def _process_clusters(self, raw_clusters, gcmd):
parent_conn, child_conn = multiprocessing.Pipe() parent_conn, child_conn = multiprocessing.Pipe()
dump_file = gcmd.get("FILENAME", None)
def do(): def do():
try: try:
child_conn.send((False, self._do_process_clusters(raw_clusters))) child_conn.send((False, self._do_process_clusters(raw_clusters,dump_file)))
except: except:
child_conn.send((True, traceback.format_exc())) child_conn.send((True, traceback.format_exc()))
child_conn.close() child_conn.close()
@@ -1819,101 +1832,114 @@ class IDMMeshHelper:
else: else:
return inner_result return inner_result
def _do_process_clusters(self, raw_clusters): def _do_process_clusters(self, raw_clusters, dump_file):
clusters = self._interpolate_faulty(raw_clusters) if dump_file:
return self._generate_matrix(clusters) with open(dump_file, "w") as f:
f.write("x,y,xp,xy,dist\n")
for yi in range(self.res_y):
line = []
for xi in range(self.res_x):
cluster = raw_clusters.get((xi, yi), [])
xp = xi * self.step_x + self.min_x
yp = yi * self.step_y + self.min_y
for dist in cluster:
f.write("%d,%d,%f,%f,%f\n" % (xi, yi, xp, yp, dist))
def _is_faulty_coordinate(self, x, y, add_offsets=False): mask = self._generate_fault_mask()
if add_offsets: matrix, faulty_regions = self._generate_matrix(raw_clusters, mask)
xo, yo = self.idm.x_offset, self.idm.y_offset if len(faulty_regions) > 0:
x += xo (error, interpolator_or_msg) = self._load_interpolator()
y += yo if error:
for r in self.faulty_regions: return (True, interpolator_or_msg)
if r.is_point_within(x, y): matrix = self._interpolate_faulty(
return True matrix, faulty_regions, interpolator_or_msg
return False )
err = self._check_matrix(matrix)
if err is not None:
return (True, err)
return (False, self._finalize_matrix(matrix))
def _interpolate_faulty(self, clusters): def _generate_fault_mask(self):
faulty_indexes = [] if len(self.faulty_regions) == 0:
position = np.array(list(clusters.keys()))
(xi_max,yi_max) = position.T.max(axis = 1)
pos_temp = (position.T*[[self.step_x],[self.step_y]]+[[self.min_x],[self.min_y]])
if len(self.faulty_region_.shape) > 1:
length=self.faulty_region_.shape[1]
flag = np.array(
[
(pos_temp > self.faulty_region_[:2].reshape(1,2,length).T).T.all(axis=1),
(pos_temp < self.faulty_region_[2:].reshape(1,2,length).T).T.all(axis=1)
]
).all(axis = 0).any(axis = 1)
for i in range(len(flag)):
if(flag[i]):
clusters[tuple(position[i])] = None
faulty_indexes.append(tuple(position[i]))
del pos_temp
def get_nearest(start, dx, dy):
inputs = np.array(start)
inputs += [dx,dy]
while ((inputs >= 0).all() and (inputs <= [xi_max,yi_max]).all()):
if clusters.get(tuple(inputs),None) is not None:
return (abs(inputs-np.array(start)).sum(), median(clusters[tuple(inputs)]))
inputs += [dx,dy]
return None return None
mask = np.full((self.res_y, self.res_x), True)
for r in self.faulty_regions:
r_xmin = int(math.ceil((r.x_min - self.min_x) / self.step_x))
r_ymin = int(math.ceil((r.y_min - self.min_y) / self.step_y))
r_xmax = int(math.floor((r.x_max - self.min_x) / self.step_x))
r_ymax = int(math.floor((r.y_max - self.min_y) / self.step_y))
for y in range(r_ymin, r_ymax + 1):
for x in range(r_xmin, r_xmax + 1):
mask[(y, x)] = False
return mask
def interp_weighted(lower, higher): def _generate_matrix(self, raw_clusters, mask):
if lower is None and higher is None: faulty_indexes = []
return None matrix = np.empty((self.res_y, self.res_x))
if lower is None and higher is not None: for (x, y), values in raw_clusters.items():
return higher[1] if mask is None or mask[(y, x)]:
elif lower is not None and higher is None: matrix[(y, x)] = self.idm.trigger_distance - median(values)
return lower[1]
else: else:
return ((lower[1] * lower[0] + higher[1] * higher[0]) / matrix[(y, x)] = np.nan
(lower[0] + higher[0])) faulty_indexes.append((y, x))
return matrix, faulty_indexes
for coord in faulty_indexes: def _load_interpolator(self):
xl = get_nearest(coord, -1, 0) if not self.scipy:
xh = get_nearest(coord, 1, 0) try:
xavg = interp_weighted(xl, xh) self.scipy = importlib.import_module("scipy")
yl = get_nearest(coord, 0, -1) except ImportError:
yh = get_nearest(coord, 0, 1) msg = (
yavg = interp_weighted(yl, yh) "Could not load `scipy`. To install it, simply re-run "
avg = None "the IDM `install.sh` script. This module is required "
if xavg is not None and yavg is None: "when using faulty regions when bed meshing."
avg = xavg )
elif xavg is None and yavg is not None: return (True, msg)
avg = yavg if hasattr(self.scipy.interpolate, "RBFInterpolator"):
else:
avg = (xavg + yavg) / 2.0
clusters[coord] = [avg]
return clusters def rbf_interp(points, values, faulty):
return self.scipy.interpolate.RBFInterpolator(points, values, 64)(
faulty
)
def _generate_matrix(self, clusters): return (False, rbf_interp)
matrix = [] else:
td = self.idm.trigger_distance
def linear_interp(points, values, faulty):
return self.scipy.interpolate.griddata(
points, values, faulty, method="linear"
)
return (False, linear_interp)
def _interpolate_faulty(self, matrix, faulty_indexes, interpolator):
ys, xs = np.mgrid[0 : matrix.shape[0], 0 : matrix.shape[1]]
points = np.array([ys.flatten(), xs.flatten()]).T
values = matrix.reshape(-1)
good = ~np.isnan(values)
fixed = interpolator(points[good], values[good], faulty_indexes)
matrix[tuple(np.array(faulty_indexes).T)] = fixed
return matrix
def _check_matrix(self, matrix):
empty_clusters = [] empty_clusters = []
for yi in range(self.res_y): for yi in range(self.res_y):
line = []
for xi in range(self.res_x): for xi in range(self.res_x):
cluster = clusters.get((xi, yi), None) if np.isnan(matrix[(yi, xi)]):
if cluster is None or len(cluster) == 0:
xc = xi * self.step_x + self.min_x xc = xi * self.step_x + self.min_x
yc = yi * self.step_y + self.min_y yc = yi * self.step_y + self.min_y
empty_clusters.append(" (%.3f,%.3f)[%d,%d]" % (xc, yc, xi, yi)) empty_clusters.append(" (%.3f,%.3f)[%d,%d]" % (xc, yc, xi, yi))
else:
data = [td - d for d in cluster]
line.append(median(data))
matrix.append(line)
if empty_clusters: if empty_clusters:
err = ( err = (
"Empty clusters found\n" "Empty clusters found\n"
"Try increasing mesh cluster_size or slowing down.\n" "Try increasing mesh cluster_size or slowing down.\n"
"The following clusters were empty:\n" "The following clusters were empty:\n"
) + "\n".join(empty_clusters) ) + "\n".join(empty_clusters)
return (True, err) return err
else:
return None
def _finalize_matrix(self, matrix):
z_offset = None z_offset = None
if self.zero_ref_mode and self.zero_ref_mode[0] == "rri": if self.zero_ref_mode and self.zero_ref_mode[0] == "rri":
rri = self.zero_ref_mode[1] rri = self.zero_ref_mode[1]
@@ -1924,12 +1950,11 @@ class IDMMeshHelper:
rri_y = int(math.floor(rri / self.res_x)) rri_y = int(math.floor(rri / self.res_x))
z_offset = matrix[rri_y][rri_x] z_offset = matrix[rri_y][rri_x]
elif self.zero_ref_mode and self.zero_ref_mode[0] == "pos": elif self.zero_ref_mode and self.zero_ref_mode[0] == "pos":
z_offset = td - self.zero_ref_val z_offset = self.idm.trigger_distance - self.zero_ref_val
if z_offset is not None: if z_offset is not None:
for i, line in enumerate(matrix): matrix = matrix - z_offset
matrix[i] = [z - z_offset for z in line] return matrix.tolist()
return (False, matrix)
def _apply_mesh(self, matrix, gcmd): def _apply_mesh(self, matrix, gcmd):
params = self.bm.bmc.mesh_config params = self.bm.bmc.mesh_config

View File

@@ -12,9 +12,6 @@ fi
# install idm requirements to env # install idm requirements to env
echo "idm: installing python requirements to env, this may take 10+ minutes." echo "idm: installing python requirements to env, this may take 10+ minutes."
sudo apt-get install g++
sudo apt-get install gfortran
sudo apt-get install libopenblas-dev
"${KENV}/bin/pip" install -r "${BKDIR}/requirements.txt" "${KENV}/bin/pip" install -r "${BKDIR}/requirements.txt"
# update link to idm.py # update link to idm.py

View File

@@ -1,4 +1,4 @@
# klipper python virtual environment requirements for IDM # klipper python virtual environment requirements for IDM
numpy>=1.16.6 numpy>=1.16.6
scipy>=1.10.0 scipy>=1.2.3
matplotlib>=3.7.0 matplotlib>=3.7.0