Skip to main content

Figure-Statistics Bundle: A specification for reproducible scientific figures

Project description

FSB: Figure-Statistics Bundle

Python 3.9+ License: MIT

FSB is a specification and Python API for reproducible scientific figures. It defines a structured bundle format that captures everything needed to reproduce a figure: data, encodings, theme, and statistical analyses.

Why FSB?

Scientific figures typically become "black boxes" once exported to PNG/PDF:

  • Data is lost or separated from the figure
  • Statistical test parameters are not recorded
  • Visual encoding decisions (color = condition) are implicit
  • Reproducing or modifying the figure requires the original script

FSB solves this by packaging figures as self-contained bundles with:

  • Canonical data (CSV + metadata)
  • Encoding specification (data → visual channel mappings)
  • Theme (decorative styles, separable from meaning)
  • Statistical results (with full reproducibility info)
  • Derived outputs (PNG, SVG, PDF)

Installation

pip install fsb

Or install with optional dependencies:

pip install fsb[all]  # Include matplotlib, scipy, statsmodels

Quick Start

Create a Bundle

import fsb

# Create a new plot bundle
bundle = fsb.Bundle(
    "my_plot",
    create=True,
    node_type="plot",
    name="My First Plot",
    size_mm={"width": 80, "height": 60},
)

# Configure encoding
bundle._encoding = {
    "traces": [{
        "trace_id": "line1",
        "data_ref": "data/data.csv",
        "x": {"column": "time", "scale": "linear"},
        "y": {"column": "value", "scale": "linear"},
    }]
}

# Save
bundle.save()

Load and Inspect

bundle = fsb.Bundle("my_plot")
print(f"Type: {bundle.bundle_type}")
print(f"Size: {bundle.node.size_mm}")
print(f"Traces: {len(bundle.encoding['traces'])}")

Create Figure with Children

# Create a multi-panel figure
figure = fsb.Bundle(
    "my_figure",
    create=True,
    node_type="figure",
    size_mm={"width": 170, "height": 130},
)

# Add child plots
for panel in ["A", "B", "C", "D"]:
    figure.add_child(f"plot_{panel}", node_type="plot")

figure.save()

Validate Against Schemas

bundle = fsb.Bundle("my_plot")
results = bundle.validate(raise_on_error=False)

for file_name, (is_valid, error) in results.items():
    status = "✓" if is_valid else "✗"
    print(f"{status} {file_name}")

Bundle Structure

my_plot/
├── node.json           # Canonical: structure, bbox, refs
├── encoding.json       # Canonical: data → visual mappings
├── theme.json          # Aesthetics: colors, fonts, styles
├── data/
│   ├── data.csv        # Canonical: raw data
│   └── data_info.json  # Canonical: column metadata
├── stats/
│   ├── stats.json      # Canonical: statistical results
│   └── stats.csv       # Derived: human-readable table
├── exports/            # Derived: rendered outputs
│   ├── plot.png
│   ├── plot.svg
│   └── plot.pdf
├── cache/              # Regenerable: can be deleted
│   ├── geometry_px.json
│   ├── render_manifest.json
│   └── hitmap.svg
└── children/           # For figures: child plot bundles

File Categories

