Skip to content

Visualization data

Most Skin Analysis findings come with spatial information — where on the face the metric was measured, and what specifically was detected. Partners use this to draw markers on the user’s photo, generate before/after panels, or feed an AR pipeline.

The API exposes spatial data in three tiers, ordered by detail:

  1. AOI (Area Of Interest) — a single circle (x, y, radius) per region. Always present on metrics that have any spatial output. Cheapest to render.
  2. Mask — a binary mask described as a list of drawing operations. Returned only when the masks component is requested. Captures exact region boundaries.
  3. Advanced (concern) visualization — per-metric feature data: spots, polylines, or polygon-areas with intensities. Returned only when the <metric>_visualization opt-in is included in the request.

Tier 3 is the right tier for live AR overlays. Tier 1 is enough for “zoom in on this region” UX. Tier 2 is for diagnostic dashboards and before/after panels where exact region shapes matter.

All spatial values — AOI centers, mask shape vertices, advanced-visualization x/y arrays — are in the input image’s pixel coordinate system:

  • Origin (0, 0) is the top-left corner.
  • X axis points right.
  • Y axis points down.
  • Bottom-right corner is (W, H) where W, H are the input image’s pixel width and height.

You can paint visualizations directly onto a canvas of the original image without any coordinate transform.

Every numeric finding’s measurement_locations[] includes AOI_x_o, AOI_y_o, AOI_radius_o:

{
"description": "left cheek",
"value": 0.21,
"AOI_x_o": 412,
"AOI_y_o": 580,
"AOI_radius_o": 90,
"ut_zone": "u-zone"
}

The AOI is an approximation of the region the score was computed over — a circle that bounds the actual mask data. One simple use-case is zooming in on a specific sub-measurement when the user drills into a metric.

When the masks component is included in the request, each measurement_locations[] entry gains a mask_shapes array describing the exact region boundary as a small drawing program (ordered ellipse / polygon / polyline operations, plus a mask shape multiplied at the end).

See the dedicated Masks page for the full vocabulary and the buffer-A/buffer-B implementation walkthrough.

Tier 3 — advanced (concern) visualization

Section titled “Tier 3 — advanced (concern) visualization”

Each metric that supports it has a paired <metric>_visualization request component. Adding it to your ?components=... query enables the per-feature data inside measurement_locations[].visualization_data[].

?components=acne,acne_normalized_visualization,wrinkles,wrinkles_visualization

The supported opt-ins (see the API reference for the full list):

acne_visualization wrinkles_visualization melasma_visualization
acne_normalized_visualization uneven_skin_tone_visualization
texture_visualization redness_visualization skin_sagging_visualization
shine_visualization hyperpigmentation_visualization
freckles_visualization dark_spots_visualization
pore_dilation_visualization skin_type_visualization

If both acne_visualization and acne_normalized_visualization are requested, the response will include only the normalized one.

visualization_data[] is a list of features. Each feature is one of three types — the type is implicit (no kind field), determined by the metric:

Feature typeWhat it drawsMetrics
SpotA single point with an intensity. Length is always 1.acne, freckles, dark spots, hyperpigmentation
PolylineA connected line segment of length-N points (one detected wrinkle, one fold).wrinkles (and its sub-region variants — see Wrinkles)
Polygon areaAn area mask delimited by a coordinate path. Intensity is a categorical integer indexing levels — except skin sagging, whose outlines carry a continuous intensity.redness, melasma, shine, skin type, texture, uneven skin tone, skin sagging, pore dilation

For most polygon-area metrics the response includes one feature per intensity level, and the levels come from thresholding the same underlying field at increasing cutoffs — so higher levels generally sit inside lower ones. That is what makes layered low-opacity fills read as a gradient. Containment is not guaranteed pixel-for-pixel, though: each level is cleaned up with its own morphological pass, so edges can differ slightly.

Three metrics do not follow the nested-levels model at all:

  • shine — levels 1–2 come from the diffuse-sheen field, levels 3–4 from the specular-highlight field. They are two independent pairs, not one four-step ramp, so level 3 is not “worse” than level 2 and does not sit inside it.
  • skin_type — a composite stream: levels 1–4 carry shine data, 5–6 pore_dilation, 7–9 redness. Three unrelated sources sharing one integer axis.
  • skin_sagging — one continuous-intensity outline per contributing region, not levels.

Every feature carries:

{
"length": 27, // 1 for spots, N for polylines/areas
"x": [288.77, 287.29, /* ... */], // length-N x coords (pixels)
"y": [51.61, 51.60, /* ... */], // length-N y coords (pixels)
"intensity": 0.24, // number for spots; integer for area categories; array for polylines
"mean_intensity": 0.240, // average across the feature
"median_intensity": 0.248, // median across the feature
"length_weighted_intensity": 0.231 // present on every feature; only meaningful for wrinkles
}

Intensity shape per type:

  • Spot — intensity is a number; equivalently, an array of length 1.
  • Polyline — intensity is an array of length-N (per-point intensities along the line).
  • Polygon area — intensity is a single integer (the category index, e.g. 1 or 2).

length_weighted_intensity appears on every feature but is only meaningful for wrinkles — there it ranks findings by both how long and how intense the wrinkle is, useful when surfacing the worst few to a user. On spot and polygon-area features it is computed from a single value and carries no information; ignore it. The same applies to mean_intensity / median_intensity on polygon-area features: with one integer level per feature they simply repeat intensity.

