API Reference

The package root exposes a small convenience facade for the most common calibration and digitization records. Implementation code should prefer the module-level imports documented below while the package remains pre-release.

Calibration

class figrecover.calibration.AxisCalibration(*, pixel_min: float, pixel_max: float, data_min: float, data_max: float, scale: Literal['linear', 'log10'] = 'linear', inverted: bool = False)[source]

Map a pixel interval to a data interval for one plot axis.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.calibration.Calibration(*, x: AxisCalibration, y: AxisCalibration, plot_left: float | None = None, plot_right: float | None = None, plot_top: float | None = None, plot_bottom: float | None = None, notes: str | None = None)[source]

Two-axis plot calibration.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.calibration.CalibrationProposal(*, calibration: ~figrecover.calibration.Calibration, confidence: ~typing.Annotated[float, ~annotated_types.Ge(ge=0.0), ~annotated_types.Le(le=1.0)], source: str, evidence: dict[str, object] = <factory>)[source]

Candidate calibration emitted by OCR/VLM/layout adapters.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

Digitization Models

class figrecover.models.DataPoint(*, series: str, x: float, y: float, x_pixel: float | None = None, y_pixel: float | None = None, confidence: Annotated[float, Ge(ge=0.0), Le(le=1.0)] = 1.0)[source]

One recovered data point and its source pixel coordinate.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.models.Diagnostic(*, level: ~typing.Literal['info', 'warning', 'error'], code: str, message: str, context: dict[str, object] = <factory>)[source]

A structured extraction or workflow diagnostic.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.models.DigitizeResult(*, spec: ~figrecover.models.DigitizeSpec, image_path: ~pathlib.Path, width: int, height: int, series: list[~figrecover.models.SeriesResult], diagnostics: list[~figrecover.models.Diagnostic] = <factory>)[source]

Result of digitizing one image crop.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

points() list[DataPoint][source]

Return all recovered points from all series in output order.

to_dataframe(*, include_provenance: bool = False) DataFrame[source]

Return recovered points as a pandas DataFrame.

Set include_provenance to include image/document/figure/tool fields that are useful when combining results across a corpus.

class figrecover.models.DigitizeSpec(*, calibration: ~figrecover.calibration.Calibration, series: list[~figrecover.models.SeriesSpec], image_id: str | None = None, source_document_id: str | None = None, source_figure_id: str | None = None, figure_label: str | None = None, source_pdf: ~pathlib.Path | None = None, source_page: int | None = None, source_crop_bbox: tuple[float, float, float, float] | None = None, extraction_tool: str = 'figrecover.digitize', extraction_tool_version: str | None = None, extraction_settings: dict[str, object] = <factory>, diagnostics: list[~figrecover.models.Diagnostic] = <factory>)[source]

Complete settings and provenance for digitizing one image crop.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.models.SeriesResult(*, spec: SeriesSpec, points: list[DataPoint] = <factory>, diagnostics: list[Diagnostic] = <factory>)[source]

Recovered points and diagnostics for one requested series.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.models.SeriesSpec(*, name: str, color: str, mode: Literal['line', 'scatter', 'bar'] = 'line', tolerance: Annotated[float, Ge(ge=0)] = 40.0, min_component_pixels: Annotated[int, Ge(ge=1)] = 4, sample_every_px: Annotated[int, Ge(ge=1)] = 1, bar_baseline: float | None = None, line_aggregation: Literal['median', 'min', 'max'] = 'median', plot_edge_margin_px: Annotated[int, Ge(ge=0)] = 0)[source]

Extraction settings for one visual series.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

Extraction diagnostics are structured records with a severity level, stable code, human-readable message, and context dictionary. Downstream workflows should use diagnostic codes rather than parsing messages.

DigitizeResult.to_dataframe(include_provenance=True) returns a long table with document, figure, crop, and extraction-tool metadata suitable for combining recovered points from many figures.

Workflow Records

Public record types for figure recovery workflows.

class figrecover.records.AxisCalibration(*, pixel_min: float, pixel_max: float, data_min: float, data_max: float, scale: Literal['linear', 'log10'] = 'linear', inverted: bool = False)[source]

Map a pixel interval to a data interval for one plot axis.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.records.AxisMetadata(*, label: str | None = None, units: str | None = None, scale: str = 'linear', tick_labels: list[str] = <factory>, source: str = 'manual', confidence: Annotated[float, ~annotated_types.Ge(ge=0.0), ~annotated_types.Le(le=1.0)] = 1.0)[source]

Human-facing axis metadata from manual review, OCR, or VLM proposals.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.records.BoundingBox(*, left: float, top: float, right: float, bottom: float)[source]

Pixel-space rectangle in left, top, right, bottom order.

property height: float

Rectangle height in pixels.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