Category Files Description
Canonical node.json, encoding.json, theme.json, data/*, stats/stats.json Source of truth. Don't regenerate.
Derived exports/*, stats/stats.csv Generated from canonical. Can regenerate.
Cache cache/* Temporary/UI data. Can delete.

Schema Reference

node.json

Defines the figure/plot structure:

{
  "id": "my_plot",
  "type": "plot",
  "name": "Firing Rate Over Time",
  "bbox_norm": {"x0": 0, "y0": 0, "x1": 1, "y1": 1},
  "size_mm": {"width": 80, "height": 60},
  "axes": {
    "xlim": [0, 100],
    "ylim": [0, 50],
    "xlabel": "Time (ms)",
    "ylabel": "Rate (Hz)"
  },
  "refs": {
    "encoding": "encoding.json",
    "theme": "theme.json",
    "data": "data/",
    "stats": "stats/stats.json"
  },
  "children": ["subplot_A", "subplot_B"]
}

encoding.json

Maps data to visual channels:

{
  "traces": [
    {
      "trace_id": "control",
      "data_ref": "data/data.csv",
      "x": {"column": "time", "scale": "linear"},
      "y": {"column": "firing_rate", "scale": "linear"},
      "group": {"column": "condition"}
    }
  ],
  "legends": {
    "color": {
      "control": "Control Group",
      "treatment": "Treatment Group"
    }
  }
}

theme.json

Visual aesthetics (decorative, not semantic):

{
  "colors": {
    "palette": ["#1f77b4", "#ff7f0e", "#2ca02c"],
    "background": "#ffffff"
  },
  "typography": {
    "family": "sans-serif",
    "size_pt": 10
  },
  "lines": {"width_pt": 1.5},
  "markers": {"size_pt": 6}
}

stats/stats.json

Statistical analyses with reproducibility info:

{
  "analyses": [
    {
      "result_id": "ttest_main",
      "method": {
        "name": "t-test",
        "variant": "independent",
        "correction": {"method": "bonferroni", "n_comparisons": 3}
      },
      "inputs": {
        "data_refs": [{"path": "data/data.csv", "sha256": "abc123..."}],
        "groups": ["control", "treatment"],
        "n_per_group": [25, 23]
      },
      "results": {
        "statistic": 2.45,
        "p_value": 0.018,
        "p_corrected": 0.054,
        "effect_size": {"name": "cohens_d", "value": 0.72}
      },
      "display": {
        "significance_label": "*",
        "bracket": {"start": "control", "end": "treatment"}
      }
    }
  ],
  "software": {
    "python": "3.11",
    "scipy": "1.11.0",
    "fsb": "0.1.0"
  }
}

data/data_info.json

Column metadata:

{
  "source": {
    "path": "data/data.csv",
    "sha256": "abc123...",
    "created_at": "2025-12-19T12:00:00"
  },
  "shape": {"rows": 100, "columns": 4},
  "columns": [
    {
      "name": "time",
      "dtype": "float64",
      "unit": "ms",
      "role": "x",
      "min": 0,
      "max": 100
    },
    {
      "name": "firing_rate",
      "dtype": "float64",
      "unit": "Hz",
      "role": "y"
    },
    {
      "name": "condition",
      "dtype": "category",
      "role": "group",
      "categories": ["control", "treatment"]
    }
  ]
}

Key Concepts

Canonical vs Derived

  • Canonical: The source of truth. These files define the figure.
  • Derived: Generated from canonical files. Can be regenerated.
  • Cache: Temporary files for UI/rendering. Can be deleted.

Recursive Structure

Figures and plots share the same node structure. A figure is simply a node with children:

figure/
├── node.json (type: "figure", children: ["plot_A", "plot_B"])
└── children/
    ├── plot_A/
    │   └── node.json (type: "plot")
    └── plot_B/
        └── node.json (type: "plot")

Encoding vs Theme

  • Encoding: Semantic mappings (color = condition, linestyle = group)
  • Theme: Decorative properties (font family, line width, color palette)

Changing theme = same meaning, different look. Changing encoding = different meaning.

API Reference

Bundle

class Bundle:
    def __init__(self, path, create=False, node_type="plot", name=None, size_mm=None): ...

    # Properties
    @property def node(self) -> Node: ...
    @property def encoding(self) -> dict: ...
    @property def theme(self) -> dict: ...
    @property def stats(self) -> dict: ...
    @property def bundle_type(self) -> str: ...
    @property def children(self) -> List[str]: ...

    # Methods
    def save(self, path=None) -> Path: ...
    def validate(self, raise_on_error=True) -> dict: ...
    def add_child(self, child_id, **kwargs) -> Bundle: ...
    def get_child(self, child_id) -> Bundle: ...
    def clear_cache(self) -> None: ...
    def to_dict(self) -> dict: ...

Schema Validation

from fsb import validate, load_schema, SCHEMA_NAMES

# Validate data against a schema
is_valid, error = validate(data, "node", raise_on_error=False)

# Load a schema
schema = load_schema("encoding")

# Available schemas
print(SCHEMA_NAMES)  # ['node', 'encoding', 'theme', 'stats', 'data_info', 'render_manifest']

Models

from fsb import Node, Encoding, Theme, Stats, DataInfo
from fsb.models import BBox, SizeMM, Axes

# Create a node programmatically
node = Node(
    id="my_plot",
    type="plot",
    bbox_norm=BBox(0, 0, 1, 1),
    size_mm=SizeMM(80, 60),
)

Examples

See the examples/ directory for complete working examples:

  1. 01_create_bundle.py - Create a basic plot bundle
  2. 02_figure_with_children.py - Create multi-panel figures
  3. 03_validate_bundle.py - Validate bundles against schemas
  4. 04_data_and_stats.py - Work with data files and statistics

Development

# Clone repository
git clone https://github.com/ywatanabe1989/fsb.git
cd fsb

# Install in development mode
pip install -e ".[dev]"

# Run tests
pytest

# Format code
black src tests
ruff check src tests

License

MIT License - see LICENSE for details.

Citation

If you use FSB in your research, please cite:

@software{fsb2025,
  title = {FSB: Figure-Statistics Bundle},
  author = {Watanabe, Yusuke},
  year = {2025},
  url = {https://github.com/ywatanabe1989/fsb}
}

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fsb-0.1.0.tar.gz (35.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fsb-0.1.0-py3-none-any.whl (31.1 kB view details)

Uploaded Python 3

File details

Details for the file fsb-0.1.0.tar.gz.

File metadata

  • Download URL: fsb-0.1.0.tar.gz
  • Upload date:
  • Size: 35.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.12

File hashes

Hashes for fsb-0.1.0.tar.gz
Algorithm Hash digest
SHA256 cd063b218c40e4543c513684e7f1e9a486b36313a299e037127b4684789d12af
MD5 94b609b41635a9f68d15cb60877d864c
BLAKE2b-256 bf984ea041a9358fdaa85f6f560de8549ddb36461b6d624894ff7f2d35429445

See more details on using hashes here.

File details

Details for the file fsb-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: fsb-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 31.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.12

File hashes

Hashes for fsb-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c3b94e2866f173dce1d49e397412ba760f71b955876d97d7afb8de07e2e0c71f
MD5 3b2fd0ba22cd1dfaf4cecfa407fe5482
BLAKE2b-256 0fcbd3d8cff6d65e9d905f32df0b776d2d20194fba2be3d7c78557fa8c3d70fe

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page