Skip to main content

fs-ocr

Fast OCR library for Foxhole stockpile screenshots, written in Rust with Python bindings.

Features

  • Fast Template Matching: pHash filtering + NCC scoring with adaptive candidate escalation
  • ROI + Grey Mask Detection: black-box ROI localization followed by grey-mask box detection
  • Quantity Recognition: template-based glyph matching (no OCR engine needed for digits)
  • Pure-Rust OCR: ocrs/rten with an embedded model — reads Latin + Russian and localized stockpile types; Chinese custom names via the optional system tesseract CLI (see Language support)
  • Python API + CLI: PyO3 bindings (import fs_ocr) and an fs-ocr command-line tool

Installation

Standalone CLI (no Python required)

Prebuilt fs-ocr binaries are attached to each GitHub Release for Linux, Windows, and macOS (x86_64 + Apple Silicon). Download, extract, run:

tar -xzf fs-ocr-linux-x86_64.tar.gz   # or unzip the .zip on Windows
./fs-ocr scan screenshot.png -d templates.h5 --faction wardens

One static build is published per platform (fs-ocr-<os>-<arch>) — pure-Rust OCR, no system dependencies. (The system tesseract CLI is used at runtime only if present, for Chinese custom names.)

From PyPI

pip install fs-ocr

OCR is pure Rust (ocrs/rten) with the recognition model embedded in the wheel — no system OCR libraries are required to install or run.

You still need a template database (.h5) to scan against; it is not bundled in the wheel. See Template Database.

Language support

The embedded ocrs recognizer reads Latin and Russian (Cyrillic) text natively, and the closed set of localized stockpile-type names (including Chinese). No extra packages or language flags are needed for any of that.

The one exception is free-form Chinese custom names, which are read via the system tesseract CLI if it is installed — detected at runtime, entirely optional. If tesseract is absent, everything else still works and only the Chinese custom name is left unread (no error).

OS Optional install (Chinese custom names)
Debian/Ubuntu sudo apt install tesseract-ocr tesseract-ocr-chi-sim
Fedora/RHEL sudo dnf install tesseract tesseract-langpack-chi_sim
macOS (Homebrew) brew install tesseract tesseract-lang
Windows UB Mannheim installer (select Chinese) or choco install tesseract

Override the binary or language via FS_OCR_TESSERACT / FS_OCR_TESSERACT_LANG (defaults tesseract / chi_sim).

From Source

The build needs only a C/C++ toolchain — HDF5 is built from source via the static-hdf5 feature. No OpenCV or Tesseract dev libraries required:

# Build deps (Ubuntu/Debian)
sudo apt-get install cmake gcc g++ libclang-dev

# Build and install the Python module
pip install maturin
maturin develop --release

# Or build the standalone CLI (no Python / libpython linkage)
cargo build --release --no-default-features --bin fs-ocr

Python Usage

from fs_ocr import StockpileScanner, ScanConfig
import numpy as np

# Create scanner (data_path holds the OCR model files, default "data")
scanner = StockpileScanner(database_path="templates.h5", data_path="data")

# Scan from NumPy array (H x W x 3, uint8, BGR)
image = np.array(...)  # Your image data
result = scanner.scan(image, faction="wardens")
print(result.to_json())

# Scan from file
result = scanner.scan_file("screenshot.png", faction="colonials")

# With custom config
config = ScanConfig(confidence_gap=0.02)
result = scanner.scan(image, config=config)

# Access result data
for item in result.items:
    print(f"{item.code}: {item.quantity} (confidence: {item.confidence:.2f})")

API Reference

StockpileScanner

Main scanner class.

scanner = StockpileScanner(
    database_path: str,      # Path to HDF5 template database
    data_path: str = "data"  # Path to OCR model files directory
)

result = scanner.scan(
    image: np.ndarray,       # BGR image (H x W x 3, uint8)
    faction: str = None,     # "wardens", "colonials", or None
    config: ScanConfig = None
)

result = scanner.scan_file(
    image_path: str,         # Path to image file
    faction: str = None,
    config: ScanConfig = None
)

scan_debug(image, ...) and scan_debug_file(path, ...) take the same arguments and return the same Stockpile, but additionally populate each item's debug_candidates with the broad diagnostic candidate set (see StockpileItem). The normal scan/scan_file output is unchanged. Intended for tooling that needs to inspect why an icon matched.

ScanConfig

Configuration options for tuning the matching pipeline.