property width: float

Rectangle width in pixels.

class figrecover.records.Calibration(*, x: AxisCalibration, y: AxisCalibration, plot_left: float | None = None, plot_right: float | None = None, plot_top: float | None = None, plot_bottom: float | None = None, notes: str | None = None)[source]

Two-axis plot calibration.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.records.CalibrationProposal(*, calibration: ~figrecover.calibration.Calibration, confidence: ~typing.Annotated[float, ~annotated_types.Ge(ge=0.0), ~annotated_types.Le(le=1.0)], source: str, evidence: dict[str, object] = <factory>)[source]

Candidate calibration emitted by OCR/VLM/layout adapters.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.records.ChartMetadata(*, chart_type: Literal['line', 'scatter', 'bar', 'histogram', 'boxplot', 'heatmap', 'unknown']='unknown', title: str | None = None, x_axis: AxisMetadata = <factory>, y_axis: AxisMetadata = <factory>, legend: dict[str, str]=<factory>, source: str = 'manual', confidence: Annotated[float, ~annotated_types.Ge(ge=0.0), ~annotated_types.Le(le=1.0)] = 1.0, metadata: dict[str, object]=<factory>)[source]

Chart-level metadata used to guide extraction and QA.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.records.DataPoint(*, series: str, x: float, y: float, x_pixel: float | None = None, y_pixel: float | None = None, confidence: Annotated[float, Ge(ge=0.0), Le(le=1.0)] = 1.0)[source]

One recovered data point and its source pixel coordinate.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.records.Diagnostic(*, level: ~typing.Literal['info', 'warning', 'error'], code: str, message: str, context: dict[str, object] = <factory>)[source]

A structured extraction or workflow diagnostic.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.records.DigitizeResult(*, spec: ~figrecover.models.DigitizeSpec, image_path: ~pathlib.Path, width: int, height: int, series: list[~figrecover.models.SeriesResult], diagnostics: list[~figrecover.models.Diagnostic] = <factory>)[source]

Result of digitizing one image crop.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

points() list[DataPoint][source]

Return all recovered points from all series in output order.

to_dataframe(*, include_provenance: bool = False) DataFrame[source]

Return recovered points as a pandas DataFrame.

Set include_provenance to include image/document/figure/tool fields that are useful when combining results across a corpus.

class figrecover.records.DigitizeSpec(*, calibration: ~figrecover.calibration.Calibration, series: list[~figrecover.models.SeriesSpec], image_id: str | None = None, source_document_id: str | None = None, source_figure_id: str | None = None, figure_label: str | None = None, source_pdf: ~pathlib.Path | None = None, source_page: int | None = None, source_crop_bbox: tuple[float, float, float, float] | None = None, extraction_tool: str = 'figrecover.digitize', extraction_tool_version: str | None = None, extraction_settings: dict[str, object] = <factory>, diagnostics: list[~figrecover.models.Diagnostic] = <factory>)[source]

Complete settings and provenance for digitizing one image crop.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.records.ExportRecord(*, export_id: str, path: ~pathlib.Path, format: ~typing.Literal['csv', 'json', 'jsonl', 'parquet'], row_count: ~typing.Annotated[int, ~annotated_types.Ge(ge=0)], source_run_ids: list[str] = <factory>, diagnostics: list[~figrecover.models.Diagnostic] = <factory>)[source]

Metadata for a recovered-data export artifact.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.records.ExtractionRun(*, run_id: str, figure: FigureCandidate, chart: ChartMetadata = <factory>, calibration: Calibration | None = None, toolchain: ExtractionToolchain, result: DigitizeResult | None = None, diagnostics: list[Diagnostic] = <factory>)[source]

A complete auditable extraction record for one figure.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.records.ExtractionToolchain(*, extractor: str, extractor_version: str | None = None, parser: str | None = None, renderer: str | None = None, vlm_model: str | None = None, settings: dict[str, object]=<factory>)[source]

Tools and settings used to recover data from a figure.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.records.FigureCandidate(*, figure_id: str, document_id: str | None = None, page_number: Annotated[int | None, ~annotated_types.Ge(ge=1)] = None, image_path: Path, source_image_path: Path | None = None, bbox: BoundingBox | None = None, label: str | None = None, caption: str | None = None, source: str = 'manual', confidence: Annotated[float, ~annotated_types.Ge(ge=0.0), ~annotated_types.Le(le=1.0)] = 1.0, metadata: dict[str, object]=<factory>)[source]

Candidate figure crop and provenance.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.records.RenderedPage(*, document_id: str, page_number: ~typing.Annotated[int, ~annotated_types.Ge(ge=1)], image_path: ~pathlib.Path, source_pdf: ~pathlib.Path | None = None, width_px: ~typing.Annotated[int, ~annotated_types.Ge(ge=1)], height_px: ~typing.Annotated[int, ~annotated_types.Ge(ge=1)], dpi: ~typing.Annotated[int, ~annotated_types.Ge(ge=1)], renderer: str = 'manual', metadata: dict[str, object] = <factory>)[source]

