Downloads · 30 days
0
shreayshAcxtech/my-cool-model
my-cool-model is a machine learning model from shreayshAcxtech. Use it for the machine learning task on the model card, and read the license before you ship it in a product.
Batch-measures the tilt angle of the post on flat-lay earring photos (plain/white background).
Downloads · 30 days
0
Access
Public
Updated Jul 28, 2026
Repo size
15.7 MB
Likes
0
Public
Click a slice to open those files.
.jpg7.8 MB · 36%
From the Hugging Face model README
Batch-measures the tilt angle of the post on flat-lay earring photos (plain/white background).
Validated on three synthetic test suites at known angles (0°–45°):
All three suites tracked ground truth within ~1–3°, with one known edge case documented below.
pip install opencv-python scikit-image scipy numpy --break-system-packages
python earring_post_angle.py --input_dir /path/to/photos --output_dir /path/to/results
results/angles.csv — filename, angle_deg, post_length_px, notesresults/annotated/*.png — each photo with the detected post line drawn on it (green line + angle label), so you can spot-check accuracy at a glanceEvery annotated output image now shows three things distinctly, so you can see exactly what the algorithm found instead of just a single number:
angle_deg/relative_angle_deg: a direct vector from the post's base to
its tip. Comparing the green arrow to the red path shows you the bend at
a glance — if they closely overlap, the post is straight; if they
diverge, that gap is the bend.The results also include a post_straightness value (1.0 = perfectly
straight, lower = more bent) and an on-image "BENT" flag when straightness
drops below 0.97 — useful as a defect signal in its own right, separate
from the angle numbers, since a post can be bent partway along in a way
that doesn't fully show up in the net tip-angle alone.
I validated this against a synthetic bent-post test (straight for the first half, then bending by a known angle for the second half) — the algorithm correctly measured the net tip deviation in each case (e.g. a 60° bend at the midpoint correctly read as ~30° net deviation, matching the geometry exactly), and the drawn red path visibly showed the kink at the bend point.
(Any Orientation)
If your batch samples are loose parts placed in any position/rotation
(not a fixed fixture), use --reference_image:
python earring_post_angle.py --input_dir batch/ --output_dir results/ \
--reference_image good_sample.jpg --tolerance 5.0
This measures the known-good sample once, then compares every batch image against it using a rotation-invariant angle: the post's direction relative to the earring's own body geometry (specifically, relative to the line from the body's centroid out to where the post attaches), rather than relative to the image's vertical axis. Since the post and the body rotate together as one rigid part, this relationship stays the same no matter how the part is rotated on the tray — a good part scores the same whether it's photographed right-side up or spun around.
angles.csv gains extra columns: diff_from_reference_deg and (if
--tolerance is set) pass_fail.
Requirement: this only works correctly when the post attaches at the earring body's edge/surface (true for hoop, knot, and most real stud designs) — not for a body where the post would run straight through the geometric center.
If automatic segmentation struggles on your parts (e.g. a thin post with weak contrast against the background -- see the Debugging section on diagnosing this), this mode leans on human vision instead: you drag the calibrated body outline to align with each sample by eye, and only the narrow, aligned, masked region gets auto-detected -- avoiding the whole-image noise that causes wrong-branch selection.
# First, calibrate normally (fixed-position mode, NOT --any_orientation):
python live_camera_angle.py --calibrate
# Then run the stencil batch workflow:
python live_camera_angle.py --stencil_batch --tolerance 5.0
Workflow:
m (or spacebar) to measure. Behind the scenes: the region
under the aligned stencil is cropped, rotated back to the calibration's
original orientation, masked to just the body outline, and measured —
directly comparable to the calibration's reference angle.m). Press
r to reset the stencil to center, s to save a snapshot.q/ESC when done — writes a summary CSV (default
stencil_batch_results.csv, change with --output_csv) with each
sample's stencil angle used, detected angle, diff from reference, and
pass/fail if --tolerance was set.I validated the underlying math with a controlled test: measuring a rotated copy of the same physical part came back within ~0.6° of zero at every rotation angle tested (0°–170°), confirming the crop/rotate-back/ mask math correctly cancels out the manual rotation and isolates the real angular defect.
IMPORTANT -- calibrate with a generously-sized outline: if the calibrated body outline/polygon only extends a short distance along the post (just enough to cover a straight reference sample), a BENT post on a different sample can bend beyond that boundary -- the mask flattens everything past it to background, so the detector never sees the bent portion at all. This can silently produce a false PASS on a genuinely bent part, since it only measures the short straight stub that's actually visible to it.
Fix: when calibrating, extend the polygon well past the post's normal length -- with margin for a bend in any direction, not just tracing the reference sample's post tightly.
Safeguard: the tool now automatically checks whether the detected tip lands suspiciously close to the polygon/mask boundary, and if so, flags it clearly:
notes column records the same warning for any
measurement affected.If you see this warning, don't trust that reading -- recalibrate with a larger outline first.
Requirement: the calibration must be fixed-position mode (drawn with
--calibrate, not --calibrate --any_orientation) since this mode reuses
its ROI polygon and reference angle directly.
Both the batch script and the live camera script support --debug for
detailed per-image/per-frame diagnostics:
python earring_post_angle.py --input_dir photos/ --output_dir results/ --debug
python live_camera_angle.py --any_orientation --debug
This prints: background detection (light/dark) and mask stats, every candidate post branch considered with its straightness/length/tip-ratio score (and why any were rejected), the winning branch's geometry, the centroid/vectors behind the rotation-invariant angle calculation, and (live only) camera settings actually applied, raw-vs-smoothed values each frame with outlier flagging, FPS, and calibration save/load contents. Useful for understanding why a particular image or frame got the angle it did, rather than just seeing the final number.
live_camera_angle.py shows a real-time video window with the detected
post angle overlaid, using Basler's pypylon SDK. It supports two modes:
1. Absolute mode (no calibration) — shows each sample's angle from vertical, same as the batch pipeline.
2. Reference comparison mode — calibrate once against a known-good sample in your fixture, then every subsequent sample (placed in the same ROI/fixture position) is compared against that reference and shown as a difference, with an optional pass/fail tolerance.
pip install pypylon --break-system-packages # if not already installed
# First time: calibrate against a known-good sample
python live_camera_angle.py --calibrate --tolerance 3.0
# After that, just run it (loads the saved calibration automatically)
python live_camera_angle.py --tolerance 3.0
Calibration workflow (triggered by --calibrate, or press c any time
while running) depends on the mode:
Fixed-position mode (default) — for parts placed in the same fixture/ROI every time:
A frame is grabbed and a window pops up showing an auto-detected outline around the earring body, with draggable points on it. Adjust it to match the body precisely:
r to reset to the auto-detected outline, ENTER to confirm, ESC
to cancelAnything inside the bounding box but outside this outline gets flattened to background before detection runs — useful if another part or clutter sits near the earring in frame.
A zoomed view of that ROI pops up — click the post's TIP (the far
end, away from the body), then click the BASE (where the post meets
the body). A line and its angle are shown; press ENTER to accept, r to
redo the two clicks, or ESC to cancel.
This ROI + reference angle is saved to calibration.json.
Any-orientation mode (--any_orientation) — for loose parts placed
anywhere, at any rotation:
python live_camera_angle.py --calibrate --any_orientation --tolerance 5.0
Worth knowing: the reference itself is measured manually (by you, once, carefully) so it's as accurate as possible — it's the baseline every batch sample gets judged against. Batch samples afterward are still fully automatic and can be in any position/rotation; only the one-time reference calibration involves drawing.
From then on, place each sample anywhere in frame at any rotation — the
overlay shows Ref X° | Sample Y° | Diff Z° (plus PASS/FAIL with
--tolerance) the same as fixed-position mode, but using the
rotation-invariant angle so orientation on the tray doesn't matter. Press
c any time to recalibrate; remember to add --any_orientation again next
time you launch, or it'll load a fixed-ROI calibration by default.
Other options:
--serial 22928445 — pick a specific camera if you have more than one--exposure 8000 — set exposure time in microseconds--scale 0.5 — run detection on a downscaled copy for speed (in
reference mode, this downscales just the ROI crop, not the whole frame,
so it's rarely needed there)--every 2 — only run detection every Nth frame while still displaying
every frame live--calibration_path myline.json — use a different calibration file
(handy if you run multiple product lines/fixtures)--debug — print per-frame diagnostics to the console: which camera
settings actually got applied (exposure/gain lock), each detection
frame's raw vs. smoothed angle (flagging any reading more than 15° from
the smoothed value as a likely branch-flip outlier), FPS, and
recalibration events. Useful for confirming whether jitter is a real
detection problem or already being absorbed by smoothing.Controls in the video window: q/ESC quit, s save the current
annotated frame to ./snapshots/, c (re)calibrate.
If the live reading jumps around erratically frame to frame (rather than being a small jitter), two things usually fix it:
ExposureAuto and
GainAuto automatically on startup (best-effort — not all camera models
expose these nodes) and prints a note if you didn't also pass
--exposure. A lingering auto-exposure adjustment shifts frame
brightness slightly each frame, which can shift the segmentation
threshold enough to flip which part of the earring gets picked as "the
post." Passing a fixed --exposure <microseconds> value locks this down
further.--smoothing_window frames (default 7), not just the latest single
frame — this absorbs an occasional bad frame (e.g. detection briefly
latching onto the wrong skeleton branch) without it showing up as a
visible jump, while still updating quickly once a new value is
consistent. Use --smoothing_window 1 to see raw per-frame values (for
debugging), or raise it (e.g. 15) for an even steadier reading at the
cost of slightly slower response to a genuine change.Important: I wrote and validated this against pypylon's documented API and tested the detection, calibration, ROI-cropping, and diff-calculation logic extensively offline (including simulating the full calibrate → compare workflow against test images), but I don't have access to physical Basler hardware in this environment, so the camera grab loop itself hasn't been tested end-to-end. Please try it on your machine — if pypylon throws anything unexpected (pixel format, timeout behavior, etc. can vary by camera model/firmware), send me the error and I'll fix it.
CONFIG["background"]: "auto" (default), or force "light"/"dark" if
auto-detection misjudges your setup.CONFIG["min_post_straightness"] (default 0.85): how straight a branch
must be to count as a post. Lower this if your post itself has an
expected slight curve; raise it if a curved part of a hoop/knot is being
mistaken for the post.CONFIG["max_tip_thickness_ratio"] (default 0.65): filters out skeleton
artifacts that can appear near a solid body's center (common on rounder
stud designs) by requiring a real post's tip to be meaningfully thinner
than the body's single thickest point.CONFIG["morph_close_kernel"] (default 11): bridges gaps in the mask —
bump this up if a post is getting cut into disconnected segments by a
specular highlight or shadow.CONFIG["morph_open_kernel"] (default 3): removes small noise specks.
Keep this smaller than your post's pixel width, or it will erode the
post away entirely and break detection.notes column says so), try raising
min_post_pixels if noise is being picked up as a post, or check the
annotated image to see what the mask actually captured.morph_close_kernel further first.annotated/ images against
angles.csv before trusting the pipeline on a full production batch —
especially the first run on your actual photos, since real lighting and
design variation is always messier than any synthetic test can fully
capture.