update
This commit is contained in:
219
idm.py
219
idm.py
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
Reference in New Issue
Block a user