A rendered page image derived from a source document.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.records.SeriesResult(*, spec: SeriesSpec, points: list[DataPoint] = <factory>, diagnostics: list[Diagnostic] = <factory>)[source]

Recovered points and diagnostics for one requested series.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.records.SeriesSpec(*, name: str, color: str, mode: Literal['line', 'scatter', 'bar'] = 'line', tolerance: Annotated[float, Ge(ge=0)] = 40.0, min_component_pixels: Annotated[int, Ge(ge=1)] = 4, sample_every_px: Annotated[int, Ge(ge=1)] = 1, bar_baseline: float | None = None, line_aggregation: Literal['median', 'min', 'max'] = 'median', plot_edge_margin_px: Annotated[int, Ge(ge=0)] = 0)[source]

Extraction settings for one visual series.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.records.SourceDocument(*, document_id: str, path: Path | None = None, title: str | None = None, sha256: str | None = None, metadata: dict[str, object]=<factory>)[source]

A source document that may contain recoverable figures.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

Documents And Manifests

PDF rendering and figure-candidate preparation helpers.

exception figrecover.documents.OptionalDependencyError[source]

Raised when an optional dependency group is required but unavailable.

figrecover.documents.crop_figure_candidate(candidate: FigureCandidate, output_dir: Path, *, source_image_path: Path | None = None, overwrite: bool = False, image_format: str | None = None) FigureCandidate[source]

Crop one figure candidate from its source image and return an updated record.

figrecover.documents.crop_figure_candidates(candidates: Iterable[FigureCandidate], output_dir: Path, *, overwrite: bool = False, image_format: str | None = None) list[FigureCandidate][source]

Crop a sequence of figure candidates from their source images.

figrecover.documents.parse_page_selection(pages: str | Iterable[int] | None, *, page_count: int) list[int][source]

Normalize a one-based page selection.

pages may be None for all pages, an iterable of one-based page numbers, or a comma-separated string with ranges such as "1,3-5". Returned page numbers are one-based, de-duplicated, and sorted by first occurrence.

figrecover.documents.render_pdf_pages(pdf_path: Path, output_dir: Path, *, pages: str | Sequence[int] | None = None, dpi: int = 300, image_format: str = 'png', document_id: str | None = None, overwrite: bool = False) list[RenderedPage][source]

Render selected PDF pages to image files.

Page numbers are one-based in the public API. PyMuPDF is loaded lazily so the default package installation can remain free of PDF dependencies.

figrecover.documents.require_pymupdf()[source]

Return the PyMuPDF module or raise a helpful optional-dependency error.

Manifest records for batch figure-candidate workflows.

class figrecover.manifest.FigureManifest(*, candidates: list[FigureCandidate] = <factory>, metadata: dict[str, object]=<factory>)[source]

A JSONL-serializable collection of figure candidates.

classmethod from_candidates(candidates: Iterable[FigureCandidate], *, metadata: dict[str, object] | None = None) FigureManifest[source]

Build a manifest from candidate records.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

classmethod read_jsonl(path: Path) FigureManifest[source]

Read a figure manifest from newline-delimited JSON records.

summary() dict[str, object][source]

Return a JSON-friendly manifest summary.

write_jsonl(path: Path) Path[source]

Write the manifest as newline-delimited JSON candidate records.

Parser Adapters

class figrecover.adapters.ChartMetadata(*, chart_type: Literal['line', 'scatter', 'bar', 'histogram', 'boxplot', 'heatmap', 'unknown']='unknown', title: str | None = None, x_axis: AxisMetadata = <factory>, y_axis: AxisMetadata = <factory>, legend: dict[str, str]=<factory>, source: str = 'manual', confidence: Annotated[float, ~annotated_types.Ge(ge=0.0), ~annotated_types.Le(le=1.0)] = 1.0, metadata: dict[str, object]=<factory>)[source]

Chart-level metadata used to guide extraction and QA.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.adapters.FigureCandidate(*, figure_id: str, document_id: str | None = None, page_number: Annotated[int | None, ~annotated_types.Ge(ge=1)] = None, image_path: Path, source_image_path: Path | None = None, bbox: BoundingBox | None = None, label: str | None = None, caption: str | None = None, source: str = 'manual', confidence: Annotated[float, ~annotated_types.Ge(ge=0.0), ~annotated_types.Le(le=1.0)] = 1.0, metadata: dict[str, object]=<factory>)[source]

Candidate figure crop and provenance.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.adapters.FigureCandidateAdapter(*args, **kwargs)[source]

