A modular Go-based tool for extracting and analyzing dataset-level metrics from Bigpicture metadata files.
This tool processes structured metadata (e.g. image.xml, sample.xml, staining.xml, etc.) within a dataset archive and computes standardized metrics for post-upload analysis, dashboards, and validation support.
It is designed to:
- Handle thousands of datasets in a memory-efficient manner
- Output machine-readable public summaries and structured per-dataset metrics
- Enforce dataset policy constraints for
type_of_dataset
⚠️ Note: This tool assumes specific XML structure as used in the current Bigpicture metadata standard. It expects entries such asSTRING_ATTRIBUTEandCODE_ATTRIBUTEand does not normalize values unless explicitly configured.
Public metrics are defined by PublicMetrics and include only dataset-wide totals:
number_of_datasetsnumber_of_wsisnumber_of_imagesnumber_of_filesnumber_of_observationsnumber_of_casesnumber_of_specimensnumber_of_blocksnumber_of_slidesnumber_of_biological_beingsnumber_of_stainingsnumber_of_annotations
These represent flat aggregated counts across all datasets.
No internal structures, no compound metrics, no organisations, no node affiliation.
Internal metrics are defined by InternalMetrics and include:
datasets_per_organisation(PIC → dataset count)number_of_unique_organisations
anatomical_site_labels(SNOMED CT code → meaning)
sex_counts_of_beingsspecies_counts_of_beingsanatomical_site_countsage_group_counts
sex_proportionsspecies_proportionsanatomical_site_proportionsage_group_proportions
wsi_staining_usage_per_diagnosiswsi_per_specieswsi_per_species_proportionwsi_per_sexwsi_per_sex_proportion
Node affiliation is not part of internal metrics, as that information is not available in the metadata files.
Extracted via ExtractDatasetFileMetrics:
- Metadata Standard Version
Read from either<METADATA_STANDARD>. - Dataset Version
Read from<VERSION>.
Extracted via ExtractImageMetrics:
- Number of WSIs
Count of uniquealiasvalues under<IMAGE_OF>. - Number of images
Count of<IMAGE>elements. - Number of image files
Count of<FILE>elements inside<FILES>.
Extracted via ExtractObservationMetrics:
- Number of observations
Count of<OBSERVATION>elements. - Statement types
Frequency of<STATEMENT_TYPE>values. - Diagnosis counts
Based on<MEANING>inside<CODE_ATTRIBUTE>only for statements of type "Diagnosis". - ObsDiagnosis
Internal map (observation → diagnosis) used in compound metrics.
Extracted via ExtractSampleMetrics:
- Number of biological beings
- Number of cases
- Number of specimens
- Number of blocks
- Number of slides
- Sex counts (per biological being)
- Species counts (per biological being)
- Anatomical sites (unique list and SNOMED CT mappings)
- Age groups (combinations of
interval_start+interval_length)
Internal maps for compound metrics:
SexOfBeingSpeciesOfBeing
Extracted via ExtractStainingMetrics:
- Number of stainings
- Lists of staining methods, targets, and compounds
- SNOMED CT codes per staining (via
staining_procedure) - H&E vs Other count (based on SNOMED CT
104210008)
Note: No anomaly detection is implemented in the current code.
Extracted via ExtractAnnotationMetrics:
- Number of annotations
Extracted via ExtractOrganisationMetrics:
- Submitting organisation PIC
- Submitting organisation name
Extracted via ExtractPolicyFileMetrics:
- Validates
type_of_datasetagainst the supported controlled vocabulary (case/whitespace tolerant).
Datasets withtype_of_dataset = "Clinical/Anonymized"are eligible for the public aggregate (global_summary.json). Other allowed types are still processed but excluded from public metrics.
Compound metrics use the alias-based reference graph extracted from:
sample.xmlimage.xmlobservation.xmlthroughExtractReferences, allowing traversal across beings → specimens → blocks → slides → stainings.
Computed via CountWSIsPerDiagnosisAndStain.
- Produces:
Diagnosis → { "Hematoxylin and eosin": N, "Other": M } - Uses:
ObsDiagnosis(observation → diagnosis)- Reference graph (observation → slide resolution)
StainCodes(stainingID → SNOMED CT code)
Slides with SNOMED CT code 104210008 are counted as H&E, all others as Other.
Computed via CountWSIsPerSpeciesAndSex.
- Produces:
- species → WSI count
- sex → WSI count
- Logic:
- WSIs = all slides recorded in the reference graph.
- For each slide: resolve block(s) → specimen(s) → being.
- Read species/sex from
SpeciesOfBeing/SexOfBeing. - If a slide has no species/sex or multiple values → counted as
"Unknown species" or "Unknown sex".
The current implementation of bpmetrics delivers the KPI extraction required for BIGPICTURE Deliverable D3.05.
All KPIs that can be derived from the existing metadata files (dataset.xml, image.xml, sample.xml, observation.xml, staining.xml, annotation.xml, organisation.xml) are fully implemented.
Note: Landingpage entity is not considered in the current scope of bpmetrics.
Several KPIs defined in the deliverable cannot be computed because the required information is not present in the Bigpicture metadata. These include:
- Total dataset size (GB)
- List of Geographical Areas
- Contributing Center Names
- Node affiliation
bpmetrics /path/to/archive_root- Outputs public metrics in
global_summary.json - Writes dataset-level IO/parse issues to
dataset_errors.jsonwhen present
Optional outputs (written only when CRYPT4GH_RECIPIENT_PUBLIC_KEY is set):
per_dataset_metrics.json.c4gh(enabled withWRITE_PER_DATASET_METRICS=true)refgraph_<dataset>.json.c4ghdebug files (enabled withWRITE_DEBUG_REFGRAPHS=true)
Internal metrics are never written in plaintext.
If the environment variable CRYPT4GH_RECIPIENT_PUBLIC_KEY is provided,
the runner encrypts the internal summary directly in memory and writes only:
internal_summary.json.c4gh
No plaintext internal_summary.json is written to disk.
The program is split across
main.go(tiny entrypoint) andrunner.go(logic).
Usego run . <archive_root>or the built binary.
If encrypted internal metrics are enabled, the runtime environment must provide
the crypt4gh CLI. The official container image includes this dependency.
.
├─ main.go # Minimal entrypoint: initializes logging, parses args, calls Run()
├─ runner.go # Core pipeline: dataset scan, step execution, aggregation, JSON outputs
├─ steps_definitions.go # Registry of dataset-level and compound metric steps
├─ constants.go # Filenames, environment defaults, node names
├─ helpers.go # Shared helpers (normalization, merging, utilities)
├─ process_dataset.go # Per-dataset orchestration and error handling
└─ metrics/
├─ dataset.go # dataset.xml extractor
├─ image.go # image.xml extractor
├─ observation.go # observation.xml extractor
├─ sample.go # sample.xml extractor
├─ staining.go # staining.xml extractor
├─ annotation.go # annotation.xml extractor
├─ organisation.go # organisation.xml extractor
├─ policy.go # policy.xml validation
├─ references.go # Reference graph extraction (alias-based relationships)
├─ compound.go # graph-based compound metrics
├─ types.go # shared metric types
└─ xmltree/ # generic XML tree loader (used by reference graph)
bpmetrics emits structured logs to stderr (JSON by default). Fields:
ts— RFC3339Nano UTC timestamplevel—debug|info|warn|errormsg— messagerun_id— 16‑char hex ID for the current run- Context (when available):
dataset_id,op,code,path,err
Env vars:
LOG_LEVEL(debug|info|warn|error, defaultinfo)LOG_FORMAT(jsondefault, ortext)LOG_SOURCE(trueto include file:line)RUN_ID(set your own 16‑char hex, else random)
Example:
RUN_ID=2025cafebabe1234 LOG_LEVEL=debug LOG_FORMAT=json ./bpmetrics /path/to/archive_root 2> bpmetrics.log# grep (JSON lines)
grep '"run_id":"2025cafebabe1234"' bpmetrics.log
# jq
jq 'select(.run_id=="2025cafebabe1234")' bpmetrics.logApache 2.0
This project is part of the Bigpicture Initiative.