Capture
I attach the photo to the screening task and save it. Input: a guided lateral view. Output: stored image bytes. Limitation: a stored photo is not a measurement.
Engineering
This page exists so the computing architecture can be inspected: stages, math convention, and source excerpts. I kept the angle math separate from any step that tries to find landmarks. That finding step is still a stub, so a number stays research-only.
A quality pass only means the photo can continue. It is not a validated measurement. Placeholder landmarks can still produce numbers.
Each stage has one job. Finding landmarks is experimental. Clinicians already use profile-angle and depth-ratio ideas when they examine clubbing by hand [1]. That is bedside measurement literature. It is not evidence that my pipeline works.
I can change the landmark model without rewriting the measurement math.
I attach the photo to the screening task and save it. Input: a guided lateral view. Output: stored image bytes. Limitation: a stored photo is not a measurement.
If the photo is missing, blurry, glaring, or the nail is hidden, I reject it. Input: client metrics. Output: pass or reason codes. A pass only means the photo can continue.
Today the default path is a stub. It can return placeholder points. Trained weights are still experimental. Input: image. Output: named points or failure. Known limit: placeholder points still produce numbers.
I compute Lovibond angle from three points, and depth ratio as a length comparison. This is ordinary vector math. The displayed angle is unsigned, 0 to 180°, matching geometry.py. Classification bands in code mention values above 180°, which this function cannot produce. That is a real limit, not something I hide.
If confidence is too low, I return Unable to assess. The current research cutoff is 0.55. That behavior exists in code. It does not prove the cutoff is clinically right. A stored ±3° band on Lovibond is a placeholder, not a statistical interval.
When the path is still a stub, I do not show a clinical label in clinic tools. A technical failure must not become a reassuring finding.
Myers and Farquhar discuss bedside profile angles around 176° and 180° [1]. I used that paper as a provisional threshold scaffold, versioned as provisional-myers-v0. My geometry function is different.
Same educational fixture as the homepage. Labeled points match the measurement guide. Lovibond is the unsigned profile angle at the fold, drawn along the dorsal contour and calculated in the browser with the Python definition. The 179.7° label is that fixture, not a Myers threshold.
I specified the measurement contract and the tests that can fail. AI tools helped implement drafts. I reviewed the geometry, the failing cases, and what this site may claim. I did not write every line by hand, and I did not hide the assistance.
Measurement
Ordinary vector math. Zero-length vectors raise INVALID_GEOMETRY. The public JavaScript port matches this definition.
def lovibond_angle_degrees(nail_matrix, proximal_nail_fold, nail_plate):
v1 = (nail_matrix[0] - proximal_nail_fold[0],
nail_matrix[1] - proximal_nail_fold[1])
v2 = (nail_plate[0] - proximal_nail_fold[0],
nail_plate[1] - proximal_nail_fold[1])
return angle_between(v1, v2) # acos, 0 to 180°
Verified test: test_lovibond_angle_finite asserts 0 < angle < 180. Degenerate coinciding points must fail closed.
A test that challenges it
classify_finger still contains Myers scaffold bands, including values above 180°. The angle function cannot produce those values. The unit test therefore injects 181° instead of computing it.
def test_classify_provisional_bands():
assert classify_finger(170, 0.95) == "No morphological evidence"
assert classify_finger(178, 1.02) == "Borderline"
assert classify_finger(181, 1.06) == (
"Morphological evidence consistent with digital clubbing"
)
What this proves: injected numbers map to the provisional labels. What it does not prove: that LungLens can measure 181°, or that those cutoffs are clinically approved. Status of the scaffold: study team must approve.
Decision
I could have used one combined model that outputs angles. I did not. Detector, keypoint, and segmentation adapters can change. Deterministic geometry stays the authority. MedSAM stays annotation-only until validated.
DEFAULT_POINTS = {
"nail_matrix": (10.0, 40.0),
"proximal_nail_fold": (30.0, 42.0),
"nail_plate": (55.0, 38.0),
...
}
Those stub coordinates are not detections on a photograph. They still produce about 165.2°. A successful run is not proof I found the anatomy. That is why Demo captions say when placeholders are active, and why a stub must not look clinical.