Protocol for document parsers that emit normalized figure candidates.

discover() list[FigureCandidate][source]

Return normalized figure candidates.

class figrecover.adapters.ManualCandidateAdapter(candidates: Sequence[FigureCandidate])[source]

Adapter that returns user-supplied candidates unchanged.

discover() list[FigureCandidate][source]

Return manually supplied candidate records.

class figrecover.adapters.PyMuPDFImageBlockAdapter(pdf_path: Path, rendered_pages: Sequence[RenderedPage], *, pages: str | Sequence[int] | None = None, min_width_px: float = 8.0, min_height_px: float = 8.0, confidence: float = 0.65)[source]

Discover embedded PDF image blocks and normalize them as candidates.

The adapter maps PyMuPDF page-space bounding boxes, measured in PDF points, to rendered-page pixel coordinates using the dpi recorded on figrecover.records.RenderedPage.

discover() list[FigureCandidate][source]

Return embedded image-block candidates for rendered pages.

figrecover.adapters.extract_pymupdf_image_candidates(pdf_path: Path, rendered_pages: Sequence[RenderedPage], *, pages: str | Sequence[int] | None = None, min_width_px: float = 8.0, min_height_px: float = 8.0, confidence: float = 0.65) list[FigureCandidate][source]

Extract embedded image-block candidates using the PyMuPDF adapter.

figrecover.adapters.load_docling_figures(*_: object, **__: object) list[FigureCandidate][source]

Placeholder adapter for future Docling figure extraction.

VLM Proposals

Records for local VLM-assisted chart understanding.

The VLM layer records proposals and provenance. It does not make model output authoritative recovered data by default.

class figrecover.vlm.AxisRangeProposal(*, axis: ~typing.Literal['x', 'y'], data_min: float | None = None, data_max: float | None = None, scale: ~typing.Literal['linear', 'log10', 'unknown'] = 'unknown', label: str | None = None, units: str | None = None, confidence: ~typing.Annotated[float, ~annotated_types.Ge(ge=0.0), ~annotated_types.Le(le=1.0)] = 0.0, evidence: dict[str, object] = <factory>)[source]

Candidate data range and scale for one chart axis.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.vlm.CalibrationHintProposal(*, plot_bbox: BoundingBox | None = None, x_axis: AxisRangeProposal | None = None, y_axis: AxisRangeProposal | None = None, confidence: Annotated[float, ~annotated_types.Ge(ge=0.0), ~annotated_types.Le(le=1.0)] = 0.0, source: str = 'vlm', evidence: dict[str, object]=<factory>)[source]

Candidate calibration hints that still require deterministic validation.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.vlm.ChartMetadataProposal(*, chart_type: Literal['line', 'scatter', 'bar', 'histogram', 'boxplot', 'heatmap', 'unknown']='unknown', title: str | None = None, x_axis: AxisMetadata = <factory>, y_axis: AxisMetadata = <factory>, legend: LegendProposal = <factory>, tick_labels: list[TickLabelProposal] = <factory>, series_colors: list[SeriesColorProposal] = <factory>, calibration: CalibrationHintProposal | None = None, warnings: list[str] = <factory>, confidence: Annotated[float, ~annotated_types.Ge(ge=0.0), ~annotated_types.Le(le=1.0)] = 0.0, source: str = 'vlm', evidence: dict[str, object]=<factory>)[source]

Candidate chart metadata emitted by a VLM.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.vlm.ChartTriageRequest(*, request_id: str, image_path: Path, figure: FigureCandidate | None = None, context: dict[str, object]=<factory>, prompt: VLMPromptRecord | None = None)[source]

One VLM chart-understanding request for a prepared figure crop.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.vlm.ChartTriageResult(*, request: ChartTriageRequest, proposal: ChartMetadataProposal | None = None, raw_response: VLMRawResponse | None = None, diagnostics: list[Diagnostic] = <factory>)[source]

A VLM chart-understanding result with proposal and diagnostics.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.vlm.EnsembleFieldSummary(*, field: str, winner: str | None = None, counts: dict[str, int]=<factory>, agreement: Annotated[float, ~annotated_types.Ge(ge=0.0), ~annotated_types.Le(le=1.0)] = 0.0, mean_confidence: Annotated[float, ~annotated_types.Ge(ge=0.0), ~annotated_types.Le(le=1.0)] = 0.0, needs_review: bool = True)[source]

Agreement summary for one VLM-proposed metadata field.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.vlm.HttpxJSONTransport[source]

Default JSON transport using the optional httpx dependency.

post_json(*, url: str, headers: dict[str, str], payload: dict[str, object], timeout_s: float) dict[str, object][source]

Post JSON with httpx when the vlm extra is installed.

