Skip to main content

anatomapa

anatomapa

Paint the human body with your data.
From a dict or a spreadsheet to a publish-ready anatomical heatmap in SVG, in a few lines of Python and with zero dependencies.

EN English  ·  PT-BR Português

PyPI Python 3.10+ License MIT zero dependencies DOI

Male and female anatomical heatmap with legend

About

anatomapa is a Python library that generates anatomical heatmaps of the external human body surface: you feed it values per region (frequency, intensity or event density) and it returns the body colored, front and back views, male or female, with color proportional to each region's value. It fits any field that records the body region: venomous animal accidents, occupational trauma, sports injuries, forensics, burns and dermatology.

  • Zero dependencies: Python stdlib only at the core; PNG/JPG is an optional extra.
  • Deterministic: the same input always produces the exact same SVG.
  • Bilingual: understands region names in Portuguese and English and writes labels in either.

Installation

pip install anatomapa

Requires Python 3.10+. For PNG/JPG/JPEG (raster output), install the optional extra, which brings cairosvg and Pillow. The quotes matter, zsh and fish treat brackets as a glob:

pip install "anatomapa[raster]"

Quick start

import anatomapa as am

fig = am.heatmap({"hand": 5153, "foot": 13666, "face": 845})
fig.save("map.svg")

Done: an SVG with hands, feet and face colored by value. The rest of this manual is optional.

Quick examples

import anatomapa as am
from anatomapa import Region

am.heatmap({
    Region.UPPER_CHEST: 90,
    Region.HAND: 40,
    Region.KNEE: 12,
}).save("map.svg")

A dict with three regions is enough. A region with no value comes out in neutral grey, meaning "no data", not "low value"; and a hand with no side suffix paints both hands.

Map with upper chest, hands and knees colored and everything else in grey

am.heatmap(
    {Region.LEG: 80, Region.UPPER_BACK: 30},
    view="posterior",
).save("map.svg")

leg is the whole lower limb: a single value flows down to buttocks, thigh, knee, lower leg, ankle, foot and toes. Inheritance in action: send data at whatever level you have.

Posterior view with the whole lower limb hot and the upper back cooler

am.heatmap({
    Region.LEG_RIGHT: 80,        # whole right lower limb
    Region.THIGH_LEFT: 20,       # the left one, segment by segment
    Region.LOWER_LEG_LEFT: 95,
    Region.FOOT_LEFT: 40,
    Region.UPPER_CHEST_RIGHT: 60,
}).save("map.svg")

One side as a whole, the other in detail, in the same call. On the right everything comes out at 80; on the left only what was declared paints, and the toes inherit 40 from the foot, their closest ancestor. The left hip gets no value and stays unpainted: whoever details a side owns covering it all.

Uniform right side and left side detailed segment by segment

data = {
    Region.UPPER_BACK: 120,
    Region.LOWER_BACK: 340,
    Region.SHOULDER_LEFT: 90,
    Region.KNEE: 55,
}

fig = am.heatmap(data, view="both", body="female", title="Injuries by region")
fig.save("map.svg")
fig.save("map.png")  # the format comes from the extension

The figure is an object: build the data in a variable, render once and save to as many formats as you want, with nothing recomputed (.png needs the raster extra). view="both" draws front and back sharing one scale and one legend.

# one row per region; you declare the columns, nothing is guessed
data = am.from_xlsx("injuries.xlsx", sheet="2024",
                    region_col="Region", value_col="Total", header=True)

# spreadsheet labels rarely match the ids, so the mapping is yours
mapping = {"HAND": Region.HAND, "FORE-ARM": Region.FOREARM, "LOWER LEG": Region.LOWER_LEG}

print(am.validate(data, region_map=mapping))  # check before drawing
am.heatmap(data, region_map=mapping).save("map.svg")

Resolution is strict, so case, accents, spaces and hyphens all count: that is what region_map is for. validate() is a dry-run that draws nothing, so you can check the mapping before rendering. Mind the cut: a spreadsheet with ARM and FORE-ARM on separate rows wants UPPER_ARM (the segment above the elbow), not ARM, which is the whole limb; the same goes for LEG next to THIGH, which is LOWER_LEG.

heatmap() parameters

am.heatmap(values, view="anterior", body="male", lang="pt", format="svg",
           title=None, background="transparent", on_unknown="error",
           region_map=None, split=False)