config = ScanConfig(
    phash_threshold: int = 15,            # Max Hamming distance for pHash filter (lower=faster)
    max_ncc_candidates: int = 100,        # Hard cap on NCC candidates (upper bound of escalation)
    ncc_initial_candidates: int = 25,     # Initial NCC batch before adaptive escalation
    ncc_escalation_threshold: float = 0.9,# Escalate candidate count if best confidence below this
    confidence_gap: float = 0.0,          # Return alternatives within this gap of best match
    ncc_tiebreaker_threshold: float = 0.003  # Edge(Sobel)-based tiebreaker; 0.0 disables
)

# Serialize/deserialize
config.to_json() -> str
ScanConfig.from_json(json_str) -> ScanConfig

Stockpile

Scan result containing detected items.

stockpile.name             # Custom stockpile name (if applicable)
stockpile.type             # StockpileType enum
stockpile.is_reserve       # True when named something other than "Public"
stockpile.items            # List[StockpileItem]
stockpile.timestamp        # ISO 8601 scan timestamp
stockpile.shard            # Game shard name
stockpile.ingame_timestamp # In-game time (e.g. "Day 1293, 1906 Hours")
stockpile.resolution       # Screenshot resolution ("WxH")
stockpile.errors           # List of error messages
stockpile.timing           # Optional[Timing] per-stage metrics (None unless collected)
stockpile.to_json()        # Serialize to JSON (to_json_compact() for one line)

StockpileItem

Individual detected item.

item.code             # Item code or "Unknown"
item.quantity         # Detected quantity (-1 if failed)
item.crated           # Whether item is crated
item.confidence       # Match confidence (0.0 - 1.0)
item.x, item.y        # Icon top-left (px) in the source screenshot
item.candidates       # Alternative matches (if confidence_gap > 0)
item.debug_candidates # Broad diagnostic set; None unless scanned via scan_debug

DebugCandidate

Populated only by scan_debug / scan_debug_file. One entry per template that passed the icon's pHash threshold for its crated state — any code/category/mod/faction — NCC-scored and ranked descending, capped by max_ncc_candidates. The item's code/confidence equals the top candidate.

candidate.code           # Item code
candidate.confidence     # NCC score (0.0 - 1.0)
candidate.mod            # Mod name (e.g. "vanilla", "airborne")
candidate.category       # "item" / "vehicle" / "shippable" / "invalid"
candidate.crated         # Whether the template is crated
candidate.faction        # "neutral" / "Colonials" / "Wardens"
candidate.phash_distance # Hamming distance icon-to-template (lower = closer)

CLI Usage

The crate also builds an fs-ocr binary that emits JSON.

# Scan a file
fs-ocr scan screenshot.png -d templates.h5 --faction wardens

# Read image from stdin ("-" or omit the path)
cat screenshot.png | fs-ocr scan -d templates.h5 --compact

# Print version
fs-ocr version

Matching can be tuned with --phash-threshold, --max-ncc-candidates, --ncc-initial-candidates, --ncc-escalation-threshold, --ncc-tiebreaker, and --confidence-gap. Set FS_OCR_TIMING=1 to include per-stage timing in the output. Exit codes: 0 ok, 1 runtime error, 2 bad input.

Template Database

fs-ocr matches icons against a pre-built HDF5 template database, supplied at runtime via the CLI --database flag or the StockpileScanner(database_path=...) argument. It is not bundled with the wheel or the CLI binary.

The database is generated separately from game assets by the foxhole-stockpiles project. Grab the .h5 file from its data/ directory and pass its path via --database / database_path.

fs-ocr scan screenshot.png -d fs_airborne.h5

Development

Commands

Command Description
cargo test Run the Rust test suite
cargo clippy --all-targets -- -D warnings Run linter (warnings as errors)
cargo fmt Format code with rustfmt
cargo build --release Build optimized library
cargo build --release --no-default-features --bin fs-ocr Build the standalone CLI (no libpython)
maturin develop --release Build and install Python module (dev)
maturin build --release Build Python wheel for distribution

Feature Flags

Feature Default Description
python on PyO3 + numpy bindings; drop with --no-default-features for a pure CLI
static-hdf5 off Build/statically link libhdf5 from source (used by CI wheels)

Requirements

  • Rust toolchain (edition 2021)
  • Python 3.10+ (for bindings)
  • Build tools: cmake, gcc/g++, libclang-dev
  • Optional runtime: system tesseract CLI (Chinese custom names only)

Dev Dependencies

pip install pytest numpy  # Python dev deps

Architecture

