To Be or Not to Be

I have seen the rise of the ultimate mega. And I have heard about its potential. And like what Lincoln Stephens wrote in 1919, he exclaimed:

I have seen the future, and it works!

Mirroring to Stephens’ awe at the Bolshevik, I do, and yes I do at the current mega today.

And what is the purpose if the mega can do things much better.
What is the purpose if all of your questions can be answered.
And what if, what if, if everything at the start is wrong, and the mega is the only solution.

And in my final process of finishing the 3d scanner, I realized that I cannot push forward without the mega. Yet, the mega can move forward without me.

Microstepping

Behind

Well, ignore all the words I just said couple weeks ago. Github have been a pandora box in my current position. And I feel that I can manipulate my access easily.

I think I found a parallel connection between my relationship with Github and the public relationship with the stock markets. It goes red when after you bought it, it goes green after you sold it.

Micro-stepping

Referring back to this document:

A4988_DMOS

CNC Shield

These two documents showed that microstepping is simply adding jumpers:

I have converted the official documents into a chart for reference:

A4988 Stepper Driver configuration

M0 M1 M2 Microstep Resolution
Low Low Low Full step
High Low Low Half step
Low High Low Quarter step
High High Low Eighth step
High High High Sixteenth step

So I have tested both Quarter Step and Sixteenth Step and trust me, once you get the smooth operation it is as if the song Smooth Operator from Carlos Sainz.

Just as a fun clip:

So how to use microstepping:

Microstepping increases resolution not precision –– unknown author named Pigtt

Formulas

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
```M = microstep factor``` 

```k = microstep index``` (integer)

I LOVE LATEX

$$
\text{Total\_Steps} = S \times M
$$

$$
\Delta\theta = \frac{360}{S \times M}
$$

$$
\theta(k) = \frac{360k}{S \times M}
$$



So for example is M = 4 or ```Quarter Step```

```arduino
N = 200 × 4 = 800
Δθ = 360 / 800 = 0.45°
θ(k) = 0.45k

If M = 16 or Sixteenth Step

1
2
3
N = 200 × 16 = 3200
Δθ = 360 / 3200 = 0.1125°
θ(k) = 0.1125k

HankShakes:

What are handshakes?

What are tokens?

What are Private Keys and public keys?

Essentially: it is a method of identification.

For the arduino code to communicate with python, it needs a hand shake.

My python plot code needs to see a green-light signal to began, or else it will not be on the same panel with the code and thus it fails.

Some basic token demonstrations:

1
2
3
4
5
6
7
8
9
10
11
12
void setup() {
Serial.begin(115200);
while (!Serial); // INFINITE LOOP wait for USB
Serial.println("READY"); // say ready
}

void loop() {
if (Serial.readStringUntil('\n') == "GO") {
Serial.println("OK"); // confirm
while (1); // stop
}
}

Python receiving handshake and giving back a kiss.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import serial
import time

ser = serial.Serial("/dev/cu.usbmodem1301", 115200, timeout=2)
time.sleep(2)

# wait for READY
while ser.readline().decode().strip() != "READY":
pass

# send GO
ser.write(b"GO\n")

# read response
print(ser.readline().decode().strip())

On the Arduino side, Serial.begin(115200) starts the USB serial connection at the same port speed 115200 that math Python’s.

WARING: If not you will receive ````???```.

(!Serial)``` forces the Arduino to wait until the computer actually opens the USB connection, which prevents the first message from being lost during reset.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

```Serial.println("READY")``` is sent once to clearly tell Python “HELLO there. I have EARS. I am not daydreaming”

Inside ```loop(), Serial.readStringUntil('\n')``` waits until Python sends a full line ending in a newline, which guarantees Arduino reads a complete command.

```"GO"``` ensures Arduino only proceeds when it receives the exact expected signal.

When ```"GO"``` is received, ```Serial.println("OK") ``` responds, and ```while (1)``` stops the program so this exchange happens only once.



Videos (Very Long):
<iframe width="560" height="315" src="https://www.youtube.com/embed/CITZIv3SmvU?si=IDvPfL7c0gn9T1A_" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

#### My mistake of over-relying on ```WebbGPT```.

When you add too much handshakes and maybe, maybe deleted your own handshake.

I am not going to paste an entire chunk of code. But I am going to show you the specific bugs that I've encounter:

```py
ser = serial.Serial(port, baudrate, timeout=2)
time.sleep(2) # Give Arduino time to reset
ser.reset_input_buffer()
...
print("Waiting for 'start' signal...")