Parameter Values Default What it does
values dict {region: value} or (region, value) pairs required Data; keys may be Region; accepts reader output directly
view "anterior", "posterior", "both" "anterior" Front, back, or the two side by side sharing one legend
body "male", "female" "male" Male or female body
lang "pt", "en" "pt" Language of labels written in the SVG
format "svg", "png", "jpg", "jpeg" "svg" Default when the saved path has no extension; png/jpg/jpeg need the raster extra
title str or None None Optional: the title is only drawn when given
background "dark", "light", "transparent" "transparent" Figure background
on_unknown "error", "skip", "warn" "error" What to do with an unrecognized name
region_map dict {your label: region id} None Your own name mapping; takes precedence
split True, False False Only with view="both", otherwise ValueError: True returns the (front, back) pair of independent figures sharing the same color scale

Returns a Figure object. An unknown region name raises ResolutionError (the library's public exception). The palette is always the thermal one (cold blue to hot orange, thermal-camera look) and the scale always linear, no choice.

Background and title

  • background: "dark" (#0a0a0a), "light" (#ffffff) or "transparent" (default); legend colors adapt to the chosen background.
  • title: bold, centered above the body; a long title shrinks its font to fit and the text is also kept as the SVG <title>. With split=True, each figure gets the title.
  • Legend and thermal gradient: native, no parameter. The scale only counts values that actually paint (an aggregator fully covered by its children is left out), and tick labels gain decimal places whenever rounding to integers would repeat two of them.

Region names

Identification is strict: a label must be the exact region id or a key of your region_map; nothing is guessed. An unknown name raises ResolutionError, listing what failed and suggesting the closest match. Control it with on_unknown: "error" (default), "skip" or "warn".

In code: the Region enum. from anatomapa import Region gives 94 constants: the 32 canonical ids plus _LEFT/_RIGHT versions of the 31 bilateral ones. Each member is the id string itself (Region.HAND == "hand"), with autocomplete and typos becoming immediate errors.

Laterality. Every region is bilateral except genital, central. The id suffix picks the side: Region.HAND_RIGHT paints only the right hand; without it, both sides. Sides follow the observer's convention (left of the image), not the anatomical one; a side on a central region is an error.

Your spreadsheet names: region_map. You declare the correspondence once, in code; keys compare exactly as written in the source, accents and case included. Full Excel example in the Quick examples.

Regions and hierarchy

There are 32 regions in a tree up to 3 levels deep; the Inside of column shows each region's parent. A row with no mark under Front or Back is an aggregator: it draws nothing, and a value on it flows down to its children.

Id Region Inside of Front Back
head Head root
face Face head ✓
skull Skull head ✓
neck Neck head ✓ ✓
trunk Trunk root
shoulder Shoulder trunk ✓ ✓
chest Chest trunk
upper_chest Upper chest chest ✓
lower_chest Lower chest chest ✓
abdomen Abdomen trunk
upper_abdomen Upper abdomen abdomen ✓
lower_abdomen Lower abdomen abdomen ✓
back Back trunk
upper_back Upper back back ✓
lower_back Lower back back ✓
genital Genital region trunk ✓
arm Upper limb root
upper_arm Upper arm arm ✓ ✓
elbow Elbow arm ✓ ✓
forearm Forearm arm ✓ ✓
wrist Wrist arm ✓ ✓
hand Hand arm ✓ ✓
finger Fingers hand ✓ ✓
leg Lower limb root
hip Hip leg ✓
buttocks Buttocks leg ✓
thigh Thigh leg ✓ ✓
knee Knee leg ✓ ✓
lower_leg Lower leg leg ✓ ✓
ankle Ankle leg ✓ ✓
foot Foot leg ✓ ✓
toe Toes foot ✓ ✓

Inheritance (rollup), side-aware. Send data at whatever level you have. To paint each region, the library walks up the tree and uses the first ancestor holding a value. Three rules:

  1. Walk up until a value is found, and stop at the first one.
  2. At each step, the explicit side beats the general key (foot_left before foot).
  3. A deeper region always beats a shallower ancestor: in {"leg_left": 10, "foot": 2}, the left foot is 2, not 10.

The real forensic case, one side as a whole and the other in detail, is the third of the Quick examples.

Migrating from 0.3 (breaking changes). leg was the calf (now lower_leg) and arm was the segment above the elbow (now upper_arm); today they are the whole limbs. Since 0.4.0 there is no runtime warning anymore: old code runs silently while painting something else, so review your leg and arm usages before upgrading. pelvis became hip and buttocks moved under leg, so a value on trunk no longer paints the buttocks; chest, abdomen and back remain valid, but became aggregators.

Regions with no data natively show in neutral grey (#9aa0a6), distinct from cold: "no data" never reads as "few cases". No parameter.

Data input

heatmap() accepts a dict or any iterable of (region, value) pairs, so every reader's output goes in directly. In all of them you declare the columns; nothing is guessed.

  • from_dict(data): normalizes a {region: value} dict into pairs.
  • from_csv(source, region_col, value_col, delimiter=","): CSV file or string.
  • from_json(source, region_key, value_key): object {"hand": 10} or list [{"region": ..., "value": ...}].
  • from_records(records, region_col, value_col): dicts, namedtuples, dataclasses and pandas DataFrames (duck typing).
  • from_xlsx(source, sheet, region_col, value_col, header, aggregate): Excel .xlsx with no external dependency; column by index, letter "D" or header name. aggregate="count" counts one row per case, "sum" sums and None (default) expects one row per region. Full example in the Quick examples.

Output: the Figure object

Usage Result
fig.save("map.svg") writes the file; the path extension (.svg, .png, .jpg) decides the format, fig.save(path, format="png") overrides it and, with no extension, the heatmap's format applies
fig.to_svg() returns the SVG as a str
fig.to_png() returns the PNG as bytes (requires anatomapa[raster])
fig.to_jpeg() returns the JPEG as bytes (requires anatomapa[raster])
str(fig) same as to_svg(), raw SVG (handy in templates)
Jupyter cell renders inline automatically

For raster output, install the extra pip install "anatomapa[raster]" (cairosvg and Pillow, imported only on demand; the core stays zero-dep). Figures rasterise with the same thermal look as the SVG; without the extra, png/jpg/jpeg raises ImportError with the hint.

Utilities

validate(values, body="male", region_map=None) does the dry-run: shows what resolves and what does not, without rendering.

am.validate({"hand": 1, "haand": 2, "hand_right": 3})
# {'resolved': {'hand': 'hand', 'hand_right': 'hand_right'},
#  'unresolved': {'haand': {'reason': 'região desconhecida', 'suggestions': ['hand', 'head', 'hand_left']}}}

list_regions(lang="pt", body="male", view=None) lists the regions, each as {"id", "label", "bilateral", "parent", "views"}; view filters per view (aggregators, whose views is empty, always appear).

Gallery

Same map over dark, light and transparent backgrounds, male and female Male and female anatomical model

Citation

Every release is archived on Zenodo with a permanent DOI. Cite the concept DOI below: it always resolves to the latest archived version.

@software{rosa_anatomapa,
  author    = {Rosa, Pedro and Reis, Marcelo and Melo, Mozart},
  title     = {anatomapa: a Python library for anatomical heatmaps
               of the human body surface},
  publisher = {Zenodo},
  doi       = {10.5281/zenodo.22017924},
  url       = {https://doi.org/10.5281/zenodo.22017924}
}

GitHub also generates the citation for you: use the Cite this repository button in the sidebar, backed by CITATION.cff.

License and attribution

Released under the MIT license. See LICENSE.

The SVG model silhouettes derive from a public-domain (CC0) source; details in app/anatomapa/assets/ATTRIBUTION.txt.

Credits

  • Pedro Rosa: developer/creator
  • Marcelo Reis: Professor of the Graduate Program in Environmental Systems Analysis, CESMAC
  • Mozart Melo: coordinator/advisor, CESMAC
  • Centro Universitário CESMAC: institution

⭐ If anatomapa helped you, leave a star on the repo! ⭐

Metadata

Release files for anatomapa 0.4.5

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for anatomapa 0.4.5
File Size Uploaded
anatomapa-0.4.5.tar.gz 104.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for anatomapa 0.4.5
File Interpreter ABI Platform
anatomapa-0.4.5-py3-none-any.whl Python 3 none any Details

Total release size: 210.5 kB

Release files / anatomapa-0.4.5.tar.gz

Download URL anatomapa-0.4.5.tar.gz
Size 104.6 kB
Tags Source
SHA-256 checksum
How to use checksums
2a561fc27aa009da30f7d3b4a8d6d76004d63f6ce60bac3cba62e93b0d55d153
BLAKE2b-256 checksum
How to use checksums
faa1d950ab72ea838f0bdf0cf476544374baa758058733283af73f9a364e2b3c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

Release files / anatomapa-0.4.5-py3-none-any.whl

Download URL anatomapa-0.4.5-py3-none-any.whl
Size 105.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9fbd75f43b22a383dd3947638c289f0052de671ec44a7eaaad8c256979c12d03
BLAKE2b-256 checksum
How to use checksums
46940c88b51320e7372984e48ae067bb51d9145027e1f683ff3e193ccf2bf72f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.4.5 This release

2 release files

0.4.4

2 release files

0.4.3

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.9

2 release files

0.3.8

2 release files

0.3.7

2 release files

0.3.6

2 release files

0.3.5

2 release files

0.3.4

2 release files

0.3.3

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page