Engineering

How LungLens processes a capture

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.

From photo to a careful result

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.

  1. 01CaptureGuided photo
  2. 02QualityReject blur and glare
  3. 03Find landmarksExperimental
  4. 04MeasureAngle and depth ratio
  5. 05AbstainIf confidence is low
  6. 06ReleaseResearch only for now

What each stage does

I can change the landmark model without rewriting the measurement math.

Implemented

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.

Why the output cannot exceed 180°

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.

  • Vertex is the proximal nail fold. One ray goes toward the DIP / nail matrix. The other follows the dorsal nail contour.
  • I take the dot product, clamp cosine to [-1, 1], then convert acos to degrees. That is the smaller angle between the vectors, so the output is 0° to 180°.
  • Literature that asks clinicians to notice values past 180° is using a different profile or reflex convention. I do not map a LungLens number onto those cutoffs.
  • Classification bands in code still mention >180° and ≥185°. The angle function cannot produce them. Depth ratio can still cross 1.00, 1.05, and 1.10 because those are lengths, not reflex angles.
  • Hyponychial angle is computed when those points exist. It does not drive classify_finger.
  • The educational overlay near 179.7° is almost colinear dorsal points. It is not a near-clubbing call.

Geometry sandbox

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.

Educational fixture
Lovibond
—
DPD/IPD
—
Educational fixture. Not live model inference.
DIP joint
Dorsal interphalangeal landmark on the finger.
Nail fold
Proximal nail fold; vertex of the educational Lovibond angle.
Nail plate
Point along the nail plate used for the second vector.

Open the interactive demonstration

Inside the code

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

Unsigned Lovibond from three points

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

Band mapping of an injected 181°

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

Geometry is the measurement authority

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.

See it running