What I did is that I open serial which resets arduino.
Then I wait 2 seconds while Arduino boots and prints ready

However, the code reset_input_buffer delete everything Arduino just sent, including ready.

WHAT AM I DOING

Solution:

A simply solution would be re-ordering the code, so that it clears the trash first:

1
2
3
4
5
ser = serial.Serial(port, baudrate, timeout=2)

ser.reset_input_buffer()
time.sleep(2)

Even better, we can actually wait for ready

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
print("Waiting for 'ready'...")
while True:
line = ser.readline().decode("utf-8", errors="ignore").strip()
if not line:
continue
print("RX:", line)
if "ready" in line.lower():
break

print("Sending 'go'...")
ser.write(b"go\n")
ser.flush()

print("Waiting for 'start'...")
while True:
line = ser.readline().decode("utf-8", errors="ignore").strip()
if not line:
continue
print("RX:", line)
if "start" in line.lower():
break

Code simply for reference:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
#include <math.h>
#include <Arduino.h>

// ---------------- Pins ----------------
const int X_STEP = 2, X_DIR = 5;
const int Y_STEP = 3, Y_DIR = 6;
const int ENABLE_PIN = 8;
const int SENSOR_PIN = A2;

// --------- Motion / scan settings -----
const int STEP_DELAY_US = 300;
const int SAMPLE_PAUSE_US = 2000;

const long STEPS_PER_REV = 3200; // 200 * 16 microsteps
const float X_SWEEP_DEG = 90.0;
const float X_SAMPLE_DEG = 1.0; // desired angle spacing
const float Y_STEP_DEG = 1.0;
const float Y_MAX_DEG = 90.0;

// --------- Sensor calibration ----------
const float VCC_VOLTS = 5.0;
const float D_MIN_MM = 20.0, D_MAX_MM = 800.0;

// V(L) = A - B ln(L) => L = exp((A - V)/B)
float voltsToDistMM(float v) {
const float A = 10.02f;
const float B = 1.40f;

if (v < 0.01f) v = 0.01f;
if (v > VCC_VOLTS) v = VCC_VOLTS;

float d = expf((A - v) / B);
if (d < D_MIN_MM) d = D_MIN_MM;
if (d > D_MAX_MM) d = D_MAX_MM;
return d;
}

// --------------- Helpers --------------
inline long stepsForDeg(float deg) {
return lroundf(deg * (float)STEPS_PER_REV / 360.0f);
}

inline void stepOnce(int stepPin) {
digitalWrite(stepPin, HIGH);
delayMicroseconds(STEP_DELAY_US);
digitalWrite(stepPin, LOW);
delayMicroseconds(STEP_DELAY_US);
}

void stepN(int stepPin, long n) {
for (long i = 0; i < n; i++) stepOnce(stepPin);
}

int readAdcAvg4() {
uint32_t sum = 0;
for (int i = 0; i < 4; i++) sum += analogRead(SENSOR_PIN);
return (sum + 2) / 4;
}

float readDistanceMM() {
int adc = readAdcAvg4();
float v = (adc * VCC_VOLTS) / 1023.0f;
return voltsToDistMM(v);
}

// Wait for a full line "go"
bool waitForGo(unsigned long timeout_ms) {
unsigned long t0 = millis();
while (millis() - t0 < timeout_ms) {
if (Serial.available()) {
String s = Serial.readStringUntil('\n');
s.trim();
s.toLowerCase();
if (s == "go") return true;
}
delay(5);
}
return false;
}