class figrecover.vlm.JSONTransport(*args, **kwargs)[source]

Small HTTP transport boundary used to keep tests backend-free.

post_json(*, url: str, headers: dict[str, str], payload: dict[str, object], timeout_s: float) dict[str, object][source]

Post JSON and return a decoded JSON object.

class figrecover.vlm.LegendProposal(*, entries: dict[str, str]=<factory>, confidence: Annotated[float, ~annotated_types.Ge(ge=0.0), ~annotated_types.Le(le=1.0)] = 0.0, source: str = 'vlm', evidence: dict[str, object]=<factory>)[source]

Candidate chart legend interpretation emitted by a VLM.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.vlm.OpenAICompatibleBackendConfig(*, model: str, base_url: str = 'http://127.0.0.1:8000/v1', endpoint_path: str = '/chat/completions', api_key: str | None = None, timeout_s: Annotated[float, ~annotated_types.Ge(ge=0)] = 60.0, temperature: Annotated[float, ~annotated_types.Ge(ge=0.0), ~annotated_types.Le(le=2.0)] = 0.0, max_tokens: Annotated[int, ~annotated_types.Ge(ge=1)] = 1024, extra_body: dict[str, object]=<factory>, backend_name: str = 'openai-compatible')[source]

Configuration for an OpenAI-compatible local VLM endpoint.

backend_info() VLMBackendInfo[source]

Return audit-safe backend metadata without secrets.

property endpoint_url: str

Return the chat-completions endpoint URL.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.vlm.OpenAICompatibleVLMBackend(config: OpenAICompatibleBackendConfig, *, transport: JSONTransport | None = None)[source]

Local OpenAI-compatible backend for chart metadata proposals.

describe_chart(request: ChartTriageRequest) ChartTriageResult[source]

Request a structured chart metadata proposal from a local VLM.

class figrecover.vlm.SeriesColorProposal(*, series_name: str | None = None, color: str, label: str | None = None, confidence: Annotated[float, ~annotated_types.Ge(ge=0.0), ~annotated_types.Le(le=1.0)] = 0.0, evidence: dict[str, object]=<factory>)[source]

Candidate visual-series colour emitted by a VLM.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.vlm.TickLabelProposal(*, axis: ~typing.Literal['x', 'y'], labels: list[str] = <factory>, confidence: ~typing.Annotated[float, ~annotated_types.Ge(ge=0.0), ~annotated_types.Le(le=1.0)] = 0.0, evidence: dict[str, object] = <factory>)[source]

Candidate tick-label readings for one axis.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.vlm.VLMBackend(*args, **kwargs)[source]

Protocol for local chart-understanding VLM backends.

describe_chart(request: ChartTriageRequest) ChartTriageResult[source]

Return a chart metadata proposal for one prepared figure image.

class figrecover.vlm.VLMBackendInfo(*, backend: str, model: str, endpoint: str | None = None, parameters: dict[str, object]=<factory>)[source]

Model backend metadata for a local VLM request.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.vlm.VLMEnsembleSummary(*, input_count: ~typing.Annotated[int, ~annotated_types.Ge(ge=0)], valid_proposal_count: ~typing.Annotated[int, ~annotated_types.Ge(ge=0)], fields: list[~figrecover.vlm.EnsembleFieldSummary] = <factory>, diagnostics: list[~figrecover.models.Diagnostic] = <factory>, proposal_request_ids: list[str] = <factory>)[source]

Summary of repeated VLM metadata proposal agreement.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.vlm.VLMParsedResponse(*, parsed_json: dict[str, object] | None=None, proposal: ChartMetadataProposal | None = None, diagnostics: list[Diagnostic] = <factory>)[source]

Parsed structured response and diagnostics from a VLM output string.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.vlm.VLMPromptRecord(*, task: ~typing.Literal['chart_triage', 'chart_metadata', 'calibration_hint'], template_id: str, template_version: str, text: str, variables: dict[str, object] = <factory>)[source]

Prompt text and template provenance sent to a VLM backend.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.vlm.VLMPromptTemplate(*, task: ~typing.Literal['chart_triage', 'chart_metadata', 'calibration_hint'], template_id: str, template_version: str, text: str, required_variables: list[str] = <factory>)[source]

Versioned prompt template for structured VLM proposal requests.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

render(variables: dict[str, object] | None = None) VLMPromptRecord[source]

Render the template into a prompt record.

class figrecover.vlm.VLMRawResponse(*, backend: VLMBackendInfo, prompt: VLMPromptRecord, response_text: str, parsed_json: dict[str, object] | list[object] | None=None, usage: dict[str, object]=<factory>, latency_ms: Annotated[float | None, ~annotated_types.Ge(ge=0.0)] = None, metadata: dict[str, object]=<factory>)[source]