Six categories:

CategoryWhat’s availableMetrics
A — noneNo spatial data; the metric is either inverted or a derived combination. value only.dull skin, skin firmness, radiance, radiance adjusted, redness absolute, dark spots adjusted, hyperpigmentation adjusted, skin age
B — AOI onlyAOI_* fields per measurement location.(currently empty)
C — AOI + maskAOI_* + mask_shapes (when masks requested). No advanced viz.eyes
M — mask onlymask_shapes (when masks requested), with no AOI and no score.makeup, hair
D− — advanced (simpler)The advanced visualization structure but with simpler outputs (relevant finding areas only, no per-feature detail).skin sagging
D — AOI + mask + advancedAll three tiers when respective opt-ins are requested.acne, dark spots, freckles, hyperpigmentation, melasma, pore dilation, redness, shine, skin type, smoothness, texture, uneven skin tone, wrinkles

Category M is the odd one. Both metrics carry shapes but nothing numeric attached to them:

  • makeup returns nine cosmetic-application areas as measurement_locations[] — Lips, Liplines, Eyelines, Eyeshadow, Eyebrows, Nose, Left cheek, Right cheek, Skin — each with value: null and no AOI. They exist only to carry mask_shapes.
  • hair has no measurement_locations at all; its outline is on the finding’s own mask_shapes. See Masks → Two places to look.

Without the masks component neither returns anything spatial.

Smoothness footgun: smoothness shares its advanced-visualization data with uneven_skin_tone. To get advanced data for smoothness, request uneven_skin_tone_visualization (the API does not have a separate switch). Both metrics will then carry advanced viz in the response.

intensity means two different things depending on the feature type, and mixing them up is the most common rendering bug:

  • Continuous metrics — intensity is a magnitude. Scaling it into a colour ramp is correct, but the range differs per metric, so parameterize the ramp; don’t assume [0, 1] everywhere.
  • Categorical (polygon-area) metrics — intensity is a level index, an integer counting up from 1. It is not a normalized score. Feeding it straight into a 0..1 colour ramp renders every level at full saturation. Map each integer to a colour explicitly instead.

Continuous

Metricintensity rangeNotes
Wrinkles~[0, 1] per pointintensity is an array — one value per point along the line.
Acne[0, 2500] legacy, [0, 1] normalizedRequest acne_normalized_visualization for the normalized scale.
Hyperpigmentation / dark spots[0, 400]
Freckles[0, 400]
Skin sagging[0, 1]The contributing region’s own score, not a level index.

Categorical — integer level index

MetricLevelsNotes
Redness1..3Count follows your visualization_thresholds; default [.2, .5, .8].
Melasma1..3Default [1.5, 6.0, 10.0].
Texture1..2Default [.2, .6].
Uneven skin tone1..2Default [2.0, 6.0]. Shared with smoothness.
Pore dilation1..2Default [0.0, 0.5].
Shine1..4Fixed meanings, two independent pairs: 1/2 low/high diffuse, 3/4 low/high specular.
Skin type1..9Composite: 1..4 shine, 5..6 pore dilation, 7..9 redness.

For the categorical metrics whose count follows visualization_thresholds, changing that request parameter changes the number of levels you get back — so drive your colour map off the levels actually present in the response rather than hard-coding a count.

Three patterns to bake into your renderer:

1. Prefer mean_intensity ?? intensity for color decisions. When the metric provides a mean_intensity (most do for area metrics), it gives a more stable color than the raw per-point intensity. Fall back to intensity when mean_intensity is absent.

2. Sort visualization_data ascending by intensity before drawing. For the nested-level metrics, drawing low → high gives correct overpainting — the high-intensity area lands on top of the low. For spot/line metrics the sort is harmless and keeps render order deterministic. For shine and skin_type the sort is still worth keeping for determinism, but don’t read the ordering as severity: their levels come from different source fields (see Feature shape), so give each level its own colour rather than a single ramp.

3. Decimate dense polylines. A wrinkle feature can carry hundreds of points. For canvas / SVG / WebGL rendering, keeping every Nth point above a threshold (every 3rd above 20 is a good default) is visually identical and much cheaper.

For paste-ready code for the three feature types, see Rendering recipes.

  • Skin sagging value can be null for males in the lower-face / beard region — beard analysis isn’t reliable, so the API returns null rather than guessing. Render skin sagging with an “n/a” treatment in those cases.
  • Eyeglasses are detected but not compensated. When eyeglasses are present, the API emits status code 314 as a warning and processes the rest of the face anyway; the eye-region results are unreliable. There’s no per-region masking-out of the lens area.
  • Pore dilation works best with ≥1200px face width (status codes 2000 / 2001). On lower-resolution input, pore-dilation findings will be unreliable or aborted.
  • Advanced viz adds payload weight. Don’t request <metric>_visualization for metrics whose visualizations you won’t render — it materially increases response size.
  • Masks — the tier-2 region-boundary format with full shape vocabulary and drawing-syntax walkthrough.
  • Sub-area divisions — which face-region split each metric uses for its measurement_locations[].
  • Rendering recipes — paste-ready spot / polyline / area renderers.
  • Interpreting results — full response-shape walkthrough; the value / value_raw / local_warnings fields.