void setup() {
Serial.begin(115200);

pinMode(X_STEP, OUTPUT); pinMode(X_DIR, OUTPUT);
pinMode(Y_STEP, OUTPUT); pinMode(Y_DIR, OUTPUT);
pinMode(ENABLE_PIN, OUTPUT);
pinMode(SENSOR_PIN, INPUT);

// Motors disabled until Python says go
digitalWrite(ENABLE_PIN, HIGH);

Serial.println("ready");
Serial.flush();

if (!waitForGo(30000)) {
Serial.println("ERROR: timeout waiting for go");
while (true) delay(1000);
}

digitalWrite(ENABLE_PIN, LOW); // enable motors
delay(50);

Serial.println("start");
Serial.flush();
}

void loop() {
// ---- Define consistent X sampling in integer counts ----
// Example: 90 deg sweep at 1 deg spacing => 91 samples (0..90 inclusive)
const int xSamples = (int)lroundf(X_SWEEP_DEG / X_SAMPLE_DEG) + 1;
const long xSweepSteps = stepsForDeg(X_SWEEP_DEG);

// steps between samples (integer). This makes motion consistent.
// Any remainder is handled by snapping the last sample with a correction step.
const long xStepPerSample = max(1L, xSweepSteps / (xSamples - 1));
const long xTotalPlannedSteps = xStepPerSample * (xSamples - 1);
const long xRemainderSteps = xSweepSteps - xTotalPlannedSteps; // can be >= 0

long yPosSteps = 0;
bool forward = true;

for (float yDeg = 0.0; yDeg <= Y_MAX_DEG + 0.0001f; yDeg += Y_STEP_DEG) {
// ---- Move Y to target ----
long yTarget = stepsForDeg(yDeg);
long yDelta = yTarget - yPosSteps;

digitalWrite(Y_DIR, (yDelta >= 0) ? LOW : HIGH);
stepN(Y_STEP, labs(yDelta));
yPosSteps = yTarget;

// ---- Set X direction for snaking ----
digitalWrite(X_DIR, forward ? HIGH : LOW);

// ---- Scan one row ----
for (int i = 0; i < xSamples; i++) {
float xDeg = forward ? (i * X_SAMPLE_DEG) : (X_SWEEP_DEG - i * X_SAMPLE_DEG);
float dist = readDistanceMM();

// CSV: x,y,dist
Serial.print(xDeg, 2);
Serial.print(",");
Serial.print(yDeg, 2);
Serial.print(",");
Serial.println(dist, 1);

// move to next sample (except after last)
if (i < xSamples - 1) {
stepN(X_STEP, xStepPerSample);
// apply the remainder only once at the very end of the sweep
if (i == xSamples - 2 && xRemainderSteps > 0) {
stepN(X_STEP, xRemainderSteps);
}
}

delayMicroseconds(SAMPLE_PAUSE_US);
}

forward = !forward;
}

Serial.println("done");
Serial.flush();

digitalWrite(ENABLE_PIN, HIGH); // disable motors
while (true) delay(1000);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import time
import serial
from math import sin, cos, radians
import matplotlib.pyplot as plt

PORT = "/dev/cu.usbmodem1301"
BAUD = 115200

SENSOR_OFFSET_MM = 60.0
PAN_OFFSET_DEG = 90.0

READY_TIMEOUT_S = 10
START_TIMEOUT_S = 10
SCAN_TIMEOUT_S = 300
PLOT_EVERY_N = 200


def wait_for(ser, token, timeout_s, echo=True):
t0 = time.time()
while time.time() - t0 < timeout_s:
line = ser.readline().decode("utf-8", errors="ignore").strip()
if not line:
continue
if echo:
print("RX:", line)
if token in line.lower():
return True
return False


def parse_csv3(line):
parts = line.split(",")
if len(parts) != 3:
return None
try:
return float(parts[0]), float(parts[1]), float(parts[2])
except ValueError:
return None


def to_xyz(x_deg_raw, y_deg_raw, dist_raw_mm):
r = dist_raw_mm + SENSOR_OFFSET_MM
pan = radians(x_deg_raw - PAN_OFFSET_DEG)
tilt = radians(y_deg_raw)

z = r * sin(tilt)
plane = r * cos(tilt)
x = plane * cos(pan)
y = plane * sin(pan)
return x, y, z


# ---- connect ----
ser = serial.Serial(PORT, BAUD, timeout=1)

# Immediately clear junk that existed BEFORE the reset (safe)
ser.reset_input_buffer()

# Opening the port resets many Arduino boards; give it a moment to boot
time.sleep(1.5)

print("Waiting for 'ready'...")
if not wait_for(ser, "ready", READY_TIMEOUT_S, echo=True):
print("Did not see 'ready'. Sending 'go' anyway (failsafe).")

print("TX: go")
ser.write(b"go\n")
ser.flush()

print("Waiting for 'start' (optional)...")
wait_for(ser, "start", START_TIMEOUT_S, echo=True)

# ---- plot setup ----
xs, ys, zs = [], [], []
plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")

print("Receiving data...")
t0 = time.time()
n = 0

try:
while True:
if time.time() - t0 > SCAN_TIMEOUT_S:
print("Safety timeout stopping.")
break

line = ser.readline().decode("utf-8", errors="ignore").strip()
if not line:
continue

low = line.lower()
if "done" in low:
print("Scan complete!")
break
if low.startswith("#") or "error" in low or "ready" in low or "start" in low:
continue

parsed = parse_csv3(line)
if not parsed:
continue

x_deg, y_deg, dist = parsed
x, y, z = to_xyz(x_deg, y_deg, dist)
xs.append(x)
ys.append(y)
zs.append(z)
n += 1

if n % 50 == 0:
print(f"{n} pts | x={x_deg:.1f} y={y_deg:.1f} d={dist:.1f} -> z={z:.1f}")

if n % PLOT_EVERY_N == 0:
ax.cla()
ax.scatter(xs, ys, zs, s=1)
plt.draw()
plt.pause(0.001)

except KeyboardInterrupt:
print("Stopped by user.")
finally:
ser.close()
plt.ioff()
plt.show()

Super duper GPT version

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
import time
import serial
import numpy as np
from math import sin, cos, radians
import matplotlib.pyplot as plt
from collections import deque

PORT = "/dev/cu.usbmodem1301"
BAUD = 115200

SENSOR_OFFSET_MM = 60.0
PAN_OFFSET_DEG = 90.0

READY_TIMEOUT_S = 10
START_TIMEOUT_S = 10
SCAN_TIMEOUT_S = 300

# ---- must match Arduino ----
X_SWEEP_DEG = 90.0
X_SAMPLE_DEG = 1.0
Y_MAX_DEG = 90.0
Y_STEP_DEG = 1.0

# ---- basic sanity (still helpful) ----
DIST_MIN_MM = 30.0
DIST_MAX_MM = 800.0

# ---- “keep only the real surface” knobs ----
# 1) Local consistency: keep points whose distance agrees with neighbors
NEIGHBOR_WIN = 1 # 1 => 3x3 neighborhood
NEIGHBOR_ABS_MM = 35.0 # allow +/- mm difference vs neighbors
NEIGHBOR_REL = 0.06 # allow +/- % of distance (helps at far range)
MIN_GOOD_NEIGHBORS = 2 # must have at least this many agreeing neighbors

# 2) Connected component: keep only the largest connected region
CONNECT_8 = True # True => 8-connectivity (recommended)
MIN_COMPONENT_SIZE = 300 # safety: ignore tiny blobs as “noise”

# 3) Final downsample for elegance (optional)
KEEP_EVERY_N = 1 # 1=keep all, 2=keep half, 3=keep one-third, etc.


def wait_for_token(ser, token: str, timeout_s: float, echo=True):
token = token.lower()
t0 = time.time()
while time.time() - t0 < timeout_s:
line = ser.readline().decode("utf-8", errors="ignore").strip()
if not line:
continue
low = line.lower()
if echo:
print("RX:", line)
if token in low:
return True
if low.startswith("error"):
return False
return False


def parse_csv3(line: str):
parts = line.split(",")
if len(parts) != 3:
return None
try:
return float(parts[0]), float(parts[1]), float(parts[2])
except ValueError:
return None


def to_xyz(x_deg_raw, y_deg_raw, dist_raw_mm):
r = dist_raw_mm + SENSOR_OFFSET_MM
pan = radians(x_deg_raw - PAN_OFFSET_DEG)
tilt = radians(y_deg_raw)

z = r * sin(tilt)
plane = r * cos(tilt)
x = plane * cos(pan)
y = plane * sin(pan)
return x, y, z


def build_grid_axes():
x_vals = np.round(np.arange(0.0, X_SWEEP_DEG + 1e-6, X_SAMPLE_DEG), 2)
y_vals = np.round(np.arange(0.0, Y_MAX_DEG + 1e-6, Y_STEP_DEG), 2)
return x_vals, y_vals


def neighbor_consistency_mask(dist_grid):
"""
Keep a point if it agrees with enough neighbors in a window.
Agreement threshold is max(NEIGHBOR_ABS_MM, NEIGHBOR_REL * dist).
"""
H, W = dist_grid.shape
valid = ~np.isnan(dist_grid)
keep = np.zeros((H, W), dtype=bool)

for r in range(H):
r0 = max(0, r - NEIGHBOR_WIN)
r1 = min(H, r + NEIGHBOR_WIN + 1)
for c in range(W):
if not valid[r, c]:
continue

c0 = max(0, c - NEIGHBOR_WIN)
c1 = min(W, c + NEIGHBOR_WIN + 1)

center = dist_grid[r, c]
window = dist_grid[r0:r1, c0:c1].reshape(-1)
window = window[~np.isnan(window)]

if window.size <= 1:
continue

thr = max(NEIGHBOR_ABS_MM, NEIGHBOR_REL * center)
# count neighbors (excluding itself) that are close in distance
close = np.sum(np.abs(window - center) <= thr) - 1
if close >= MIN_GOOD_NEIGHBORS:
keep[r, c] = True

return keep


def largest_connected_component(mask):
"""
Return a mask containing only the largest connected component of True cells.
"""
H, W = mask.shape
visited = np.zeros_like(mask, dtype=bool)

if CONNECT_8:
neighbors = [(-1, -1), (-1, 0), (-1, 1),
( 0, -1), ( 0, 1),
( 1, -1), ( 1, 0), ( 1, 1)]
else:
neighbors = [(-1, 0), (1, 0), (0, -1), (0, 1)]

best_cells = None
best_size = 0

for r in range(H):
for c in range(W):
if not mask[r, c] or visited[r, c]:
continue

q = deque([(r, c)])
visited[r, c] = True
cells = [(r, c)]

while q:
rr, cc = q.popleft()
for dr, dc in neighbors:
nr, nc = rr + dr, cc + dc
if 0 <= nr < H and 0 <= nc < W and mask[nr, nc] and not visited[nr, nc]:
visited[nr, nc] = True
q.append((nr, nc))
cells.append((nr, nc))

size = len(cells)
if size > best_size:
best_size = size
best_cells = cells

out = np.zeros_like(mask, dtype=bool)
if best_cells is not None and best_size >= MIN_COMPONENT_SIZE:
for r, c in best_cells:
out[r, c] = True

return out, best_size


def set_equalish_3d(ax, X, Y, Z):
if len(X) < 10:
return
x_min, x_max = float(np.min(X)), float(np.max(X))
y_min, y_max = float(np.min(Y)), float(np.max(Y))
z_min, z_max = float(np.min(Z)), float(np.max(Z))

cx = 0.5 * (x_min + x_max)
cy = 0.5 * (y_min + y_max)
cz = 0.5 * (z_min + z_max)

span = max(x_max - x_min, y_max - y_min, z_max - z_min)
if span <= 1e-6:
return
half = 0.5 * span
ax.set_xlim(cx - half, cx + half)
ax.set_ylim(cy - half, cy + half)
ax.set_zlim(cz - half, cz + half)


# ------------------- READ SCAN INTO GRID -------------------
x_vals, y_vals = build_grid_axes()
nx, ny = len(x_vals), len(y_vals)

dist_grid = np.full((ny, nx), np.nan, dtype=np.float32)
x_to_i = {float(x): i for i, x in enumerate(x_vals)}
y_to_j = {float(y): j for j, y in enumerate(y_vals)}

ser = serial.Serial(PORT, BAUD, timeout=1)
ser.reset_input_buffer()
time.sleep(1.5)

print("Waiting for 'ready'...")
wait_for_token(ser, "ready", READY_TIMEOUT_S, echo=True)

print("TX: go")
ser.write(b"go\n")
ser.flush()

print("Waiting for 'start'...")
wait_for_token(ser, "start", START_TIMEOUT_S, echo=True)

print("Receiving data into range image...")
t0 = time.time()
n_recv = 0
n_store = 0

try:
while True:
if time.time() - t0 > SCAN_TIMEOUT_S:
print("Safety timeout stopping.")
break

line = ser.readline().decode("utf-8", errors="ignore").strip()
if not line:
continue

low = line.lower()
if "done" in low:
print("Scan complete!")
break
if low.startswith("error"):
print("Arduino error:", line)
break
if low in ("ready", "start"):
continue

parsed = parse_csv3(line)
if not parsed:
continue

x_deg, y_deg, dist = parsed
n_recv += 1

if dist < DIST_MIN_MM or dist > DIST_MAX_MM:
continue

# quantize to grid (handles float formatting tiny errors)
xq = float(np.round(x_deg / X_SAMPLE_DEG) * X_SAMPLE_DEG)
yq = float(np.round(y_deg / Y_STEP_DEG) * Y_STEP_DEG)
xq = float(np.round(xq, 2))
yq = float(np.round(yq, 2))

if xq in x_to_i and yq in y_to_j:
dist_grid[y_to_j[yq], x_to_i[xq]] = dist
n_store += 1

if n_recv % 3000 == 0:
print(f"recv={n_recv} stored={n_store}")

finally:
ser.close()

raw_valid = np.count_nonzero(~np.isnan(dist_grid))
print(f"Raw valid grid points: {raw_valid}")

# ------------------- FILTER: KEEP ONLY REAL SECTION -------------------
print("Filtering: neighbor consistency...")
mask_consistent = neighbor_consistency_mask(dist_grid)
print(f"Consistent points: {np.count_nonzero(mask_consistent)}")

print("Filtering: largest connected component...")
mask_lcc, lcc_size = largest_connected_component(mask_consistent)
print(f"Largest component size: {lcc_size}")

# If LCC is too small, fall back to consistent mask (still better than raw)
final_mask = mask_lcc if lcc_size >= MIN_COMPONENT_SIZE else mask_consistent

# ------------------- CONVERT TO XYZ -------------------
ys_idx, xs_idx = np.where(final_mask)
if KEEP_EVERY_N > 1:
sel = np.arange(len(xs_idx)) % KEEP_EVERY_N == 0
ys_idx, xs_idx = ys_idx[sel], xs_idx[sel]

Xlist, Ylist, Zlist = [], [], []
for j, i in zip(ys_idx, xs_idx):
x_deg = float(x_vals[i])
y_deg = float(y_vals[j])
d = float(dist_grid[j, i])
x, y, z = to_xyz(x_deg, y_deg, d)
Xlist.append(x); Ylist.append(y); Zlist.append(z)

X = np.array(Xlist, dtype=np.float32)
Y = np.array(Ylist, dtype=np.float32)
Z = np.array(Zlist, dtype=np.float32)

print(f"Final plotted points: {len(X)}")

# ------------------- ELEGANT PLOT -------------------
plt.figure()
ax = plt.axes(projection="3d")
sc = ax.scatter(X, Y, Z, s=3, c=Z, cmap="viridis", alpha=0.9)

ax.set_xlabel("X (mm)")
ax.set_ylabel("Y (mm)")
ax.set_zlabel("Z (mm)")
ax.set_title("Scanned Object Section (denoised + largest region)")

set_equalish_3d(ax, X, Y, Z)
plt.colorbar(sc, ax=ax, shrink=0.6, label="Z (mm)")
plt.show()

Joke

If you read till here. Congrats, you’re done with my nonsense.
I have a gift for you.
What is the best language in the world.

A) Java
B) Python
C) C++
D) ALL of the ABOUVE

Answer D, so
Python + Java + C = Phavac

Go to phavac.com for the mirror of pigtt.com