Raw VLM response and backend metadata preserved for audit.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

figrecover.vlm.default_chart_metadata_prompt() VLMPromptRecord[source]

Return a minimal default prompt for local backend smoke tests.

figrecover.vlm.parse_chart_metadata_response(response_text: str) VLMParsedResponse[source]

Parse and validate a chart metadata proposal response.

figrecover.vlm.parse_json_object_response(response_text: str) tuple[dict[str, object] | None, list[Diagnostic]][source]

Parse a JSON object from a VLM response string.

figrecover.vlm.summarize_chart_metadata_ensemble(results: list[ChartTriageResult], *, min_agreement: float = 0.67) VLMEnsembleSummary[source]

Summarize agreement across repeated chart metadata proposals.

QA And Review

Quality-assurance helpers for recovered figure data.

class figrecover.qa.FigureQualityMetrics(*, image_path: ~pathlib.Path, width_px: ~typing.Annotated[int, ~annotated_types.Ge(ge=1)], height_px: ~typing.Annotated[int, ~annotated_types.Ge(ge=1)], series_count: ~typing.Annotated[int, ~annotated_types.Ge(ge=0)], point_count: ~typing.Annotated[int, ~annotated_types.Ge(ge=0)], extraction_density: ~typing.Annotated[float, ~annotated_types.Ge(ge=0)] = 0.0, plot_bounds_available: bool = False, mean_confidence: ~typing.Annotated[float, ~annotated_types.Ge(ge=0.0), ~annotated_types.Le(le=1.0)] = 0.0, diagnostics_by_level: dict[str, int] = <factory>, series: list[~figrecover.qa.SeriesQualityMetrics] = <factory>, vlm_disagreement_count: ~typing.Annotated[int, ~annotated_types.Ge(ge=0)] = 0, review_priority: ~typing.Literal['low', 'medium', 'high'] = 'low')[source]

Quality metrics for one recovered figure result.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.qa.OverlayArtifact(*, path: ~pathlib.Path, source_image_path: ~pathlib.Path, width_px: ~typing.Annotated[int, ~annotated_types.Ge(ge=1)], height_px: ~typing.Annotated[int, ~annotated_types.Ge(ge=1)], series_count: ~typing.Annotated[int, ~annotated_types.Ge(ge=0)], point_count: ~typing.Annotated[int, ~annotated_types.Ge(ge=0)], diagnostics: list[~figrecover.models.Diagnostic] = <factory>)[source]

Metadata for a generated review overlay image.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.qa.SeriesQualityMetrics(*, series_name: str, mode: str, point_count: ~typing.Annotated[int, ~annotated_types.Ge(ge=0)], mean_confidence: ~typing.Annotated[float, ~annotated_types.Ge(ge=0.0), ~annotated_types.Le(le=1.0)] = 0.0, diagnostics_by_level: dict[str, int] = <factory>)[source]

Quality metrics for one recovered series.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

figrecover.qa.compute_quality_metrics(result: DigitizeResult, *, vlm_ensemble: VLMEnsembleSummary | None = None) FigureQualityMetrics[source]

Compute transparent QA metrics for one digitization result.

figrecover.qa.render_overlay(result: DigitizeResult, output_path: Path, *, source_image_path: Path | None = None, point_radius: int = 3, line_width: int = 2) OverlayArtifact[source]

Render recovered points/lines/bars over the source crop image.

figrecover.qa.write_quality_metrics(metrics: FigureQualityMetrics, path: Path) Path[source]

Write figure quality metrics as JSON.

Review manifest records for human-in-the-loop QA workflows.

class figrecover.review.ReviewCorrection(*, corrected_points: list[DataPoint] = <factory>, corrected_table_path: Path | None = None, notes: str | None = None, provenance: dict[str, object]=<factory>)[source]

Manual correction data associated with a review decision.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.review.ReviewEntry(*, review_id: str, figure_id: str | None = None, extraction_run_id: str | None = None, image_path: Path | None = None, overlay_path: Path | None = None, table_path: Path | None = None, status: Literal['accepted', 'rejected', 'manually_corrected', 'needs_recrop', 'needs_recalibration', 'needs_review']='needs_review', reviewer: str | None = None, reviewed_at: datetime = <factory>, notes: str | None = None, metrics: FigureQualityMetrics | None = None, diagnostics: list[Diagnostic] = <factory>, correction: ReviewCorrection | None = None, metadata: dict[str, object]=<factory>)[source]

One human review decision for a recovered figure/table.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.review.ReviewManifest(*, entries: list[ReviewEntry] = <factory>, metadata: dict[str, object]=<factory>)[source]

JSONL-serializable collection of review entries.

accepted() list[ReviewEntry][source]

Return entries that are approved for accepted-only export.

