UMUAnnotator is a modular annotation framework for enriching text with semantic, linguistic and structured information.
It is designed around configurable annotation pipelines: preprocessors, annotators, conflict resolution, metrics, serialization and rendering can be combined from YAML configuration files and executed from the command line.
The project is mainly intended for research workflows, corpus exploration, annotation prototyping and ontology-based text analysis.
- Configuration-driven annotation pipelines using YAML.
- Ontology-based semantic annotation with OWL/RDF.
- Pattern-based annotation using YAML rules, regexes and phrases.
- Temporal annotation using Duckling.
- Quantity annotation using Duckling and optional Stanza preprocessing.
- Linguistic preprocessing with Stanza and local cache.
- Named Entity Recognition with Stanza.
- Global annotation conflict resolution.
- Output profiles:
compactandfull. - Input formats: CSV, JSONL and plain text.
- Annotation output formats: JSON, JSONL and text.
- Metrics:
- corpus summary
- TF-IDF annotation salience
- ontology-aware TF-IDF-e salience
- salience explanation for individual concepts
- Metrics output formats:
- console
- JSON
- CSV
- HTML
- Console and HTML rendering for annotated documents.
- Interactive shell for testing configurations on individual texts.
- Unix-style pipelines using stdin/stdout.
pip install -e .Some optional annotators require external services or models. For example:
- Duckling must be available when using temporal, quantity or Duckling-based annotators.
- Stanza models must be installed when using Stanza preprocessing or Stanza NER.
Run a configured annotation pipeline over a CSV file:
mkdir -p outputs
umuannotator run \
--config configs/pizza_rich.yml \
--input datasets/pizza_es.csv \
--input-format csv \
--text-column text \
--output outputs/pizza_rich.jsonl \
--output-format jsonl \
--output-profile compact \
--no-progressRender annotated documents as HTML:
umuannotator run \
--config configs/pizza_rich.yml \
--input datasets/pizza_es.csv \
--input-format csv \
--text-column text \
--output - \
--output-format jsonl \
--output-profile full \
--no-progress \
| umuannotator render html \
--input - \
--input-format jsonl \
--output outputs/pizza_rich.html \
--title "Pizza Rich"For metrics, JSONL compact output is usually enough:
umuannotator run \
--config configs/news.yml \
--input ~/umuannotator-runs/news_10k/headlines_10k.csv \
--input-format csv \
--text-column headline \
--output ~/umuannotator-runs/news_10k/full_compact.jsonl \
--output-format jsonl \
--output-profile compact \
--no-progressThe shell is useful for testing a configuration quickly without creating input files.
umuannotator shell --config configs/pizza_rich.ymlExample session:
UMUAnnotator shell
Type text to annotate.
Commands: :quit, :exit, :json, :table
umuannotator> Quiero una pizza familiar con masa fina y doble queso, sin piña.
Switch output mode inside the shell:
:json
:table
:quit
The shell uses the same configuration, preprocessors, annotators and resolver as umuannotator run.
UMUAnnotator can read from files or from standard input.
Supported input formats:
csv
jsonl
text
When possible, formats are inferred from file extensions. When reading from stdin, specify the input format explicitly.
Example with stdin:
echo '{"text":"El Gobierno anuncia ayudas hoy."}' \
| umuannotator run \
--config configs/news.yml \
--input - \
--input-format jsonl \
--output - \
--output-format jsonl \
--no-progressFor annotated corpora, umuannotator run supports:
json
jsonl
text
Examples:
umuannotator run \
--config configs/news.yml \
--input headlines.csv \
--input-format csv \
--text-column headline \
--output outputs/news.jsonl \
--output-format jsonlumuannotator run \
--config configs/news.yml \
--input headlines.csv \
--input-format csv \
--text-column headline \
--output outputs/news.json \
--output-format jsonUMUAnnotator supports two output profiles:
compact
full
compact keeps the most useful fields for downstream processing and metrics.
full keeps all available metadata, including richer preprocessor and annotator data.
Example:
umuannotator run \
--config configs/news.yml \
--input headlines.csv \
--input-format csv \
--text-column headline \
--output outputs/news_full.jsonl \
--output-format jsonl \
--output-profile fullA typical configuration has this structure:
preprocessors:
- name: stanza
processors: tokenize,pos,lemma,ner
cache_dir: .cache/stanza
use_cache: true
metadata_key: stanza
ontology:
path: resources/news_es.owl
language: es
annotators:
- name: ontology
color: "#ffd6d6"
- name: pattern
layer: pattern
source: resources/patterns/news_es.yml
color: "#e2f7d4"
- name: stanza-ner
layer: ner
color: "#eadcf8"
- name: temporal
layer: temporal
color: "#d6e4ff"
- name: quantity
layer: cantidades
color: "#fff2cc"
resolver:
enabled: true
strategy: longest_non_overlappingPatternAnnotator loads YAML rule files and supports regex and phrase matching.
Example resource:
name: pizza_order_patterns
language: es
defaults:
layer: pedido
type: order_attribute
match: regex
case_sensitive: false
word_boundaries: false
priority: 0
metadata:
domain: pizza
patterns:
- id: size_small
label: SIZE_SMALL
pattern: '\b(pequeña|pequeño|individual)\b'
metadata:
category: size
- id: size_large
label: SIZE_LARGE
pattern: '\b(grande|familiar|tamaño familiar)\b'
priority: 5
metadata:
category: size
- id: extra_cheese
label: EXTRA_CHEESE
match: phrase
pattern:
- extra de queso
- doble queso
- mucho queso
metadata:
category: modifierThe official pattern format supports:
name
language
defaults
patterns
pattern / patterns
regex / phrase
priority
metadata
exceptions
case_sensitive
word_boundaries
Use it from a pipeline configuration:
annotators:
- name: pattern
layer: pedido
source: resources/patterns/pizza_es.yml
color: "#ffe0b2"The resolver is an explicit phase after annotation.
Example:
resolver:
enabled: true
strategy: longest_non_overlappingThe current resolver strategy keeps a non-overlapping set of annotations, preferring longer spans according to the selected strategy.
This is useful when several annotators or rules produce overlapping candidates.
UMUAnnotator provides corpus-level metrics over annotated documents.
The two main commands are:
umuannotator metrics summary
umuannotator metrics salienceMetrics can read annotated JSON or JSONL files, including stdin.
metrics summary gives descriptive counts over an annotated corpus.
Example:
umuannotator metrics summary \
--input outputs/news.jsonl \
--input-format jsonl \
--top 20Typical sections include:
overview
by_layer
by_label
by_layer_label
top_annotations
umuannotator metrics summary \
--input outputs/news.jsonl \
--input-format jsonl \
--top 20 \
--output-format json \
--output outputs/summary.jsonCSV output is section-based:
umuannotator metrics summary \
--input outputs/news.jsonl \
--input-format jsonl \
--top 20 \
--section by_layer \
--output-format csv \
--output outputs/summary_by_layer.csvOverview CSV:
umuannotator metrics summary \
--input outputs/news.jsonl \
--input-format jsonl \
--section overview \
--output-format csv \
--output outputs/summary_overview.csvumuannotator metrics summary \
--input outputs/news.jsonl \
--input-format jsonl \
--top 20 \
--output-format html \
--output outputs/summary.htmlSingle-section HTML:
umuannotator metrics summary \
--input outputs/news.jsonl \
--input-format jsonl \
--top 20 \
--section by_layer \
--output-format html \
--output outputs/summary_by_layer.htmlmetrics salience computes a global ranking of relevant annotations in an annotated corpus.
It tries to answer:
Which annotations are most informative or characteristic in this corpus?
It supports two methods:
tfidf
tfidf-e
The default method uses TF, DF, IDF and TF-IDF over annotations.
umuannotator metrics salience \
--input outputs/news.jsonl \
--input-format jsonl \
--method tfidf \
--top 20The metrics are:
| Metric | Meaning |
|---|---|
TF |
Total number of occurrences in the corpus |
DF |
Number of documents containing the annotation |
IDF |
Document-level rarity |
score |
TF * IDF |
The smoothed IDF formula is:
idf = log((N + 1) / (df + 1)) + 1
where:
N = total number of documents
df = documents containing the annotation
Annotations are grouped using a canonical key.
The current priority is:
1. metadata.concept_uri
2. metadata.wikidata
3. metadata.normalized + metadata.unit
4. metadata.normalized + metadata.grain
5. metadata.normalized
6. lowercased surface text
Example:
{
"text": "Gobierno",
"layer": "ontology",
"label": "Government",
"metadata": {
"concept_uri": "http://example.org/news-es#Government"
}
}Canonical key:
concept_uri:http://example.org/news-es#Government
tfidf-e extends TF-IDF using ontology relations.
It can propagate salience through an ontology graph using a configurable maximum distance, decay and direction.
Example:
umuannotator metrics salience \
--input outputs/news.jsonl \
--input-format jsonl \
--method tfidf-e \
--ontology resources/news_es.owl \
--max-distance 2 \
--decay 0.5 \
--direction both \
--top 20The output includes:
score
observed_score
expanded_score
expanded_from
Meaning:
| Field | Meaning |
|---|---|
observed_score |
Direct TF-IDF score from observed annotations |
expanded_score |
Score received from related ontology concepts |
score |
Total score |
expanded_from |
Concepts that contributed through expansion |
Available directions:
outgoing
incoming
both
For TF-IDF-e, individual concepts can be inspected with --explain.
umuannotator metrics salience \
--input outputs/news.jsonl \
--input-format jsonl \
--method tfidf-e \
--ontology resources/news_es.owl \
--max-distance 2 \
--decay 0.5 \
--direction both \
--explain TrafficAccidentJSON output:
umuannotator metrics salience \
--input outputs/news.jsonl \
--input-format jsonl \
--method tfidf-e \
--ontology resources/news_es.owl \
--max-distance 2 \
--decay 0.5 \
--direction both \
--explain TrafficAccident \
--output-format json \
--output outputs/explain_traffic_accident.jsonumuannotator metrics salience \
--input outputs/news.jsonl \
--input-format jsonl \
--method tfidf \
--top 20umuannotator metrics salience \
--input outputs/news.jsonl \
--input-format jsonl \
--method tfidf \
--top 20 \
--output-format json \
--output outputs/salience.jsonumuannotator metrics salience \
--input outputs/news.jsonl \
--input-format jsonl \
--method tfidf \
--top 20 \
--output-format csv \
--output outputs/salience.csvumuannotator metrics salience \
--input outputs/news.jsonl \
--input-format jsonl \
--method tfidf \
--top 50 \
--output-format html \
--output outputs/salience.htmlTF-IDF-e HTML:
umuannotator metrics salience \
--input outputs/news.jsonl \
--input-format jsonl \
--method tfidf-e \
--ontology resources/news_es.owl \
--max-distance 2 \
--decay 0.5 \
--direction both \
--top 50 \
--output-format html \
--output outputs/salience_tfidfe.htmlFilter by layer:
umuannotator metrics salience \
--input outputs/news.jsonl \
--input-format jsonl \
--layer ontology \
--top 20Filter by label:
umuannotator metrics salience \
--input outputs/news.jsonl \
--input-format jsonl \
--label DATE \
--top 20run, render and metrics can be combined using stdin/stdout.
Run and calculate salience:
umuannotator run \
--config configs/news.yml \
--input headlines.csv \
--input-format csv \
--text-column headline \
--output - \
--output-format jsonl \
--output-profile compact \
--no-progress \
| umuannotator metrics salience \
--input - \
--input-format jsonl \
--method tfidf \
--top 20Run and render HTML:
umuannotator run \
--config configs/pizza_rich.yml \
--input datasets/pizza_es.csv \
--input-format csv \
--text-column text \
--output - \
--output-format jsonl \
--output-profile full \
--no-progress \
| umuannotator render html \
--input - \
--input-format jsonl \
--output outputs/pizza_rich.html \
--title "Pizza Rich"Show ontology statistics:
umuannotator ontology info \
--config configs/pizza_rich.ymlList ontology concepts:
umuannotator ontology concepts \
--config configs/pizza_rich.ymlInspect semantic distances:
umuannotator ontology distances \
--config configs/pizza_rich.yml \
--concept HawaianPizzaInspect generated graph relations:
umuannotator ontology relations \
--config configs/pizza_rich.ymlUMUAnnotator can also be used from Python. The programmatic API is currently oriented to research and experimentation, and may still evolve.
For most use cases, prefer run_from_config, which mirrors the CLI behavior.
Este es el más estable porque reutiliza exactamente el CLI:
from umuannotator.pipeline.runner import run_from_config
from umuannotator.io.output import write_output
data = run_from_config(
config_path="configs/news.yml",
input_path="headlines.csv",
input_format="csv",
text_column="headline",
show_progress=False,
)
write_output(
data,
"outputs/news.jsonl",
output_format="jsonl",
output_profile="compact",
)
from umuannotator.config.loader import load_config
from umuannotator.document.model import Document
from umuannotator.pipeline.runner import build_pipeline_from_config
from umuannotator.resolution.resolver import (
apply_resolver_if_enabled,
resolver_config_from_dict,
)
from umuannotator.serialization.documents import serialize_document
config = load_config("configs/news.yml")
pipeline, _context = build_pipeline_from_config(config)
resolver_config = resolver_config_from_dict(config.get("resolver"))
document = Document(
text="El Real Madrid ganó al Barcelona.",
metadata={},
)
document = pipeline.run_document(document)
document.annotations = apply_resolver_if_enabled(
document.annotations,
config=resolver_config,
)
serialized = serialize_document(
document,
output_profile="compact",
)
print(serialized)
umuannotator/
├── annotators/
├── cli/
├── config/
├── document/
├── io/
├── lang/
├── metrics/
│ └── output/
├── ontology/
├── pipeline/
├── preprocessors/
├── renderers/
├── resolution/
├── resources/
└── serialization/
Run tests:
pytest -qCheck for likely unused code:
vulture src/umuannotator --min-confidence 80 --exclude "*/__pycache__/*"Remove Python caches:
find . -type d -name "__pycache__" -prune -exec rm -rf {} +
find . -name "*.pyc" -deleteUMUAnnotator is under active development.
The architecture is stable enough for experimentation and research workflows. The tool is suitable for iterative annotation experiments, ontology prototyping, corpus exploration and metrics generation.
It should still be considered a research-oriented tool rather than a fully stable public API.
Current priorities include:
- clearer configuration validation
- more regression tests with real corpora
- improved documentation
- better handling of raw vs resolved annotations
- larger-scale streaming workflows
MIT License