src/
├── lib.rs              # PyO3 module + StockpileScanner
├── bin/fs-ocr.rs       # CLI binary (clap)
├── constants.rs        # Hardcoded values / resolution scaling
├── error.rs            # Error types
├── config.rs           # ScanConfig
├── image_utils.rs      # RGB→grayscale, crop helpers
├── models/             # Output structs
│   ├── stockpile.rs
│   ├── stockpile_item.rs
│   └── timing.rs       # Per-stage Timing
├── enums/              # Type enums
│   ├── stockpile_type.rs
│   ├── item_faction.rs
│   └── item_category.rs
├── detector/           # ROI + grey mask detection
│   ├── black_box.rs    # Dark ROI localization (first pass)
│   ├── geometry.rs
│   └── grey_mask/      # detector + morphology + grouping
├── template/           # Template matching
│   ├── database.rs     # HDF5 loading
│   ├── matching.rs     # NCC + adaptive escalation + tiebreaker
│   ├── phash.rs        # Perceptual hashing
│   └── {label,public,type}_match.rs  # embedded-asset template matchers
├── ocr/                # Text + quantity extraction
│   ├── engine.rs       # OcrEngine trait + OcrConfig
│   ├── basic.rs        # OcrsEngine (pure-Rust ocrs)
│   ├── digit_matcher.rs# Glyph-template digit recognition (quantities)
│   ├── preprocess.rs   # upscale helpers
│   ├── quantity.rs     # Quantity validation
│   └── tesseract.rs    # ChineseNameReader (system `tesseract` CLI)
└── coordinator/        # Pipeline orchestration
    ├── pipeline.rs
    ├── region_preprocess.rs # OCR image prep (luma/contrast/framing/upscale)
    ├── metadata_parse.rs    # shard / timestamp / public-name parsing
    └── validation.rs

License

MIT

Metadata

Release files for fs-ocr 1.1.0

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

Source distribution (sdist)

Source distribution for fs-ocr 1.1.0
File Size Uploaded Provenance
fs_ocr-1.1.0.tar.gz 9.5 MB With provenance Details

Built distributions (wheels)

Table of built distributions (wheels) for fs-ocr 1.1.0
File Interpreter ABI Platform
fs_ocr-1.1.0-cp310-abi3-win_amd64.whl CPython 3.10 abi3 Windows x86-64 Details
fs_ocr-1.1.0-cp310-abi3-manylinux_2_28_x86_64.whl CPython 3.10 abi3 Linux glibc 2.28+ x86-64 Details
fs_ocr-1.1.0-cp310-abi3-macosx_11_0_arm64.whl CPython 3.10 abi3 macOS 11.0+ ARM64 Details

Total release size: 50.8 MB

Release files / fs_ocr-1.1.0.tar.gz

Download URL fs_ocr-1.1.0.tar.gz
Size 9.5 MB
Tags Source
SHA-256 checksum
How to use checksums
c3b7dab0bcc6c7626c5aca5ddc337311fb5b23aa4987d2f57ec1324df70920d3
BLAKE2b-256 checksum
How to use checksums
c3371095aacad0d41064586b7727e37162d9979d0fbbb59768fdbe4539c5243f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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 Jun 23, 2026.

Transparency log

Release files / fs_ocr-1.1.0-cp310-abi3-win_amd64.whl

Download URL fs_ocr-1.1.0-cp310-abi3-win_amd64.whl
Size 14.6 MB
Tags CPython 3.10 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
600c1e45837a2b7117f43fb5d044596dc60799264c8daca543007c3d4ac48831
BLAKE2b-256 checksum
How to use checksums
ed857b40a2c2905584aadcb0e5cb88c74bc6d405334e59e1d55f2393d82e238f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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 Jun 23, 2026.

Transparency log

Release files / fs_ocr-1.1.0-cp310-abi3-manylinux_2_28_x86_64.whl

Download URL fs_ocr-1.1.0-cp310-abi3-manylinux_2_28_x86_64.whl
Size 13.7 MB
Tags CPython 3.10 Linux glibc 2.28+ x86-64 abi3
SHA-256 checksum
How to use checksums
5d77a366b346f61df451d2058f1d538cf9d1f501a9f46024d68647ffc16ed62b
BLAKE2b-256 checksum
How to use checksums
53327e793edc7b0436a7ef665e2c3594a63ff6218a350b84595cafdd7987f624
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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 Jun 23, 2026.

Transparency log

Release files / fs_ocr-1.1.0-cp310-abi3-macosx_11_0_arm64.whl

Download URL fs_ocr-1.1.0-cp310-abi3-macosx_11_0_arm64.whl
Size 12.9 MB
Tags CPython 3.10 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
9650d5e773967ee945abf9edcce498f260af4624a5f9ba167adec1e50b49a538
BLAKE2b-256 checksum
How to use checksums
2876028551b51b0fe560b2e55a22f856fe1dd4f63ef6db1e45357328321350c1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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 Jun 23, 2026.

Transparency log

Security for fs-ocr 1.1.0

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.

Full provenance

All 4 files in this release have been uploaded with provenance metadata.

Select from 4 files

Attestation for fs_ocr-1.1.0.tar.gz

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 Jun 23, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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