classmethod from_entries(entries: Iterable[ReviewEntry], *, metadata: dict[str, object] | None = None) ReviewManifest[source]

Build a manifest from review entries.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

classmethod read_jsonl(path: Path) ReviewManifest[source]

Read a review manifest from newline-delimited JSON records.

summary() dict[str, object][source]

Return a JSON-friendly status summary.

write_jsonl(path: Path) Path[source]

Write the manifest as newline-delimited JSON review entries.

Corpus Pipeline

Batch corpus pipeline records and helpers.

class figrecover.pipeline.ArtifactLayout(*, output_root: Path)[source]

Stable artifact paths under a corpus output root.

crop_path(figure_id: str, *, ext: str = 'png') Path[source]

Return the stable crop path for a figure.

property crops_dir: Path

Figure crop image directory.

directories() dict[str, Path][source]

Return standard artifact directory names and paths.

initialize() ArtifactLayout[source]

Create the standard corpus artifact directories.

log_path(name: str, *, ext: str = 'log') Path[source]

Return a stable log path.

property logs_dir: Path

Corpus log directory.

manifest_path(name: str, *, ext: str = 'json') Path[source]

Return a stable manifest path.

property manifests_dir: Path

Run, figure, and review manifest directory.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

overlay_path(figure_id: str, *, ext: str = 'png') Path[source]

Return the stable overlay path for a figure.

property overlays_dir: Path

QA overlay image directory.

page_path(document_id: str, page_number: int, *, ext: str = 'png') Path[source]

Return the stable rendered-page path for a document page.

property pages_dir: Path

Rendered page image directory.

table_path(table_id: str, *, ext: str = 'csv') Path[source]

Return the stable table path for a figure or export.

property tables_dir: Path

Recovered or accepted table directory.

class figrecover.pipeline.CorpusDocumentRecord(*, document_id: str, source_pdf: ~pathlib.Path, status: ~typing.Literal['pending', 'completed', 'skipped', 'failed'], page_count: ~typing.Annotated[int, ~annotated_types.Ge(ge=0)] = 0, rendered_pages: list[~figrecover.records.RenderedPage] = <factory>, diagnostics: list[~figrecover.models.Diagnostic] = <factory>)[source]

Run status for one source PDF.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.pipeline.CorpusFigureRecord(*, figure_id: str, status: ~typing.Literal['pending', 'completed', 'skipped', 'failed'], candidate: ~figrecover.records.FigureCandidate | None = None, crop_path: ~pathlib.Path | None = None, diagnostics: list[~figrecover.models.Diagnostic] = <factory>)[source]

Run status for one figure candidate.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.pipeline.CorpusInput(*, pdfs: list[Path] = <factory>, input_dir: Path | None = None, pdf_glob: str = '*.pdf')[source]

PDF input selection for a corpus run.

discover_pdfs() list[Path][source]

Return sorted, de-duplicated PDF paths from explicit and directory inputs.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.pipeline.CorpusRenderOptions(*, dpi: Annotated[int, Ge(ge=1)] = 300, pages: str | None = None, image_format: str = 'png')[source]

PDF rendering options for a corpus run.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.pipeline.CorpusRunConfig(*, run_id: str = 'corpus-run', inputs: CorpusInput, output_root: Path, render: CorpusRenderOptions = <factory>, workers: CorpusWorkerOptions = <factory>, figure_manifest: Path | None = None, overwrite: bool = False, resume: bool = True, metadata: dict[str, object]=<factory>)[source]

Configuration for one batch corpus run.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

classmethod read(path: Path) CorpusRunConfig[source]

Read a corpus config from JSON or optional YAML.

write_json(path: Path) Path[source]

Write this config as formatted JSON.

class figrecover.pipeline.CorpusStepRecord(*, step: ~typing.Literal['config', 'render', 'crop', 'review', 'export'], status: ~typing.Literal['pending', 'completed', 'skipped', 'failed'], identifier: str, artifact_path: ~pathlib.Path | None = None, diagnostics: list[~figrecover.models.Diagnostic] = <factory>, metadata: dict[str, object] = <factory>)[source]

Status record for one corpus processing step.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.pipeline.CorpusWorkerOptions(*, max_workers: Annotated[int, Ge(ge=1)] = 1, use_processes: bool = True, vlm_max_workers: Annotated[int, Ge(ge=1)] = 1)[source]

Worker settings for workstation corpus execution.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class figrecover.pipeline.RunManifest(*, run_id: str, output_root: Path, started_at: datetime = <factory>, completed_at: datetime | None = None, documents: list[CorpusDocumentRecord] = <factory>, figures: list[CorpusFigureRecord] = <factory>, steps: list[CorpusStepRecord] = <factory>, diagnostics: list[Diagnostic] = <factory>, metadata: dict[str, object]=<factory>)[source]

Manifest for a corpus run.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

classmethod read_json(path: Path) RunManifest[source]

Read a run manifest from JSON.

summary() dict[str, object][source]

Return a JSON-friendly run summary.

write_json(path: Path) Path[source]

Write this run manifest as formatted JSON.

figrecover.pipeline.exception_diagnostic(step: Literal['config', 'render', 'crop', 'review', 'export'], item: Path, exc: Exception) Diagnostic[source]

Convert an exception into a concise structured diagnostic.

figrecover.pipeline.export_accepted_tables(review_manifest_path: Path, output_dir: Path) dict[str, object][source]

Copy accepted review tables into a corpus export directory.

figrecover.pipeline.initialize_corpus(config: CorpusRunConfig) dict[str, object][source]

Create a corpus output root and write an initial config and manifest.

figrecover.pipeline.run_corpus(config: CorpusRunConfig) RunManifest[source]

Run a deterministic corpus workflow and return its run manifest.

figrecover.pipeline.safe_slug(value: str) str[source]

Return a filesystem-safe identifier slug.

figrecover.pipeline.summarize_run(manifest_path: Path) dict[str, object][source]

Read a run manifest and return its summary.

Integration Exports

Generic modelling export contract for recovered figure data.

class figrecover.integrations.generic.GenericModellingExport(*, table_path: Path, sidecar_path: Path, row_count: Annotated[int, Ge(ge=0)])[source]

Paths and row count for a modelling export.

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

figrecover.integrations.generic.build_modelling_dataframe(results: Iterable[DigitizeResult], *, review_manifest: ReviewManifest | None = None, accepted_only: bool = True, x_units: str | None = None, y_units: str | None = None) DataFrame[source]

Build a long modelling table from digitization results.

When review_manifest is supplied and accepted_only is true, only results with accepted or manually_corrected review entries are exported.

figrecover.integrations.generic.write_modelling_export(results: Iterable[DigitizeResult], table_path: Path, *, sidecar_path: Path | None = None, review_manifest: ReviewManifest | None = None, accepted_only: bool = True, x_units: str | None = None, y_units: str | None = None) GenericModellingExport[source]

Write a generic modelling CSV and JSON provenance sidecar.

FEMIC-oriented projections for recovered figure exports.

figrecover.integrations.femic.project_femic_export(generic_frame: DataFrame, *, signal_family: str | None = None, model_input_role: str | None = None, curve_hint: str | None = None) DataFrame[source]

Project a generic modelling export into FEMIC-facing columns.

figrecover.integrations.femic.write_femic_export(generic_frame: DataFrame, path: Path, **kwargs: object) Path[source]

Write a FEMIC-projected CSV export.

FHOPS-oriented projections for recovered figure exports.

figrecover.integrations.fhops.project_fhops_export(generic_frame: DataFrame, *, reference_id: str | None = None, source_label: str | None = None, method: str = 'figrecover_digitized_figure', predictor: str | None = None, response: str | None = None) DataFrame[source]

Project a generic modelling export into FHOPS-facing reference rows.

figrecover.integrations.fhops.write_fhops_export(generic_frame: DataFrame, path: Path, **kwargs: object) Path[source]

Write a FHOPS-projected CSV export.

Digitization

Extraction Boundary

Deterministic image-level extraction APIs.

This module is the public extraction boundary. It currently exposes calibrated colour-based extraction for prepared chart crops. Future extractors should live behind this boundary rather than being wired directly into the CLI.

figrecover.extraction.extract_image(path: Path, spec: DigitizeSpec) DigitizeResult[source]

Extract calibrated series from an image crop.

Parameters:
  • path – Path to a prepared chart image crop.

  • spec – Calibration and series extraction settings.

Returns:

Recovered points and diagnostics for each requested series.

Return type:

DigitizeResult

Export Helpers

Export helpers for recovered figure data.

figrecover.io.read_result_json(path: Path) DigitizeResult[source]

Read a digitization result from a JSON file.

figrecover.io.write_points_csv(result: DigitizeResult, path: Path, *, include_provenance: bool = False) Path[source]

Write recovered points as a flat CSV table.

figrecover.io.write_result(result: DigitizeResult, path: Path) Path[source]

Write a result as JSON when the suffix is .json, otherwise CSV.

figrecover.io.write_result_json(result: DigitizeResult, path: Path) Path[source]

Write a digitization result, including metadata and diagnostics, as JSON.

PDF Helpers

figrecover.pdf.render_pdf_page(pdf_path: Path, *, page_number: int, out_path: Path, dpi: int = 300) Path[source]

Render one one-based PDF page to an image file.

This compatibility wrapper preserves the Phase 0 helper API. New code should use figrecover.documents.render_pdf_pages().