Showing preview only (1,120K chars total). Download the full file or copy to clipboard to get everything.
Repository: brandonmpetty/Doxa
Branch: master
Commit: be67520e93a4
Files: 163
Total size: 845.9 KB
Directory structure:
gitextract_kry5ydl6/
├── .claude/
│ └── settings.local.json
├── .gitattributes
├── .github/
│ └── workflows/
│ ├── benchmarks.yml
│ ├── npm-publish.yml
│ ├── pythonpackage.yml
│ └── test-and-coverage.yml
├── .gitignore
├── Bindings/
│ ├── Matlab/
│ │ ├── +Doxa/
│ │ │ ├── Algorithms.m
│ │ │ ├── Grayscale.m
│ │ │ ├── Image.m
│ │ │ ├── binarize.m
│ │ │ ├── buildParams.m
│ │ │ ├── calculatePerformance.m
│ │ │ ├── readWeights.m
│ │ │ └── updateToBinary.m
│ │ ├── BinarizeMex.cpp
│ │ ├── CMakeLists.txt
│ │ ├── CalculatePerformanceMex.cpp
│ │ ├── Doxa.prj
│ │ ├── DoxaMexUtils.hpp
│ │ ├── ImageMex.cpp
│ │ ├── README.md
│ │ ├── UpdateToBinaryMex.cpp
│ │ └── test/
│ │ └── TestDoxa.m
│ ├── Python/
│ │ ├── .gitignore
│ │ ├── CMakeLists.txt
│ │ ├── DoxaPy.ipynb
│ │ ├── README.md
│ │ ├── copy-cpp-files.py
│ │ ├── pyproject.toml
│ │ ├── requirements.txt
│ │ ├── src/
│ │ │ ├── DoxaPy.cpp
│ │ │ └── doxapy/
│ │ │ └── __init__.py
│ │ └── test/
│ │ ├── test_doxa.py
│ │ └── test_speed.py
│ └── WebAssembly/
│ ├── CMakeLists.txt
│ ├── DoxaJs.nnb
│ ├── DoxaWasm.cpp
│ ├── README.md
│ ├── dist/
│ │ ├── doxa.js
│ │ ├── doxaWasm.js
│ │ └── doxaWasm.wasm
│ ├── doxa.js
│ ├── package.json
│ └── spec/
│ ├── binarization.spec.js
│ ├── image.spec.js
│ ├── speed.spec.js
│ └── support/
│ └── jasmine.json
├── CLAUDE.md
├── CMakeLists.txt
├── CMakePresets.json
├── Demo/
│ ├── Cpp/
│ │ ├── .gitignore
│ │ ├── demo.cpp
│ │ ├── demoOpenCV.cpp
│ │ ├── demoQt.cpp
│ │ └── demoQt.pro
│ ├── Matlab/
│ │ └── demo.m
│ ├── NodeJS/
│ │ ├── .gitignore
│ │ ├── index.js
│ │ └── package.json
│ ├── Python/
│ │ └── demo.py
│ └── WebJS/
│ └── index.html
├── Doxa/
│ ├── AdOtsu.hpp
│ ├── Algorithm.hpp
│ ├── Bataineh.hpp
│ ├── Bernsen.hpp
│ ├── BinarizationFactory.hpp
│ ├── ChanMeanCalc.hpp
│ ├── ChanMeanVarianceCalc.hpp
│ ├── ClassifiedPerformance.hpp
│ ├── ContrastImage.hpp
│ ├── DIBCOUtils.hpp
│ ├── DRDM.hpp
│ ├── Doxa.vcxitems
│ ├── Gatos.hpp
│ ├── Grayscale.hpp
│ ├── GridCalc.hpp
│ ├── ISauvola.hpp
│ ├── Image.hpp
│ ├── IntegralImageMeanVarianceCalc.hpp
│ ├── LocalWindow.hpp
│ ├── Morphology.hpp
│ ├── MultiScale.hpp
│ ├── Niblack.hpp
│ ├── Nick.hpp
│ ├── Otsu.hpp
│ ├── PNM.hpp
│ ├── Palette.hpp
│ ├── Parameters.hpp
│ ├── Phansalkar.hpp
│ ├── Region.hpp
│ ├── SIMD.h
│ ├── SIMDOps.hpp
│ ├── Sauvola.hpp
│ ├── Su.hpp
│ ├── TRSingh.hpp
│ ├── Types.hpp
│ ├── Wan.hpp
│ ├── WienerFilter.hpp
│ └── Wolf.hpp
├── Doxa.Bench/
│ ├── BenchmarkHarness.hpp
│ ├── BinarizationBenchmarks.cpp
│ ├── CMakeLists.txt
│ ├── CalculatorBenchmarks.cpp
│ ├── ClassifiedPerformanceBenchmarks.cpp
│ ├── DRDMBenchmarks.cpp
│ ├── GlobalThresholdBenchmarks.cpp
│ ├── config.hpp.in
│ └── pch.h
├── Doxa.Test/
│ ├── AlgorithmTests.cpp
│ ├── BatainehTests.cpp
│ ├── BinarizationTests.cpp
│ ├── CMakeLists.txt
│ ├── CalculatorTests.cpp
│ ├── ClassifiedPerformanceTests.cpp
│ ├── ContrastImageTests.cpp
│ ├── DIBCOUtilsTests.cpp
│ ├── DRDMTests.cpp
│ ├── Doxa.Test.vcxproj
│ ├── Doxa.Test.vcxproj.filters
│ ├── GrayscaleTests.cpp
│ ├── GridCalcTests.cpp
│ ├── ISauvolaTests.cpp
│ ├── ImageFixture.hpp
│ ├── ImageTests.cpp
│ ├── LocalWindowTests.cpp
│ ├── MorphologyTests.cpp
│ ├── PNMTests.cpp
│ ├── PaletteTests.cpp
│ ├── ParametersTests.cpp
│ ├── RegionTests.cpp
│ ├── Resources/
│ │ ├── 2JohnC1V3-AdOtsu.pbm
│ │ ├── 2JohnC1V3-AdOtsuG.pbm
│ │ ├── 2JohnC1V3-AdOtsuMS.pbm
│ │ ├── 2JohnC1V3-AdOtsuMSG.pbm
│ │ ├── 2JohnC1V3-Bataineh.pbm
│ │ ├── 2JohnC1V3-Bensen.pbm
│ │ ├── 2JohnC1V3-ContrastImage.ppm
│ │ ├── 2JohnC1V3-Gatos.pbm
│ │ ├── 2JohnC1V3-GroundTruth.pbm
│ │ ├── 2JohnC1V3-HighContrastImage.pbm
│ │ ├── 2JohnC1V3-ISauvola.pbm
│ │ ├── 2JohnC1V3-NICK.pbm
│ │ ├── 2JohnC1V3-Niblack.pbm
│ │ ├── 2JohnC1V3-Otsu.pbm
│ │ ├── 2JohnC1V3-Phansalkar.pbm
│ │ ├── 2JohnC1V3-Sauvola.pbm
│ │ ├── 2JohnC1V3-Su.pbm
│ │ ├── 2JohnC1V3-TRSingh.pbm
│ │ ├── 2JohnC1V3-WAN.pbm
│ │ ├── 2JohnC1V3-Wolf.pbm
│ │ ├── 2JohnC1V3.ppm
│ │ └── 2JohnC1V3.psd
│ ├── SIMDTests.cpp
│ ├── SuTests.cpp
│ ├── TestUtilities.hpp
│ ├── WienerFilterTests.cpp
│ ├── packages.config
│ ├── pch.cpp
│ └── pch.h
├── Doxa.sln
├── LICENSE
└── README.md
================================================
FILE CONTENTS
================================================
================================================
FILE: .claude/settings.local.json
================================================
{
"permissions": {
"allow": [
"Bash(cmake:*)"
]
}
}
================================================
FILE: .gitattributes
================================================
## Prevent Git from affecting line endings
## GRAPHICS
*.psd binary
*.ppm binary
*.pbm binary
================================================
FILE: .github/workflows/benchmarks.yml
================================================
name: Performance Benchmarks
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
permissions:
contents: write
pull-requests: write
jobs:
benchmark:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
include:
- os: ubuntu-latest
platform: Linux
bench_exe: ./build-bench/Doxa.Bench/doxa_bench
- os: windows-latest
platform: Windows
bench_exe: ./build-bench/Doxa.Bench/Release/doxa_bench.exe
- os: macos-latest
platform: macOS
bench_exe: ./build-bench/Doxa.Bench/doxa_bench
steps:
- uses: actions/checkout@v4
- name: Configure CMake
run: cmake --preset benchmarks
- name: Build
run: cmake --build build-bench --config Release
- name: Run Benchmarks
run: ${{ matrix.bench_exe }}
--benchmark_out=benchmark_results.json
--benchmark_out_format=json
--benchmark_min_time=1s
--benchmark_repetitions=5
--benchmark_report_aggregates_only=true
- name: Upload Benchmark Results
uses: actions/upload-artifact@v4
with:
name: benchmark-results-${{ matrix.platform }}
path: benchmark_results.json
- name: Store Benchmark Results
uses: benchmark-action/github-action-benchmark@v1
with:
tool: 'googlecpp'
output-file-path: benchmark_results.json
# Each platform gets its own independent dataset and history
name: 'Doxa Benchmarks (${{ matrix.platform }})'
github-token: ${{ secrets.GITHUB_TOKEN }}
# Store history in gh-pages branch (only on push to master)
auto-push: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
# Alert on PRs if perf regresses beyond threshold
alert-threshold: '120%'
comment-on-alert: true
fail-on-alert: false
================================================
FILE: .github/workflows/npm-publish.yml
================================================
name: Publish to npm
permissions:
contents: read
# Disabled: NPM_TOKEN secret not yet configured.
# Re-enable by uncommenting the release trigger below.
on:
# release:
# types: [published]
workflow_dispatch: # manual trigger only for now
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
- name: Setup Emscripten
uses: mymindstorm/setup-emsdk@v14
- name: Install dependencies
working-directory: ./Bindings/WebAssembly
run: npm install
- name: Build WASM
working-directory: ./Bindings/WebAssembly
run: npm run build
- name: Run tests
working-directory: ./Bindings/WebAssembly
run: npm test
- name: Publish to npm
working-directory: ./Bindings/WebAssembly
run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
================================================
FILE: .github/workflows/pythonpackage.yml
================================================
name: Python Package
on:
release:
types:
- published
jobs:
build_sdist:
name: Build SDist
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./Bindings/Python
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Build SDist
run: |
python copy-cpp-files.py
pipx run build --sdist
- name: Check metadata
run: pipx run twine check dist/*
- uses: actions/upload-artifact@v4
with:
name: dist-sdist
path: Bindings/Python/dist/*.tar.gz
build_wheels:
name: Build Wheels
runs-on: ${{ matrix.os }}
defaults:
run:
working-directory: ./Bindings/Python
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/setup-python@v5
- uses: actions/checkout@v4
with:
submodules: true
- name: Build setup
run: |
python copy-cpp-files.py
pip install -r requirements.txt
python -m pip install cibuildwheel==2.22.0
- name: Build wheels
run: python -m cibuildwheel --output-dir wheelhouse
- name: Upload wheels
uses: actions/upload-artifact@v4
with:
path: Bindings/Python/wheelhouse/*.whl
name: dist-${{ matrix.os }}
upload_all:
name: Upload to PyPi
needs: [build_wheels, build_sdist]
runs-on: ubuntu-latest
if: github.event_name == 'release' && github.event.action == 'published'
steps:
- uses: actions/setup-python@v5
- uses: actions/download-artifact@v4
with:
path: dist
pattern: dist-*
merge-multiple: true
- uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.pypi_password }}
================================================
FILE: .github/workflows/test-and-coverage.yml
================================================
# This starter workflow is for a CMake project running on multiple platforms. There is a different starter workflow if you just want a single platform.
# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml
name: Build and Test with Coverage
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
# Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable.
fail-fast: false
# Set up a matrix to run the following 3 configurations:
# 1. <Windows, Release, latest MSVC compiler toolchain on the default runner image, default generator>
# 2. <Linux, Release, latest GCC compiler toolchain on the default runner image, default generator>
# 3. <macOS, Release, latest Clang compiler toolchain on the default runner image, default generator>
#
# To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list.
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
build_type: [Debug] # Changed to Debug for better coverage
c_compiler: [gcc, clang, cl]
include:
- os: windows-latest
c_compiler: cl
cpp_compiler: cl
- os: ubuntu-latest
c_compiler: gcc
cpp_compiler: g++
- os: macos-latest
c_compiler: clang
cpp_compiler: clang++
exclude:
- os: windows-latest
c_compiler: gcc
- os: windows-latest
c_compiler: clang
- os: ubuntu-latest
c_compiler: cl
- os: ubuntu-latest
c_compiler: clang
- os: macos-latest
c_compiler: gcc
- os: macos-latest
c_compiler: cl
steps:
- uses: actions/checkout@v4
- name: Set reusable strings
# Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file.
id: strings
shell: bash
run: |
echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT"
- name: Install dependencies (Ubuntu)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y cmake
pipx install gcovr
- name: Install dependencies (macOS)
if: runner.os == 'macOS'
run: |
brew install cmake
- name: Configure CMake
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
run: >
cmake -B ${{ steps.strings.outputs.build-output-dir }}
-DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }}
-DCMAKE_C_COMPILER=${{ matrix.c_compiler }}
-DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
-DCMAKE_CXX_FLAGS="${{ runner.os == 'Linux' && '-fprofile-arcs -ftest-coverage' || '' }}"
-DCMAKE_C_FLAGS="${{ runner.os == 'Linux' && '-fprofile-arcs -ftest-coverage' || '' }}"
-S ${{ github.workspace }}/Doxa.Test
- name: Build
# Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator).
run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }}
- name: Test
working-directory: ${{ steps.strings.outputs.build-output-dir }}
# Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator).
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
run: ctest --build-config ${{ matrix.build_type }} --output-on-failure
- name: Generate Coverage Report
if: runner.os == 'Linux'
working-directory: ${{ steps.strings.outputs.build-output-dir }}
run: |
# Use gcovr to generate XML coverage report for GCC
# Filter to only the core library headers - exclude test files and build artifacts
gcovr --xml --xml-pretty --gcov-ignore-parse-errors=suspicious_hits.warn --root .. --filter '../Doxa/' > coverage.xml
- name: Upload Coverage Report
if: runner.os == 'Linux'
uses: codecov/codecov-action@v3
with:
file: ${{ steps.strings.outputs.build-output-dir }}/coverage.xml
fail_ci_if_error: false
================================================
FILE: .gitignore
================================================
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
build/
build-cpp-tests/
build-python/
build-wasm/
build-matlab/
build-bench/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# DNX
project.lock.json
artifacts/
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# NuGet v3's project.json files produces more ignoreable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
node_modules/
orleans.codegen.cs
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
================================================
FILE: Bindings/Matlab/+Doxa/Algorithms.m
================================================
classdef Algorithms
%ALGORITHMS Binarization algorithms available in the Doxa framework.
%
% Global Thresholding:
% Doxa.Algorithms.OTSU
%
% Local Adaptive Thresholding:
% Doxa.Algorithms.BERNSEN, NIBLACK, SAUVOLA, WOLF, NICK, SU,
% TRSINGH, BATAINEH, PHANSALKAR, ISAUVOLA, WAN, GATOS, ADOTSU
%
% See also Doxa.binarize
enumeration
OTSU
BERNSEN
NIBLACK
SAUVOLA
WOLF
NICK
SU
TRSINGH
BATAINEH
PHANSALKAR
ISAUVOLA
WAN
GATOS
ADOTSU
end
end
================================================
FILE: Bindings/Matlab/+Doxa/Grayscale.m
================================================
classdef Grayscale
%GRAYSCALE Grayscale conversion algorithms available in the Doxa framework.
%
% Algorithms:
% Doxa.Grayscale.MEAN - Mean of R, G, B (Gleam/Intensity)
% Doxa.Grayscale.QT - Qt framework formula (default)
% Doxa.Grayscale.BT601 - ITU-R BT.601 (NTSC)
% Doxa.Grayscale.BT709 - ITU-R BT.709 (sRGB)
% Doxa.Grayscale.BT2100 - ITU-R BT.2100
% Doxa.Grayscale.VALUE - HSV Value (max channel)
% Doxa.Grayscale.LUSTER - HLS Lightness
% Doxa.Grayscale.LIGHTNESS - CIELAB/CIELUV Lightness
% Doxa.Grayscale.MINAVG - Min-Average (for multi-color text)
% Doxa.Grayscale.LABDIST - L*a*b* Euclidean Distance (used by Phansalkar)
%
% See also Doxa.Image
enumeration
MEAN
QT
BT601
BT709
BT2100
VALUE
LUSTER
LIGHTNESS
MINAVG
LABDIST
end
end
================================================
FILE: Bindings/Matlab/+Doxa/Image.m
================================================
classdef Image < handle
%IMAGE Doxa image container wrapping a C++ Doxa::Image.
% Handles the column-major (Matlab) to row-major (C++) memory layout
% conversion at construction and extraction boundaries.
%
% Construction:
% img = Doxa.Image('file.ppm') % from file
% img = Doxa.Image('file.ppm', Doxa.Grayscale.QT) % from color file
% img = Doxa.Image(gray_uint8) % from 2D uint8
% img = Doxa.Image(rgb_uint8) % from 3D, default grayscale
% img = Doxa.Image(rgb_uint8, Doxa.Grayscale.QT) % from 3D, specific algorithm
%
% Methods:
% arr = img.toArray() % convert back to Matlab uint8 matrix
% disp(img) % display dimensions
%
% See also Doxa.Grayscale, Doxa.binarize
properties (Access = {?Doxa.Image})
Handle uint64 = uint64(0)
end
methods
function obj = Image(input, algorithm)
%IMAGE Construct a Doxa.Image from a file path, 2D, or 3D uint8 array.
if nargin == 0
% Internal: empty construction for fromHandle factory
return;
end
% Load from file if given a path
if ischar(input) || isstring(input)
input = imread(input);
% imread returns logical for PBM; scale to 0/255
if islogical(input)
input = uint8(input) * 255;
end
input = uint8(input);
end
if ~isa(input, 'uint8')
error('Doxa:Image:InvalidInput', 'Input must be uint8.');
end
if ndims(input) == 3
% Color image: convert to grayscale
if nargin < 2
algorithm = Doxa.Grayscale.MEAN;
end
obj.Handle = image_mex('from_grayscale', char(algorithm), input);
elseif ismatrix(input)
% 2D grayscale or binary
obj.Handle = image_mex('create', input);
else
error('Doxa:Image:InvalidInput', 'Input must be a 2D or 3D uint8 array.');
end
end
function arr = toArray(obj)
%TOARRAY Convert back to a Matlab uint8 matrix.
% arr = img.toArray()
obj.checkValid();
arr = image_mex('to_array', obj.Handle);
end
function w = width(obj)
%WIDTH Image width in pixels.
obj.checkValid();
w = image_mex('width', obj.Handle);
end
function h = height(obj)
%HEIGHT Image height in pixels.
obj.checkValid();
h = image_mex('height', obj.Handle);
end
function disp(obj)
%DISP Display Doxa.Image summary.
if obj.Handle == uint64(0)
fprintf(' Doxa.Image: [empty]\n');
else
fprintf(' Doxa.Image: %dx%d uint8\n', obj.width(), obj.height());
end
end
function delete(obj)
%DELETE Free the underlying C++ image memory.
if obj.Handle ~= uint64(0)
image_mex('destroy', obj.Handle);
obj.Handle = uint64(0);
end
end
end
methods (Hidden)
function h = getHandle(obj)
%GETHANDLE Return the raw uint64 handle for MEX interop.
obj.checkValid();
h = obj.Handle;
end
end
methods (Static, Hidden)
function obj = fromHandle(handle)
%FROMHANDLE Wrap an existing C++ image pointer without re-transposing.
obj = Doxa.Image();
obj.Handle = handle;
end
end
methods (Access = private)
function checkValid(obj)
if obj.Handle == uint64(0)
error('Doxa:Image:InvalidHandle', 'Image has been destroyed or is uninitialized.');
end
end
end
end
================================================
FILE: Bindings/Matlab/+Doxa/binarize.m
================================================
function outputImage = binarize(algorithm, inputImage, options)
%BINARIZE Convert a grayscale Doxa.Image to binary.
% binary = Doxa.binarize(Doxa.Algorithms.SAUVOLA, img)
% binary = Doxa.binarize(Doxa.Algorithms.SAUVOLA, img, window=75, k=0.2)
%
% Common parameters (defaults vary by algorithm):
% window - Local window size in pixels (default: 75)
% k - Sensitivity parameter (default: 0.2)
%
% Algorithm-specific parameters:
% threshold - Bernsen: global threshold (default: 100)
% contrastLimit - Bernsen: contrast limit (default: 25)
% R - AdOtsu: range parameter (default: 0.1)
% distance - AdOtsu: grid distance (default: window/2)
% minN - Su: minimum neighborhood (default: window)
% glyph - Gatos: estimated stroke width (default: 60)
%
% See also Doxa.Algorithms, Doxa.updateToBinary, Doxa.Image
arguments
algorithm Doxa.Algorithms
inputImage Doxa.Image
options.window = []
options.k = []
options.threshold = []
options.contrastLimit = []
options.R = []
options.distance = []
options.minN = []
options.glyph = []
end
params = Doxa.buildParams(options);
handle = binarize_mex(char(algorithm), inputImage.getHandle(), params);
outputImage = Doxa.Image.fromHandle(handle);
end
================================================
FILE: Bindings/Matlab/+Doxa/buildParams.m
================================================
function params = buildParams(options)
%BUILDPARAMS Convert name-value options to a parameter struct for MEX.
% Internal utility. Strips empty fields before passing to the MEX layer.
params = struct();
fields = fieldnames(options);
for i = 1:numel(fields)
value = options.(fields{i});
if ~isempty(value)
params.(fields{i}) = value;
end
end
end
================================================
FILE: Bindings/Matlab/+Doxa/calculatePerformance.m
================================================
function metrics = calculatePerformance(gtImage, binaryImage, options)
%CALCULATEPERFORMANCE Calculate binarization quality metrics.
% metrics = Doxa.calculatePerformance(gt, binary)
% metrics = Doxa.calculatePerformance(gt, binary, precisionWeights=pw, recallWeights=rw)
%
% Returns a struct with all standard metrics:
% accuracy, fm, recall, precision, psnr, nrm, mcc, drdm
%
% When precisionWeights and recallWeights are provided, pseudo-metrics
% are also included: pseudoFM, pseudoPrecision, pseudoRecall
%
% See also Doxa.Image, Doxa.readWeights
arguments
gtImage Doxa.Image
binaryImage Doxa.Image
options.precisionWeights double = []
options.recallWeights double = []
end
metrics = calculate_performance_mex( ...
gtImage.getHandle(), binaryImage.getHandle(), ...
options.precisionWeights, options.recallWeights);
end
================================================
FILE: Bindings/Matlab/+Doxa/readWeights.m
================================================
function weights = readWeights(filePath)
%READWEIGHTS Load a DIBCO-format weight file for pseudo-metrics.
% pw = Doxa.readWeights('precision_weights.dat')
% rw = Doxa.readWeights('recall_weights.dat')
%
% Returns a double column vector of weights.
%
% See also Doxa.calculatePerformance
arguments
filePath {mustBeTextScalar, mustBeFile}
end
fid = fopen(filePath, 'r');
weights = fscanf(fid, '%f');
fclose(fid);
end
================================================
FILE: Bindings/Matlab/+Doxa/updateToBinary.m
================================================
function updateToBinary(algorithm, inputImage, options)
%UPDATETOBINARY Binarize a Doxa.Image in-place.
% Doxa.updateToBinary(Doxa.Algorithms.SAUVOLA, img)
% Doxa.updateToBinary(Doxa.Algorithms.SAUVOLA, img, window=75, k=0.2)
%
% Modifies the Doxa.Image directly. No new image is created.
% See Doxa.binarize for available parameters.
%
% See also Doxa.Algorithms, Doxa.binarize, Doxa.Image
arguments
algorithm Doxa.Algorithms
inputImage Doxa.Image
options.window = []
options.k = []
options.threshold = []
options.contrastLimit = []
options.R = []
options.distance = []
options.minN = []
options.glyph = []
end
params = Doxa.buildParams(options);
update_to_binary_mex(char(algorithm), inputImage.getHandle(), params);
end
================================================
FILE: Bindings/Matlab/BinarizeMex.cpp
================================================
// Δoxa Binarization Framework
// License: CC0 2026, "Freely you have received; freely give." - Matt 10:8
#include "mex.h"
#include "DoxaMexUtils.hpp"
/// <summary>
/// MEX function to create a new binarized image from a Doxa.Image handle.
/// Matlab Signature: handle = binarize_mex(algorithm_name, image_handle, params_struct)
/// </summary>
void mexFunction(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[])
{
if (nrhs < 2 || nrhs > 3) {
mexErrMsgIdAndTxt("Doxa:binarize:InvalidInput", "Usage: binarize_mex(algorithm, image_handle, params_struct)");
}
// 1. Get Algorithm Enum
std::string algorithmStr = mxArrayToString(prhs[0]);
Doxa::Algorithms algorithmEnum = DoxaMexUtils::StringToAlgorithmEnum(algorithmStr);
// 2. Get Input Image from handle
Doxa::Image* grayImage = DoxaMexUtils::HandleToImage(prhs[1]);
// 3. Get Parameters
const mxArray* paramsMx = (nrhs == 3) ? prhs[2] : nullptr;
Doxa::Parameters params = DoxaMexUtils::MxStructToParameters(paramsMx);
// 4. Create output image and run algorithm
Doxa::Image* binaryImage = new Doxa::Image(grayImage->width, grayImage->height);
Doxa::IAlgorithm* algorithm = Doxa::BinarizationFactory::Algorithm(algorithmEnum);
algorithm->Initialize(*grayImage);
algorithm->ToBinary(*binaryImage, params);
delete algorithm;
// 5. Return handle to the new binary image
plhs[0] = DoxaMexUtils::ImageToHandle(binaryImage);
}
================================================
FILE: Bindings/Matlab/CMakeLists.txt
================================================
cmake_minimum_required(VERSION 3.16)
project(DoxaMatlab)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Find MATLAB and its components
find_package(Matlab COMPONENTS MAIN_PROGRAM)
# Include the root Doxa directory for header files
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../..)
# Define output directory for MEX files and Matlab sources
set(MEX_OUTPUT_DIR ${CMAKE_BINARY_DIR}/mex)
file(MAKE_DIRECTORY ${MEX_OUTPUT_DIR})
# Add each MEX file as a separate target
matlab_add_mex(NAME image_mex SRC ImageMex.cpp)
matlab_add_mex(NAME binarize_mex SRC BinarizeMex.cpp)
matlab_add_mex(NAME update_to_binary_mex SRC UpdateToBinaryMex.cpp)
matlab_add_mex(NAME calculate_performance_mex SRC CalculatePerformanceMex.cpp)
# All MEX targets
set(MEX_TARGETS image_mex binarize_mex update_to_binary_mex calculate_performance_mex)
# Set output directory for all MEX targets
set_property(TARGET ${MEX_TARGETS} PROPERTY RUNTIME_OUTPUT_DIRECTORY ${MEX_OUTPUT_DIR})
# SIMD optimizations
option(DOXA_ENABLE_SIMD "Enable SIMD optimizations" ON)
if(DOXA_ENABLE_SIMD)
if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64|x64")
if(NOT MSVC)
foreach(target ${MEX_TARGETS})
target_compile_options(${target} PRIVATE -msse2)
endforeach()
endif()
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|ARM64")
# ARM64: NEON is enabled by default
endif()
endif()
# Copy test files to the build directory (for CTest)
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/test/TestDoxa.m DESTINATION ${MEX_OUTPUT_DIR})
# Copy +Doxa package alongside the MEX files.
# TARGET_FILE_DIR resolves to the correct output directory on both
# single-config (mex/) and multi-config (mex/Release/) generators.
add_custom_command(TARGET image_mex POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/+Doxa
$<TARGET_FILE_DIR:image_mex>/+Doxa
)
# Enable testing and add the MATLAB tests to CTest
enable_testing()
matlab_add_unit_test(
NAME MatlabTests
UNITTEST_FILE ${MEX_OUTPUT_DIR}/TestDoxa.m
ADDITIONAL_PATH ${MEX_OUTPUT_DIR}/$<CONFIG>
)
================================================
FILE: Bindings/Matlab/CalculatePerformanceMex.cpp
================================================
// Δoxa Binarization Framework
// License: CC0 2026, "Freely you have received; freely give." - Matt 10:8
#include "mex.h"
#include "DoxaMexUtils.hpp"
/// <summary>
/// MEX function to calculate performance metrics between two Doxa.Image handles.
///
/// Signatures:
/// metrics = calculate_performance_mex(gt_handle, bin_handle)
/// metrics = calculate_performance_mex(gt_handle, bin_handle, p_weights, r_weights)
/// </summary>
void mexFunction(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[])
{
if (nrhs != 2 && nrhs != 4) {
mexErrMsgIdAndTxt("Doxa:calculatePerformance:InvalidInput",
"Usage: calculate_performance_mex(gt_handle, bin_handle, [p_weights, r_weights])");
}
// 1. Get Images from handles
Doxa::Image* groundTruthImage = DoxaMexUtils::HandleToImage(prhs[0]);
Doxa::Image* binaryImage = DoxaMexUtils::HandleToImage(prhs[1]);
if (groundTruthImage->width != binaryImage->width || groundTruthImage->height != binaryImage->height) {
mexErrMsgIdAndTxt("Doxa:calculatePerformance:MismatchedDimensions",
"Input images must have the same dimensions.");
}
// 2. Compute classifications (with or without pseudo-weights)
Doxa::ClassifiedPerformance::Classifications classifications;
bool hasPseudoWeights = false;
if (nrhs == 4 && !mxIsEmpty(prhs[2]) && !mxIsEmpty(prhs[3])) {
std::vector<double> precisionWeights = DoxaMexUtils::MxArrayToDoubleVector(prhs[2]);
std::vector<double> recallWeights = DoxaMexUtils::MxArrayToDoubleVector(prhs[3]);
Doxa::ClassifiedPerformance::CompareImages(
classifications, *groundTruthImage, *binaryImage, precisionWeights, recallWeights);
hasPseudoWeights = true;
} else {
Doxa::ClassifiedPerformance::CompareImages(
classifications, *groundTruthImage, *binaryImage);
}
// 3. Build output struct with all metrics
int numFields = hasPseudoWeights ? 11 : 8;
const char* fieldNames[] = {
"accuracy", "fm", "recall", "precision", "psnr", "nrm", "mcc", "drdm",
"pseudoFM", "pseudoPrecision", "pseudoRecall"
};
plhs[0] = mxCreateStructMatrix(1, 1, numFields, fieldNames);
mxSetField(plhs[0], 0, "accuracy", mxCreateDoubleScalar(Doxa::ClassifiedPerformance::CalculateAccuracy(classifications)));
mxSetField(plhs[0], 0, "fm", mxCreateDoubleScalar(Doxa::ClassifiedPerformance::CalculateFMeasure(classifications)));
mxSetField(plhs[0], 0, "recall", mxCreateDoubleScalar(Doxa::ClassifiedPerformance::CalculateRecall(classifications)));
mxSetField(plhs[0], 0, "precision", mxCreateDoubleScalar(Doxa::ClassifiedPerformance::CalculatePrecision(classifications)));
mxSetField(plhs[0], 0, "psnr", mxCreateDoubleScalar(Doxa::ClassifiedPerformance::CalculatePSNR(classifications)));
mxSetField(plhs[0], 0, "nrm", mxCreateDoubleScalar(Doxa::ClassifiedPerformance::CalculateNRM(classifications)));
mxSetField(plhs[0], 0, "mcc", mxCreateDoubleScalar(Doxa::ClassifiedPerformance::CalculateMCC(classifications)));
mxSetField(plhs[0], 0, "drdm", mxCreateDoubleScalar(Doxa::DRDM::CalculateDRDM(*groundTruthImage, *binaryImage)));
if (hasPseudoWeights) {
mxSetField(plhs[0], 0, "pseudoFM", mxCreateDoubleScalar(Doxa::ClassifiedPerformance::CalculatePseudoFMeasure(classifications)));
mxSetField(plhs[0], 0, "pseudoPrecision", mxCreateDoubleScalar(Doxa::ClassifiedPerformance::CalculatePseudoPrecision(classifications)));
mxSetField(plhs[0], 0, "pseudoRecall", mxCreateDoubleScalar(Doxa::ClassifiedPerformance::CalculatePseudoRecall(classifications)));
}
}
================================================
FILE: Bindings/Matlab/Doxa.prj
================================================
<deployment-project plugin="plugin.toolbox" plugin-version="1.0">
<configuration file="${PROJECT_ROOT}\Doxa.prj" location="${PROJECT_ROOT}" name="Doxa" target="target.toolbox" target-name="Package Toolbox">
<param.appname>Doxa</param.appname>
<param.authnamewatermark>Brandon M. Petty</param.authnamewatermark>
<param.email>brandonpetty1981@gmail.com/param.email>
<param.company />
<param.summary>Fast, header-only C++ image binarization framework with MATLAB bindings</param.summary>
<param.description>Doxa is an image binarization library focusing on local adaptive thresholding algorithms. It provides 13 binarization algorithms including Sauvola, Niblack, Wolf, and others, with performance metrics (Pseudo F-Measure, PSNR, DRDM) and grayscale conversion options.</param.description>
<param.screenshot>${PROJECT_ROOT}\..\..\Demo\2JohnC1V3.png</param.screenshot>
<param.version>1.0.0</param.version>
<param.output>${PROJECT_ROOT}\Doxa.mltbx</param.output>
<param.products.name />
<param.products.id />
<param.products.version />
<param.platforms />
<param.guid>a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d</param.guid>
<param.exclude.filters>% List files contained in your toolbox folder that you would like to exclude
% from packaging. Excludes should be listed relative to the toolbox folder.
% Some examples of how to specify excludes are provided below:
%
% A single file in the toolbox folder:
% .svn
%
% A single file in a subfolder of the toolbox folder:
% example/.svn
%
% All files in a subfolder of the toolbox folder:
% example/*
%
% All files of a certain name in all subfolders of the toolbox folder:
% **/.svn
%
% All files matching a pattern in all subfolders of the toolbox folder:
% **/*.bak
%
build
build-*
CMakeLists.txt
*.cpp
*.hpp
test
.git*
*.yml</param.exclude.filters>
<param.exclude.pcodedmfiles>true</param.exclude.pcodedmfiles>
<param.examples />
<param.demosxml />
<param.apps />
<param.registered.apps />
<param.docs />
<param.getting.started.guide />
<param.matlabpath.excludes />
<param.javaclasspath.excludes />
<param.exported.on.package>false</param.exported.on.package>
<param.required.addons />
<param.matlab.project.id />
<param.matlab.project.name />
<param.release.start>R2019b</param.release.start>
<param.release.end>latest</param.release.end>
<param.release.current.only>false</param.release.current.only>
<param.compatiblity.windows>true</param.compatiblity.windows>
<param.compatiblity.mac>true</param.compatiblity.mac>
<param.compatiblity.linux>true</param.compatiblity.linux>
<param.compatiblity.matlabonline>false</param.compatiblity.matlabonline>
<param.installation.map />
<param.additional.sw.names />
<param.additional.sw.licenses />
<param.additional.sw.win.url />
<param.additional.sw.mac.url />
<param.additional.sw.linux.url />
<unset>
<param.company />
<param.output />
<param.products.name />
<param.products.id />
<param.products.version />
<param.platforms />
<param.exclude.pcodedmfiles />
<param.examples />
<param.demosxml />
<param.apps />
<param.registered.apps />
<param.docs />
<param.getting.started.guide />
<param.matlabpath.excludes />
<param.javaclasspath.excludes />
<param.exported.on.package />
<param.required.addons />
<param.matlab.project.id />
<param.matlab.project.name />
<param.release.current.only />
<param.compatiblity.windows />
<param.compatiblity.mac />
<param.compatiblity.linux />
<param.installation.map />
<param.additional.sw.names />
<param.additional.sw.licenses />
<param.additional.sw.win.url />
<param.additional.sw.mac.url />
<param.additional.sw.linux.url />
</unset>
<fileset.rootdir>
<file>${PROJECT_ROOT}</file>
</fileset.rootdir>
<fileset.rootfiles>
<file>${PROJECT_ROOT}\+Doxa</file>
<file>${PROJECT_ROOT}\README.md</file>
</fileset.rootfiles>
<fileset.depfun.included />
<fileset.depfun.excluded />
<fileset.package />
<build-deliverables>
<file location="${PROJECT_ROOT}" name="Doxa.mltbx" optional="false">${PROJECT_ROOT}\Doxa.mltbx</file>
</build-deliverables>
<workflow />
<matlab>
<root>${MATLAB_ROOT}</root>
<toolboxes />
</matlab>
<platform>
<unix>false</unix>
<mac>false</mac>
<windows>true</windows>
<win2k>false</win2k>
<winxp>false</winxp>
<vista>false</vista>
<linux>false</linux>
<solaris>false</solaris>
<osver>10.0</osver>
<os32>false</os32>
<os64>true</os64>
<arch>win64</arch>
<matlab>true</matlab>
</platform>
</configuration>
</deployment-project>
================================================
FILE: Bindings/Matlab/DoxaMexUtils.hpp
================================================
// Δoxa Binarization Framework
// License: CC0 2026, "Freely you have received; freely give." - Matt 10:8
#ifndef DOXAMEXUTILS_HPP
#define DOXAMEXUTILS_HPP
#include "mex.h"
#include "Doxa/Image.hpp"
#include "Doxa/Parameters.hpp"
#include "Doxa/BinarizationFactory.hpp"
#include "Doxa/ClassifiedPerformance.hpp"
#include "Doxa/DRDM.hpp"
#include "Doxa/Grayscale.hpp"
#include <string>
#include <vector>
#include <unordered_map>
#include <cstring>
namespace DoxaMexUtils
{
/// <summary>
/// Casts a uint64 scalar mxArray handle to a Doxa::Image pointer with validation.
/// </summary>
inline Doxa::Image* HandleToImage(const mxArray* arr)
{
if (!mxIsUint64(arr) || !mxIsScalar(arr)) {
mexErrMsgIdAndTxt("Doxa:InvalidHandle", "Expected a Doxa.Image handle (uint64 scalar).");
}
uint64_t handle = *static_cast<uint64_t*>(mxGetData(arr));
if (handle == 0) {
mexErrMsgIdAndTxt("Doxa:NullHandle", "Image handle is null or has been destroyed.");
}
return reinterpret_cast<Doxa::Image*>(static_cast<uintptr_t>(handle));
}
/// <summary>
/// Wraps a Doxa::Image pointer into a uint64 scalar mxArray.
/// </summary>
inline mxArray* ImageToHandle(Doxa::Image* image)
{
mxArray* arr = mxCreateNumericMatrix(1, 1, mxUINT64_CLASS, mxREAL);
*static_cast<uint64_t*>(mxGetData(arr)) = static_cast<uint64_t>(reinterpret_cast<uintptr_t>(image));
return arr;
}
/// <summary>
/// Creates a new Doxa::Image from a Matlab 2D uint8 array, transposing from column-major to row-major.
/// </summary>
inline Doxa::Image* CreateImageFromMxArray(const mxArray* arr)
{
if (!mxIsUint8(arr) || mxGetNumberOfDimensions(arr) != 2) {
mexErrMsgIdAndTxt("Doxa:InvalidImage", "Image must be a 2D uint8 matrix.");
}
const mwSize* dims = mxGetDimensions(arr);
int height = static_cast<int>(dims[0]);
int width = static_cast<int>(dims[1]);
const uint8_t* matlabData = static_cast<const uint8_t*>(mxGetData(arr));
Doxa::Image* image = new Doxa::Image(width, height);
// Transpose: col-major (Matlab) → row-major (Doxa)
for (int row = 0; row < height; ++row) {
for (int col = 0; col < width; ++col) {
image->data[row * width + col] = matlabData[col * height + row];
}
}
return image;
}
/// <summary>
/// Creates a new Matlab 2D uint8 mxArray from a Doxa::Image, transposing from row-major to column-major.
/// </summary>
inline mxArray* ImageToMxArray(const Doxa::Image& image)
{
mwSize dims[2] = { (mwSize)image.height, (mwSize)image.width };
mxArray* arr = mxCreateNumericArray(2, dims, mxUINT8_CLASS, mxREAL);
uint8_t* matlabData = static_cast<uint8_t*>(mxGetData(arr));
// Transpose: row-major (Doxa) → col-major (Matlab)
for (int row = 0; row < image.height; ++row) {
for (int col = 0; col < image.width; ++col) {
matlabData[col * image.height + row] = image.data[row * image.width + col];
}
}
return arr;
}
/// <summary>
/// Creates a new Doxa::Image from a Matlab 3D uint8 color array by converting to grayscale.
/// Matlab stores [H,W,C] as C separate column-major planes; Doxa expects interleaved row-major RGB.
/// </summary>
inline Doxa::Image* CreateImageFromGrayscale(const mxArray* arr, Doxa::GrayscaleAlgorithms algorithm)
{
if (!mxIsUint8(arr)) {
mexErrMsgIdAndTxt("Doxa:InvalidImage", "Color image must be a uint8 array.");
}
mwSize ndims = mxGetNumberOfDimensions(arr);
const mwSize* dims = mxGetDimensions(arr);
if (ndims != 3 || (dims[2] != 3 && dims[2] != 4)) {
mexErrMsgIdAndTxt("Doxa:InvalidImage", "Color image must be [H,W,3] or [H,W,4] uint8.");
}
int height = static_cast<int>(dims[0]);
int width = static_cast<int>(dims[1]);
int channels = static_cast<int>(dims[2]);
const uint8_t* matlabData = static_cast<const uint8_t*>(mxGetData(arr));
int pixelCount = width * height;
// Reorder Matlab planar col-major to interleaved row-major
std::vector<Doxa::Pixel8> interleaved(pixelCount * channels);
for (int row = 0; row < height; ++row) {
for (int col = 0; col < width; ++col) {
int doxaIdx = (row * width + col) * channels;
for (int c = 0; c < channels; ++c) {
interleaved[doxaIdx + c] = matlabData[c * pixelCount + col * height + row];
}
}
}
// Convert to grayscale using Doxa
Doxa::Image* image = new Doxa::Image(width, height);
Doxa::Grayscale::ToGrayscale(image->data, interleaved.data(), width, height, channels, algorithm);
return image;
}
/// <summary>
/// Converts a Matlab double array to a std::vector of doubles.
/// </summary>
inline std::vector<double> MxArrayToDoubleVector(const mxArray* arr)
{
if (!mxIsDouble(arr)) {
mexErrMsgIdAndTxt("Doxa:InvalidWeights", "Weights must be a double array.");
}
double* data = mxGetPr(arr);
size_t n = mxGetNumberOfElements(arr);
return std::vector<double>(data, data + n);
}
/// <summary>
/// Converts a Matlab struct into a Doxa::ParameterMap.
/// </summary>
/// <summary>
/// Maps Matlab-friendly field names to C++ parameter names.
/// Matlab struct fields cannot contain hyphens, so camelCase is mapped here.
/// </summary>
inline std::string MapParameterName(const std::string& matlabName)
{
static const std::unordered_map<std::string, std::string> nameMap = {
{"contrastLimit", "contrast-limit"}
};
auto it = nameMap.find(matlabName);
return (it != nameMap.end()) ? it->second : matlabName;
}
inline Doxa::Parameters MxStructToParameters(const mxArray* aStruct)
{
Doxa::ParameterMap paramMap;
if (aStruct != nullptr && !mxIsEmpty(aStruct)) {
if (!mxIsStruct(aStruct)) {
mexErrMsgIdAndTxt("Doxa:InvalidParams", "Parameters must be a struct.");
}
int numFields = mxGetNumberOfFields(aStruct);
for (int i = 0; i < numFields; ++i) {
const char* matlabFieldName = mxGetFieldNameByNumber(aStruct, i);
mxArray* fieldValue = mxGetFieldByNumber(aStruct, 0, i);
std::string paramName = MapParameterName(matlabFieldName);
if (mxIsScalar(fieldValue)) {
if (mxIsDouble(fieldValue)) {
paramMap[paramName] = mxGetScalar(fieldValue);
} else { // Treat other numeric types as int
paramMap[paramName] = static_cast<int>(mxGetScalar(fieldValue));
}
}
}
}
return Doxa::Parameters(paramMap);
}
/// <summary>
/// Converts a string algorithm name to a Doxa::Algorithms enum.
/// </summary>
inline Doxa::Algorithms StringToAlgorithmEnum(const std::string& algorithmStr)
{
static const std::unordered_map<std::string, Doxa::Algorithms> algorithmMap = {
{"OTSU", Doxa::Algorithms::OTSU}, {"BERNSEN", Doxa::Algorithms::BERNSEN},
{"NIBLACK", Doxa::Algorithms::NIBLACK}, {"SAUVOLA", Doxa::Algorithms::SAUVOLA},
{"WOLF", Doxa::Algorithms::WOLF}, {"NICK", Doxa::Algorithms::NICK},
{"SU", Doxa::Algorithms::SU}, {"TRSINGH", Doxa::Algorithms::TRSINGH},
{"BATAINEH", Doxa::Algorithms::BATAINEH}, {"ISAUVOLA", Doxa::Algorithms::ISAUVOLA},
{"WAN", Doxa::Algorithms::WAN}, {"GATOS", Doxa::Algorithms::GATOS},
{"ADOTSU", Doxa::Algorithms::ADOTSU}, {"PHANSALKAR", Doxa::Algorithms::PHANSALKAR}
};
auto it = algorithmMap.find(algorithmStr);
if (it == algorithmMap.end()) {
mexErrMsgIdAndTxt("Doxa:UnknownAlgorithm", "Unknown algorithm specified: %s", algorithmStr.c_str());
}
return it->second;
}
/// <summary>
/// Converts a string grayscale algorithm name to a Doxa::GrayscaleAlgorithms enum.
/// </summary>
inline Doxa::GrayscaleAlgorithms StringToGrayscaleEnum(const std::string& algorithmStr)
{
static const std::unordered_map<std::string, Doxa::GrayscaleAlgorithms> grayscaleMap = {
{"MEAN", Doxa::GrayscaleAlgorithms::MEAN},
{"QT", Doxa::GrayscaleAlgorithms::QT},
{"BT601", Doxa::GrayscaleAlgorithms::BT601},
{"BT709", Doxa::GrayscaleAlgorithms::BT709},
{"BT2100", Doxa::GrayscaleAlgorithms::BT2100},
{"VALUE", Doxa::GrayscaleAlgorithms::VALUE},
{"LUSTER", Doxa::GrayscaleAlgorithms::LUSTER},
{"LIGHTNESS", Doxa::GrayscaleAlgorithms::LIGHTNESS},
{"MINAVG", Doxa::GrayscaleAlgorithms::MINAVG},
{"LABDIST", Doxa::GrayscaleAlgorithms::LABDIST}
};
auto it = grayscaleMap.find(algorithmStr);
if (it == grayscaleMap.end()) {
mexErrMsgIdAndTxt("Doxa:UnknownGrayscale", "Unknown grayscale algorithm: %s", algorithmStr.c_str());
}
return it->second;
}
}
#endif // DOXAMEXUTILS_HPP
================================================
FILE: Bindings/Matlab/ImageMex.cpp
================================================
// Δoxa Binarization Framework
// License: CC0 2026, "Freely you have received; freely give." - Matt 10:8
#include "mex.h"
#include "DoxaMexUtils.hpp"
/// <summary>
/// MEX gateway for Doxa.Image lifecycle management.
/// Dispatches on an action string to create, convert, extract, or destroy images.
///
/// Actions:
/// handle = image_mex('create', uint8_2d_array)
/// handle = image_mex('from_grayscale', algorithm_str, uint8_3d_array)
/// uint8_2d = image_mex('to_array', handle)
/// width = image_mex('width', handle)
/// height = image_mex('height', handle)
/// image_mex('destroy', handle)
/// </summary>
void mexFunction(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[])
{
if (nrhs < 1) {
mexErrMsgIdAndTxt("Doxa:Image:InvalidInput", "Usage: image_mex(action, ...)");
}
std::string action = mxArrayToString(prhs[0]);
if (action == "create")
{
// create(uint8_2d_array) → handle
if (nrhs != 2) {
mexErrMsgIdAndTxt("Doxa:Image:InvalidInput", "Usage: image_mex('create', uint8_2d_array)");
}
Doxa::Image* image = DoxaMexUtils::CreateImageFromMxArray(prhs[1]);
plhs[0] = DoxaMexUtils::ImageToHandle(image);
}
else if (action == "from_grayscale")
{
// from_grayscale(algorithm_str, uint8_3d_array) → handle
if (nrhs != 3) {
mexErrMsgIdAndTxt("Doxa:Image:InvalidInput", "Usage: image_mex('from_grayscale', algorithm, color_array)");
}
std::string algorithmStr = mxArrayToString(prhs[1]);
Doxa::GrayscaleAlgorithms algorithm = DoxaMexUtils::StringToGrayscaleEnum(algorithmStr);
Doxa::Image* image = DoxaMexUtils::CreateImageFromGrayscale(prhs[2], algorithm);
plhs[0] = DoxaMexUtils::ImageToHandle(image);
}
else if (action == "to_array")
{
// to_array(handle) → uint8_2d_array
if (nrhs != 2) {
mexErrMsgIdAndTxt("Doxa:Image:InvalidInput", "Usage: image_mex('to_array', handle)");
}
Doxa::Image* image = DoxaMexUtils::HandleToImage(prhs[1]);
plhs[0] = DoxaMexUtils::ImageToMxArray(*image);
}
else if (action == "width")
{
// width(handle) → double
if (nrhs != 2) {
mexErrMsgIdAndTxt("Doxa:Image:InvalidInput", "Usage: image_mex('width', handle)");
}
Doxa::Image* image = DoxaMexUtils::HandleToImage(prhs[1]);
plhs[0] = mxCreateDoubleScalar(static_cast<double>(image->width));
}
else if (action == "height")
{
// height(handle) → double
if (nrhs != 2) {
mexErrMsgIdAndTxt("Doxa:Image:InvalidInput", "Usage: image_mex('height', handle)");
}
Doxa::Image* image = DoxaMexUtils::HandleToImage(prhs[1]);
plhs[0] = mxCreateDoubleScalar(static_cast<double>(image->height));
}
else if (action == "destroy")
{
// destroy(handle) → void
if (nrhs != 2) {
mexErrMsgIdAndTxt("Doxa:Image:InvalidInput", "Usage: image_mex('destroy', handle)");
}
Doxa::Image* image = DoxaMexUtils::HandleToImage(prhs[1]);
delete image;
}
else
{
mexErrMsgIdAndTxt("Doxa:Image:UnknownAction", "Unknown action: %s", action.c_str());
}
}
================================================
FILE: Bindings/Matlab/README.md
================================================
# Δoxa Binarization Framework - Matlab
## Introduction
Doxa is an image binarization library focusing on local adaptive thresholding algorithms. In English, this means that it has the ability to turn a color or gray scale image into a black and white image.
This binding provides a simple, high-level interface for using the Doxa C++ framework directly within Matlab, with idiomatic naming and a `Doxa.*` package namespace.
## Build & Test
### Using CMake Presets (Recommended)
```sh
# From the project root
cmake --preset matlab
cmake --build build-matlab --config Release
ctest --test-dir build-matlab -C Release
```
### Package Toolbox (.mltbx)
After building MEX files, create the distributable toolbox from MATLAB:
```matlab
cd Bindings/Matlab
matlab.addons.toolbox.packageToolbox('Doxa.prj')
% Produces Doxa.mltbx
```
## Matlab Example
Add the build output directory to your Matlab path, then use the `Doxa.*` API.
```matlab
% Add the build directory to the path
addpath('path/to/build-matlab/mex');
% Read a color image and convert to grayscale
img = Doxa.Image('photo.ppm', Doxa.Grayscale.QT);
% Binarize with Sauvola (name-value parameters)
binary = Doxa.binarize(Doxa.Algorithms.SAUVOLA, img, window=75, k=0.2);
% Display the result
imshow(binary.toArray());
% Or binarize in-place for efficiency
Doxa.updateToBinary(Doxa.Algorithms.SAUVOLA, img, window=75, k=0.2);
```
## Algorithms
All 14 binarization algorithms are available via `Doxa.Algorithms`:
| Global | Local Adaptive |
|--------|---------------|
| `OTSU` | `BERNSEN`, `NIBLACK`, `SAUVOLA`, `WOLF`, `NICK`, `SU`, `TRSINGH`, `BATAINEH`, `PHANSALKAR`, `ISAUVOLA`, `WAN`, `GATOS`, `ADOTSU` |
## Grayscale Conversion
Ten grayscale algorithms are available via `Doxa.Grayscale`:
`MEAN`, `QT`, `BT601`, `BT709`, `BT2100`, `VALUE`, `LUSTER`, `LIGHTNESS`, `MINAVG`, `LABDIST`
```matlab
% Smart constructor handles files, 2D arrays, and 3D color arrays
img = Doxa.Image('color.ppm', Doxa.Grayscale.MEAN);
img = Doxa.Image(rgb_array, Doxa.Grayscale.QT);
img = Doxa.Image(gray_array);
```
## Performance Metrics
```matlab
% Calculate metrics
metrics = Doxa.calculatePerformance(gt, binary);
% With weight files
pw = Doxa.readWeights('precision_weights.dat');
rw = Doxa.readWeights('recall_weights.dat');
metrics = Doxa.calculatePerformance(gt, binary, precisionWeights=pw, recallWeights=rw);
```
**Metrics:** accuracy, fm, recall, precision, psnr, nrm, mcc, drdm, pseudoFM, pseudoPrecision, pseudoRecall
## License
CC0 - Brandon M. Petty, 2026
To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. This software is distributed without any warranty.
[View Online](https://creativecommons.org/publicdomain/zero/1.0/legalcode)
"*Freely you have received; freely give.*" - Matt 10:8
================================================
FILE: Bindings/Matlab/UpdateToBinaryMex.cpp
================================================
// Δoxa Binarization Framework
// License: CC0 2026, "Freely you have received; freely give." - Matt 10:8
#include "mex.h"
#include "DoxaMexUtils.hpp"
/// <summary>
/// MEX function to binarize a Doxa.Image in-place.
/// Matlab Signature: update_in_place_mex(algorithm_name, image_handle, params_struct)
/// </summary>
void mexFunction(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[])
{
if (nrhs < 2 || nrhs > 3) {
mexErrMsgIdAndTxt("Doxa:updateToBinary:InvalidInput", "Usage: update_in_place_mex(algorithm, image_handle, params_struct)");
}
if (nlhs > 0) {
mexErrMsgIdAndTxt("Doxa:updateToBinary:InvalidOutput", "updateToBinary modifies the image in-place and has no return values.");
}
// 1. Get Algorithm Enum
std::string algorithmStr = mxArrayToString(prhs[0]);
Doxa::Algorithms algorithmEnum = DoxaMexUtils::StringToAlgorithmEnum(algorithmStr);
// 2. Get Image from handle (already row-major, operates in-place)
Doxa::Image* image = DoxaMexUtils::HandleToImage(prhs[1]);
// 3. Get Parameters
const mxArray* paramsMx = (nrhs == 3) ? prhs[2] : nullptr;
Doxa::Parameters params = DoxaMexUtils::MxStructToParameters(paramsMx);
// 4. Run algorithm in-place on the C++ buffer
Doxa::IAlgorithm* algorithm = Doxa::BinarizationFactory::Algorithm(algorithmEnum);
algorithm->Initialize(*image);
algorithm->ToBinary(*image, params);
delete algorithm;
}
================================================
FILE: Bindings/Matlab/test/TestDoxa.m
================================================
classdef TestDoxa < matlab.unittest.TestCase
% Test suite for the Doxa Matlab bindings.
properties
TestData
end
methods (TestClassSetup)
function loadTestData(testCase)
% Locate test resources
baseDir = fileparts(mfilename('fullpath'));
resourceDir = fullfile(baseDir, '..', '..', 'Doxa.Test', 'Resources');
% Store resource dir for weight files
testCase.TestData.resourceDir = resourceDir;
% Create grayscale image using Qt algorithm (matches ground truth generation)
rgb = imread(fullfile(resourceDir, '2JohnC1V3.ppm'));
testCase.TestData.rgb = rgb;
testCase.TestData.grayscaleImage = Doxa.Image(rgb, Doxa.Grayscale.QT);
% Load ground truth images
testCase.TestData.groundTruth = Doxa.Image(fullfile(resourceDir, '2JohnC1V3-GroundTruth.pbm'));
testCase.TestData.sauvolaGroundTruth = Doxa.Image(fullfile(resourceDir, '2JohnC1V3-Sauvola.pbm'));
% Keep raw ground truth for comparison
testCase.TestData.sauvolaGroundTruthArray = ...
uint8(imread(fullfile(resourceDir, '2JohnC1V3-Sauvola.pbm'))) * 255;
end
end
methods (Test)
function testBinarize(testCase)
% Tests the binarize function for correctness.
binary = Doxa.binarize(Doxa.Algorithms.SAUVOLA, ...
testCase.TestData.grayscaleImage, window=27, k=0.10);
result = binary.toArray();
% Verify binary output (only 0 and 255)
testCase.verifyEqual(unique(result(:)), uint8([0; 255]));
% Verify against ground truth
testCase.verifyEqual(result, testCase.TestData.sauvolaGroundTruthArray, ...
'Binarized image does not match ground truth.');
end
function testUpdateToBinary(testCase)
% Tests the in-place update function.
img = Doxa.Image(testCase.TestData.grayscaleImage.toArray());
Doxa.updateToBinary(Doxa.Algorithms.SAUVOLA, img, window=27, k=0.10);
testCase.verifyEqual(img.toArray(), testCase.TestData.sauvolaGroundTruthArray, ...
'In-place binarized image does not match ground truth.');
end
function testCalculatePerformance(testCase)
% Tests performance metrics match Python/C++ expected values.
metrics = Doxa.calculatePerformance( ...
testCase.TestData.groundTruth, ...
testCase.TestData.sauvolaGroundTruth);
testCase.verifyEqual(metrics.accuracy, 97.671, 'RelTol', 1e-2);
testCase.verifyEqual(metrics.fm, 93.204, 'RelTol', 1e-2);
testCase.verifyEqual(metrics.recall, 91.381, 'RelTol', 1e-2);
testCase.verifyEqual(metrics.precision, 95.103, 'RelTol', 1e-2);
testCase.verifyEqual(metrics.psnr, 16.329, 'RelTol', 1e-2);
testCase.verifyEqual(metrics.nrm, 0.048, 'RelTol', 1e-2);
testCase.verifyEqual(metrics.mcc, 0.918, 'RelTol', 1e-2);
testCase.verifyEqual(metrics.drdm, 1.952, 'RelTol', 1e-2);
end
function testPseudoPerformance(testCase)
% Tests pseudo-metrics with weight files.
pWeights = Doxa.readWeights(fullfile( ...
testCase.TestData.resourceDir, '2JohnC1V3-GroundTruth_PWeights.dat'));
rWeights = Doxa.readWeights(fullfile( ...
testCase.TestData.resourceDir, '2JohnC1V3-GroundTruth_RWeights.dat'));
metrics = Doxa.calculatePerformance( ...
testCase.TestData.groundTruth, ...
testCase.TestData.sauvolaGroundTruth, ...
precisionWeights=pWeights, recallWeights=rWeights);
testCase.verifyEqual(metrics.pseudoFM, 93.393, 'RelTol', 1e-2);
testCase.verifyEqual(metrics.pseudoRecall, 92.795, 'RelTol', 1e-2);
testCase.verifyEqual(metrics.pseudoPrecision, 93.998, 'RelTol', 1e-2);
end
function testGrayscale(testCase)
% Tests grayscale conversion matches manual Qt formula.
rgb = testCase.TestData.rgb;
img = Doxa.Image(rgb, Doxa.Grayscale.QT);
result = img.toArray();
% Compute expected using Qt formula
r = double(rgb(:,:,1));
g = double(rgb(:,:,2));
b = double(rgb(:,:,3));
expected = uint8((r * 11 + g * 16 + b * 5) / 32);
testCase.verifyEqual(result, expected, 'AbsTol', uint8(1), ...
'Grayscale conversion does not match Qt formula.');
end
function testImageFromFile(testCase)
% Tests that loading from file matches loading from array.
resourceDir = testCase.TestData.resourceDir;
imgFromFile = Doxa.Image(fullfile(resourceDir, '2JohnC1V3.ppm'), Doxa.Grayscale.QT);
imgFromArray = Doxa.Image(imread(fullfile(resourceDir, '2JohnC1V3.ppm')), Doxa.Grayscale.QT);
testCase.verifyEqual(imgFromFile.toArray(), imgFromArray.toArray(), ...
'File-based and array-based construction produce different results.');
end
function testReadWeights(testCase)
% Tests weight file loading.
weights = Doxa.readWeights(fullfile( ...
testCase.TestData.resourceDir, '2JohnC1V3-GroundTruth_PWeights.dat'));
testCase.verifyFalse(isempty(weights));
testCase.verifyTrue(isa(weights, 'double'));
testCase.verifyGreaterThan(numel(weights), 0);
end
function testImageDisplay(testCase)
% Tests that disp works without error.
img = testCase.TestData.grayscaleImage;
testCase.verifyWarningFree(@() disp(img));
end
function testAllAlgorithmsRun(testCase)
% Ensures all algorithms can be called without error.
algorithms = enumeration('Doxa.Algorithms');
img = testCase.TestData.grayscaleImage;
for i = 1:length(algorithms)
alg = algorithms(i);
testCase.verifyWarningFree(@() Doxa.binarize(alg, img), ...
['Algorithm ' char(alg) ' failed to run.']);
end
end
end
end
================================================
FILE: Bindings/Python/.gitignore
================================================
build
dist
src/Doxa
__pycache__
================================================
FILE: Bindings/Python/CMakeLists.txt
================================================
message(STATUS "DoxaPy - CMake Build")
cmake_minimum_required(VERSION 3.16...3.27)
project(doxapy)
if (CMAKE_VERSION VERSION_LESS 3.18)
set(DEV_MODULE Development)
else()
set(DEV_MODULE Development.Module)
endif()
find_package(Python 3.12
REQUIRED COMPONENTS Interpreter ${DEV_MODULE}
OPTIONAL_COMPONENTS Development.SABIModule
)
if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "RelWithDebInfo")
endif()
message(STATUS "Build Type: ${CMAKE_BUILD_TYPE}")
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_compile_definitions(NB_TARGET_ABI_VERSION=312)
# SIMD support for Python bindings
option(DOXAPY_ENABLE_SIMD "Enable SIMD in Python bindings" ON)
if(MSVC)
# so far so good
else()
add_compile_options("-Wno-narrowing")
endif()
# Detect the installed nanobind package and import it into CMake
execute_process(
COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir
OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT
)
find_package(nanobind CONFIG REQUIRED)
include_directories(./src/Doxa)
nanobind_add_module(
doxapy
NB_STATIC STABLE_ABI LTO NOMINSIZE
src/DoxaPy.cpp
)
# Speed optimization for header-only library (algorithms compile within binding module)
target_compile_options(doxapy PRIVATE
$<$<AND:$<NOT:$<CONFIG:Debug>>,$<CXX_COMPILER_ID:MSVC>>:/O2>
$<$<AND:$<NOT:$<CONFIG:Debug>>,$<NOT:$<CXX_COMPILER_ID:MSVC>>>:-O3>
)
# Platform-specific SIMD flags (same logic as test)
if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64|x64")
# x86-64: SSE2 is always available (baseline for x64)
if(MSVC)
# MSVC x64: SSE2 is enabled by default, no special flags needed
message(STATUS "DoxaPy SIMD: x86-64 with SSE2 (MSVC)")
else()
# GCC/Clang: SSE2 is default for x64, but be explicit
target_compile_options(doxapy PRIVATE -msse2)
message(STATUS "DoxaPy SIMD: x86-64 with SSE2 (GCC/Clang)")
endif()
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|ARM64")
message(STATUS "DoxaPy SIMD: ARM64 with NEON")
endif()
# Copy built module + __init__.py to dist/doxapy/ as a proper package
add_custom_command(TARGET doxapy POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_SOURCE_DIR}/dist/doxapy
COMMAND ${CMAKE_COMMAND} -E copy
$<TARGET_FILE:doxapy>
${CMAKE_CURRENT_SOURCE_DIR}/dist/doxapy/
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/src/doxapy/__init__.py
${CMAKE_CURRENT_SOURCE_DIR}/dist/doxapy/
COMMENT "Copying doxapy package to dist/"
)
install(TARGETS doxapy LIBRARY DESTINATION doxapy)
================================================
FILE: Bindings/Python/DoxaPy.ipynb
================================================
{
"cells": [
{
"cell_type": "markdown",
"id": "17ccb794-7c43-4c40-89ff-693df6b7b513",
"metadata": {},
"source": [
"# DoxaPy Notebook\n",
"\n",
"https://github.com/brandonmpetty/Doxa\n",
"\n",
"DoxaPy is an image binarization library focused on local adaptive algorithms and metrics.\n",
"This notebook will document the API while allowing you to interact with it.\n",
"\n",
"## Setup\n",
"The first thing to do when getting started with this library is to install it.\n",
"```\n",
"pip install doxapy\n",
"```\n",
"Form more details, see: https://pypi.org/project/doxapy\n",
"\n",
"Alternatively, you can build the library from source as described in the README.MD.\n",
"\n",
"From there, it is as simple as importing the library. NumPy and Pillow are two other libraries we will use in this demonstration."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "532b64af-c7f0-4f68-87fe-c17d3322c219",
"metadata": {},
"outputs": [],
"source": [
"# Prioritize a local build first\n",
"import sys, os\n",
"sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(\"__file__\")), \"dist\"))\n",
"\n",
"from PIL import Image\n",
"import numpy as np\n",
"import doxapy"
]
},
{
"cell_type": "markdown",
"id": "5fd90377-1177-4e16-aeb8-b1da0a525269",
"metadata": {},
"source": [
"## Reading an Image\n",
"The first step is to read the image you intend on processing. The *read_image* helper function uses Pillow to read in a local image and convert it to grayscale. We then use NumPy to turn that image into an array.\n",
"\n",
"DoxaPy exposes a number of Color of Grayscale algorithms.\n",
"\n",
"### Grayscale Algorithms\n",
"- **MEAN**\n",
"- **QT**\n",
"- **BT601**\n",
"- **BT709**\n",
"- **BT2100**\n",
"- **VALUE**\n",
"- **LUSTER**\n",
"- **LIGHTNESS**\n",
"- **MINAVG**"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5993f8a0-a499-412f-b832-767d97c8efe9",
"metadata": {},
"outputs": [],
"source": [
"def read_image(file, algorithm=doxapy.GrayscaleAlgorithms.MEAN):\n",
" '''Read an image. If it is color, turn it into 8 bit grayscale.'''\n",
" image = Image.open(file)\n",
"\n",
" # If already in grayscale or binary, do not convert it\n",
" if image.mode == 'L':\n",
" return np.array(image)\n",
" \n",
" # Read the color image\n",
" rgb_image = np.array(image.convert('RGB') if image.mode not in ('RGB', 'RGBA') else image)\n",
"\n",
" # Use Doxa to convert to grayscale\n",
" return doxapy.to_grayscale(algorithm, rgb_image)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a475bd49-b1ec-4ebd-b146-8cb819817a18",
"metadata": {},
"outputs": [],
"source": [
"grayscale_image = read_image(\"../../Doxa.Test/Resources/2JohnC1V3.ppm\", doxapy.GrayscaleAlgorithms.LIGHTNESS)\n",
"display(Image.fromarray(grayscale_image))"
]
},
{
"cell_type": "markdown",
"id": "3665c6d0-0a4e-4950-a025-69ae0dae60e3",
"metadata": {},
"source": [
"## Converting the Image to Binary\n",
"Converting an image into black and white may seem easy, but it has been the focus of much research spanning decades. Doxa was designed to expose this research, traditionally mired by PHD technical jargon, in a very easy to consume fashion. A lot of work was put into ensuring these algorithms were implemented correctly and effeciently. Many of these algorithms were first made public by this project and many of them leverage state of the art enhacements to reduce memory utilization and increase speed of operation found nowhere else.\n",
"\n",
"### Algorithms\n",
"The Doxa library implements a large number of popular and unique local adaptive binarization algorithms. Each algorithm has a set of parameters that are required for it to operate. These parameters can vary from algorithm to algorithm. Doxa provides sensible defaults that are applied automatically unless you supply your own. Below is a list of algorithms and their defaults:\n",
"\n",
"* **OTSU**\n",
"* **BERNSEN** - {\"window\": 75, \"threshold\": 100, \"contrast-limit\": 25}\n",
"* **NIBLACK** - {\"window\": 75, \"k\": 0.2}\n",
"* **SAUVOLA** - {\"window\": 75, \"k\": 0.2}\n",
"* **WOLF** - {\"window\": 75, \"k\": 0.2}\n",
"* **NICK** - {\"window\": 75, \"k\": -0.2}\n",
"* **SU** - {\"window\": 9, \"minN\": 9}\n",
"* **TRSINGH** - {\"window\": 75, \"k\": 0.2}\n",
"* **BATAINEH**\n",
"* **ISAUVOLA** - {\"window\": 75, \"k\": 0.2}\n",
"* **WAN** - {\"window\": 75, \"k\": 0.2}\n",
"* **GATOS** - {\"window\": 75, \"k\": 0.2, \"glyph\": 60}\n",
"* **ADOTSU** - {\"window\": 75, \"k\": 1.0, \"R\": 0.1, \"distance\": window/2}\n",
"* **PHANSALKAR** - {\"window\": 75, \"k\": 0.2, \"p\": 3.0, \"q\": 10.0}"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "656a290b-9c83-4ee4-9eee-6a5126e1e574",
"metadata": {},
"outputs": [],
"source": [
"# Initialize an image array with the same shape as our grayscale image\n",
"binary_image = doxapy.to_binary(doxapy.Binarization.Algorithms.SAUVOLA, grayscale_image, {\"window\": 75, \"k\": 0.2})\n",
"display(Image.fromarray(binary_image))"
]
},
{
"cell_type": "markdown",
"id": "58ae263b-3341-4388-b0f8-0bae06f8a13d",
"metadata": {},
"source": [
"One of the quickest and most efficient ways of turning your grayscale image into a binary image is to use the *update_to_binary* function. Instead of allocating more memory to write the image to, it will update the existing image in-place. It also only takes one line to write!"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6b444625-0b48-4197-b884-2f2ba591906e",
"metadata": {},
"outputs": [],
"source": [
"doxapy.update_to_binary(doxapy.Binarization.Algorithms.SAUVOLA, grayscale_image, {\"window\": 27, \"k\": 0.12})\n",
"display(Image.fromarray(grayscale_image))"
]
},
{
"cell_type": "markdown",
"id": "24d8b5dc-9b75-4c33-8814-50fc00082d40",
"metadata": {},
"source": [
"## Performance Metrics\n",
"In order to analyze the performance of an algorithm, Doxa provides a set of common metrics that can all be calculated with one function. To start that process you need an exemplar binary image, or \"ground truth.\" By comparing the ground truth to the resulting image of the binarization algorithm, you can start to compare the affects of different algorithms and algorithm parameters."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "70ba6fef-7a29-4dcd-a75b-5fa4cf016414",
"metadata": {},
"outputs": [],
"source": [
"groundtruth_image = read_image(\"../../Doxa.Test/Resources/2JohnC1V3-GroundTruth.pbm\")\n",
"display(Image.fromarray(groundtruth_image))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "73cddbae",
"metadata": {},
"outputs": [],
"source": [
"# Read our Pseudo F-Measure weights\n",
"p_weights = doxapy.read_weights(\"../../Doxa.Test/Resources/2JohnC1V3-GroundTruth_PWeights.dat\")\n",
"r_weights = doxapy.read_weights(\"../../Doxa.Test/Resources/2JohnC1V3-GroundTruth_RWeights.dat\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "90439c66-67d5-4b2d-a450-5c1c823f2414",
"metadata": {},
"outputs": [],
"source": [
"# Help us 'pretty print' our JSON\n",
"import json\n",
"\n",
"# Both of these were done with the Sauvola algorithm, but with different parameters\n",
"# NOTE: grayscale_image was updated in-place above into binary \n",
"performance1 = doxapy.calculate_performance(groundtruth_image, binary_image, p_weights, r_weights)\n",
"performance2 = doxapy.calculate_performance_ex(groundtruth_image, grayscale_image, drdm=True, accuracy=True, mcc=True)\n",
"\n",
"print(\"Sauvola - Window = 75, K = 0.2\") # Default\n",
"print(json.dumps(performance1, indent=2))\n",
"print()\n",
"print(\"Sauvola - Window = 27, K = 0.12\") # Adjusted\n",
"print(json.dumps(performance2, indent=2))"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
================================================
FILE: Bindings/Python/README.md
================================================
# Δoxa Binarization Framework - Python
## Introduction
DoxaPy is an image binarization library focusing on local adaptive thresholding algorithms. In English, this means that it has the ability to turn a color or gray scale image into a black and white image.
**Algorithms**
* Otsu - "A threshold selection method from gray-level histograms", 1979.
* Bernsen - "Dynamic thresholding of gray-level images", 1986.
* Niblack - "An Introduction to Digital Image Processing", 1986.
* Sauvola - "Adaptive document image binarization", 1999.
* Wolf - "Extraction and Recognition of Artificial Text in Multimedia Documents", 2003.
* Gatos - "Adaptive degraded document image binarization", 2005. (Partial)
* NICK - "Comparison of Niblack inspired Binarization methods for ancient documents", 2009.
* AdOtsu - "A multi-scale framework for adaptive binarization of degraded document images", 2010.
* Su - "Binarization of Historical Document Images Using the Local Maximum and Minimum", 2010.
* T.R. Singh - "A New local Adaptive Thresholding Technique in Binarization", 2011.
* Bataineh - "An adaptive local binarization method for document images based on a novel thresholding method and dynamic windows", 2011. (unreproducible)
* Phansalkar - "Adaptive Local Thresholding for Detection of Nuclei in Diversely Stained Cytology Images", 2011.
* ISauvola - "ISauvola: Improved Sauvola's Algorithm for Document Image Binarization", 2016.
* WAN - "Binarization of Document Image Using Optimum Threshold Modification", 2018.
**Optimizations**
* Shafait - "Efficient Implementation of Local Adaptive Thresholding Techniques Using Integral Images", 2008.
* Petty - An algorithm for efficiently calculating the min and max of a local window. Unpublished, 2019.
* Chan - "Memory-efficient and fast implementation of local adaptive binarization methods", 2019.
* SIMD - SSE2, ARM NEON
**Performance Metrics**
* Overall Accuracy
* F-Measure, Precision, Recall
* Pseudo F-Measure, Precision, Recall - "Performance Evaluation Methodology for Historical Document Image Binarization", 2013.
* Peak Signal-To-Noise Ratio (PSNR)
* Negative Rate Metric (NRM)
* Matthews Correlation Coefficient (MCC)
* Distance-Reciprocal Distortion Measure (DRDM) - "An Objective Distortion Measure for Binary Document Images Based on Human Visual Perception", 2002.
## Overview
DoxaPy uses the Δoxa Binarization Framework for quickly processing python Image files. It is comprised of three major sets of algorithms: Color to Grayscale, Grayscale to Binary, and Performance Metrics. It can be used as a full DIBCO Metrics replacement that is significantly smaller, faster, and easier to integrate into existing projects.
### Example
This short demo uses DoxaPy to read in a color image, converts it to binary, and then compares it to a Ground Truth image in order to calculate performance.
```python
from PIL import Image
import numpy as np
import doxapy
def read_image(file, algorithm=doxapy.GrayscaleAlgorithms.MEAN):
"""Read an image. If its color, use one of our many Grayscale algorithms to convert it."""
image = Image.open(file)
# If already in grayscale or binary, do not convert it
if image.mode == 'L':
return np.array(image)
# Read the color image
rgb_image = np.array(image.convert('RGB') if image.mode not in ('RGB', 'RGBA') else image)
# Use Doxa to convert grayscale
return doxapy.to_grayscale(algorithm, rgb_image)
# Read our target image and convert it to grayscale
grayscale_image = read_image("2JohnC1V3.png")
# Convert the grayscale image to a binary image (algorithm parameters optional)
binary_image = doxapy.to_binary(doxapy.Binarization.Algorithms.SAUVOLA, grayscale_image, {"window": 75, "k": 0.2})
# Calculate the binarization performance using a Ground Truth image
groundtruth_image = read_image("2JohnC1V3-GroundTruth.png")
performance = doxapy.calculate_performance(groundtruth_image, binary_image)
print(performance)
# Display our resulting image
Image.fromarray(binary_image).show()
```
### DoxaPy Notebook
For more details, open the [DoxaPy Notebook](https://github.com/brandonmpetty/Doxa/blob/master/Bindings/Python/DoxaPy.ipynb) and to get an interactive demo.
## Building and Test
DoxaPy supports 64b Linux, Windows, and Mac OSX on Python 3.x. Starting with DoxaPy 0.9.4, Python 3.12 and above are supported with full ABI compatibility. This means that new versions of DoxaPy will only be published due to feature enhancements, not Python version support.
**Build from Project Root**
```bash
# From the Doxa project root
git clone --depth 1 https://github.com/brandonmpetty/Doxa.git
cd Doxa
cmake --preset python
cmake --build build-python --config Release
pip install -r Bindings/Python/requirements.txt
ctest --test-dir build-python -C Release
```
**Local Package Build**
```bash
python -m build
```
**Local Wheel Build**
```bash
pip wheel . --no-deps
```
## License
CC0 - Brandon M. Petty, 2026
To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. This software is distributed without any warranty.
[View Online](https://creativecommons.org/publicdomain/zero/1.0/legalcode)
"*Freely you have received; freely give.*" - Matt 10:8
================================================
FILE: Bindings/Python/copy-cpp-files.py
================================================
import glob, os, shutil
from itertools import chain
src_dir=os.path.join("..", "..", "Doxa")
dst_dir=os.path.join("src", "Doxa")
os.makedirs(dst_dir, exist_ok=True)
# Copy only modified .HPP files, since the last run
files = chain(
glob.iglob(os.path.join(src_dir, "*.hpp")),
glob.iglob(os.path.join(src_dir, "*.h"))
)
for src_file in files:
if os.path.isfile(src_file):
dst_file=os.path.join(dst_dir, os.path.basename(src_file))
if not(os.path.isfile(dst_file)) or (os.stat(src_file).st_mtime - os.stat(dst_file).st_mtime > 0):
shutil.copy2(src_file, dst_dir)
print(f"Copying {src_file}")
================================================
FILE: Bindings/Python/pyproject.toml
================================================
[build-system]
requires = ["cibuildwheel >= 2.22.0","scikit-build-core >= 0.11.3", "nanobind >= 2.7.0"]
build-backend = "scikit_build_core.build"
[project]
name = "doxapy"
version = "0.9.6"
description = "An image binarization library focussing on local adaptive thresholding"
readme = "README.md"
authors = [
{name = "Brandon M. Petty", email = "brandonpetty1981@gmail.com"}
]
license = {text = "CC0-1.0"}
requires-python = ">=3.12"
dependencies = [
"numpy>=1.20.0"
]
[project.urls]
Homepage = "https://github.com/brandonmpetty/doxa"
[tool.scikit-build]
cmake.build-type = "Release"
minimum-version = "0.4"
build-dir = "build/{wheel_tag}"
wheel.py-api = "cp312"
sdist.include = [
"src/Doxa" # Include all files in the Doxa directory
]
sdist.exclude = [
"copy-cpp-files.py",
"DoxaPy.ipynb",
".gitignore",
"test",
"dist"
]
[tool.cibuildwheel]
# Necessary to see build output from the actual compilation
build-verbosity = 1
# Run tests to ensure correct builds
#test-command = "python test/test_doxa.py"
archs = ["auto64"] # Only target 64 bit architectures
================================================
FILE: Bindings/Python/requirements.txt
================================================
numpy>=1.20.0
Pillow>=8.0.0
build>=1.2.0
================================================
FILE: Bindings/Python/src/DoxaPy.cpp
================================================
#include <nanobind/nanobind.h>
#include <nanobind/ndarray.h>
#include <nanobind/stl/string.h>
#include <nanobind/stl/map.h>
#include <nanobind/stl/vector.h>
#include <nanobind/stl/variant.h>
#include "Doxa/BinarizationFactory.hpp"
#include "Doxa/ClassifiedPerformance.hpp"
#include "Doxa/DRDM.hpp"
#include "Doxa/DIBCOUtils.hpp"
#include "Doxa/Grayscale.hpp"
namespace nb = nanobind;
using namespace Doxa;
// Nanobind helper for converting from an array to a Doxa Image. This will reference the array, not create a copy.
Image ArrayToImage(const nb::ndarray<Pixel8, nb::ndim<2>>& imageArray)
{
return Image::Reference(imageArray.shape(1), imageArray.shape(0), reinterpret_cast<Pixel8*>(imageArray.data()));
}
nb::dict CalculatePerformance(
const nb::ndarray<Pixel8, nb::ndim<2>>& groundTruthImageArray,
const nb::ndarray<Pixel8, nb::ndim<2>>& binaryImageArray,
const std::vector<double>& precisionWeights = {},
const std::vector<double>& recallWeights = {})
{
Image groundTruthImage = ArrayToImage(groundTruthImageArray);
Image binaryImage = ArrayToImage(binaryImageArray);
auto dict = nb::dict();
ClassifiedPerformance::Classifications classifications;
if (!precisionWeights.empty() && !recallWeights.empty())
ClassifiedPerformance::CompareImages(classifications, groundTruthImage, binaryImage, precisionWeights, recallWeights);
else
ClassifiedPerformance::CompareImages(classifications, groundTruthImage, binaryImage);
dict["accuracy"] = ClassifiedPerformance::CalculateAccuracy(classifications);
dict["fm"] = ClassifiedPerformance::CalculateFMeasure(classifications);
dict["recall"] = ClassifiedPerformance::CalculateRecall(classifications);
dict["precision"] = ClassifiedPerformance::CalculatePrecision(classifications);
dict["mcc"] = ClassifiedPerformance::CalculateMCC(classifications);
dict["psnr"] = ClassifiedPerformance::CalculatePSNR(classifications);
dict["nrm"] = ClassifiedPerformance::CalculateNRM(classifications);
dict["drdm"] = DRDM::CalculateDRDM(groundTruthImage, binaryImage);
if (!precisionWeights.empty() && !recallWeights.empty())
{
dict["pseudo_fm"] = ClassifiedPerformance::CalculatePseudoFMeasure(classifications);
dict["pseudo_precision"] = ClassifiedPerformance::CalculatePseudoPrecision(classifications);
dict["pseudo_recall"] = ClassifiedPerformance::CalculatePseudoRecall(classifications);
}
return dict;
}
nb::dict CalculatePerformanceEx(
const nb::ndarray<Pixel8, nb::ndim<2>>& groundTruthImageArray,
const nb::ndarray<Pixel8, nb::ndim<2>>& binaryImageArray,
bool accuracy = false,
bool fm = false,
bool recall = false,
bool precision = false,
bool mcc = false,
bool psnr = false,
bool nrm = false,
bool drdm = false,
bool pseudo_fm = false,
bool pseudo_precision = false,
bool pseudo_recall = false,
const std::vector<double>& precisionWeights = {},
const std::vector<double>& recallWeights = {})
{
Image groundTruthImage = ArrayToImage(groundTruthImageArray);
Image binaryImage = ArrayToImage(binaryImageArray);
auto dict = nb::dict();
const bool needsPseudo = (pseudo_fm || pseudo_precision || pseudo_recall)
&& !precisionWeights.empty() && !recallWeights.empty();
if (accuracy || fm || recall || precision || mcc || psnr || nrm || needsPseudo)
{
ClassifiedPerformance::Classifications classifications;
if (needsPseudo)
ClassifiedPerformance::CompareImages(classifications, groundTruthImage, binaryImage, precisionWeights, recallWeights);
else
ClassifiedPerformance::CompareImages(classifications, groundTruthImage, binaryImage);
if (accuracy)
dict["accuracy"] = ClassifiedPerformance::CalculateAccuracy(classifications);
if (fm)
dict["fm"] = ClassifiedPerformance::CalculateFMeasure(classifications);
if (recall)
dict["recall"] = ClassifiedPerformance::CalculateRecall(classifications);
if (precision)
dict["precision"] = ClassifiedPerformance::CalculatePrecision(classifications);
if (mcc)
dict["mcc"] = ClassifiedPerformance::CalculateMCC(classifications);
if (psnr)
dict["psnr"] = ClassifiedPerformance::CalculatePSNR(classifications);
if (nrm)
dict["nrm"] = ClassifiedPerformance::CalculateNRM(classifications);
if (needsPseudo)
{
if (pseudo_fm)
dict["pseudo_fm"] = ClassifiedPerformance::CalculatePseudoFMeasure(classifications);
if (pseudo_precision)
dict["pseudo_precision"] = ClassifiedPerformance::CalculatePseudoPrecision(classifications);
if (pseudo_recall)
dict["pseudo_recall"] = ClassifiedPerformance::CalculatePseudoRecall(classifications);
}
}
if (drdm)
{
dict["drdm"] = DRDM::CalculateDRDM(groundTruthImage, binaryImage);
}
return dict;
}
nb::ndarray<nb::numpy, Pixel8, nb::ndim<2>> ToGrayscale(
GrayscaleAlgorithms algorithm,
const nb::ndarray<Pixel8, nb::ndim<3>>& colorImageArray)
{
const int height = colorImageArray.shape(0);
const int width = colorImageArray.shape(1);
const int channels = colorImageArray.shape(2);
Pixel8* output = new Pixel8[width * height];
Grayscale::ToGrayscale(
output,
reinterpret_cast<const Pixel8*>(colorImageArray.data()),
width, height, channels, algorithm);
// Return an ND Array that will correctly manage the allocated memory
const size_t shape[2] = { (size_t)height, (size_t)width };
nb::capsule owner(output, [](void* p) noexcept { delete[] static_cast<Pixel8*>(p); });
return nb::ndarray<nb::numpy, Pixel8, nb::ndim<2>>(output, 2, shape, owner);
}
/// <summary>
/// Binarization is a helper class to help interface the C++ Doxa framework with Python.
/// It exposes through enumeration all of the algorithms supported by the library.
/// </summary>
class Binarization
{
public:
Binarization(const Algorithms algorithm)
: algorithm(algorithm)
{
algorithmPtr = BinarizationFactory::Algorithm(algorithm);
}
~Binarization()
{
delete algorithmPtr;
}
void Initialize(const nb::ndarray<Pixel8, nb::ndim<2>>& grayScaleImageArray)
{
Image image = ArrayToImage(grayScaleImageArray);
algorithmPtr->Initialize(image);
}
void ToBinary(const nb::ndarray<Pixel8, nb::ndim<2>>& binaryImageArray, const ParameterMap& parameters={})
{
Image image = ArrayToImage(binaryImageArray);
algorithmPtr->ToBinary(image, parameters);
}
Algorithms CurrentAlgorithm() { return algorithm; }
protected:
const Algorithms algorithm;
IAlgorithm* algorithmPtr = nullptr;
}; // Class: Binarization
nb::ndarray<nb::numpy, Pixel8, nb::ndim<2>> ToBinary(
const Algorithms algorithm,
const nb::ndarray<Pixel8, nb::ndim<2>>& grayImage,
const ParameterMap& parameters = {})
{
const int height = grayImage.shape(0);
const int width = grayImage.shape(1);
// Allocate new memory for the image data
Pixel8* binaryImage = new Pixel8[width * height];
// Apply the algorithm
const Image grayImageRef = ArrayToImage(grayImage);
Image binaryImageRef = Image::Reference(width, height, binaryImage);
IAlgorithm* algorithmPtr = BinarizationFactory::Algorithm(algorithm);
algorithmPtr->Initialize(grayImageRef);
algorithmPtr->ToBinary(binaryImageRef, parameters);
delete algorithmPtr;
// Return an ND Array that will correctly manage the allocated memory
const size_t shape[2] = { (size_t)height, (size_t)width };
nb::capsule owner(binaryImage, [](void* p) noexcept { delete[] static_cast<Pixel8*>(p); });
return nb::ndarray<nb::numpy, Pixel8, nb::ndim<2>>(binaryImage, 2, shape, owner);
}
void UpdateToBinary(
const Algorithms algorithm,
const nb::ndarray<Pixel8, nb::ndim<2>>& imageArray,
const ParameterMap& parameters = {})
{
Binarization binAlg(algorithm);
binAlg.Initialize(imageArray);
binAlg.ToBinary(imageArray, parameters);
}
// Expose routines using the PEP8 style guide
NB_MODULE(doxapy, m) {
m.doc() = "DoxaPy: Python bindings for the Doxa image binarization framework";
m.def("calculate_performance", &CalculatePerformance,
nb::arg("groundTruthImageArray"),
nb::arg("binaryImageArray"),
nb::arg("precision_weights") = std::vector<double>{},
nb::arg("recall_weights") = std::vector<double>{},
"Obtain binarization performance information based on a Ground Truth. "
"Optionally pass precision and recall weight arrays for pseudo-metrics.");
m.def("calculate_performance_ex", &CalculatePerformanceEx,
nb::arg("groundTruthImageArray"),
nb::arg("binaryImageArray"),
nb::arg("accuracy") = false,
nb::arg("fm") = false,
nb::arg("recall") = false,
nb::arg("precision") = false,
nb::arg("mcc") = false,
nb::arg("psnr") = false,
nb::arg("nrm") = false,
nb::arg("drdm") = false,
nb::arg("pseudo_fm") = false,
nb::arg("pseudo_precision") = false,
nb::arg("pseudo_recall") = false,
nb::arg("precision_weights") = std::vector<double>{},
nb::arg("recall_weights") = std::vector<double>{},
"Obtain specific binarization performance information based on a Ground Truth. "
"Pseudo-metrics require precision and recall weight arrays.");
nb::enum_<GrayscaleAlgorithms>(m, "GrayscaleAlgorithms")
.value("MEAN", GrayscaleAlgorithms::MEAN)
.value("QT", GrayscaleAlgorithms::QT)
.value("BT601", GrayscaleAlgorithms::BT601)
.value("BT709", GrayscaleAlgorithms::BT709)
.value("BT2100", GrayscaleAlgorithms::BT2100)
.value("VALUE", GrayscaleAlgorithms::VALUE)
.value("LUSTER", GrayscaleAlgorithms::LUSTER)
.value("LIGHTNESS", GrayscaleAlgorithms::LIGHTNESS)
.value("MINAVG", GrayscaleAlgorithms::MINAVG)
.value("LABDIST", GrayscaleAlgorithms::LABDIST)
.export_values();
m.def("to_grayscale", &ToGrayscale,
nb::arg("algorithm"),
nb::arg("color_image"),
"Convert an RGB or RGBA image to 8-bit grayscale.");
m.def("to_binary", &ToBinary,
nb::arg("algorithm"),
nb::arg("image"),
nb::arg("parameters") = ParameterMap(),
"Convert a grayscale image to binary, returning a new image.");
m.def("update_to_binary", &UpdateToBinary,
nb::arg("algorithm"),
nb::arg("image"),
nb::arg("parameters") = ParameterMap(),
"Convert a grayscale image to binary in-place.");
nb::class_<Binarization> binarization(m, "Binarization");
binarization.def(nb::init<const Algorithms>())
.def("initialize", &Binarization::Initialize)
.def("to_binary", &Binarization::ToBinary,
nb::arg("binaryImageArray"),
nb::arg("parameters") = ParameterMap())
.def("algorithm", &Binarization::CurrentAlgorithm);
nb::enum_<Algorithms>(binarization, "Algorithms")
.value("OTSU", Algorithms::OTSU)
.value("BERNSEN", Algorithms::BERNSEN)
.value("NIBLACK", Algorithms::NIBLACK)
.value("SAUVOLA", Algorithms::SAUVOLA)
.value("WOLF", Algorithms::WOLF)
.value("NICK", Algorithms::NICK)
.value("SU", Algorithms::SU)
.value("TRSINGH", Algorithms::TRSINGH)
.value("BATAINEH", Algorithms::BATAINEH)
.value("ISAUVOLA", Algorithms::ISAUVOLA)
.value("WAN", Algorithms::WAN)
.value("GATOS", Algorithms::GATOS)
.value("ADOTSU", Algorithms::ADOTSU)
.value("PHANSALKAR", Algorithms::PHANSALKAR)
.export_values();
}
================================================
FILE: Bindings/Python/src/doxapy/__init__.py
================================================
from .doxapy import *
def read_weights(file):
with open(file) as f:
return [float(x) for x in f.read().split()]
================================================
FILE: Bindings/Python/test/test_doxa.py
================================================
import unittest
from PIL import Image, ImageChops
import numpy as np
import os
import sys
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'dist')))
import doxapy
# Get the absolute path to the RESOURCES directory (at project root)
RESOURCES_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'Doxa.Test', 'Resources'))
def read_image(file, algorithm=doxapy.GrayscaleAlgorithms.QT):
full_path = os.path.join(RESOURCES_DIR, os.path.basename(file))
image = Image.open(full_path)
# If already in grayscale or binary, do not convert it
if image.mode == 'L':
return np.array(image)
# Read the color image
rgb_image = np.array(image.convert('RGB') if image.mode not in ('RGB', 'RGBA') else image)
# Use Doxa to convert to grayscale
return doxapy.to_grayscale(algorithm, rgb_image)
def read_weights(file):
full_path = os.path.join(RESOURCES_DIR, os.path.basename(file))
return doxapy.read_weights(full_path)
class DoxaPyTests(unittest.TestCase):
def test_binarization(self):
# Read in a color image, transforming it into a grayscale image
image = read_image("2JohnC1V3.ppm")
# Create a new binary image
binary_image = np.empty(image.shape, image.dtype)
params = {"window": 27, "k": 0.10}
sauvola = doxapy.Binarization(doxapy.Binarization.Algorithms.SAUVOLA)
sauvola.initialize(image)
sauvola.to_binary(binary_image, params)
# Update the grayscale image to binary in-place
doxapy.update_to_binary(doxapy.Binarization.Algorithms.SAUVOLA, image, params)
# Ensure they are equal
self.assertTrue((binary_image == image).all())
# Compare against our control
expected_image_path = os.path.join(RESOURCES_DIR, os.path.basename("2JohnC1V3-Sauvola.pbm"))
expected_image = Image.open(expected_image_path)
#expected_image.save("expected_image.png")
output_image = Image.fromarray(binary_image)
#output_image.save('output_image.png')
diff = ImageChops.difference(expected_image, output_image);
#diff.show()
self.assertIsNone(diff.getbbox())
def test_performance(self):
# Setup
image = read_image("2JohnC1V3.ppm")
groundtruth_image = read_image("2JohnC1V3-GroundTruth.pbm")
params = {"window": 27, "k": 0.10}
doxapy.update_to_binary(doxapy.Binarization.Algorithms.SAUVOLA, image, params)
# Functions under test
performanceAll = doxapy.calculate_performance(groundtruth_image, image)
performance = doxapy.calculate_performance_ex(groundtruth_image, image, drdm=True, psnr=True)
# Assert All
self.assertAlmostEqual(performanceAll.get("accuracy"), 97.671, 2)
self.assertAlmostEqual(performanceAll.get("fm"), 93.204, 2)
self.assertAlmostEqual(performanceAll.get("recall"), 91.3811, 2)
self.assertAlmostEqual(performanceAll.get("precision"), 95.1025, 2)
self.assertAlmostEqual(performanceAll.get("psnr"), 16.329, 2)
self.assertAlmostEqual(performanceAll.get("nrm"), 0.048, 2)
self.assertAlmostEqual(performanceAll.get("mcc"), 0.918, 2)
self.assertAlmostEqual(performanceAll.get("drdm"), 1.9519, 3)
# Assert Partial
self.assertEqual(performance.get("accuracy"), None)
self.assertEqual(performance.get("fm"), None)
self.assertEqual(performance.get("nrm"), None)
self.assertEqual(performance.get("mcc"), None)
self.assertEqual(performance.get("psnr"), performanceAll.get("psnr"))
self.assertEqual(performance.get("drdm"), performanceAll.get("drdm"))
def test_pseudo_performance(self):
# Setup
image = read_image("2JohnC1V3.ppm")
groundtruth_image = read_image("2JohnC1V3-GroundTruth.pbm")
params = {"window": 27, "k": 0.10}
doxapy.update_to_binary(doxapy.Binarization.Algorithms.SAUVOLA, image, params)
# Load pseudo weights
p_weights = read_weights("2JohnC1V3-GroundTruth_PWeights.dat")
r_weights = read_weights("2JohnC1V3-GroundTruth_RWeights.dat")
# Calculate performance with pseudo weights
performance = doxapy.calculate_performance(groundtruth_image, image, p_weights, r_weights)
# Assert pseudo metrics
self.assertAlmostEqual(performance.get("pseudo_fm"), 93.393, 2)
self.assertAlmostEqual(performance.get("pseudo_recall"), 92.7954, 2)
self.assertAlmostEqual(performance.get("pseudo_precision"), 93.9983, 2)
if __name__ == '__main__':
unittest.main()
================================================
FILE: Bindings/Python/test/test_speed.py
================================================
"""
Speed Tests for DoxaPy
Measures execution time for:
- Sauvola binarization
- WAN binarization
- GlobalThresholding (Otsu) binarization
- DRDM calculation
- Classified Performance calculation
Run with: python test_speed.py
"""
import unittest
import time
import numpy as np
from PIL import Image
import os
import sys
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'dist')))
import doxapy
# Get the absolute path to the RESOURCES directory
RESOURCES_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'Doxa.Test', 'Resources'))
# Number of iterations for timing
WARMUP_ITERATIONS = 3
TIMING_ITERATIONS = 10
def read_image(file, algorithm=doxapy.GrayscaleAlgorithms.MEAN):
full_path = os.path.join(RESOURCES_DIR, os.path.basename(file))
image = Image.open(full_path)
# If already in grayscale or binary, do not convert it
if image.mode == 'L':
return np.array(image)
# Read the color image
rgb_image = np.array(image.convert('RGB') if image.mode not in ('RGB', 'RGBA') else image)
# Use Doxa to convert to grayscale
return doxapy.to_grayscale(algorithm, rgb_image)
def measure_time(func, warmup=WARMUP_ITERATIONS, iterations=TIMING_ITERATIONS):
"""Measure execution time of a function."""
# Warmup runs
for _ in range(warmup):
func()
# Timed runs
times = []
for _ in range(iterations):
start = time.perf_counter()
func()
end = time.perf_counter()
times.append((end - start) * 1000) # Convert to ms
return {
'min': min(times),
'max': max(times),
'avg': sum(times) / len(times),
'median': sorted(times)[len(times) // 2]
}
def format_results(name, results, image_shape):
"""Format timing results as a string."""
pixels = image_shape[0] * image_shape[1]
mpixels = pixels / 1_000_000
throughput = mpixels / (results['avg'] / 1000)
return (
f"\n{name}\n"
f" Image size: {image_shape[1]}x{image_shape[0]} ({mpixels:.2f} MP)\n"
f" Min: {results['min']:8.3f} ms\n"
f" Max: {results['max']:8.3f} ms\n"
f" Avg: {results['avg']:8.3f} ms\n"
f" Median: {results['median']:8.3f} ms\n"
f" Throughput: {throughput:.2f} MP/s"
)
class DoxaPySpeedTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
"""Load test images once for all tests."""
print("\n" + "=" * 60)
print("DoxaPy Speed Tests")
print("=" * 60)
print(f"\nWarmup iterations: {WARMUP_ITERATIONS}")
print(f"Timing iterations: {TIMING_ITERATIONS}")
print("\nLoading test images...")
cls.image = read_image("2JohnC1V3.ppm")
cls.groundtruth_image = read_image("2JohnC1V3-GroundTruth.pbm")
print(f"Grayscale image shape: {cls.image.shape}")
print(f"Ground truth shape: {cls.groundtruth_image.shape}")
# Create a binary image for performance metric tests
cls.binary_image = np.empty(cls.image.shape, cls.image.dtype)
params = {"window": 27, "k": 0.10}
sauvola = doxapy.Binarization(doxapy.Binarization.Algorithms.SAUVOLA)
sauvola.initialize(cls.image)
sauvola.to_binary(cls.binary_image, params)
print("\n" + "-" * 60)
print("BINARIZATION ALGORITHMS")
print("-" * 60)
def test_sauvola_speed(self):
"""Measure Sauvola binarization speed."""
binary_image = np.empty(self.image.shape, self.image.dtype)
params = {"window": 75, "k": 0.2}
sauvola = doxapy.Binarization(doxapy.Binarization.Algorithms.SAUVOLA)
sauvola.initialize(self.image)
def run():
sauvola.to_binary(binary_image, params)
results = measure_time(run)
print(format_results("Sauvola", results, self.image.shape))
self.assertGreater(results['avg'], 0)
def test_wan_speed(self):
"""Measure WAN binarization speed."""
binary_image = np.empty(self.image.shape, self.image.dtype)
params = {"window": 75, "k": 0.2}
wan = doxapy.Binarization(doxapy.Binarization.Algorithms.WAN)
wan.initialize(self.image)
def run():
wan.to_binary(binary_image, params)
results = measure_time(run)
print(format_results("WAN", results, self.image.shape))
self.assertGreater(results['avg'], 0)
def test_otsu_speed(self):
"""Measure Otsu (GlobalThresholding) binarization speed."""
binary_image = np.empty(self.image.shape, self.image.dtype)
params = {}
otsu = doxapy.Binarization(doxapy.Binarization.Algorithms.OTSU)
otsu.initialize(self.image)
def run():
otsu.to_binary(binary_image, params)
results = measure_time(run)
print(format_results("Otsu (GlobalThresholding)", results, self.image.shape))
self.assertGreater(results['avg'], 0)
def test_drdm_speed(self):
"""Measure DRDM calculation speed."""
print("\n" + "-" * 60)
print("PERFORMANCE METRICS")
print("-" * 60)
def run():
doxapy.calculate_performance_ex(self.groundtruth_image, self.binary_image, drdm=True)
results = measure_time(run)
print(format_results("DRDM", results, self.image.shape))
self.assertGreater(results['avg'], 0)
def test_classified_performance_speed(self):
"""Measure Classified Performance calculation speed (all metrics except DRDM)."""
def run():
doxapy.calculate_performance_ex(
self.groundtruth_image, self.binary_image,
accuracy=True, fm=True, psnr=True, nrm=True, mcc=True
)
results = measure_time(run)
print(format_results("ClassifiedPerformance (no DRDM)", results, self.image.shape))
self.assertGreater(results['avg'], 0)
def test_full_performance_speed(self):
"""Measure full performance calculation speed (all metrics including DRDM)."""
def run():
doxapy.calculate_performance(self.groundtruth_image, self.binary_image)
results = measure_time(run)
print(format_results("Full Performance (all metrics)", results, self.image.shape))
self.assertGreater(results['avg'], 0)
@classmethod
def tearDownClass(cls):
print("\n" + "=" * 60)
print("Speed tests completed")
print("=" * 60)
if __name__ == '__main__':
unittest.main(verbosity=2)
================================================
FILE: Bindings/WebAssembly/CMakeLists.txt
================================================
cmake_minimum_required(VERSION 3.16)
project(doxajs)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# This file is only used when building with Emscripten
if(NOT EMSCRIPTEN)
message(FATAL_ERROR "This CMakeLists.txt requires Emscripten. Use: emcmake cmake ..")
endif()
option(DOXAJS_ENABLE_SIMD "Enable WASM SIMD128" ON)
# Include Doxa headers
include_directories(${CMAKE_SOURCE_DIR}/Doxa)
# Create WASM module
add_executable(doxaWasm DoxaWasm.cpp)
# Emscripten compile/link flags (matches build.js)
target_compile_options(doxaWasm PRIVATE
$<IF:$<CONFIG:Debug>,-O0,-O3>
$<$<CONFIG:Debug>:-g>
)
target_link_options(doxaWasm PRIVATE
-sWASM=1
-sNO_EXIT_RUNTIME=1
-sALLOW_MEMORY_GROWTH=1
"-sEXPORTED_FUNCTIONS=['_malloc','_free']"
"-sEXPORTED_RUNTIME_METHODS=['HEAPU8']"
--bind
# Debug-only options
$<$<CONFIG:Debug>:-sEXCEPTION_DEBUG>
$<$<CONFIG:Debug>:-sNO_DISABLE_EXCEPTION_CATCHING>
$<$<CONFIG:Debug>:-g4>
"$<$<CONFIG:Debug>:--source-map-base=http://localhost:8080/>"
)
# SIMD support
if(DOXAJS_ENABLE_SIMD)
target_compile_options(doxaWasm PRIVATE -msimd128)
message(STATUS "DoxaJs SIMD: ENABLED (SIMD128)")
else()
message(STATUS "DoxaJs SIMD: DISABLED")
endif()
# Output to dist/ directory
set_target_properties(doxaWasm PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/dist
)
# Copy JavaScript wrapper after build
add_custom_command(TARGET doxaWasm POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/doxa.js
${CMAKE_CURRENT_SOURCE_DIR}/dist/doxa.js
COMMENT "Copying doxa.js wrapper to dist/"
)
# CTest integration - run Jasmine tests via Node
include(CTest)
enable_testing()
# Platform-specific script extension (avoid picking up .cmd on WSL2)
if(WIN32)
set(CMD_EXT ".cmd")
else()
set(CMD_EXT "")
endif()
find_program(NPM_EXECUTABLE NAMES npm${CMD_EXT})
if(NPM_EXECUTABLE)
add_test(NAME wasm_tests
COMMAND ${NPM_EXECUTABLE} test
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
endif()
================================================
FILE: Bindings/WebAssembly/DoxaJs.nnb
================================================
{
"cells": [
{
"language": "markdown",
"source": [
"# DoxaJs Notebook\r\n\r\nhttps://github.com/brandonmpetty/Doxa\r\n\r\nDoxaJs is an image binarization library focused on local adaptive algorithms and metrics.\r\nThis notebook will document the API while allowing you to interact with it.\r\nIt was designed in VS Code using the \"Node.js Notebooks (REPL)\" notebook kernel.\r\n\r\n## Setup\r\nDoxaJs does not currently have an NPM package you can download, but distributables are packaged in this repo. To run the notebook, you first need to install dev dependencies like'sharp'.\r\n\r\n```\r\nnpm install\r\n```\r\n\r\nThe next thing you should do is to load the **doxa.js** module. It is actually a wrapper around WASM assets built with EMScripten and the C++ based Doxa binarization framework."
],
"outputs": []
},
{
"language": "javascript",
"source": [
"const { Doxa } = require('./dist/doxa.js');\r\nconst { display } = require('node-kernel');\r\nconst sharp = require('sharp');"
],
"outputs": []
},
{
"language": "markdown",
"source": [
"While this demo highlights NodeJs with *sharp*, a Web demo is also available leveraging the standard HTML5 canvas.\r\nBelow are two helper functions leveraging *sharp*. Unlike other language targets of Doxa, DoxaJs will actually take in a 32 RGBA image and convert it to grayscale automatically. This approach was prefered due to the demands of 32b images when working with web technologies."
],
"outputs": []
},
{
"language": "javascript",
"source": [
"// Read an image file and convert it to an 8-bit grayscale Doxa Image\r\nasync function readImage(file) {\r\n\treturn sharp(file)\r\n\t\t.raw()\r\n\t\t.toBuffer({ resolveWithObject: true })\r\n\t\t.then(content => {\r\n\t\t\treturn new Doxa.Image(content.info.width, content.info.height, content.data);\r\n\t\t});\r\n}\r\n\r\n// Add Notebook display support for Doxa Images\r\ndisplay.dimage = (image) => {\r\n\treturn sharp(image.data(), {\r\n\t\traw: {\r\n\t\t\twidth: image.width,\r\n\t\t\theight: image.height,\r\n\t\t\tchannels: 1\r\n\t\t}\r\n\t})\r\n\t.png()\r\n\t.toBuffer()\r\n\t.then((buf) => {\r\n\t\tdisplay.image(buf);\r\n\t});\r\n}"
],
"outputs": []
},
{
"language": "markdown",
"source": [
"Because the JS bindings are leveraging WASM for portability and speed, the library must be initialized to register the WASM. Once initialized it will return back an enum object that represents all of the binarization algorithms available in the library."
],
"outputs": []
},
{
"language": "javascript",
"source": [
"const Algorithms = await Doxa.initialize();"
],
"outputs": []
},
{
"language": "markdown",
"source": [
"## Reading an Image\r\nThe next step is to read in an image. At the heart of a Doxa Image is an 8-bit byte array. Your 32-bit color or grayscale image will be converted to an 8-bit image using the mean of the R-G-B channels."
],
"outputs": []
},
{
"language": "javascript",
"source": [
"// A snippet from a 1500 year Greek codex, hand written on velum.\r\nconst image = await readImage('../../README/2JohnC1V3.png');\r\n\r\ndisplay.dimage(image);"
],
"outputs": []
},
{
"language": "markdown",
"source": [
"## Converting the Image to Binary\r\nConverting an image into black and white may seem easy, but it has been the focus of much research spanning decades. Doxa was designed to expose this research, traditionally mired by PHD technical jargon, in a very easy to consume fashion. A lot of work was put into ensuring these algorithms were implemented correctly and effeciently. Many of these algorithms were first made public by this project and many of them leverage state of the art enhacements to reduce memory utilization and increase speed of operation, found nowhere else. For more information on an individual algorithm, click one of the links below.\r\n\r\n### Algorithms\r\nThe Doxa library implements a large number of popular and unique local adaptive binarization algorithms. Each algorithm has a set of parameters that are required for it to operate. These parameters can vary from algorithm to algorithm. Doxa provides sensible defaults that are applied automatically unless you supply your own. Below is a list of algorithms and their defaults:\r\n\r\n* **OTSU**\r\n* **BERNSEN** - {\"window\": 75, \"threshold\": 100, \"contrast-limit\": 25}\r\n* **NIBLACK** - {\"window\": 75, \"k\": 0.2}\r\n* **SAUVOLA** - {\"window\": 75, \"k\": 0.2}\r\n* **WOLF** - {\"window\": 75, \"k\": 0.2}\r\n* **NICK** - {\"window\": 75, \"k\": -0.2}\r\n* **SU** - {\"window\": 9, \"minN\": 9}\r\n* **TRSINGH** - {\"window\": 75, \"k\": 0.2}\r\n* **BATAINEH**\r\n* **ISAUVOLA** - {\"window\": 75, \"k\": 0.2}\r\n* **WAN** - {\"window\": 75, \"k\": 0.2}\r\n* **GATOS** - {\"window\": 75, \"k\": 0.2, \"glyph\": 60}\r\n* **ADOTSU** - {\"window\": 75, \"k\": 1.0, \"R\": 0.1, \"distance\": window/2}"
],
"outputs": []
},
{
"language": "javascript",
"source": [
"const binaryImage = Doxa.Binarization.toBinary(Algorithms.SAUVOLA, image, { window: 27, k: 0.12 });\r\n\r\ndisplay.dimage(binaryImage);"
],
"outputs": []
},
{
"language": "markdown",
"source": [
"## Performance Metrics\r\nIn order to analyze the performance of an algorithm, Doxa provides a set of common metrics that can all be calculated with one function. To start that process you need an exemplar binary image, or \"ground truth.\" By comparing the ground truth to the resulting image of the binarization algorithm, you can start to compare the affects of different algorithms and algorithm parameters."
],
"outputs": []
},
{
"language": "javascript",
"source": [
"const groundTruthImage = await readImage('../../README/2JohnC1V3-GroundTruth.png');\r\n\r\ndisplay.dimage(groundTruthImage);"
],
"outputs": []
},
{
"language": "javascript",
"source": [
"const perf = Doxa.Binarization.calculatePerformance(groundTruthImage, binaryImage);\r\n\r\nconsole.dir(perf);"
],
"outputs": []
},
{
"language": "markdown",
"source": [
"## Freeing Resources\r\nUnlike Javascript, Web Assembly (WASM) is not as forgiving when it comes to resource management. When memory is allocated in the WASM system it must be freed, much like traditional C/C++ code. While this may not be critical for short lived executions, it is best practice to free the image resources when you are finished with them."
],
"outputs": []
},
{
"language": "javascript",
"source": [
"groundTruthImage.free();\r\nimage.free();"
],
"outputs": []
}
]
}
================================================
FILE: Bindings/WebAssembly/DoxaWasm.cpp
================================================
#include <emscripten/emscripten.h>
#include <emscripten/bind.h>
#include <sstream>
//#include <iostream>
#include "../../Doxa/BinarizationFactory.hpp"
#include "../../Doxa/ClassifiedPerformance.hpp"
#include "../../Doxa/DRDM.hpp"
#include "../../Doxa/DIBCOUtils.hpp"
#include "../../Doxa/Grayscale.hpp"
//#include "../Doxa/PNM.hpp"
using namespace Doxa;
using namespace emscripten;
/// <summary>
/// Structure for measuring performance characteristics of the different algorithms
/// </summary>
struct Performance
{
double Accuracy;
double FM;
double Recall;
double Precision;
double MCC;
double PSNR;
double NRM;
double DRDM;
};
/// <summary>
/// Structure for measuring pseudo performance characteristics, including standard and pseudo metrics
/// </summary>
struct PseudoPerformance
{
double Accuracy;
double FM;
double Recall;
double Precision;
double MCC;
double PSNR;
double NRM;
double DRDM;
double PseudoFM;
double PseudoPrecision;
double PseudoRecall;
};
Performance CalculatePerformance(intptr_t groundTruthData, intptr_t binaryData, const int width, const int height)
{
Image groundTruthImage = Image::Reference(width, height, reinterpret_cast<Pixel8*>(groundTruthData));
Image binaryImage = Image::Reference(width, height, reinterpret_cast<Pixel8*>(binaryData));
ClassifiedPerformance::Classifications classifications;
ClassifiedPerformance::CompareImages(classifications, groundTruthImage, binaryImage);
// TODO: If this fails, we may want to 0 out the return values
Performance perf;
perf.Accuracy = ClassifiedPerformance::CalculateAccuracy(classifications);
perf.FM = ClassifiedPerformance::CalculateFMeasure(classifications);
perf.Recall = ClassifiedPerformance::CalculateRecall(classifications);
perf.Precision = ClassifiedPerformance::CalculatePrecision(classifications);
perf.MCC = ClassifiedPerformance::CalculateMCC(classifications);
perf.PSNR = ClassifiedPerformance::CalculatePSNR(classifications);
perf.NRM = ClassifiedPerformance::CalculateNRM(classifications);
perf.DRDM = DRDM::CalculateDRDM(groundTruthImage, binaryImage);
return perf;
}
PseudoPerformance CalculatePseudoPerformance(
intptr_t groundTruthData, intptr_t binaryData,
const int width, const int height,
const std::string& precisionWeightsText,
const std::string& recallWeightsText)
{
Image groundTruthImage = Image::Reference(width, height, reinterpret_cast<Pixel8*>(groundTruthData));
Image binaryImage = Image::Reference(width, height, reinterpret_cast<Pixel8*>(binaryData));
// Parse weight text via DIBCOUtils
std::stringstream precisionStream(precisionWeightsText);
std::stringstream recallStream(recallWeightsText);
const size_t allocatedSize = static_cast<size_t>(width) * height;
auto precisionWeights = DIBCOUtils::ReadWeights(precisionStream, allocatedSize);
auto recallWeights = DIBCOUtils::ReadWeights(recallStream, allocatedSize);
// Compute weighted classifications
ClassifiedPerformance::Classifications classifications;
ClassifiedPerformance::CompareImages(classifications, groundTruthImage, binaryImage, precisionWeights, recallWeights);
PseudoPerformance perf;
perf.Accuracy = ClassifiedPerformance::CalculateAccuracy(classifications);
perf.FM = ClassifiedPerformance::CalculateFMeasure(classifications);
perf.Recall = ClassifiedPerformance::CalculateRecall(classifications);
perf.Precision = ClassifiedPerformance::CalculatePrecision(classifications);
perf.MCC = ClassifiedPerformance::CalculateMCC(classifications);
perf.PSNR = ClassifiedPerformance::CalculatePSNR(classifications);
perf.NRM = ClassifiedPerformance::CalculateNRM(classifications);
perf.DRDM = DRDM::CalculateDRDM(groundTruthImage, binaryImage);
perf.PseudoFM = ClassifiedPerformance::CalculatePseudoFMeasure(classifications);
perf.PseudoPrecision = ClassifiedPerformance::CalculatePseudoPrecision(classifications);
perf.PseudoRecall = ClassifiedPerformance::CalculatePseudoRecall(classifications);
return perf;
}
void ToGrayscale(intptr_t outputData, intptr_t inputData,
int width, int height, int channels, GrayscaleAlgorithms algorithm)
{
Grayscale::ToGrayscale(
reinterpret_cast<Pixel8*>(outputData),
reinterpret_cast<const Pixel8*>(inputData),
width, height, channels, algorithm);
}
/// <summary>
/// Binarization is a helper class to help interface the C++ Doxa framework with WebAssembly.
/// It exposes through enumeration all of the algorithms supported by the library.
/// The Doxa.js code wraps the exported symbols to make it easier to use in JavaScript.
/// </summary>
class Binarization
{
public:
Binarization(const Algorithms algorithm)
: algorithm(algorithm)
{
algorithmPtr = BinarizationFactory::Algorithm(algorithm);
}
~Binarization()
{
delete algorithmPtr;
}
void Initialize(intptr_t data, const int width, const int height)
{
// Set width and height
this->width = width;
this->height = height;
Image image = Image::Reference(width, height, reinterpret_cast<Pixel8*>(data));
algorithmPtr->Initialize(image);
}
void ToBinary(intptr_t data, const std::string& params)
{
Image image = Image::Reference(this->width, this->height, reinterpret_cast<Pixel8*>(data));
algorithmPtr->ToBinary(image, Parameters::FromJson(params));
}
Algorithms CurrentAlgorithm() { return algorithm; }
/*
static void Debug(Pixel8* data, const int width, const int height)
{
std::cout << "Debug - start (" << width << "x" << height << ")" << std::endl;
const size_t size = width * height;
for (size_t idx = 0; idx < size; ++idx)
{
const Pixel8 pixel = data[idx];
std::cout
<< "Index: " << idx << " Value: " << std::to_string(pixel) << std::endl;
}
data[0] = 42; // Verify the ability to set
std::cout << "Debug - stop" << std::endl;
}
*/
protected:
const Algorithms algorithm;
IAlgorithm* algorithmPtr = nullptr;
int width = 0;
int height = 0;
}; // Class: Binarization
EMSCRIPTEN_BINDINGS(doxa_wasm) {
class_<Binarization>("Binarization")
.constructor<Algorithms>()
.function("initialize", &Binarization::Initialize, allow_raw_pointers())
.function("toBinary", &Binarization::ToBinary, allow_raw_pointers())
.function("currentAlgorithm", &Binarization::CurrentAlgorithm);
enum_<Algorithms>("Binarization.Algorithms")
.value("OTSU", Algorithms::OTSU)
.value("BERNSEN", Algorithms::BERNSEN)
.value("NIBLACK", Algorithms::NIBLACK)
.value("SAUVOLA", Algorithms::SAUVOLA)
.value("WOLF", Algorithms::WOLF)
.value("NICK", Algorithms::NICK)
.value("ADOTSU", Algorithms::ADOTSU)
.value("SU", Algorithms::SU)
.value("TRSINGH", Algorithms::TRSINGH)
.value("BATAINEH", Algorithms::BATAINEH)
.value("ISAUVOLA", Algorithms::ISAUVOLA)
.value("WAN", Algorithms::WAN)
.value("GATOS", Algorithms::GATOS)
.value("PHANSALKAR", Algorithms::PHANSALKAR);
EM_ASM(
Module['Binarization']['Algorithms'] = Module['Binarization.Algorithms'];
delete Module['Binarization.Algorithms'];
);
value_object<Performance>("Performance")
.field("accuracy", &Performance::Accuracy)
.field("fm", &Performance::FM)
.field("recall", &Performance::Recall)
.field("precision", &Performance::Precision)
.field("mcc", &Performance::MCC)
.field("psnr", &Performance::PSNR)
.field("nrm", &Performance::NRM)
.field("drdm", &Performance::DRDM);
function("calculatePerformance", &CalculatePerformance, allow_raw_pointers());
value_object<PseudoPerformance>("PseudoPerformance")
.field("accuracy", &PseudoPerformance::Accuracy)
.field("fm", &PseudoPerformance::FM)
.field("recall", &PseudoPerformance::Recall)
.field("precision", &PseudoPerformance::Precision)
.field("mcc", &PseudoPerformance::MCC)
.field("psnr", &PseudoPerformance::PSNR)
.field("nrm", &PseudoPerformance::NRM)
.field("drdm", &PseudoPerformance::DRDM)
.field("pseudoFM", &PseudoPerformance::PseudoFM)
.field("pseudoPrecision", &PseudoPerformance::PseudoPrecision)
.field("pseudoRecall", &PseudoPerformance::PseudoRecall);
function("calculatePseudoPerformance", &CalculatePseudoPerformance, allow_raw_pointers());
enum_<GrayscaleAlgorithms>("Grayscale.Algorithms")
.value("MEAN", GrayscaleAlgorithms::MEAN)
.value("QT", GrayscaleAlgorithms::QT)
.value("BT601", GrayscaleAlgorithms::BT601)
.value("BT709", GrayscaleAlgorithms::BT709)
.value("BT2100", GrayscaleAlgorithms::BT2100)
.value("VALUE", GrayscaleAlgorithms::VALUE)
.value("LUSTER", GrayscaleAlgorithms::LUSTER)
.value("LIGHTNESS", GrayscaleAlgorithms::LIGHTNESS)
.value("MINAVG", GrayscaleAlgorithms::MINAVG)
.value("LABDIST", GrayscaleAlgorithms::LABDIST);
EM_ASM(
Module['Grayscale'] = { 'Algorithms': Module['Grayscale.Algorithms'] };
delete Module['Grayscale.Algorithms'];
);
function("toGrayscale", &ToGrayscale, allow_raw_pointers());
};
================================================
FILE: Bindings/WebAssembly/README.md
================================================
# Δoxa Binarization Framework - WebAssembly
## Introduction
This is an **experimental** project that exposes the ΔBF, written in C++, to JavaScript via WebAssembly. It works both server side and client side. For a simple example of how it works, checkout the [WebJS](../../Demo/WebJS) and [NodeJS](../../Demo/NodeJS) demos.
A Visual Studio Code [Notebook](./DoxaJs.nnb) was developed to easily test and document the API. It uses the *Node.js Notebooks (REPL)* kernel.
## Building
DoxaJs is built using CMake with the Emscripten toolchain. You must have [Emscripten](https://emscripten.org/docs/getting_started/downloads.html) installed and available in your path.
### Using npm (Recommended)
```bash
cd Bindings/WebAssembly
npm install
# Release build
npm run build
# Debug build (with source maps and exception debugging)
npm run build:dev
# Run tests
npm test
```
### Using CMake
```bash
# From project root
cmake --preset wasm
cmake --build build-wasm --config Release
npm install --prefix Bindings/WebAssembly
ctest --test-dir build-wasm -C Release
```
### Run the Web Demo
```bash
emrun --no_browser --port 8080 .
# Navigate to: http://localhost:8080/Demo/WebJS
```
## License
CC0 - Brandon M. Petty, 2023
To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. This software is distributed without any warranty.
[View Online](https://creativecommons.org/publicdomain/zero/1.0/legalcode)
"*Freely you have received; freely give.*" - Matt 10:8
================================================
FILE: Bindings/WebAssembly/dist/doxa.js
================================================
/**
* Doxa WASM
* A set of classes that further simplify the Doxa WASM interface.
* This same wrapper can be run in NodeJS or directly in the web.
* See Demo/WebJs and Demo/NodeJS for an example of how to use it.
*/
const Doxa = {
Wasm: (typeof Module !== 'undefined') ? Module : require('./doxaWasm.js'),
initialize: async function() {
if (!Doxa.Wasm) throw new Error('Missing: Doxa WASM Module');
// Extract enum values from an Emscripten enum object
const extractEnums = function(enumObj) {
const enums = {};
for (const key in enumObj) {
const entry = enumObj[key];
if (entry?.value === undefined) continue;
enums[key] = entry.value;
}
return enums;
}
const buildInstance = function() {
const instance = {
binarization: extractEnums(Doxa.Wasm.Binarization.Algorithms),
grayscale: extractEnums(Doxa.Wasm.Grayscale.Algorithms),
/**
* Convert raw pixel data to an 8-bit grayscale Doxa.Image.
* If the data is already single-channel, it is copied directly.
* @param {Uint8Array|Uint8ClampedArray} data - Raw pixel data (1, 3, or 4 channels)
* @param {number} width - Image width
* @param {number} height - Image height
* @param {number} channels - 1 for grayscale, 3 for RGB, 4 for RGBA
* @param {number} algorithm - Grayscale algorithm enum value (e.g. doxa.grayscale.MEAN). Defaults to MEAN.
* @returns {Doxa.Image} 8-bit grayscale image (caller must free)
*/
toGrayscale: function(data, width, height, channels, algorithm) {
// Already grayscale — just copy directly
if (channels === 1) {
return new Doxa.Image(width, height, data);
}
// Allocate WASM heap for input
const inputSize = width * height * channels;
const inputPtr = Doxa.Wasm._malloc(inputSize);
Doxa.Wasm.HEAPU8.set(data.subarray(0, inputSize), inputPtr);
// Allocate output image
const outputImage = new Doxa.Image(width, height);
const algEnum = Doxa.Wasm.Grayscale.Algorithms.values[
algorithm ?? instance.grayscale.MEAN
];
Doxa.Wasm.toGrayscale(outputImage.heapPtr, inputPtr, width, height, channels, algEnum);
// Free input buffer
Doxa.Wasm._free(inputPtr);
return outputImage;
},
/**
* Convert an HTML5 Canvas ImageData to an 8-bit grayscale Doxa.Image.
* Convenience wrapper for browser usage.
* @param {ImageData} imageData - Canvas ImageData (32-bit RGBA)
* @param {number} algorithm - Grayscale algorithm enum value (e.g. doxa.grayscale.MEAN). Defaults to MEAN.
* @returns {Doxa.Image} 8-bit grayscale image (caller must free)
*/
fromImageData: function(imageData, algorithm) {
return instance.toGrayscale(
imageData.data, imageData.width, imageData.height, 4, algorithm
);
},
/**
* Binarize a grayscale image using the specified algorithm.
* @param {number} algorithm - Binarization algorithm enum value (e.g. doxa.binarization.SAUVOLA)
* @param {Doxa.Image} imageIn - Input grayscale image
* @param {object} parameters - Algorithm parameters (e.g. { window: 75, k: 0.2 })
* @param {Doxa.Image} imageOut - Optional output image (allocated if not provided)
* @returns {Doxa.Image} Binary image (caller must free if newly allocated)
*/
toBinary: function(algorithm, imageIn, parameters, imageOut) {
if (!imageOut) {
imageOut = new Doxa.Image(imageIn.width, imageIn.height);
}
const algEnum = Doxa.Wasm.Binarization.Algorithms.values[algorithm];
const paramString = JSON.stringify(parameters || {});
const binarization = new Doxa.Wasm.Binarization(algEnum);
binarization.initialize(imageIn.heapPtr, imageIn.width, imageIn.height);
binarization.toBinary(imageOut.heapPtr, paramString);
return imageOut;
},
/**
* Calculate performance metrics comparing a binary image against ground truth.
* If precision/recall weight texts are provided, pseudo metrics are included.
* @param {Doxa.Image} groundTruth - Ground truth binary image
* @param {Doxa.Image} binary - Binary image to evaluate
* @param {string} precisionWeightsText - Optional precision weights (enables pseudo metrics)
* @param {string} recallWeightsText - Optional recall weights (enables pseudo metrics)
* @returns {object} Performance metrics (accuracy, fm, precision, recall, mcc, psnr, nrm, drdm, and optionally pseudoFM, pseudoPrecision, pseudoRecall)
*/
calculatePerformance: function(groundTruth, binary, precisionWeightsText, recallWeightsText) {
if (precisionWeightsText && recallWeightsText) {
return Doxa.Wasm.calculatePseudoPerformance(
groundTruth.heapPtr, binary.heapPtr,
binary.width, binary.height,
precisionWeightsText, recallWeightsText
);
}
return Doxa.Wasm.calculatePerformance(
groundTruth.heapPtr, binary.heapPtr,
binary.width, binary.height
);
}
};
return instance;
}
return new Promise((resolve) => {
// Ensure the library has not already been initialized
if (typeof Doxa.Wasm.Binarization !== 'undefined') {
return resolve(buildInstance());
}
Doxa.Wasm.onRuntimeInitialized = async _ => {
resolve(buildInstance());
};
});
},
Image: class {
bufferSize = 0;
/**
* Initialize an Image object. All Image objects allocate WASM memory.
* That memory should be freed, explicitly. If 8-bit data is passed,
* it will be copied directly into WASM memory.
* Use doxa.fromImageData to convert from RGBA to grayscale.
* @param {number} width Image Width
* @param {number} height Image Height
* @param {Uint8Array} data 8-bit grayscale data. Optional.
*/
constructor (width, height, data) {
this.initialize(width || 0, height || 0);
if (data) {
Doxa.Wasm.HEAPU8.set(data.subarray(0, this.size), this.heapPtr);
}
}
/**
* The number of pixels in the image.
*/
get size() {
return this.width * this.height;
}
/**
* Draws the Image directly to an HTML5 Canvas.
* The Canvas should accept 32bit data, which is standard.
* @param {*} canvas HTML5 Canvas, or a node-canvas
*/
draw(canvas) {
canvas.width = this.width;
canvas.height = this.height;
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
this.toImageData(imageData);
ctx.putImageData(imageData, 0, 0);
}
data() {
return new Uint8ClampedArray(Doxa.Wasm.HEAPU8.buffer, this.heapPtr, this.size);
}
/**
* Frees the memory allocated by this object.
*/
free() {
if (this.heapPtr != null) Doxa.Wasm._free(this.heapPtr);
}
initialize(width, height) {
this.width = width;
this.height = height;
if (this.size > this.bufferSize) {
this.free();
this.bufferSize = this.size;
this.heapPtr = Doxa.Wasm._malloc(this.bufferSize);
}
}
/**
* Writes the 8-bit grayscale image data into a 32-bit RGBA ImageData object.
* @param {ImageData} imageData - Target ImageData to populate
*/
toImageData(imageData) {
const buffer = this.data();
const size32 = imageData.width * imageData.height * 4;
for (var idx = 0; idx < size32; idx += 4) {
const gsIdx = idx / 4;
imageData.data[idx] = buffer[gsIdx];
imageData.data[idx+1] = buffer[gsIdx];
imageData.data[idx+2] = buffer[gsIdx];
imageData.data[idx+3] = 255;
}
}
}
}
// Only export if running in NodeJS
if (typeof module !== 'undefined') {
module.exports = { Doxa };
}
================================================
FILE: Bindings/WebAssembly/dist/doxaWasm.js
================================================
var Module=typeof Module!="undefined"?Module:{};var ENVIRONMENT_IS_WEB=!!globalThis.window;var ENVIRONMENT_IS_WORKER=!!globalThis.WorkerGlobalScope;var ENVIRONMENT_IS_NODE=globalThis.process?.versions?.node&&globalThis.process?.type!="renderer";var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var _scriptName=globalThis.document?.currentScript?.src;if(typeof __filename!="undefined"){_scriptName=__filename}else if(ENVIRONMENT_IS_WORKER){_scriptName=self.location.href}var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_NODE){var fs=require("node:fs");scriptDirectory=__dirname+"/";readBinary=filename=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename);return ret};readAsync=async(filename,binary=true)=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename,binary?undefined:"utf8");return ret};if(process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}arguments_=process.argv.slice(2);if(typeof module!="undefined"){module["exports"]=Module}quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}{if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=async url=>{if(isFileURI(url)){return new Promise((resolve,reject)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){resolve(xhr.response);return}reject(xhr.status)};xhr.onerror=reject;xhr.send(null)})}var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var ABORT=false;var isFileURI=filename=>filename.startsWith("file://");var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;var HEAP64,HEAPU64;var runtimeInitialized=false;function updateMemoryViews(){var b=wasmMemory.buffer;HEAP8=new Int8Array(b);HEAP16=new Int16Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);HEAPU16=new Uint16Array(b);HEAP32=new Int32Array(b);HEAPU32=new Uint32Array(b);HEAPF32=new Float32Array(b);HEAPF64=new Float64Array(b);HEAP64=new BigInt64Array(b);HEAPU64=new BigUint64Array(b)}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(onPreRuns)}function initRuntime(){runtimeInitialized=true;wasmExports["A"]()}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(onPostRuns)}function abort(what){Module["onAbort"]?.(what);what="Aborted("+what+")";err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);throw e}var wasmBinaryFile;function findWasmBinary(){return locateFile("doxaWasm.wasm")}function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary&&!isFileURI(binaryFile)&&!ENVIRONMENT_IS_NODE){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){var imports={a:wasmImports};return imports}async function createWasm(){function receiveInstance(instance,module){wasmExports=instance.exports;assignWasmExports(wasmExports);updateMemoryViews();removeRunDependency("wasm-instantiate");return wasmExports}addRunDependency("wasm-instantiate");function receiveInstantiationResult(result){return receiveInstance(result["instance"])}var info=getWasmImports();if(Module["instantiateWasm"]){return new Promise((resolve,reject)=>{Module["instantiateWasm"](info,(inst,mod)=>{resolve(receiveInstance(inst,mod))})})}wasmBinaryFile??=findWasmBinary();var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);var exports=receiveInstantiationResult(result);return exports}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var onPostRuns=[];var addOnPostRun=cb=>onPostRuns.push(cb);var onPreRuns=[];var addOnPreRun=cb=>onPreRuns.push(cb);var runDependencies=0;var dependenciesFulfilled=null;var removeRunDependency=id=>{runDependencies--;Module["monitorRunDependencies"]?.(runDependencies);if(runDependencies==0){if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}};var addRunDependency=id=>{runDependencies++;Module["monitorRunDependencies"]?.(runDependencies)};var noExitRuntime=true;class ExceptionInfo{constructor(excPtr){this.excPtr=excPtr;this.ptr=excPtr-24}set_type(type){HEAPU32[this.ptr+4>>2]=type}get_type(){return HEAPU32[this.ptr+4>>2]}set_destructor(destructor){HEAPU32[this.ptr+8>>2]=destructor}get_destructor(){return HEAPU32[this.ptr+8>>2]}set_caught(caught){caught=caught?1:0;HEAP8[this.ptr+12]=caught}get_caught(){return HEAP8[this.ptr+12]!=0}set_rethrown(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+13]=rethrown}get_rethrown(){return HEAP8[this.ptr+13]!=0}init(type,destructor){this.set_adjusted_ptr(0);this.set_type(type);this.set_destructor(destructor)}set_adjusted_ptr(adjustedPtr){HEAPU32[this.ptr+16>>2]=adjustedPtr}get_adjusted_ptr(){return HEAPU32[this.ptr+16>>2]}}var exceptionLast=0;var uncaughtExceptionCount=0;var ___cxa_throw=(ptr,type,destructor)=>{var info=new ExceptionInfo(ptr);info.init(type,destructor);exceptionLast=ptr;uncaughtExceptionCount++;throw exceptionLast};var __abort_js=()=>abort("");var structRegistrations={};var runDestructors=destructors=>{while(destructors.length){var ptr=destructors.pop();var del=destructors.pop();del(ptr)}};function readPointer(pointer){return this.fromWireType(HEAPU32[pointer>>2])}var awaitingDependencies={};var registeredTypes={};var typeDependencies={};var InternalError=class InternalError extends Error{constructor(message){super(message);this.name="InternalError"}};var throwInternalError=message=>{throw new InternalError(message)};var whenDependentTypesAreResolved=(myTypes,dependentTypes,getTypeConverters)=>{myTypes.forEach(type=>typeDependencies[type]=dependentTypes);function onComplete(typeConverters){var myTypeConverters=getTypeConverters(typeConverters);if(myTypeConverters.length!==myTypes.length){throwInternalError("Mismatched type converter count")}for(var i=0;i<myTypes.length;++i){registerType(myTypes[i],myTypeConverters[i])}}var typeConverters=new Array(dependentTypes.length);var unregisteredTypes=[];var registered=0;for(let[i,dt]of dependentTypes.entries()){if(registeredTypes.hasOwnProperty(dt)){typeConverters[i]=registeredTypes[dt]}else{unregisteredTypes.push(dt);if(!awaitingDependencies.hasOwnProperty(dt)){awaitingDependencies[dt]=[]}awaitingDependencies[dt].push(()=>{typeConverters[i]=registeredTypes[dt];++registered;if(registered===unregisteredTypes.length){onComplete(typeConverters)}})}}if(0===unregisteredTypes.length){onComplete(typeConverters)}};var __embind_finalize_value_object=structType=>{var reg=structRegistrations[structType];delete structRegistrations[structType];var rawConstructor=reg.rawConstructor;var rawDestructor=reg.rawDestructor;var fieldRecords=reg.fields;var fieldTypes=fieldRecords.map(field=>field.getterReturnType).concat(fieldRecords.map(field=>field.setterArgumentType));whenDependentTypesAreResolved([structType],fieldTypes,fieldTypes=>{var fields={};for(var[i,field]of fieldRecords.entries()){const getterReturnType=fieldTypes[i];const getter=field.getter;const getterContext=field.getterContext;const setterArgumentType=fieldTypes[i+fieldRecords.length];const setter=field.setter;const setterContext=field.setterContext;fields[field.fieldName]={read:ptr=>getterReturnType.fromWireType(getter(getterContext,ptr)),write:(ptr,o)=>{var destructors=[];setter(setterContext,ptr,setterArgumentType.toWireType(destructors,o));runDestructors(destructors)},optional:getterReturnType.optional}}return[{name:reg.name,fromWireType:ptr=>{var rv={};for(var i in fields){rv[i]=fields[i].read(ptr)}rawDestructor(ptr);return rv},toWireType:(destructors,o)=>{for(var fieldName in fields){if(!(fieldName in o)&&!fields[fieldName].optional){throw new TypeError(`Missing field: "${fieldName}"`)}}var ptr=rawConstructor();for(fieldName in fields){fields[fieldName].write(ptr,o[fieldName])}if(destructors!==null){destructors.push(rawDestructor,ptr)}return ptr},readValueFromPointer:readPointer,destructorFunction:rawDestructor}]})};var AsciiToString=ptr=>{var str="";while(1){var ch=HEAPU8[ptr++];if(!ch)return str;str+=String.fromCharCode(ch)}};var BindingError=class BindingError extends Error{constructor(message){super(message);this.name="BindingError"}};var throwBindingError=message=>{throw new BindingError(message)};function sharedRegisterType(rawType,registeredInstance,options={}){var name=registeredInstance.name;if(!rawType){throwBindingError(`type "${name}" must have a positive integer typeid pointer`)}if(registeredTypes.hasOwnProperty(rawType)){if(options.ignoreDuplicateRegistrations){return}else{throwBindingError(`Cannot register type '${name}' twice`)}}registeredTypes[rawType]=registeredInstance;delete typeDependencies[rawType];if(awaitingDependencies.hasOwnProperty(rawType)){var callbacks=awaitingDependencies[rawType];delete awaitingDependencies[rawType];callbacks.forEach(cb=>cb())}}function registerType(rawType,registeredInstance,options={}){return sharedRegisterType(rawType,registeredInstance,options)}var integerReadValueFromPointer=(name,width,signed)=>{switch(width){case 1:return signed?pointer=>HEAP8[pointer]:pointer=>HEAPU8[pointer];case 2:return signed?pointer=>HEAP16[pointer>>1]:pointer=>HEAPU16[pointer>>1];case 4:return signed?pointer=>HEAP32[pointer>>2]:pointer=>HEAPU32[pointer>>2];case 8:return signed?pointer=>HEAP64[pointer>>3]:pointer=>HEAPU64[pointer>>3];default:throw new TypeError(`invalid integer width (${width}): ${name}`)}};var __embind_register_bigint=(primitiveType,name,size,minRange,maxRange)=>{name=AsciiToString(name);const isUnsignedType=minRange===0n;let fromWireType=value=>value;if(isUnsignedType){const bitSize=size*8;fromWireType=value=>BigInt.asUintN(bitSize,value);maxRange=fromWireType(maxRange)}registerType(primitiveType,{name,fromWireType,toWireType:(destructors,value)=>{if(typeof value=="number"){value=BigInt(value)}return value},readValueFromPointer:integerReadValueFromPointer(name,size,!isUnsignedType),destructorFunction:null})};var __embind_register_bool=(rawType,name,trueValue,falseValue)=>{name=AsciiToString(name);registerType(rawType,{name,fromWireType:function(wt){return!!wt},toWireType:function(destructors,o){return o?trueValue:falseValue},readValueFromPointer:function(pointer){return this.fromWireType(HEAPU8[pointer])},destructorFunction:null})};var shallowCopyInternalPointer=o=>({count:o.count,deleteScheduled:o.deleteScheduled,preservePointerOnDelete:o.preservePointerOnDelete,ptr:o.ptr,ptrType:o.ptrType,smartPtr:o.smartPtr,smartPtrType:o.smartPtrType});var throwInstanceAlreadyDeleted=obj=>{function getInstanceTypeName(handle){return handle.$$.ptrType.registeredClass.name}throwBindingError(getInstanceTypeName(obj)+" instance already deleted")};var finalizationRegistry=false;var detachFinalizer=handle=>{};var runDestructor=$$=>{if($$.smartPtr){$$.smartPtrType.rawDestructor($$.smartPtr)}else{$$.ptrType.registeredClass.rawDestructor($$.ptr)}};var releaseClassHandle=$$=>{$$.count.value-=1;var toDelete=0===$$.count.value;if(toDelete){runDestructor($$)}};var attachFinalizer=handle=>{if(!globalThis.FinalizationRegistry){attachFinalizer=handle=>handle;return handle}finalizationRegistry=new FinalizationRegistry(info=>{releaseClassHandle(info.$$)});attachFinalizer=handle=>{var $$=handle.$$;var hasSmartPtr=!!$$.smartPtr;if(hasSmartPtr){var info={$$};finalizationRegistry.register(handle,info,handle)}return handle};detachFinalizer=handle=>finalizationRegistry.unregister(handle);return attachFinalizer(handle)};var deletionQueue=[];var flushPendingDeletes=()=>{while(deletionQueue.length){var obj=deletionQueue.pop();obj.$$.deleteScheduled=false;obj["delete"]()}};var delayFunction;var init_ClassHandle=()=>{let proto=ClassHandle.prototype;Object.assign(proto,{isAliasOf(other){if(!(this instanceof ClassHandle)){return false}if(!(other instanceof ClassHandle)){return false}var leftClass=this.$$.ptrType.registeredClass;var left=this.$$.ptr;other.$$=other.$$;var rightClass=other.$$.ptrType.registeredClass;var right=other.$$.ptr;while(leftClass.baseClass){left=leftClass.upcast(left);leftClass=leftClass.baseClass}while(rightClass.baseClass){right=rightClass.upcast(right);rightClass=rightClass.baseClass}return leftClass===rightClass&&left===right},clone(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.preservePointerOnDelete){this.$$.count.value+=1;return this}else{var clone=attachFinalizer(Object.create(Object.getPrototypeOf(this),{$$:{value:shallowCopyInternalPointer(this.$$)}}));clone.$$.count.value+=1;clone.$$.deleteScheduled=false;return clone}},delete(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}detachFinalizer(this);releaseClassHandle(this.$$);if(!this.$$.preservePointerOnDelete){this.$$.smartPtr=undefined;this.$$.ptr=undefined}},isDeleted(){return!this.$$.ptr},deleteLater(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}deletionQueue.push(this);if(deletionQueue.length===1&&delayFunction){delayFunction(flushPendingDeletes)}this.$$.deleteScheduled=true;return this}});const symbolDispose=Symbol.dispose;if(symbolDispose){proto[symbolDispose]=proto["delete"]}};function ClassHandle(){}var createNamedFunction=(name,func)=>Object.defineProperty(func,"name",{value:name});var registeredPointers={};var ensureOverloadTable=(proto,methodName,humanName)=>{if(undefined===proto[methodName].overloadTable){var prevFunc=proto[methodName];proto[methodName]=function(...args){if(!proto[methodName].overloadTable.hasOwnProperty(args.length)){throwBindingError(`Function '${humanName}' called with an invalid number of arguments (${args.length}) - expects one of (${proto[methodName].overloadTable})!`)}return proto[methodName].overloadTable[args.length].apply(this,args)};proto[methodName].overloadTable=[];proto[methodName].overloadTable[prevFunc.argCount]=prevFunc}};var exposePublicSymbol=(name,value,numArguments)=>{if(Module.hasOwnProperty(name)){if(undefined===numArguments||undefined!==Module[name].overloadTable&&undefined!==Module[name].overloadTable[numArguments]){throwBindingError(`Cannot register public name '${name}' twice`)}ensureOverloadTable(Module,name,name);if(Module[name].overloadTable.hasOwnProperty(numArguments)){throwBindingError(`Cannot register multiple overloads of a function with the same number of arguments (${numArguments})!`)}Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}};var char_0=48;var char_9=57;var makeLegalFunctionName=name=>{name=name.replace(/[^a-zA-Z0-9_]/g,"$");var f=name.charCodeAt(0);if(f>=char_0&&f<=char_9){return`_${name}`}return name};function RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast){this.name=name;this.constructor=constructor;this.instancePrototype=instancePrototype;this.rawDestructor=rawDestructor;this.baseClass=baseClass;this.getActualType=getActualType;this.upcast=upcast;this.downcast=downcast;this.pureVirtualFunctions=[]}var upcastPointer=(ptr,ptrClass,desiredClass)=>{while(ptrClass!==desiredClass){if(!ptrClass.upcast){throwBindingError(`Expected null or instance of ${desiredClass.name}, got an instance of ${ptrClass.name}`)}ptr=ptrClass.upcast(ptr);ptrClass=ptrClass.baseClass}return ptr};var embindRepr=v=>{if(v===null){return"null"}var t=typeof v;if(t==="object"||t==="array"||t==="function"){return v.toString()}else{return""+v}};function constNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}return 0}if(!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function genericPointerToWireType(destructors,handle){var ptr;if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}if(this.isSmartPointer){ptr=this.rawConstructor();if(destructors!==null){destructors.push(this.rawDestructor,ptr)}return ptr}else{return 0}}if(!handle||!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}if(!this.isConst&&handle.$$.ptrType.isConst){throwBindingError(`Cannot convert argument of type ${handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name} to parameter type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);if(this.isSmartPointer){if(undefined===handle.$$.smartPtr){throwBindingError("Passing raw pointer to smart pointer is illegal")}switch(this.sharingPolicy){case 0:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{throwBindingError(`Cannot convert argument of type ${handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name} to parameter type ${this.name}`)}break;case 1:ptr=handle.$$.smartPtr;break;case 2:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{var clonedHandle=handle["clone"]();ptr=this.rawShare(ptr,Emval.toHandle(()=>clonedHandle["delete"]()));if(destructors!==null){destructors.push(this.rawDestructor,ptr)}}break;default:throwBindingError("Unsupported sharing policy")}}return ptr}function nonConstNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}return 0}if(!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}if(handle.$$.ptrType.isConst){throwBindingError(`Cannot convert argument of type ${handle.$$.ptrType.name} to parameter type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}var downcastPointer=(ptr,ptrClass,desiredClass)=>{if(ptrClass===desiredClass){return ptr}if(undefined===desiredClass.baseClass){return null}var rv=downcastPointer(ptr,ptrClass,desiredClass.baseClass);if(rv===null){return null}return desiredClass.downcast(rv)};var registeredInstances={};var getBasestPointer=(class_,ptr)=>{if(ptr===undefined){throwBindingError("ptr should not be undefined")}while(class_.baseClass){ptr=class_.upcast(ptr);class_=class_.baseClass}return ptr};var getInheritedInstance=(class_,ptr)=>{ptr=getBasestPointer(class_,ptr);return registeredInstances[ptr]};var makeClassHandle=(prototype,record)=>{if(!record.ptrType||!record.ptr){throwInternalError("makeClassHandle requires ptr and ptrType")}var hasSmartPtrType=!!record.smartPtrType;var hasSmartPtr=!!record.smartPtr;if(hasSmartPtrType!==hasSmartPtr){throwInternalError("Both smartPtrType and smartPtr must be specified")}record.count={value:1};return attachFinalizer(Object.create(prototype,{$$:{value:record,writable:true}}))};function RegisteredPointer_fromWireType(ptr){var rawPointer=this.getPointee(ptr);if(!rawPointer){this.destructor(ptr);return null}var registeredInstance=getInheritedInstance(this.registeredClass,rawPointer);if(undefined!==registeredInstance){if(0===registeredInstance.$$.count.value){registeredInstance.$$.ptr=rawPointer;registeredInstance.$$.smartPtr=ptr;return registeredInstance["clone"]()}else{var rv=registeredInstance["clone"]();this.destructor(ptr);return rv}}function makeDefaultHandle(){if(this.isSmartPointer){return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:rawPointer,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this,ptr})}}var actualType=this.registeredClass.getActualType(rawPointer);var registeredPointerRecord=registeredPointers[actualType];if(!registeredPointerRecord){return makeDefaultHandle.call(this)}var toType;if(this.isConst){toType=registeredPointerRecord.constPointerType}else{toType=registeredPointerRecord.pointerType}var dp=downcastPointer(rawPointer,this.registeredClass,toType.registeredClass);if(dp===null){return makeDefaultHandle.call(this)}if(this.isSmartPointer){return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp})}}var init_RegisteredPointer=()=>{Object.assign(RegisteredPointer.prototype,{getPointee(ptr){if(this.rawGetPointee){ptr=this.rawGetPointee(ptr)}return ptr},destructor(ptr){this.rawDestructor?.(ptr)},readValueFromPointer:readPointer,fromWireType:RegisteredPointer_fromWireType})};function RegisteredPointer(name,registeredClass,isReference,isConst,isSmartPointer,pointeeType,sharingPolicy,rawGetPointee,rawConstructor,rawShare,rawDestructor){this.name=name;this.registeredClass=registeredClass;this.isReference=isReference;this.isConst=isConst;this.isSmartPointer=isSmartPointer;this.pointeeType=pointeeType;this.sharingPolicy=sharingPolicy;this.rawGetPointee=rawGetPointee;this.rawConstructor=rawConstructor;this.rawShare=rawShare;this.rawDestructor=rawDestructor;if(!isSmartPointer&®isteredClass.baseClass===undefined){if(isConst){this.toWireType=constNoSmartPtrRawPointerToWireType;this.destructorFunction=null}else{this.toWireType=nonConstNoSmartPtrRawPointerToWireType;this.destructorFunction=null}}else{this.toWireType=genericPointerToWireType}}var replacePublicSymbol=(name,value,numArguments)=>{if(!Module.hasOwnProperty(name)){throwInternalError("Replacing nonexistent public symbol")}if(undefined!==Module[name].overloadTable&&undefined!==numArguments){Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}};var wasmTableMirror=[];var getWasmTableEntry=funcPtr=>{var func=wasmTableMirror[funcPtr];if(!func){wasmTableMirror[funcPtr]=func=wasmTable.get(funcPtr)}return func};var embind__requireFunction=(signature,rawFunction,isAsync=false)=>{signature=AsciiToString(signature);function makeDynCaller(){var rtn=getWasmTableEntry(rawFunction);return rtn}var fp=makeDynCaller();if(typeof fp!="function"){throwBindingError(`unknown function pointer with signature ${signature}: ${rawFunction}`)}return fp};class UnboundTypeError extends Error{}var getTypeName=type=>{var ptr=___getTypeName(type);var rv=AsciiToString(ptr);_free(ptr);return rv};var throwUnboundTypeError=(message,types)=>{var unboundTypes=[];var seen={};function visit(type){if(seen[type]){return}if(registeredTypes[type]){return}if(typeDependencies[type]){typeDependencies[type].forEach(visit);return}unboundTypes.push(type);seen[type]=true}types.forEach(visit);throw new UnboundTypeError(`${message}: `+unboundTypes.map(getTypeName).join([", "]))};var __embind_register_class=(rawType,rawPointerType,rawConstPointerType,baseClassRawType,getActualTypeSignature,getActualType,upcastSignature,upcast,downcastSignature,downcast,name,destructorSignature,rawDestructor)=>{name=AsciiToString(name);getActualType=embind__requireFunction(getActualTypeSignature,getActualType);upcast&&=embind__requireFunction(upcastSignature,upcast);downcast&&=embind__requireFunction(downcastSignature,downcast);rawDestructor=embind__requireFunction(destructorSignature,rawDestructor);var legalFunctionName=makeLegalFunctionName(name);exposePublicSymbol(legalFunctionName,function(){throwUnboundTypeError(`Cannot construct ${name} due to unbound types`,[baseClassRawType])});whenDependentTypesAreResolved([rawType,rawPointerType,rawConstPointerType],baseClassRawType?[baseClassRawType]:[],base=>{base=base[0];var baseClass;var basePrototype;if(baseClassRawType){baseClass=base.registeredClass;basePrototype=baseClass.instancePrototype}else{basePrototype=ClassHandle.prototype}var constructor=createNamedFunction(name,function(...args){if(Object.getPrototypeOf(this)!==instancePrototype){throw new BindingError(`Use 'new' to construct ${name}`)}if(undefined===registeredClass.constructor_body){throw new BindingError(`${name} has no accessible constructor`)}var body=registeredClass.constructor_body[args.length];if(undefined===body){throw new BindingError(`Tried to invoke ctor of ${name} with invalid number of parameters (${args.length}) - expected (${Object.keys(registeredClass.constructor_body).toString()}) parameters instead!`)}return body.apply(this,args)});var instancePrototype=Object.create(basePrototype,{constructor:{value:constructor}});constructor.prototype=instancePrototype;var registeredClass=new RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast);if(registeredClass.baseClass){registeredClass.baseClass.__derivedClasses??=[];registeredClass.baseClass.__derivedClasses.push(registeredClass)}var referenceConverter=new RegisteredPointer(name,registeredClass,true,false,false);var pointerConverter=new RegisteredPointer(name+"*",registeredClass,false,false,false);var constPointerConverter=new RegisteredPointer(name+" const*",registeredClass,false,true,false);registeredPointers[rawType]={pointerType:pointerConverter,constPointerType:constPointerConverter};replacePublicSymbol(legalFunctionName,constructor);return[referenceConverter,pointerConverter,constPointerConverter]})};var heap32VectorToArray=(count,firstElement)=>{var array=[];for(var i=0;i<count;i++){array.push(HEAPU32[firstElement+i*4>>2])}return array};function usesDestructorStack(argTypes){for(var i=1;i<argTypes.length;++i){if(argTypes[i]!==null&&argTypes[i].destructorFunction===undefined){return true}}return false}function createJsInvoker(argTypes,isClassMethodFunc,returns,isAsync){var needsDestructorStack=usesDestructorStack(argTypes);var argCount=argTypes.length-2;var argsList=[];var argsListWired=["fn"];if(isClassMethodFunc){argsListWired.push("thisWired")}for(var i=0;i<argCount;++i){argsList.push(`arg${i}`);argsListWired.push(`arg${i}Wired`)}argsList=argsList.join(",");argsListWired=argsListWired.join(",");var invokerFnBody=`return function (${argsList}) {\n`;if(needsDestructorStack){invokerFnBody+="var destructors = [];\n"}var dtorStack=needsDestructorStack?"destructors":"null";var args1=["humanName","throwBindingError","invoker","fn","runDestructors","fromRetWire","toClassParamWire"];if(isClassMethodFunc){invokerFnBody+=`var thisWired = toClassParamWire(${dtorStack}, this);\n`}for(var i=0;i<argCount;++i){var argName=`toArg${i}Wire`;invokerFnBody+=`var arg${i}Wired = ${argName}(${dtorStack}, arg${i});\n`;args1.push(argName)}invokerFnBody+=(returns||isAsync?"var rv = ":"")+`invoker(${argsListWired});\n`;if(needsDestructorStack){invokerFnBody+="runDestructors(destructors);\n"}else{for(var i=isClassMethodFunc?1:2;i<argTypes.length;++i){var paramName=i===1?"thisWired":"arg"+(i-2)+"Wired";if(argTypes[i].destructorFunction!==null){invokerFnBody+=`${paramName}_dtor(${paramName});\n`;args1.push(`${paramName}_dtor`)}}}if(returns){invokerFnBody+="var ret = fromRetWire(rv);\n"+"return ret;\n"}else{}invokerFnBody+="}\n";return new Function(args1,invokerFnBody)}function craftInvokerFunction(humanName,argTypes,classType,cppInvokerFunc,cppTargetFunc,isAsync){var argCount=argTypes.length;if(argCount<2){throwBindingError("argTypes array size mismatch! Must at least get return value and 'this' types!")}var isClassMethodFunc=argTypes[1]!==null&&classType!==null;var needsDestructorStack=usesDestructorStack(argTypes);var returns=!argTypes[0].isVoid;var retType=argTypes[0];var instType=argTypes[1];var closureArgs=[humanName,throwBindingError,cppInvokerFunc,cppTargetFunc,runDestructors,retType.fromWireType.bind(retType),instType?.toWireType.bind(instType)];for(var i=2;i<argCount;++i){var argType=argTypes[i];closureArgs.push(argType.toWireType.bind(argType))}if(!needsDestructorStack){for(var i=isClassMethodFunc?1:2;i<argTypes.length;++i){if(argTypes[i].destructorFunction!==null){closureArgs.push(argTypes[i].destructorFunction)}}}let invokerFactory=createJsInvoker(argTypes,isClassMethodFunc,returns,isAsync);var invokerFn=invokerFactory(...closureArgs);return createNamedFunction(humanName,invokerFn)}var __embind_register_class_constructor=(rawClassType,argCount,rawArgTypesAddr,invokerSignature,invoker,rawConstructor)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);invoker=embind__requireFunction(invokerSignature,invoker);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`constructor ${classType.name}`;if(undefined===classType.registeredClass.constructor_body){classType.registeredClass.constructor_body=[]}if(undefined!==classType.registeredClass.constructor_body[argCount-1]){throw new BindingError(`Cannot register multiple constructors with identical number of parameters (${argCount-1}) for class '${classType.name}'! Overload resolution is currently only performed using the parameter count, not actual type info!`)}classType.registeredClass.constructor_body[argCount-1]=()=>{throwUnboundTypeError(`Cannot construct ${classType.name} due to unbound types`,rawArgTypes)};whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{argTypes.splice(1,0,null);classType.registeredClass.constructor_body[argCount-1]=craftInvokerFunction(humanName,argTypes,null,invoker,rawConstructor);return[]});return[]})};var getFunctionName=signature=>{signature=signature.trim();const argsIndex=signature.indexOf("(");if(argsIndex===-1)return signature;return signature.slice(0,argsIndex)};var __embind_register_class_function=(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,context,isPureVirtual,isAsync,isNonnullReturn)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=AsciiToString(methodName);methodName=getFunctionName(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker,isAsync);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`${classType.name}.${methodName}`;if(methodName.startsWith("@@")){methodName=Symbol[methodName.substring(2)]}if(isPureVirtual){classType.registeredClass.pureVirtualFunctions.push(methodName)}function unboundTypesHandler(){throwUnboundTypeError(`Cannot call ${humanName} due to unbound types`,rawArgTypes)}var proto=classType.registeredClass.instancePrototype;var method=proto[methodName];if(undefined===method||undefined===method.overloadTable&&method.className!==classType.name&&method.argCount===argCount-2){unboundTypesHandler.argCount=argCount-2;unboundTypesHandler.className=classType.name;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-2]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{var memberFunction=craftInvokerFunction(humanName,argTypes,classType,rawInvoker,context,isAsync);if(undefined===proto[methodName].overloadTable){memberFunction.argCount=argCount-2;proto[methodName]=memberFunction}else{proto[methodName].overloadTable[argCount-2]=memberFunction}return[]});return[]})};var emval_freelist=[];var emval_handles=[0,1,,1,null,1,true,1,false,1];var __emval_decref=handle=>{if(handle>9&&0===--emval_handles[handle+1]){emval_handles[handle]=undefined;emval_freelist.push(handle)}};var Emval={toValue:handle=>{if(!handle){throwBindingError(`Cannot use deleted val. handle = ${handle}`)}return emval_handles[handle]},toHandle:value=>{switch(value){case undefined:return 2;case null:return 4;case true:return 6;case false:return 8;default:{const handle=emval_freelist.pop()||emval_handles.length;emval_handles[handle]=value;emval_handles[handle+1]=1;return handle}}}};var EmValType={name:"emscripten::val",fromWireType:handle=>{var rv=Emval.toValue(handle);__emval_decref(handle);return rv},toWireType:(destructors,value)=>Emval.toHandle(value),readValueFromPointer:readPointer,destructorFunction:null};var __embind_register_emval=rawType=>registerType(rawType,EmValType);var enumReadValueFromPointer=(name,width,signed)=>{switch(width){case 1:return signed?function(pointer){return this.fromWireType(HEAP8[pointer])}:function(pointer){return this.fromWireType(HEAPU8[pointer])};case 2:return signed?function(pointer){return this.fromWireType(HEAP16[pointer>>1])}:function(pointer){return this.fromWireType(HEAPU16[pointer>>1])};case 4:return signed?function(pointer){return this.fromWireType(HEAP32[pointer>>2])}:function(pointer){return this.fromWireType(HEAPU32[pointer>>2])};default:throw new TypeError(`invalid integer width (${width}): ${name}`)}};function getEnumValueType(rawValueType){return rawValueType===0?"object":rawValueType===1?"number":"string"}var __embind_register_enum=(rawType,name,size,isSigned,rawValueType)=>{name=AsciiToString(name);const valueType=getEnumValueType(rawValueType);switch(valueType){case"object":{function ctor(){}ctor.values={};registerType(rawType,{name,constructor:ctor,valueType,fromWireType:function(c){return this.constructor.values[c]},toWireType:(destructors,c)=>c.value,readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,ctor);break}case"number":{var keysMap={};registerType(rawType,{name,keysMap,valueType,fromWireType:c=>c,toWireType:(destructors,c)=>c,readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,keysMap);delete Module[name].argCount;break}case"string":{var valuesMap={};var reverseMap={};var keysMap={};registerType(rawType,{name,valuesMap,reverseMap,keysMap,valueType,fromWireType:function(c){return this.reverseMap[c]},toWireType:function(destructors,c){return this.valuesMap[c]},readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,keysMap);delete Module[name].argCount;break}}};var requireRegisteredType=(rawType,humanName)=>{var impl=registeredTypes[rawType];if(undefined===impl){throwBindingError(`${humanName} has unknown type ${getTypeName(rawType)}`)}return impl};var __embind_register_enum_value=(rawEnumType,name,enumValue)=>{var enumType=requireRegisteredType(rawEnumType,"enum");name=AsciiToString(name);switch(enumType.valueType){case"object":{var Enum=enumType.constructor;var Value=Object.create(enumType.constructor.prototype,{value:{value:enumValue},constructor:{value:createNamedFunction(`${enumType.name}_${name}`,function(){})}});Enum.values[enumValue]=Value;Enum[name]=Value;break}case"number":{enumType.keysMap[name]=enumValue;break}case"string":{enumType.valuesMap[name]=enumValue;enumType.reverseMap[enumValue]=name;enumType.keysMap[name]=name;break}}};var floatReadValueFromPointer=(name,width)=>{switch(width){case 4:return function(pointer){return this.fromWireType(HEAPF32[pointer>>2])};case 8:return function(pointer){return this.fromWireType(HEAPF64[pointer>>3])};default:throw new TypeError(`invalid float width (${width}): ${name}`)}};var __embind_register_float=(rawType,name,size)=>{name=AsciiToString(name);registerType(rawType,{name,fromWireType:value=>value,toWireType:(destructors,value)=>value,readValueFromPointer:floatReadValueFromPointer(name,size),destructorFunction:null})};var __embind_register_function=(name,argCount,rawArgTypesAddr,signature,rawInvoker,fn,isAsync,isNonnullReturn)=>{var argTypes=heap32VectorToArray(argCount,rawArgTypesAddr);name=AsciiToString(name);name=getFunctionName(name);rawInvoker=embind__requireFunction(signature,rawInvoker,isAsync);exposePublicSymbol(name,function(){throwUnboundTypeError(`Cannot call ${name} due to unbound types`,argTypes)},argCount-1);whenDependentTypesAreResolved([],argTypes,argTypes=>{var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));replacePublicSymbol(name,craftInvokerFunction(name,invokerArgsArray,null,rawInvoker,fn,isAsync),argCount-1);return[]})};var __embind_register_integer=(primitiveType,name,size,minRange,maxRange)=>{name=AsciiToString(name);const isUnsignedType=minRange===0;let fromWireType=value=>value;if(isUnsignedType){var bitshift=32-8*size;fromWireType=value=>value<<bitshift>>>bitshift;maxRange=fromWireType(maxRange)}registerType(primitiveType,{name,fromWireType,toWireType:(destructors,value)=>value,readValueFromPointer:integerReadValueFromPointer(name,size,minRange!==0),destructorFunction:null})};var __embind_register_memory_view=(rawType,dataTypeIndex,name)=>{var typeMapping=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array,BigInt64Array,BigUint64Array];var TA=typeMapping[dataTypeIndex];function decodeMemoryView(handle){var size=HEAPU32[handle>>2];var data=HEAPU32[handle+4>>2];return new TA(HEAP8.buffer,data,size)}name=AsciiToString(name);registerType(rawType,{name,fromWireType:decodeMemoryView,readValueFromPointer:decodeMemoryView},{ignoreDuplicateRegistrations:true})};var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i<str.length;++i){var u=str.codePointAt(i);if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63;i++}}heap[outIdx]=0;return outIdx-startIdx};var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);var lengthBytesUTF8=str=>{var len=0;for(var i=0;i<str.length;++i){var c=str.charCodeAt(i);if(c<=127){len++}else if(c<=2047){len+=2}else if(c>=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};var UTF8Decoder=globalThis.TextDecoder&&new TextDecoder;var findStringEnd=(heapOrArray,idx,maxBytesToRead,ignoreNul)=>{var maxIdx=idx+maxBytesToRead;if(ignoreNul)return maxIdx;while(heapOrArray[idx]&&!(idx>=maxIdx))++idx;return idx};var UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead,ignoreNul)=>{var endPtr=findStringEnd(heapOrArray,idx,maxBytesToRead,ignoreNul);if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx<endPtr){var u0=heapOrArray[idx++];if(!(u0&128)){str+=String.fromCharCode(u0);continue}var u1=heapOrArray[idx++]&63;if((u0&224)==192){str+=String.fromCharCode((u0&31)<<6|u1);continue}var u2=heapOrArray[idx++]&63;if((u0&240)==224){u0=(u0&15)<<12|u1<<6|u2}else{u0=(u0&7)<<18|u1<<12|u2<<6|heapOrArray[idx++]&63}if(u0<65536){str+=String.fromCharCode(u0)}else{var ch=u0-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}}return str};var UTF8ToString=(ptr,maxBytesToRead,ignoreNul)=>ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead,ignoreNul):"";var __embind_register_std_string=(rawType,name)=>{name=AsciiToString(name);var stdStringIsUTF8=true;registerType(rawType,{name,fromWireType(value){var length=HEAPU32[value>>2];var payload=value+4;var str;if(stdStringIsUTF8){str=UTF8ToString(payload,length,true)}else{str="";for(var i=0;i<length;++i){str+=String.fromCharCode(HEAPU8[payload+i])}}_free(value);return str},toWireType(destructors,value){if(value instanceof ArrayBuffer){value=new Uint8Array(value)}var length;var valueIsOfTypeString=typeof value=="string";if(!(valueIsOfTypeString||ArrayBuffer.isView(value)&&value.BYTES_PER_ELEMENT==1)){throwBindingError("Cannot pass non-string to std::string")}if(stdStringIsUTF8&&valueIsOfTypeString){length=lengthBytesUTF8(value)}else{length=value.length}var base=_malloc(4+length+1);var ptr=base+4;HEAPU32[base>>2]=length;if(valueIsOfTypeString){if(stdStringIsUTF8){stringToUTF8(value,ptr,length+1)}else{for(var i=0;i<length;++i){var charCode=value.charCodeAt(i);if(charCode>255){_free(base);throwBindingError("String has UTF-16 code units that do not fit in 8 bits")}HEAPU8[ptr+i]=charCode}}}else{HEAPU8.set(value,ptr)}if(destructors!==null){destructors.push(_free,base)}return base},readValueFromPointer:readPointer,destructorFunction(ptr){_free(ptr)}})};var UTF16Decoder=globalThis.TextDecoder?new TextDecoder("utf-16le"):undefined;var UTF16ToString=(ptr,maxBytesToRead,ignoreNul)=>{var idx=ptr>>1;var endIdx=findStringEnd(HEAPU16,idx,maxBytesToRead/2,ignoreNul);if(endIdx-idx>16&&UTF16Decoder)return UTF16Decoder.decode(HEAPU16.subarray(idx,endIdx));var str="";for(var i=idx;i<endIdx;++i){var codeUnit=HEAPU16[i];str+=String.fromCharCode(codeUnit)}return str};var stringToUTF16=(str,outPtr,maxBytesToWrite)=>{maxBytesToWrite??=2147483647;if(maxBytesToWrite<2)return 0;maxBytesToWrite-=2;var startPtr=outPtr;var numCharsToWrite=maxBytesToWrite<str.length*2?maxBytesToWrite/2:str.length;for(var i=0;i<numCharsToWrite;++i){var codeUnit=str.charCodeAt(i);HEAP16[outPtr>>1]=codeUnit;outPtr+=2}HEAP16[outPtr>>1]=0;return outPtr-startPtr};var lengthBytesUTF16=str=>str.length*2;var UTF32ToString=(ptr,maxBytesToRead,ignoreNul)=>{var str="";var startIdx=ptr>>2;for(var i=0;!(i>=maxBytesToRead/4);i++){var utf32=HEAPU32[startIdx+i];if(!utf32&&!ignoreNul)break;str+=String.fromCodePoint(utf32)}return str};var stringToUTF32=(str,outPtr,maxBytesToWrite)=>{maxBytesToWrite??=2147483647;if(maxBytesToWrite<4)return 0;var startPtr=outPtr;var endPtr=startPtr+maxBytesToWrite-4;for(var i=0;i<str.length;++i){var codePoint=str.codePointAt(i);if(codePoint>65535){i++}HEAP32[outPtr>>2]=codePoint;outPtr+=4;if(outPtr+4>endPtr)break}HEAP32[outPtr>>2]=0;return outPtr-startPtr};var lengthBytesUTF32=str=>{var len=0;for(var i=0;i<str.length;++i){var codePoint=str.codePointAt(i);if(codePoint>65535){i++}len+=4}return len};var __embind_register_std_wstring=(rawType,charSize,name)=>{name=AsciiToString(name);var decodeString,encodeString,lengthBytesUTF;if(charSize===2){decodeString=UTF16ToString;encodeString=stringToUTF16;lengthBytesUTF=lengthBytesUTF16}else{decodeString=UTF32ToString;encodeString=stringToUTF32;lengthBytesUTF=lengthBytesUTF32}registerType(rawType,{name,fromWireType:value=>{var length=HEAPU32[value>>2];var str=decodeString(value+4,length*charSize,true);_free(value);return str},toWireType:(destructors,value)=>{if(!(typeof value=="string")){throwBindingError(`Cannot pass non-string to C++ string type ${name}`)}var length=lengthBytesUTF(value);var ptr=_malloc(4+length+charSize);HEAPU32[ptr>>2]=length/charSize;encodeString(value,ptr+4,length+charSize);if(destructors!==null){destructors.push(_free,ptr)}return ptr},readValueFromPointer:readPointer,destructorFunction(ptr){_free(ptr)}})};var __embind_register_value_object=(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor)=>{structRegistrations[rawType]={name:AsciiToString(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),fields:[]}};var __embind_register_value_object_field=(structType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext)=>{structRegistrations[structType].fields.push({fieldName:AsciiToString(fieldName),getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext,setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext})};var __embind_register_void=(rawType,name)=>{name=AsciiToString(name);registerType(rawType,{isVoid:true,name,fromWireType:()=>undefined,toWireType:(destructors,o)=>undefined})};var __tzset_js=(timezone,daylight,std_name,dst_name)=>{var currentYear=(new Date).getFullYear();var winter=new Date(currentYear,0,1);var summer=new Date(currentYear,6,1);var winterOffset=winter.getTimezoneOffset();var summerOffset=summer.getTimezoneOffset();var stdTimezoneOffset=Math.max(winterOffset,summerOffset);HEAPU32[timezone>>2]=stdTimezoneOffset*60;HEAP32[daylight>>2]=Number(winterOffset!=summerOffset);var extractZone=timezoneOffset=>{var sign=timezoneOffset>=0?"-":"+";var absOffset=Math.abs(timezoneOffset);var hours=String(Math.floor(absOffset/60)).padStart(2,"0");var minutes=String(absOffset%60).padStart(2,"0");return`UTC${sign}${hours}${minutes}`};var winterName=extractZone(winterOffset);var summerName=extractZone(summerOffset);if(summerOffset<winterOffset){stringToUTF8(winterName,std_name,17);stringToUTF8(summerName,dst_name,17)}else{stringToUTF8(winterName,dst_name,17);stringToUTF8(summerName,std_name,17)}};var readEmAsmArgsArray=[];var readEmAsmArgs=(sigPtr,buf)=>{readEmAsmArgsArray.length=0;var ch;while(ch=HEAPU8[sigPtr++]){var wide=ch!=105;wide&=ch!=112;buf+=wide&&buf%8?4:0;readEmAsmArgsArray.push(ch==112?HEAPU32[buf>>2]:ch==106?HEAP64[buf>>3]:ch==105?HEAP32[buf>>2]:HEAPF64[buf>>3]);buf+=wide?8:4}return readEmAsmArgsArray};var runEmAsmFunction=(code,sigPtr,argbuf)=>{var args=readEmAsmArgs(sigPtr,argbuf);return ASM_CONSTS[code](...args)};var _emscripten_asm_const_int=(code,sigPtr,argbuf)=>runEmAsmFunction(code,sigPtr,argbuf);var getHeapMax=()=>2147483648;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var growMemory=size=>{var oldHeapSize=wasmMemory.buffer.byteLength;var pages=(size-oldHeapSize+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var ENV={};var getExecutableName=()=>thisProgram||"./this.program";var getEnvStrings=()=>{if(!getEnvStrings.strings){var lang=(globalThis.navigator?.language??"C").replace("-","_")+".UTF-8";var env={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:lang,_:getExecutableName()};for(var x in ENV){if(ENV[x]===undefined)delete env[x];else env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(`${x}=${env[x]}`)}getEnvStrings.strings=strings}return getEnvStrings.strings};var _environ_get=(__environ,environ_buf)=>{var bufSize=0;var envp=0;for(var string of getEnvStrings()){var ptr=environ_buf+bufSize;HEAPU32[__environ+envp>>2]=ptr;bufSize+=stringToUTF8(string,ptr,Infinity)+1;envp+=4}return 0};var _environ_sizes_get=(penviron_count,penviron_buf_size)=>{var strings=getEnvStrings();HEAPU32[penviron_count>>2]=strings.length;var bufSize=0;for(var string of strings){bufSize+=lengthBytesUTF8(string)+1}HEAPU32[penviron_buf_size>>2]=bufSize;return 0};init_ClassHandle();init_RegisteredPointer();{if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(Module["print"])out=Module["print"];if(Module["printErr"])err=Module["printErr"];if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].shift()()}}}var ASM_CONSTS={26400:()=>{Module["Binarization"]["Algorithms"]=Module["Binarization.Algorithms"];delete Module["Binarization.Algorithms"]},26516:()=>{Module["Grayscale"]={Algorithms:Module["Grayscale.Algorithms"]};delete Module["Grayscale.Algorithms"]}};var ___getTypeName,_malloc,_free,memory,__indirect_function_table,wasmMemory,wasmTable;function assignWasmExports(wasmExports){___getTypeName=wasmExports["B"];_malloc=Module["_malloc"]=wasmExports["D"];_free=Module["_free"]=wasmExports["E"];memory=wasmMemory=wasmExports["z"];__indirect_function_table=wasmTable=wasmExports["C"]}var wasmImports={e:___cxa_throw,r:__abort_js,i:__embind_finalize_value_object,n:__embind_register_bigint,w:__embind_register_bool,y:__embind_register_class,t:__embind_register_class_constructor,f:__embind_register_class_function,u:__embind_register_emval,l:__embind_register_enum,a:__embind_register_enum_value,m:__embind_register_float,h:__embind_register_function,d:__embind_register_integer,c:__embind_register_memory_view,v:__embind_register_std_string,g:__embind_register_std_wstring,j:__embind_register_value_object,b:__embind_register_value_object_field,x:__embind_register_void,o:__tzset_js,k:_emscripten_asm_const_int,s:_emscripten_resize_heap,p:_environ_get,q:_environ_sizes_get};function run(){if(runDependencies>0){dependenciesFulfilled=run;return}preRun();if(runDependencies>0){dependenciesFulfilled=run;return}function doRun(){Module["calledRun"]=true;if(ABORT)return;initRuntime();Module["onRuntimeInitialized"]?.();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(()=>{setTimeout(()=>Module["setStatus"](""),1);doRun()},1)}else{doRun()}}var wasmExports;createWasm();run();
================================================
FILE: Bindings/WebAssembly/doxa.js
================================================
/**
* Doxa WASM
* A set of classes that further simplify the Doxa WASM interface.
* This same wrapper can be run in NodeJS or directly in the web.
* See Demo/WebJs and Demo/NodeJS for an example of how to use it.
*/
const Doxa = {
Wasm: (typeof Module !== 'undefined') ? Module : require('./doxaWasm.js'),
initialize: async function() {
if (!Doxa.Wasm) throw new Error('Missing: Doxa WASM Module');
// Extract enum values from an Emscripten enum object
const extractEnums = function(enumObj) {
const enums = {};
for (const key in enumObj) {
const entry = enumObj[key];
if (entry?.value === undefined) continue;
enums[key] = entry.value;
}
return enums;
}
const buildInstance = function() {
const instance = {
binarization: extractEnums(Doxa.Wasm.Binarization.Algorithms),
grayscale: extractEnums(Doxa.Wasm.Grayscale.Algorithms),
/**
* Convert raw pixel data to an 8-bit grayscale Doxa.Image.
* If the data is already single-channel, it is copied directly.
* @param {Uint8Array|Uint8ClampedArray} data - Raw pixel data (1, 3, or 4 channels)
* @param {number} width - Image width
* @param {number} height - Image height
* @param {number} channels - 1 for grayscale, 3 for RGB, 4 for RGBA
* @param {number} algorithm - Grayscale algorithm enum value (e.g. doxa.grayscale.MEAN). Defaults to MEAN.
* @returns {Doxa.Image} 8-bit grayscale image (caller must free)
*/
toGrayscale: function(data, width, height, channels, algorithm) {
// Already grayscale — just copy directly
if (channels === 1) {
return new Doxa.Image(width, height, data);
}
// Allocate WASM heap for input
const inputSize = width * height * channels;
const inputPtr = Doxa.Wasm._malloc(inputSize);
Doxa.Wasm.HEAPU8.set(data.subarray(0, inputSize), inputPtr);
// Allocate output image
const outputImage = new Doxa.Image(width, height);
const algEnum = Doxa.Wasm.Grayscale.Algorithms.values[
algorithm ?? instance.grayscale.MEAN
];
Doxa.Wasm.toGrayscale(outputImage.heapPtr, inputPtr, width, height, channels, algEnum);
// Free input buffer
Doxa.Wasm._free(inputPtr);
return outputImage;
},
/**
* Convert an HTML5 Canvas ImageData to an 8-bit grayscale Doxa.Image.
* Convenience wrapper for browser usage.
* @param {ImageData} imageData - Canvas ImageData (32-bit RGBA)
* @param {number} algorithm - Grayscale algorithm enum value (e.g. doxa.grayscale.MEAN). Defaults to MEAN.
* @returns {Doxa.Image} 8-bit grayscale image (caller must free)
*/
fromImageData: function(imageData, algorithm) {
return instance.toGrayscale(
imageData.data, imageData.width, imageData.height, 4, algorithm
);
},
/**
* Binarize a grayscale image using the specified algorithm.
* @param {number} algorithm - Binarization algorithm enum value (e.g. doxa.binarization.SAUVOLA)
* @param {Doxa.Image} imageIn - Input grayscale image
* @param {object} parameters - Algorithm parameters (e.g. { window: 75, k: 0.2 })
* @param {Doxa.Image} imageOut - Optional output image (allocated if not provided)
* @returns {Doxa.Image} Binary image (caller must free if newly allocated)
*/
toBinary: function(algorithm, imageIn, parameters, imageOut) {
if (!imageOut) {
imageOut = new Doxa.Image(imageIn.width, imageIn.height);
}
const algEnum = Doxa.Wasm.Binarization.Algorithms.values[algorithm];
const paramString = JSON.stringify(parameters || {});
const binarization = new Doxa.Wasm.Binarization(algEnum);
binarization.initialize(imageIn.heapPtr, imageIn.width, imageIn.height);
binarization.toBinary(imageOut.heapPtr, paramString);
return imageOut;
},
/**
* Calculate performance metrics comparing a binary image against ground truth.
* If precision/recall weight texts are provided, pseudo metrics are included.
* @param {Doxa.Image} groundTruth - Ground truth binary image
* @param {Doxa.Image} binary - Binary image to evaluate
* @param {string} precisionWeightsText - Optional precision weights (enables pseudo metrics)
* @param {string} recallWeightsText - Optional recall weights (enables pseudo metrics)
* @returns {object} Performance metrics (accuracy, fm, precision, recall, mcc, psnr, nrm, drdm, and optionally pseudoFM, pseudoPrecision, pseudoRecall)
*/
calculatePerformance: function(groundTruth, binary, precisionWeightsText, recallWeightsText) {
if (precisionWeightsText && recallWeightsText) {
return Doxa.Wasm.calculatePseudoPerformance(
groundTruth.heapPtr, binary.heapPtr,
binary.width, binary.height,
precisionWeightsText, recallWeightsText
);
}
return Doxa.Wasm.calculatePerformance(
groundTruth.heapPtr, binary.heapPtr,
binary.width, binary.height
);
}
};
return instance;
}
return new Promise((resolve) => {
// Ensure the library has not already been initialized
if (typeof Doxa.Wasm.Binarization !== 'undefined') {
return resolve(buildInstance());
}
Doxa.Wasm.onRuntimeInitialized = async _ => {
resolve(buildInstance());
};
});
},
Image: class {
bufferSize = 0;
/**
* Initialize an Image object. All Image objects allocate WASM memory.
* That memory should be freed, explicitly. If 8-bit data is passed,
* it will be copied directly into WASM memory.
* Use doxa.fromImageData to convert from RGBA to grayscale.
* @param {number} width Image Width
* @param {number} height Image Height
* @param {Uint8Array} data 8-bit grayscale data. Optional.
*/
constructor (width, height, data) {
this.initialize(width || 0, height || 0);
if (data) {
Doxa.Wasm.HEAPU8.set(data.subarray(0, this.size), this.heapPtr);
}
}
/**
* The number of pixels in the image.
*/
get size() {
return this.width * this.height;
}
/**
* Draws the Image directly to an HTML5 Canvas.
* The Canvas should accept 32bit data, which is standard.
* @param {*} canvas HTML5 Canvas, or a node-canvas
*/
draw(canvas) {
canvas.width = this.width;
canvas.height = this.height;
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
this.toImageData(imageData);
ctx.putImageData(imageData, 0, 0);
}
data() {
return new Uint8ClampedArray(Doxa.Wasm.HEAPU8.buffer, this.heapPtr, this.size);
}
/**
* Frees the memory allocated by this object.
*/
free() {
if (this.heapPtr != null) Doxa.Wasm._free(this.heapPtr);
}
initialize(width, height) {
this.width = width;
this.height = height;
if (this.size > this.bufferSize) {
this.free();
this.bufferSize = this.size;
this.heapPtr = Doxa.Wasm._malloc(this.bufferSize);
}
}
/**
* Writes the 8-bit grayscale image data into a 32-bit RGBA ImageData object.
* @param {ImageData} imageData - Target ImageData to populate
*/
toImageData(imageData) {
const buffer = this.data();
const size32 = imageData.width * imageData.height * 4;
for (var idx = 0; idx < size32; idx += 4) {
const gsIdx = idx / 4;
imageData.data[idx] = buffer[gsIdx];
imageData.data[idx+1] = buffer[gsIdx];
imageData.data[idx+2] = buffer[gsIdx];
imageData.data[idx+3] = 255;
}
}
}
}
// Only export if running in NodeJS
if (typeof module !== 'undefined') {
module.exports = { Doxa };
}
================================================
FILE: Bindings/WebAssembly/package.json
================================================
{
"name": "doxajs",
"version": "1.0.0",
"description": "Doxa Binarization Framework for JavaScript/WebAssembly",
"main": "dist/doxa.js",
"files": [
"dist/"
],
"repository": {
"type": "git",
"url": "https://github.com/brandonmpetty/Doxa"
},
"keywords": [
"binarization",
"image-processing"
],
"scripts": {
"test": "jasmine",
"build": "emcmake cmake -S ../.. -B ../../build-wasm -DCMAKE_BUILD_TYPE=Release && cmake --build ../../build-wasm --config Release",
"build:dev": "emcmake cmake -S ../.. -B ../../build-wasm -DCMAKE_BUILD_TYPE=Debug && cmake --build ../../build-wasm --config Debug",
"prepublishOnly": "npm run build"
},
"author": "Brandon M. Petty",
"license": "CC0-1.0",
"devDependencies": {
"canvas": "3.2.1",
"jasmine": "6.0.0"
}
}
================================================
FILE: Bindings/WebAssembly/spec/binarization.spec.js
================================================
const fs = require('fs');
const path = require('path');
const { loadImage, createCanvas } = require('canvas');
const { Doxa } = require('../dist/doxa.js');
// Note: I would love to give an example of sharp but it cannot live
// in the same project as canvas. See the DEMO for an example.
//const sharp = require('sharp');
describe("Doxa Binarization Class Test Suite", function() {
let doxa;
async function readImage(file) {
const image = await loadImage(file);
const canvas = createCanvas(image.width, image.height);
const ctx = canvas.getContext('2d');
ctx.drawImage(image, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
return doxa.fromImageData(imageData);
}
beforeAll(async function() {
// Initializing is required for setting up the WASM module.
doxa = await Doxa.initialize();
});
it("Binarization toBinary runs successfully", async function() {
const rgba = new Buffer.from([
0,0,0,0, 255,255,255,0, 120,120,120,0,
15,15,15,0, 230,230,230,0, 90,90,90,0,
5,5,5,0, 245,245,245,0, 69,1100,77,0
]);
const image = doxa.toGrayscale(rgba, 3, 3, 4);
// Function under test
const binImage = doxa.toBinary(doxa.binarization.OTSU, image);
expect(Array.from(binImage.data())).toEqual([
0, 255, 0,
0, 255, 0,
0, 255, 0
]);
});
it("Binarization calculatePerformance runs successfully", async function() {
const groundTruthImage = await readImage('../../README/2JohnC1V3-GroundTruth.png');
const binaryImage = await readImage('../../README/2JohnC1V3-Sauvola.png');
const metrics = doxa.calculatePerformance(groundTruthImage, binaryImage);
expect(metrics.accuracy).toBeCloseTo(97.671, 3);
// NOTE: This PNG is slightly different than the PBM used by other tests
//console.log(4122975586 / (2112 * 1000000)); // DIBCO Metrics
//console.log(4122922964 / (2112 * 1000000)); // This algorithm.
// Possible rounding error due to the weighted matrix?
expect(metrics.drdm).toBeCloseTo(1.9522, 3); // TODO: Change to 4!
expect(metrics.fm).toBeCloseTo(93.204, 3);
expect(metrics.recall).toBeCloseTo(91.3811, 2);
expect(metrics.precision).toBeCloseTo(95.1025, 2);
expect(metrics.mcc).toBeCloseTo(0.918, 3);
expect(metrics.nrm).toBeCloseTo(0.048, 3);
expect(metrics.psnr).toBeCloseTo(16.329, 3);
});
it("Binarization calculatePerformance with pseudo metrics runs successfully", async function() {
const groundTruthImage = await readImage('../../README/2JohnC1V3-GroundTruth.png');
const binaryImage = await readImage('../../README/2JohnC1V3-Sauvola.png');
const pWeightsText = fs.readFileSync(path.resolve(__dirname, '../../../Doxa.Test/Resources/2JohnC1V3-GroundTruth_PWeights.dat'), 'utf8');
const rWeightsText = fs.readFileSync(path.resolve(__dirname, '../../../Doxa.Test/Resources/2JohnC1V3-GroundTruth_RWeights.dat'), 'utf8');
const metrics = doxa.calculatePerformance(groundTruthImage, binaryImage, pWeightsText, rWeightsText);
expect(metrics.pseudoFM).toBeCloseTo(93.393, 2);
expect(metrics.pseudoRecall).toBeCloseTo(92.7954, 2);
expect(metrics.pseudoPrecision).toBeCloseTo(93.9983, 2);
});
it("Algorithm defaults are applied", async function() {
const image = await readImage('../../README/2JohnC1V3.png');
const binImage1 = doxa.toBinary(doxa.binarization.SAUVOLA, image);
const binImage2 = doxa.toBinary(doxa.binarization.SAUVOLA, image, { window: 75, k: 0.2 });
const binImage3 = doxa.toBinary(doxa.binarization.SAUVOLA, image, { window: 25, k: 0.12 });
expect(binImage1.data()).toEqual(binImage2.data());
expect(binImage2.data()).not.toEqual(binImage3.data());
});
});
================================================
FILE: Bindings/WebAssembly/spec/image.spec.js
================================================
const { createCanvas, createImageData } = require('canvas');
const { Doxa } = require('../dist/doxa.js');
describe("Doxa Image Class Test Suite", function() {
let doxa;
beforeAll(async function() {
// Initializing is required for setting up the WASM module.
doxa = await Doxa.initialize();
});
it("Constructor no arguments", function() {
const image = new Doxa.Image();
expect(image.width).toBe(0);
expect(image.height).toBe(0);
expect(image.size).toBe(0);
expect(Array.from(image.data())).toEqual([]);
image.free();
});
it("Constructor no Data", function() {
const image = new Doxa.Image(2, 3);
expect(image.width).toBe(2);
expect(image.height).toBe(3);
expect(image.size).toBe(6);
// Some compilers might zero out image.data, others will not.
image.free();
});
it("Constructor with 8-bit Data", function() {
const gray = new Uint8Array([
255, 0, 128,
85, 85, 85,
80, 80, 80
]);
const image = new Doxa.Image(3, 3, gray);
expect(image.width).toBe(3);
expect(image.height).toBe(3);
expect(image.size).toBe(9);
expect(Array.from(image.data())).toEqual([
255, 0, 128,
85, 85, 85,
80, 80, 80
]);
image.free();
});
it("Image Resize", function() {
const image = new Doxa.Image(2, 3);
expect(image.width).toBe(2);
expect(image.height).toBe(3);
expect(image.size).toBe(6);
expect(image.bufferSize).toBe(6);
image.initialize(3,3);
expect(image.width).toBe(3);
expect(image.height).toBe(3);
expect(image.size).toBe(9);
expect(image.bufferSize).toBe(9); // Buffer grew
image.initialize(2,1);
expect(image.width).toBe(2);
expect(image.height).toBe(1);
expect(image.size).toBe(2); // Reflects actual image
expect(image.bufferSize).toBe(9); // Buffer retained
image.free();
});
it("Grayscale fromImageData", function() {
const rgba = new createImageData(new Uint8ClampedArray([
255,255,255,0, 0,0,0,0, 128,128,128,0,
255,0,0,0, 0,255,0,0, 0,0,255,0,
70,80,90,0, 90,80,70,0, 80,90,70,0
]), 3, 3);
const image = doxa.fromImageDat
gitextract_kry5ydl6/ ├── .claude/ │ └── settings.local.json ├── .gitattributes ├── .github/ │ └── workflows/ │ ├── benchmarks.yml │ ├── npm-publish.yml │ ├── pythonpackage.yml │ └── test-and-coverage.yml ├── .gitignore ├── Bindings/ │ ├── Matlab/ │ │ ├── +Doxa/ │ │ │ ├── Algorithms.m │ │ │ ├── Grayscale.m │ │ │ ├── Image.m │ │ │ ├── binarize.m │ │ │ ├── buildParams.m │ │ │ ├── calculatePerformance.m │ │ │ ├── readWeights.m │ │ │ └── updateToBinary.m │ │ ├── BinarizeMex.cpp │ │ ├── CMakeLists.txt │ │ ├── CalculatePerformanceMex.cpp │ │ ├── Doxa.prj │ │ ├── DoxaMexUtils.hpp │ │ ├── ImageMex.cpp │ │ ├── README.md │ │ ├── UpdateToBinaryMex.cpp │ │ └── test/ │ │ └── TestDoxa.m │ ├── Python/ │ │ ├── .gitignore │ │ ├── CMakeLists.txt │ │ ├── DoxaPy.ipynb │ │ ├── README.md │ │ ├── copy-cpp-files.py │ │ ├── pyproject.toml │ │ ├── requirements.txt │ │ ├── src/ │ │ │ ├── DoxaPy.cpp │ │ │ └── doxapy/ │ │ │ └── __init__.py │ │ └── test/ │ │ ├── test_doxa.py │ │ └── test_speed.py │ └── WebAssembly/ │ ├── CMakeLists.txt │ ├── DoxaJs.nnb │ ├── DoxaWasm.cpp │ ├── README.md │ ├── dist/ │ │ ├── doxa.js │ │ ├── doxaWasm.js │ │ └── doxaWasm.wasm │ ├── doxa.js │ ├── package.json │ └── spec/ │ ├── binarization.spec.js │ ├── image.spec.js │ ├── speed.spec.js │ └── support/ │ └── jasmine.json ├── CLAUDE.md ├── CMakeLists.txt ├── CMakePresets.json ├── Demo/ │ ├── Cpp/ │ │ ├── .gitignore │ │ ├── demo.cpp │ │ ├── demoOpenCV.cpp │ │ ├── demoQt.cpp │ │ └── demoQt.pro │ ├── Matlab/ │ │ └── demo.m │ ├── NodeJS/ │ │ ├── .gitignore │ │ ├── index.js │ │ └── package.json │ ├── Python/ │ │ └── demo.py │ └── WebJS/ │ └── index.html ├── Doxa/ │ ├── AdOtsu.hpp │ ├── Algorithm.hpp │ ├── Bataineh.hpp │ ├── Bernsen.hpp │ ├── BinarizationFactory.hpp │ ├── ChanMeanCalc.hpp │ ├── ChanMeanVarianceCalc.hpp │ ├── ClassifiedPerformance.hpp │ ├── ContrastImage.hpp │ ├── DIBCOUtils.hpp │ ├── DRDM.hpp │ ├── Doxa.vcxitems │ ├── Gatos.hpp │ ├── Grayscale.hpp │ ├── GridCalc.hpp │ ├── ISauvola.hpp │ ├── Image.hpp │ ├── IntegralImageMeanVarianceCalc.hpp │ ├── LocalWindow.hpp │ ├── Morphology.hpp │ ├── MultiScale.hpp │ ├── Niblack.hpp │ ├── Nick.hpp │ ├── Otsu.hpp │ ├── PNM.hpp │ ├── Palette.hpp │ ├── Parameters.hpp │ ├── Phansalkar.hpp │ ├── Region.hpp │ ├── SIMD.h │ ├── SIMDOps.hpp │ ├── Sauvola.hpp │ ├── Su.hpp │ ├── TRSingh.hpp │ ├── Types.hpp │ ├── Wan.hpp │ ├── WienerFilter.hpp │ └── Wolf.hpp ├── Doxa.Bench/ │ ├── BenchmarkHarness.hpp │ ├── BinarizationBenchmarks.cpp │ ├── CMakeLists.txt │ ├── CalculatorBenchmarks.cpp │ ├── ClassifiedPerformanceBenchmarks.cpp │ ├── DRDMBenchmarks.cpp │ ├── GlobalThresholdBenchmarks.cpp │ ├── config.hpp.in │ └── pch.h ├── Doxa.Test/ │ ├── AlgorithmTests.cpp │ ├── BatainehTests.cpp │ ├── BinarizationTests.cpp │ ├── CMakeLists.txt │ ├── CalculatorTests.cpp │ ├── ClassifiedPerformanceTests.cpp │ ├── ContrastImageTests.cpp │ ├── DIBCOUtilsTests.cpp │ ├── DRDMTests.cpp │ ├── Doxa.Test.vcxproj │ ├── Doxa.Test.vcxproj.filters │ ├── GrayscaleTests.cpp │ ├── GridCalcTests.cpp │ ├── ISauvolaTests.cpp │ ├── ImageFixture.hpp │ ├── ImageTests.cpp │ ├── LocalWindowTests.cpp │ ├── MorphologyTests.cpp │ ├── PNMTests.cpp │ ├── PaletteTests.cpp │ ├── ParametersTests.cpp │ ├── RegionTests.cpp │ ├── Resources/ │ │ ├── 2JohnC1V3-AdOtsu.pbm │ │ ├── 2JohnC1V3-AdOtsuG.pbm │ │ ├── 2JohnC1V3-AdOtsuMS.pbm │ │ ├── 2JohnC1V3-AdOtsuMSG.pbm │ │ ├── 2JohnC1V3-Bataineh.pbm │ │ ├── 2JohnC1V3-Bensen.pbm │ │ ├── 2JohnC1V3-ContrastImage.ppm │ │ ├── 2JohnC1V3-Gatos.pbm │ │ ├── 2JohnC1V3-GroundTruth.pbm │ │ ├── 2JohnC1V3-HighContrastImage.pbm │ │ ├── 2JohnC1V3-ISauvola.pbm │ │ ├── 2JohnC1V3-NICK.pbm │ │ ├── 2JohnC1V3-Niblack.pbm │ │ ├── 2JohnC1V3-Otsu.pbm │ │ ├── 2JohnC1V3-Phansalkar.pbm │ │ ├── 2JohnC1V3-Sauvola.pbm │ │ ├── 2JohnC1V3-Su.pbm │ │ ├── 2JohnC1V3-TRSingh.pbm │ │ ├── 2JohnC1V3-WAN.pbm │ │ ├── 2JohnC1V3-Wolf.pbm │ │ ├── 2JohnC1V3.ppm │ │ └── 2JohnC1V3.psd │ ├── SIMDTests.cpp │ ├── SuTests.cpp │ ├── TestUtilities.hpp │ ├── WienerFilterTests.cpp │ ├── packages.config │ ├── pch.cpp │ └── pch.h ├── Doxa.sln ├── LICENSE └── README.md
SYMBOL INDEX (597 symbols across 86 files)
FILE: Bindings/Matlab/BinarizeMex.cpp
function mexFunction (line 10) | void mexFunction(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prh...
FILE: Bindings/Matlab/CalculatePerformanceMex.cpp
function mexFunction (line 13) | void mexFunction(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prh...
FILE: Bindings/Matlab/DoxaMexUtils.hpp
type DoxaMexUtils (line 18) | namespace DoxaMexUtils
function mxArray (line 38) | inline mxArray* ImageToHandle(Doxa::Image* image)
function mxArray (line 73) | inline mxArray* ImageToMxArray(const Doxa::Image& image)
function MxArrayToDoubleVector (line 133) | inline std::vector<double> MxArrayToDoubleVector(const mxArray* arr)
function MapParameterName (line 150) | inline std::string MapParameterName(const std::string& matlabName)
function MxStructToParameters (line 160) | inline Doxa::Parameters MxStructToParameters(const mxArray* aStruct)
function StringToAlgorithmEnum (line 189) | inline Doxa::Algorithms StringToAlgorithmEnum(const std::string& algor...
function StringToGrayscaleEnum (line 211) | inline Doxa::GrayscaleAlgorithms StringToGrayscaleEnum(const std::stri...
FILE: Bindings/Matlab/ImageMex.cpp
function mexFunction (line 18) | void mexFunction(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prh...
FILE: Bindings/Matlab/UpdateToBinaryMex.cpp
function mexFunction (line 10) | void mexFunction(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prh...
FILE: Bindings/Python/src/DoxaPy.cpp
function Image (line 18) | Image ArrayToImage(const nb::ndarray<Pixel8, nb::ndim<2>>& imageArray)
function CalculatePerformance (line 23) | nb::dict CalculatePerformance(
function CalculatePerformanceEx (line 61) | nb::dict CalculatePerformanceEx(
function ToGrayscale (line 137) | nb::ndarray<nb::numpy, Pixel8, nb::ndim<2>> ToGrayscale(
class Binarization (line 162) | class Binarization
method Binarization (line 165) | Binarization(const Algorithms algorithm)
method Initialize (line 176) | void Initialize(const nb::ndarray<Pixel8, nb::ndim<2>>& grayScaleImage...
method ToBinary (line 182) | void ToBinary(const nb::ndarray<Pixel8, nb::ndim<2>>& binaryImageArray...
method Algorithms (line 188) | Algorithms CurrentAlgorithm() { return algorithm; }
function ToBinary (line 195) | nb::ndarray<nb::numpy, Pixel8, nb::ndim<2>> ToBinary(
function UpdateToBinary (line 221) | void UpdateToBinary(
function NB_MODULE (line 232) | NB_MODULE(doxapy, m) {
FILE: Bindings/Python/src/doxapy/__init__.py
function read_weights (line 4) | def read_weights(file):
FILE: Bindings/Python/test/test_doxa.py
function read_image (line 14) | def read_image(file, algorithm=doxapy.GrayscaleAlgorithms.QT):
function read_weights (line 28) | def read_weights(file):
class DoxaPyTests (line 33) | class DoxaPyTests(unittest.TestCase):
method test_binarization (line 34) | def test_binarization(self):
method test_performance (line 66) | def test_performance(self):
method test_pseudo_performance (line 97) | def test_pseudo_performance(self):
FILE: Bindings/Python/test/test_speed.py
function read_image (line 33) | def read_image(file, algorithm=doxapy.GrayscaleAlgorithms.MEAN):
function measure_time (line 48) | def measure_time(func, warmup=WARMUP_ITERATIONS, iterations=TIMING_ITERA...
function format_results (line 70) | def format_results(name, results, image_shape):
class DoxaPySpeedTests (line 86) | class DoxaPySpeedTests(unittest.TestCase):
method setUpClass (line 89) | def setUpClass(cls):
method test_sauvola_speed (line 115) | def test_sauvola_speed(self):
method test_wan_speed (line 130) | def test_wan_speed(self):
method test_otsu_speed (line 145) | def test_otsu_speed(self):
method test_drdm_speed (line 160) | def test_drdm_speed(self):
method test_classified_performance_speed (line 173) | def test_classified_performance_speed(self):
method test_full_performance_speed (line 185) | def test_full_performance_speed(self):
method tearDownClass (line 195) | def tearDownClass(cls):
FILE: Bindings/WebAssembly/DoxaWasm.cpp
type Performance (line 19) | struct Performance
type PseudoPerformance (line 34) | struct PseudoPerformance
function Performance (line 49) | Performance CalculatePerformance(intptr_t groundTruthData, intptr_t bina...
function PseudoPerformance (line 72) | PseudoPerformance CalculatePseudoPerformance(
function ToGrayscale (line 109) | void ToGrayscale(intptr_t outputData, intptr_t inputData,
class Binarization (line 124) | class Binarization
method Binarization (line 128) | Binarization(const Algorithms algorithm)
method Initialize (line 139) | void Initialize(intptr_t data, const int width, const int height)
method ToBinary (line 149) | void ToBinary(intptr_t data, const std::string& params)
method Algorithms (line 155) | Algorithms CurrentAlgorithm() { return algorithm; }
function EMSCRIPTEN_BINDINGS (line 184) | EMSCRIPTEN_BINDINGS(doxa_wasm) {
FILE: Bindings/WebAssembly/dist/doxa.js
method constructor (line 155) | constructor (width, height, data) {
method size (line 166) | get size() {
method draw (line 175) | draw(canvas) {
method data (line 187) | data() {
method free (line 194) | free() {
method initialize (line 198) | initialize(width, height) {
method toImageData (line 213) | toImageData(imageData) {
FILE: Bindings/WebAssembly/dist/doxaWasm.js
function locateFile (line 1) | function locateFile(path){if(Module["locateFile"]){return Module["locate...
function updateMemoryViews (line 1) | function updateMemoryViews(){var b=wasmMemory.buffer;HEAP8=new Int8Array...
function preRun (line 1) | function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="func...
function initRuntime (line 1) | function initRuntime(){runtimeInitialized=true;wasmExports["A"]()}
function postRun (line 1) | function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="f...
function abort (line 1) | function abort(what){Module["onAbort"]?.(what);what="Aborted("+what+")";...
function findWasmBinary (line 1) | function findWasmBinary(){return locateFile("doxaWasm.wasm")}
function getBinarySync (line 1) | function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return...
function getWasmBinary (line 1) | async function getWasmBinary(binaryFile){if(!wasmBinary){try{var respons...
function instantiateArrayBuffer (line 1) | async function instantiateArrayBuffer(binaryFile,imports){try{var binary...
function instantiateAsync (line 1) | async function instantiateAsync(binary,binaryFile,imports){if(!binary&&!...
function getWasmImports (line 1) | function getWasmImports(){var imports={a:wasmImports};return imports}
function createWasm (line 1) | async function createWasm(){function receiveInstance(instance,module){wa...
class ExitStatus (line 1) | class ExitStatus{name="ExitStatus";constructor(status){this.message=`Pro...
method constructor (line 1) | constructor(status){this.message=`Program terminated with exit(${statu...
class ExceptionInfo (line 1) | class ExceptionInfo{constructor(excPtr){this.excPtr=excPtr;this.ptr=excP...
method constructor (line 1) | constructor(excPtr){this.excPtr=excPtr;this.ptr=excPtr-24}
method set_type (line 1) | set_type(type){HEAPU32[this.ptr+4>>2]=type}
method get_type (line 1) | get_type(){return HEAPU32[this.ptr+4>>2]}
method set_destructor (line 1) | set_destructor(destructor){HEAPU32[this.ptr+8>>2]=destructor}
method get_destructor (line 1) | get_destructor(){return HEAPU32[this.ptr+8>>2]}
method set_caught (line 1) | set_caught(caught){caught=caught?1:0;HEAP8[this.ptr+12]=caught}
method get_caught (line 1) | get_caught(){return HEAP8[this.ptr+12]!=0}
method set_rethrown (line 1) | set_rethrown(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+13]=rethrown}
method get_rethrown (line 1) | get_rethrown(){return HEAP8[this.ptr+13]!=0}
method init (line 1) | init(type,destructor){this.set_adjusted_ptr(0);this.set_type(type);thi...
method set_adjusted_ptr (line 1) | set_adjusted_ptr(adjustedPtr){HEAPU32[this.ptr+16>>2]=adjustedPtr}
method get_adjusted_ptr (line 1) | get_adjusted_ptr(){return HEAPU32[this.ptr+16>>2]}
function readPointer (line 1) | function readPointer(pointer){return this.fromWireType(HEAPU32[pointer>>...
method constructor (line 1) | constructor(message){super(message);this.name="InternalError"}
function onComplete (line 1) | function onComplete(typeConverters){var myTypeConverters=getTypeConverte...
method constructor (line 1) | constructor(message){super(message);this.name="BindingError"}
function sharedRegisterType (line 1) | function sharedRegisterType(rawType,registeredInstance,options={}){var n...
function registerType (line 1) | function registerType(rawType,registeredInstance,options={}){return shar...
function getInstanceTypeName (line 1) | function getInstanceTypeName(handle){return handle.$$.ptrType.registered...
method isAliasOf (line 1) | isAliasOf(other){if(!(this instanceof ClassHandle)){return false}if(!(ot...
method clone (line 1) | clone(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.pr...
method delete (line 1) | delete(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.d...
method isDeleted (line 1) | isDeleted(){return!this.$$.ptr}
method deleteLater (line 1) | deleteLater(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this...
function ClassHandle (line 1) | function ClassHandle(){}
function RegisteredClass (line 1) | function RegisteredClass(name,constructor,instancePrototype,rawDestructo...
function constNoSmartPtrRawPointerToWireType (line 1) | function constNoSmartPtrRawPointerToWireType(destructors,handle){if(hand...
function genericPointerToWireType (line 1) | function genericPointerToWireType(destructors,handle){var ptr;if(handle=...
function nonConstNoSmartPtrRawPointerToWireType (line 1) | function nonConstNoSmartPtrRawPointerToWireType(destructors,handle){if(h...
function RegisteredPointer_fromWireType (line 1) | function RegisteredPointer_fromWireType(ptr){var rawPointer=this.getPoin...
method getPointee (line 1) | getPointee(ptr){if(this.rawGetPointee){ptr=this.rawGetPointee(ptr)}retur...
method destructor (line 1) | destructor(ptr){this.rawDestructor?.(ptr)}
function RegisteredPointer (line 1) | function RegisteredPointer(name,registeredClass,isReference,isConst,isSm...
function makeDynCaller (line 1) | function makeDynCaller(){var rtn=getWasmTableEntry(rawFunction);return rtn}
class UnboundTypeError (line 1) | class UnboundTypeError extends Error{}
function visit (line 1) | function visit(type){if(seen[type]){return}if(registeredTypes[type]){ret...
function usesDestructorStack (line 1) | function usesDestructorStack(argTypes){for(var i=1;i<argTypes.length;++i...
function createJsInvoker (line 1) | function createJsInvoker(argTypes,isClassMethodFunc,returns,isAsync){var...
function craftInvokerFunction (line 1) | function craftInvokerFunction(humanName,argTypes,classType,cppInvokerFun...
function unboundTypesHandler (line 1) | function unboundTypesHandler(){throwUnboundTypeError(`Cannot call ${huma...
function getEnumValueType (line 1) | function getEnumValueType(rawValueType){return rawValueType===0?"object"...
function ctor (line 1) | function ctor(){}
function decodeMemoryView (line 1) | function decodeMemoryView(handle){var size=HEAPU32[handle>>2];var data=H...
method fromWireType (line 1) | fromWireType(value){var length=HEAPU32[value>>2];var payload=value+4;var...
method toWireType (line 1) | toWireType(destructors,value){if(value instanceof ArrayBuffer){value=new...
method destructorFunction (line 1) | destructorFunction(ptr){_free(ptr)}
method destructorFunction (line 1) | destructorFunction(ptr){_free(ptr)}
function assignWasmExports (line 1) | function assignWasmExports(wasmExports){___getTypeName=wasmExports["B"];...
function run (line 1) | function run(){if(runDependencies>0){dependenciesFulfilled=run;return}pr...
FILE: Bindings/WebAssembly/doxa.js
method constructor (line 155) | constructor (width, height, data) {
method size (line 166) | get size() {
method draw (line 175) | draw(canvas) {
method data (line 187) | data() {
method free (line 194) | free() {
method initialize (line 198) | initialize(width, height) {
method toImageData (line 213) | toImageData(imageData) {
FILE: Bindings/WebAssembly/spec/binarization.spec.js
function readImage (line 15) | async function readImage(file) {
FILE: Bindings/WebAssembly/spec/speed.spec.js
constant WARMUP_ITERATIONS (line 17) | const WARMUP_ITERATIONS = 3;
constant TIMING_ITERATIONS (line 18) | const TIMING_ITERATIONS = 10;
function readImage (line 25) | async function readImage(file) {
function measureTime (line 40) | function measureTime(func, warmup = WARMUP_ITERATIONS, iterations = TIMI...
function formatResults (line 76) | function formatResults(name, results, width, height) {
FILE: Demo/Cpp/demo.cpp
function DisplayPerformance (line 14) | void DisplayPerformance(const Doxa::Image& groundTruthImage, const Doxa:...
function main (line 41) | int main()
FILE: Demo/Cpp/demoOpenCV.cpp
function ToDoxaImageReference (line 10) | Doxa::Image ToDoxaImageReference(const cv::Mat& gsImage)
function FromDoxaImage (line 16) | cv::Mat FromDoxaImage(const Doxa::Image& binaryImage)
function DisplayPerformance (line 21) | void DisplayPerformance(const Doxa::Image& groundTruthImage, const Doxa:...
function main (line 48) | int main()
FILE: Demo/Cpp/demoQt.cpp
function ToDoxaImageReference (line 13) | Doxa::Image ToDoxaImageReference(const QImage& gsImage)
function QImage (line 19) | QImage FromDoxaImage(const Doxa::Image& binaryImage)
function DisplayPerformance (line 25) | void DisplayPerformance(const Doxa::Image& groundTruthImage, const Doxa:...
function main (line 52) | int main()
FILE: Demo/NodeJS/index.js
function readImage (line 16) | async function readImage(doxa, file, algorithm) {
function writeImage (line 31) | async function writeImage(image, file) {
function demo (line 41) | async function demo() {
FILE: Demo/Python/demo.py
function read_image (line 12) | def read_image(file, algorithm=doxapy.GrayscaleAlgorithms.MEAN):
FILE: Doxa.Bench/BenchmarkHarness.hpp
type Doxa::Benchmarks (line 8) | namespace Doxa::Benchmarks
class GlobalThresholdBenchHarness (line 11) | class GlobalThresholdBenchHarness : public GlobalThreshold<GlobalThres...
method Pixel8 (line 13) | Pixel8 Threshold(const Image& grayScaleImage, const Parameters& para...
class DRDMBenchHarness (line 20) | class DRDMBenchHarness : public Doxa::DRDM
class NiblackBase (line 34) | class NiblackBase : public Algorithm<NiblackBase<Calculator>>, public ...
method ToBinary (line 37) | void ToBinary(Image& binaryImageOut, const Parameters& parameters = ...
function ResourcesDir (line 53) | inline std::string ResourcesDir()
FILE: Doxa.Bench/BinarizationBenchmarks.cpp
type Doxa::Benchmarks (line 5) | namespace Doxa::Benchmarks
function Image (line 7) | static Image ReadTestImage()
function BM_Otsu (line 14) | static void BM_Otsu(benchmark::State& state)
function BM_Niblack (line 29) | static void BM_Niblack(benchmark::State& state)
function BM_Sauvola (line 43) | static void BM_Sauvola(benchmark::State& state)
function BM_Wolf (line 57) | static void BM_Wolf(benchmark::State& state)
function BM_Nick (line 71) | static void BM_Nick(benchmark::State& state)
function BM_Bernsen (line 85) | static void BM_Bernsen(benchmark::State& state)
function BM_TRSingh (line 99) | static void BM_TRSingh(benchmark::State& state)
function BM_Wan (line 113) | static void BM_Wan(benchmark::State& state)
function BM_Gatos (line 127) | static void BM_Gatos(benchmark::State& state)
function BM_ISauvola (line 141) | static void BM_ISauvola(benchmark::State& state)
function BM_Su (line 155) | static void BM_Su(benchmark::State& state)
function BM_Bataineh (line 168) | static void BM_Bataineh(benchmark::State& state)
function BM_Phansalkar (line 181) | static void BM_Phansalkar(benchmark::State& state)
function BM_AdOtsu (line 195) | static void BM_AdOtsu(benchmark::State& state)
function BM_AdOtsuMS (line 208) | static void BM_AdOtsuMS(benchmark::State& state)
FILE: Doxa.Bench/CalculatorBenchmarks.cpp
type Doxa::Benchmarks (line 5) | namespace Doxa::Benchmarks
function BM_Niblack_Chan (line 7) | static void BM_Niblack_Chan(benchmark::State& state)
function BM_Niblack_IntegralImage (line 22) | static void BM_Niblack_IntegralImage(benchmark::State& state)
FILE: Doxa.Bench/ClassifiedPerformanceBenchmarks.cpp
type Doxa::Benchmarks (line 5) | namespace Doxa::Benchmarks
function BM_CompareImages_Scalar (line 7) | static void BM_CompareImages_Scalar(benchmark::State& state)
function BM_CompareImages_SIMD (line 26) | static void BM_CompareImages_SIMD(benchmark::State& state)
FILE: Doxa.Bench/DRDMBenchmarks.cpp
type Doxa::Benchmarks (line 5) | namespace Doxa::Benchmarks
function BM_NUBN_STD (line 7) | static void BM_NUBN_STD(benchmark::State& state)
function BM_NUBN_STD_8x8 (line 19) | static void BM_NUBN_STD_8x8(benchmark::State& state)
function BM_SumDRDk (line 31) | static void BM_SumDRDk(benchmark::State& state)
function BM_NUBN_SIMD_8x8 (line 47) | static void BM_NUBN_SIMD_8x8(benchmark::State& state)
FILE: Doxa.Bench/GlobalThresholdBenchmarks.cpp
type Doxa::Benchmarks (line 5) | namespace Doxa::Benchmarks
function BM_GlobalThreshold_Scalar (line 7) | static void BM_GlobalThreshold_Scalar(benchmark::State& state)
function BM_GlobalThreshold_SIMD (line 26) | static void BM_GlobalThreshold_SIMD(benchmark::State& state)
FILE: Doxa.Test/AlgorithmTests.cpp
type Doxa::UnitTests (line 5) | namespace Doxa::UnitTests
class AlgorithmTests (line 7) | class AlgorithmTests : public ::testing::Test {
class BinarizationTestharness (line 21) | class BinarizationTestharness : public Algorithm<BinarizationTestharness>
method ToBinary (line 24) | void ToBinary(Image& binaryImageOut, const Parameters& parameters = ...
function TEST_F (line 31) | TEST_F(AlgorithmTests, AlgorithmToBinaryTest)
function TEST_F (line 48) | TEST_F(AlgorithmTests, AlgorithmToBinaryImageTest)
function TEST_F (line 60) | TEST_F(AlgorithmTests, AlgorithmUpdateToBinaryTest)
FILE: Doxa.Test/BatainehTests.cpp
type Doxa::UnitTests (line 5) | namespace Doxa::UnitTests
class BatainehTestharness (line 8) | class BatainehTestharness : public Bataineh
method BatainehTestharness (line 11) | BatainehTestharness() : Bataineh() {}
method Image (line 28) | Image GenerateWindowImage(const Image& image, const std::vector<Bata...
function TEST (line 51) | TEST(BatainehTests, BatainehToBinaryTest)
FILE: Doxa.Test/BinarizationTests.cpp
type Doxa::UnitTests (line 6) | namespace Doxa::UnitTests
class BinarizationTests (line 8) | class BinarizationTests : public ImageFixture {}
function TEST_F (line 10) | TEST_F(BinarizationTests, BinarizationSauvolaTest)
function TEST_F (line 23) | TEST_F(BinarizationTests, BinarizationNiblackTest)
function TEST_F (line 36) | TEST_F(BinarizationTests, BinarizationWolfTest)
function TEST_F (line 49) | TEST_F(BinarizationTests, BinarizationNICKTest)
function TEST_F (line 62) | TEST_F(BinarizationTests, BinarizationBernsenTest)
function TEST_F (line 75) | TEST_F(BinarizationTests, BinarizationTRSinghTest)
function TEST_F (line 88) | TEST_F(BinarizationTests, BinarizationWANTest)
function TEST_F (line 101) | TEST_F(BinarizationTests, BinarizationGatosTest)
function TEST_F (line 114) | TEST_F(BinarizationTests, BinarizationSuTest)
function TEST_F (line 130) | TEST_F(BinarizationTests, BinarizationISauvola)
function TEST_F (line 143) | TEST_F(BinarizationTests, BinarizationOtsuTest)
function TEST_F (line 154) | TEST_F(BinarizationTests, BinarizationAdOtsuTest)
function TEST_F (line 162) | TEST_F(BinarizationTests, BinarizationAdOtsuGTest)
function TEST_F (line 174) | TEST_F(BinarizationTests, BinarizationAdOtsuMSTest)
function TEST_F (line 182) | TEST_F(BinarizationTests, BinarizationAdOtsuMSGTest)
function TEST_F (line 189) | TEST_F(BinarizationTests, BinarizationPhansalkarTest)
function TEST_F (line 203) | TEST_F(BinarizationTests, BinarizationBatainehTest)
FILE: Doxa.Test/CalculatorTests.cpp
type Doxa::UnitTests (line 6) | namespace Doxa::UnitTests
class CalculatorTests (line 8) | class CalculatorTests : public ImageFixture {}
class NiblackBase (line 13) | class NiblackBase : public Algorithm<NiblackBase<Calculator>>, public ...
method ToBinary (line 17) | void ToBinary(Image& binaryImageOut, const Parameters& parameters = ...
function TEST_F (line 34) | TEST_F(CalculatorTests, CalculateIntegralImagesTest)
function TEST_F (line 67) | TEST_F(CalculatorTests, CalculateProcessMeanVarianceTest)
function TEST_F (line 119) | TEST_F(CalculatorTests, CalculatorAlgorithmTest)
FILE: Doxa.Test/ClassifiedPerformanceTests.cpp
type Doxa::UnitTests (line 5) | namespace Doxa::UnitTests
function TEST (line 7) | TEST(ClassifiedPerformanceTests, PerformanceClassificationsTest)
function TEST (line 22) | TEST(ClassifiedPerformanceTests, PerformanceTest)
function TEST (line 68) | TEST(PerformanceTests, PseudoMetrics)
function TEST (line 97) | TEST(PerformanceTests, PSNRBoundsTest)
function TEST (line 108) | TEST(ClassifiedPerformanceTests, FMeasureBoundsTest)
function TEST (line 119) | TEST(ClassifiedPerformanceTests, NRMBoundsTest)
function TEST (line 130) | TEST(ClassifiedPerformanceTests, MCCTest)
function TEST (line 142) | TEST(ClassifiedPerformanceTests, ClassifiedPerformanceSauvola)
FILE: Doxa.Test/ContrastImageTests.cpp
type Doxa::UnitTests (line 5) | namespace Doxa::UnitTests
class ContrastImageTests (line 7) | class ContrastImageTests : public ImageFixture {}
function TEST_F (line 8) | TEST_F(ContrastImageTests, GenerateContrastImageTest)
function TEST_F (line 16) | TEST_F(ContrastImageTests, EstimateStrokeWidthTest)
function TEST_F (line 39) | TEST_F(ContrastImageTests, GenerateHighContrastImageTest)
FILE: Doxa.Test/DIBCOUtilsTests.cpp
type Doxa::UnitTests (line 7) | namespace Doxa::UnitTests
function TEST (line 9) | TEST(DIBCOUtilsTests, ReadWeightsTest)
FILE: Doxa.Test/DRDMTests.cpp
type Doxa::UnitTests (line 5) | namespace Doxa::UnitTests
class DRDMTestHarness (line 8) | class DRDMTestHarness : public Doxa::DRDM
function TEST (line 23) | TEST(DRDMTests, DRDMSauvolaTest)
function TEST (line 45) | TEST(DRDMTests, DRDMTest)
function TEST (line 78) | TEST(DRDMTests, NUBNUniformityCountTest)
function TEST (line 229) | TEST(DRDMTests, NUBNVariousWindowSizesTest)
function TEST (line 253) | TEST(DRDMTests, NUBNImplementationConsistencyTest)
FILE: Doxa.Test/GrayscaleTests.cpp
type Doxa::UnitTests (line 4) | namespace Doxa::UnitTests
function TEST (line 6) | TEST(GrayscaleTests, GrayscaleAlgorithmsTest)
function TEST (line 25) | TEST(GrayscaleTests, GrayscaleColorSpaceTest)
function TEST (line 56) | TEST(GrayscaleTests, GrayscaleLinearLightnessTest)
function TEST (line 71) | TEST(GrayscaleTests, GrayscaleLinearLABDistTest)
FILE: Doxa.Test/GridCalcTests.cpp
type Doxa::UnitTests (line 5) | namespace Doxa::UnitTests
function TEST (line 7) | TEST(GridCalcTests, GridCalcIterateInterwovenTest)
function TEST (line 45) | TEST(GridCalcTests, GridCalcIterateUnevenTest)
function TEST (line 85) | TEST(GridCalcTests, GridCalcIterateEvenTest)
function TEST (line 119) | TEST(GridCalcTests, GridCalcInterpolateTest)
function TEST (line 166) | TEST(GridCalcTests, GridCalcInterpolateWideTest)
function TEST (line 202) | TEST(GridCalcTests, GridCalcInterpolatePlusOneTest)
function TEST (line 238) | TEST(GridCalcTests, GridCalcInterpolatePlusTwoTest)
FILE: Doxa.Test/ISauvolaTests.cpp
type Doxa::UnitTests (line 5) | namespace Doxa::UnitTests
class ISauvolaTestharness (line 8) | class ISauvolaTestharness : public ISauvola
method ISauvolaTestharness (line 11) | ISauvolaTestharness() : ISauvola() {}
function TEST (line 25) | TEST(ISauvolaTests, ISauvolaSpiderTest)
function TEST (line 49) | TEST(ISauvolaTests, ISauvolaSpiderSinglePixelTest)
function TEST (line 73) | TEST(ISauvolaTests, ISauvolaCombineTest)
FILE: Doxa.Test/ImageFixture.hpp
type Doxa::UnitTests (line 8) | namespace Doxa::UnitTests
class ImageFixture (line 10) | class ImageFixture : public ::testing::Test
method SetUp (line 13) | void SetUp() override {
FILE: Doxa.Test/ImageTests.cpp
type Doxa::UnitTests (line 4) | namespace Doxa::UnitTests
class ImageTest (line 6) | class ImageTest : public ::testing::Test
method SetUp (line 11) | void SetUp() override
function TEST_F (line 25) | TEST_F(ImageTest, ImagePixelTest)
function TEST_F (line 39) | TEST_F(ImageTest, ImageCopyCTORTest)
function TEST_F (line 55) | TEST_F(ImageTest, ImageMoveCTORTest)
function TEST_F (line 65) | TEST_F(ImageTest, ImageExternalReferenceTest)
function TEST_F (line 99) | TEST_F(ImageTest, ImageReferenceTest)
function TEST_F (line 107) | TEST_F(ImageTest, ImageCopyAssignmentOperatorTest)
FILE: Doxa.Test/LocalWindowTests.cpp
type Doxa::UnitTests (line 4) | namespace Doxa::UnitTests
function TEST (line 6) | TEST(LocalWindowTests, LocalWindowIterateTest)
FILE: Doxa.Test/MorphologyTests.cpp
type Doxa::UnitTests (line 6) | namespace Doxa::UnitTests
class MorphologyTests (line 8) | class MorphologyTests : public ImageFixture {}
class MorphologyTestHarness (line 10) | class MorphologyTestHarness : public Morphology
method MorphologyTestHarness (line 13) | MorphologyTestHarness() : Morphology() {}
function TEST_F (line 20) | TEST_F(MorphologyTests, MorphologyErodeTest)
function TEST_F (line 59) | TEST_F(MorphologyTests, MorphologyDilateTest)
function TEST_F (line 99) | TEST_F(MorphologyTests, MorphologySpeedTest)
function TEST_F (line 132) | TEST_F(MorphologyTests, MorphologyErodeComparisonTest)
function TEST_F (line 149) | TEST_F(MorphologyTests, MorphologyDilateComparisonTest)
FILE: Doxa.Test/PNMTests.cpp
type Doxa::UnitTests (line 5) | namespace Doxa::UnitTests
class PNMTestharness (line 8) | class PNMTestharness : public PNM
method PNMTestharness (line 11) | PNMTestharness() :PNM() {}
class PNMTests (line 26) | class PNMTests : public ::testing::Test
method TestWriteAndReadGrayScale (line 29) | void TestWriteAndReadGrayScale(std::function<void(std::ostream& outp...
method TestWriteAndReadBinary (line 45) | void TestWriteAndReadBinary(std::function<void(std::ostream& outputS...
method TestWriteAndRead (line 90) | void TestWriteAndRead(const Image& inputImage, std::function<void(st...
function TEST_F (line 109) | TEST_F(PNMTests, PNMRead1BitBinaryTest)
function TEST_F (line 130) | TEST_F(PNMTests, PNMRead8BitBinaryTest)
function TEST_F (line 149) | TEST_F(PNMTests, PNMRead24BitBinaryTest)
function TEST_F (line 172) | TEST_F(PNMTests, PNMRead32BitBinaryTest)
function TEST_F (line 195) | TEST_F(PNMTests, PNMReadPNMBadFormatTest)
function TEST_F (line 205) | TEST_F(PNMTests, PNMWriteAndReadP4Test)
function TEST_F (line 213) | TEST_F(PNMTests, PNMWriteAndReadP5Test)
function TEST_F (line 221) | TEST_F(PNMTests, PNMWriteAndReadP6Test)
function TEST_F (line 229) | TEST_F(PNMTests, PNMWriteAndReadP7Test)
FILE: Doxa.Test/PaletteTests.cpp
type Doxa::UnitTests (line 4) | namespace Doxa::UnitTests
function TEST (line 6) | TEST(PaletteTests, PaletteTest)
function TEST (line 36) | TEST(PaletteTests, PaletteColorDistanceTest)
FILE: Doxa.Test/ParametersTests.cpp
type Doxa::UnitTests (line 4) | namespace Doxa::UnitTests
function TEST (line 6) | TEST(ParametersTests, ParametersGetTest)
function TEST (line 23) | TEST(ParametersTests, ParametersGetCastTest)
function TEST (line 36) | TEST(ParametersTests, ParametersSetTest)
function TEST (line 56) | TEST(ParametersTests, ParametersParser)
FILE: Doxa.Test/RegionTests.cpp
type Doxa::UnitTests (line 4) | namespace Doxa::UnitTests
function TEST (line 6) | TEST(RegionTests, RegionConstructorTest)
function TEST (line 23) | TEST(RegionTests, RegionTest)
function TEST (line 32) | TEST(RegionTests, RegionInRegionTest)
function TEST (line 45) | TEST(RegionTests, RegionPointTest)
FILE: Doxa.Test/SIMDTests.cpp
type Doxa::UnitTests (line 7) | namespace Doxa::UnitTests
class GlobalThresholdTestHarness (line 11) | class GlobalThresholdTestHarness: public GlobalThreshold<GlobalThresho...
method Pixel8 (line 13) | Pixel8 Threshold(const Image& grayScaleImage, const Parameters& para...
class DRDMTestHarness (line 20) | class DRDMTestHarness : public Doxa::DRDM
class SIMDTests (line 31) | class SIMDTests : public ImageFixture {}
function TEST_F (line 33) | TEST_F(SIMDTests, SIMDDetection)
function TEST_F (line 59) | TEST_F(SIMDTests, SIMDGlobalThresholdToBinaryTest)
function TEST_F (line 71) | TEST_F(SIMDTests, SIMDClassifiedPerformanceCompareImagesTest)
function TEST_F (line 93) | TEST_F(SIMDTests, SIMDDrdmNubnTest)
FILE: Doxa.Test/SuTests.cpp
type Doxa::UnitTests (line 5) | namespace Doxa::UnitTests
class SuTestHarness (line 8) | class SuTestHarness : public Su
method SuTestHarness (line 11) | SuTestHarness() : Su() {}
function TEST (line 16) | TEST(SuTests, SuAutoDetectParametersTest)
function TEST (line 49) | TEST(SuTests, SuCalculationsTest)
FILE: Doxa.Test/TestUtilities.hpp
type Doxa::UnitTests (line 7) | namespace Doxa::UnitTests
class TestUtilities (line 9) | class TestUtilities
method ProjectFolder (line 12) | static std::string ProjectFolder()
method Time (line 26) | static double Time(std::function<void()> predicate)
method AssertImageData (line 38) | static void AssertImageData(const Image& image, const Pixel8* data)
method AssertImages (line 43) | static void AssertImages(const Image& imageA, const Image& imageB)
method AssertImageFile (line 50) | static void AssertImageFile(const Image& image, std::string filePath)
method AssertImagesWithDetails (line 57) | static bool AssertImagesWithDetails(const Image& imageA, const Image...
FILE: Doxa.Test/WienerFilterTests.cpp
type Doxa::UnitTests (line 6) | namespace Doxa::UnitTests
function TEST (line 16) | TEST(WienerFilterTests, WienerFilterCenterPixelTest)
FILE: Doxa/AdOtsu.hpp
type Doxa (line 14) | namespace Doxa
class AdOtsu (line 30) | class AdOtsu : public Algorithm<AdOtsu>
method ToBinary (line 35) | void ToBinary(Image& binaryImageOut, const Parameters& parameters = ...
method Pixel8 (line 89) | Pixel8 LocalThreshold(const Otsu& otsu, const Image& grayScaleImage,...
FILE: Doxa/Algorithm.hpp
type Doxa (line 12) | namespace Doxa
class IAlgorithm (line 17) | class IAlgorithm
class Algorithm (line 45) | class Algorithm : public IAlgorithm
method Initialize (line 53) | virtual void Initialize(const Image& grayScaleImageIn)
method Image (line 61) | static Image ToBinaryImage(const Image& grayScaleImageIn, const Para...
method UpdateToBinary (line 79) | static void UpdateToBinary(Image& image, const Parameters& parameter...
class GlobalThreshold (line 95) | class GlobalThreshold : public Algorithm<BinaryAlgorithm>
method ToBinary (line 107) | void ToBinary(Image& binaryImageOut, const Parameters& parameters = ...
method ToBinary_STD (line 124) | static void ToBinary_STD(const Pixel8* input, Pixel8* output, int si...
method ToBinary_SIMD (line 135) | static void ToBinary_SIMD(const Pixel8* input, Pixel8* output, int s...
FILE: Doxa/Bataineh.hpp
type Doxa (line 15) | namespace Doxa
class Bataineh (line 34) | class Bataineh : public Algorithm<Bataineh>, public IntegralImageMeanV...
method Initialize (line 38) | void Initialize(const Image& grayScaleImageIn)
method ToBinary (line 49) | void ToBinary(Image& binaryImageOut, const Parameters& parameters = ...
type PixelColor (line 92) | enum PixelColor
type DetailedWindow (line 99) | struct DetailedWindow
method CalculateGlobals (line 106) | void CalculateGlobals(double& mean, double& stddev) const
method CalculateMeanStdDev (line 113) | inline void CalculateMeanStdDev(double& mean, double& stddev, const ...
method PrimaryWindow (line 126) | void inline constexpr PrimaryWindow(int& primaryWidth, int& primaryH...
method SigmaAdaptive (line 146) | double inline constexpr SigmaAdaptive(const double sigmaWindow, cons...
method WindowThreshold (line 152) | double inline constexpr WindowThreshold(const double meanWindow, con...
method ConfusionThreshold (line 160) | double inline constexpr ConfusionThreshold(const double meanGlobal, ...
method Pixel8 (line 169) | Pixel8 GetMaxGrayValue() const
method SigmaMinMaxAndMean (line 184) | void SigmaMinMaxAndMean(double& sigmaMin, double& sigmaMax, std::vec...
method RedBlack (line 201) | void RedBlack(int& redCountImage, int& blackCountImage, Image& redBl...
method GetWindows (line 234) | std::vector<DetailedWindow> GetWindows(
method GetPrimaryWindows (line 265) | std::vector<DetailedWindow> GetPrimaryWindows(const Image& image, co...
method UpdateWindowsWithSecondarySize (line 299) | void UpdateWindowsWithSecondarySize(std::vector<DetailedWindow>& win...
FILE: Doxa/Bernsen.hpp
type Doxa (line 11) | namespace Doxa
class Bernsen (line 17) | class Bernsen : public Algorithm<Bernsen>
method ToBinary (line 21) | void ToBinary(Image& binaryImageOut, const Parameters& parameters = ...
FILE: Doxa/BinarizationFactory.hpp
type Doxa (line 23) | namespace Doxa
type Algorithms (line 25) | enum Algorithms
class BinarizationFactory (line 47) | class BinarizationFactory
method IAlgorithm (line 50) | static IAlgorithm* Algorithm(const Algorithms algorithm)
FILE: Doxa/ChanMeanCalc.hpp
type Doxa (line 9) | namespace Doxa
class ChanMeanCalc (line 15) | class ChanMeanCalc
method Process (line 20) | void Process(Image& binaryImageOut, const Image& grayScaleImageIn, c...
method Iterate (line 30) | void Iterate(const Image& grayScaleImageIn, const int windowSize, Pr...
FILE: Doxa/ChanMeanVarianceCalc.hpp
type Doxa (line 9) | namespace Doxa
class ChanMeanVarianceCalc (line 17) | class ChanMeanVarianceCalc
method Process (line 22) | void Process(Image& binaryImageOut, const Image& grayScaleImageIn, c...
method Iterate (line 33) | void Iterate(const Image& grayScaleImageIn, const int windowSize, Pr...
FILE: Doxa/ClassifiedPerformance.hpp
type Doxa (line 12) | namespace Doxa
class ClassifiedPerformance (line 18) | class ClassifiedPerformance
type Classifications (line 21) | struct Classifications
method Total (line 33) | int Total() const noexcept
method Clear (line 38) | void Clear() noexcept
method CompareImages (line 45) | static bool CompareImages(
method CompareImages_STD (line 69) | static void CompareImages_STD(Classifications& classifications, cons...
method CompareImages_SIMD (line 90) | static void CompareImages_SIMD(Classifications& classifications, con...
method CompareImages (line 152) | static bool CompareImages(
method CalculateAccuracy (line 204) | static double CalculateAccuracy(const Classifications& classifications)
method CalculateRecall (line 209) | static double CalculateRecall(const Classifications& classifications)
method CalculatePrecision (line 219) | static double CalculatePrecision(const Classifications& classificati...
method CalculateFMeasure (line 229) | static double CalculateFMeasure(const Classifications& classifications)
method CalculatePseudoRecall (line 240) | static double CalculatePseudoRecall(const Classifications& classific...
method CalculatePseudoPrecision (line 250) | static double CalculatePseudoPrecision(const Classifications& classi...
method CalculatePseudoFMeasure (line 267) | static double CalculatePseudoFMeasure(const Classifications& classif...
method CalculatePSNR (line 285) | static double CalculatePSNR(const Classifications& classifications)
method CalculateMCC (line 303) | static double CalculateMCC(const Classifications& classifications)
method CalculateNRM (line 316) | static double CalculateNRM(const Classifications& classifications)
method Calculate (line 333) | static double Calculate(const Image& controlImage, const Image& expe...
FILE: Doxa/ContrastImage.hpp
type Doxa (line 14) | namespace Doxa
class ContrastImage (line 20) | class ContrastImage
method GenerateContrastImage (line 23) | static inline void GenerateContrastImage(Image& contrastImage, const...
method EstimateStrokeWidth (line 53) | static inline int EstimateStrokeWidth(const Image& contrastImage)
method GenerateHighContrastImage (line 94) | static inline void GenerateHighContrastImage(Image& highContrastImag...
FILE: Doxa/DIBCOUtils.hpp
type Doxa (line 11) | namespace Doxa
class DIBCOUtils (line 13) | class DIBCOUtils
method ReadWeightsFile (line 21) | static std::vector<double> ReadWeightsFile(const std::string& fileLo...
method ReadWeights (line 37) | static std::vector<double> ReadWeights(std::istream& stream, size_t ...
FILE: Doxa/DRDM.hpp
type Doxa (line 14) | namespace Doxa
class DRDM (line 20) | class DRDM
method CalculateDRDM (line 23) | static double CalculateDRDM(const Image& controlImage, const Image& ...
method SumDRDkForMismatchedPixels (line 52) | static uint64_t SumDRDkForMismatchedPixels(const Image& control, con...
method NUBN (line 134) | static unsigned int NUBN(const Image& controlImage, const int M = 8)
method NUBN_STD (line 161) | static int NUBN_STD(const Image& controlImage, int N)
method NUBN_STD_8x8 (line 256) | static int NUBN_STD_8x8(const Image& controlImage)
method NUBN_SIMD_8x8 (line 337) | static unsigned int NUBN_SIMD_8x8(const Image& controlImage)
method IsBlock8x8Uniform (line 369) | static inline bool IsBlock8x8Uniform(const uint8_t* ptr, int stride)
FILE: Doxa/Gatos.hpp
type Doxa (line 14) | namespace Doxa
class Gatos (line 27) | class Gatos : public Algorithm<Gatos>
method ToBinary (line 31) | void ToBinary(Image& binaryImageOut, const Parameters& parameters = ...
method GatosCalculations (line 73) | void GatosCalculations(double& averageFgBgDistance, double& averageB...
method Threshold (line 94) | double Threshold(const int backgroundValue, const double d, const do...
method ExtractBackground (line 101) | void ExtractBackground(Image& backgroundImage, const Image& filtered...
FILE: Doxa/Grayscale.hpp
type Doxa (line 13) | namespace Doxa
type GrayscaleAlgorithms (line 19) | enum GrayscaleAlgorithms
class Grayscale (line 63) | class Grayscale
method LinearTosRgb (line 69) | static inline void LinearTosRgb(double& y)
method LinearTo709 (line 74) | static inline void LinearTo709(double& y)
method LinearLUT (line 82) | static std::array<float, 256> LinearLUT()
method Gamma (line 98) | static inline void Gamma(double& r, double& g, double& b, const doub...
method Gamma (line 105) | static inline double Gamma(const double channel, const double gamma ...
method T (line 115) | static inline constexpr T Qt(T r, T g, T b)
method T (line 125) | static inline constexpr T Mean(T r, T g, T b)
method T (line 136) | static inline constexpr T BT601(T r, T g, T b)
method T (line 148) | static inline constexpr T BT709(T r, T g, T b)
method T (line 159) | static inline constexpr T BT2100(T r, T g, T b)
method T (line 169) | static inline constexpr T Value(T r, T g, T b)
method T (line 179) | static inline constexpr T Luster(T r, T g, T b)
method T (line 190) | static inline constexpr T MinAvg(T r, T g, T b)
method Lightness (line 202) | static inline constexpr float Lightness(float r, float g, float b)
method XYZ (line 215) | static constexpr inline XYZ RGBToXYZ(float r, float g, float b)
method Lab (line 227) | static inline Lab XYZToLab(float X, float Y, float Z)
method LABDist (line 251) | static inline float LABDist(float red, float green, float blue)
method ToGrayscale (line 262) | static void ToGrayscale(
method GrayscaleConverter (line 317) | static void GrayscaleConverter(
method GrayscaleConverterLightness (line 332) | static void GrayscaleConverterLightness(
method GrayscaleConverterLABDist (line 351) | static void GrayscaleConverterLABDist(
FILE: Doxa/GridCalc.hpp
type Doxa (line 9) | namespace Doxa
class GridCalc (line 25) | class GridCalc
method Process (line 30) | void Process(Image& binaryImageOut, const Image& grayScaleImageIn, c...
method Iterate (line 51) | static void Iterate(const Image& imageIn, const int windowSize, cons...
method Interpolate (line 90) | static void Interpolate(const Image& imageIn, const int dist, Proces...
FILE: Doxa/ISauvola.hpp
type Doxa (line 12) | namespace Doxa
class Improved (line 22) | class Improved : public Algorithm<Improved<BinarizationClass>>
method ToBinary (line 26) | void ToBinary(Image& binaryImageOut, const Parameters& parameters = ...
method Combine (line 51) | void Combine(Image& binaryImageOut, const Image& highContrastImageIn...
method Spider (line 82) | void Spider(Image& binaryImageOut, const Image& binaryImageIn, const...
FILE: Doxa/Image.hpp
type Doxa (line 10) | namespace Doxa
type Image (line 17) | struct Image
method Image (line 20) | Image() {}
method Image (line 23) | Image(int width, int height, const Pixel8* bits = nullptr)
method Image (line 43) | Image(const Image& image)
method Image (line 56) | Image(Image&& image)
method Image (line 70) | Image& operator=(const Image& that)
method Image (line 100) | Image Reference() const
method Image (line 111) | static Image Reference(int width, int height, Pixel8* data)
method Fill (line 123) | void Fill(const Pixel8 pixel)
method Pixel8 (line 142) | inline Pixel8& Pixel(int x, int y) { return data[(y * width) + x]; }
method Pixel8 (line 143) | inline Pixel8 Pixel(int x, int y) const { return data[(y * width) +...
method Pixel8 (line 144) | inline Pixel8 Pixel(int x, int y, Pixel8 defaultValue) const { retu...
FILE: Doxa/IntegralImageMeanVarianceCalc.hpp
type Doxa (line 12) | namespace Doxa
class IntegralImageMeanVarianceCalc (line 25) | class IntegralImageMeanVarianceCalc
method Process (line 30) | void Process(Image& binaryImageOut, const Image& grayScaleImageIn, c...
method Iterate (line 49) | void Iterate(const Image& grayScaleImageIn, const int windowSize, Pr...
method CalculateMeanVariance (line 67) | inline void CalculateMeanVariance(double& mean, double& variance, co...
method CalculateDiffs (line 84) | inline void CalculateDiffs(double& diff, double& sqdiff, const int i...
method BuildIntegralImages (line 125) | void BuildIntegralImages(IntegralImage& integralImage, IntegralImage...
method BuildIntegralImagesLowMem (line 171) | void BuildIntegralImagesLowMem(IntegralImage& integralImage, Integra...
FILE: Doxa/LocalWindow.hpp
type Doxa (line 12) | namespace Doxa
class LocalWindow (line 14) | class LocalWindow
method Process (line 24) | static void Process(Image& binaryImageOut, const Image& grayScaleIma...
method Iterate (line 38) | static void Iterate(const Image& imageIn, const int windowSize, Proc...
method Iterate (line 64) | static void Iterate(const int width, const Region& window, Processor...
FILE: Doxa/Morphology.hpp
type Doxa (line 11) | namespace Doxa
class Morphology (line 13) | class Morphology
method Open (line 20) | static void Open(Image& morphedImage, const Image& grayScaleImage, c...
method Close (line 31) | static void Close(Image& morphedImage, const Image& grayScaleImage, ...
method Erode (line 41) | static void Erode(Image& morphedImage, const Image& grayScaleImage, ...
method Dilate (line 59) | static void Dilate(Image& morphedImage, const Image& grayScaleImage,...
method Morph (line 114) | static inline void Morph(Image& morphedImage, const Image& grayScale...
method IterativelyErode (line 225) | static inline void IterativelyErode(Image& morphedImage, const Image...
method IterativelyDilate (line 245) | static inline void IterativelyDilate(Image& morphedImage, const Imag...
FILE: Doxa/MultiScale.hpp
type Doxa (line 11) | namespace Doxa
class MultiScale (line 29) | class MultiScale : public Algorithm<MultiScale<BinarizationClass>>
method ToBinary (line 33) | void ToBinary(Image& binaryImageOut, const Parameters& parameters = ...
FILE: Doxa/Niblack.hpp
type Doxa (line 11) | namespace Doxa
class Niblack (line 17) | class Niblack : public Algorithm<Niblack>, public ChanMeanVarianceCalc
method ToBinary (line 21) | void ToBinary(Image& binaryImageOut, const Parameters& parameters = ...
FILE: Doxa/Nick.hpp
type Doxa (line 11) | namespace Doxa
class Nick (line 17) | class Nick : public Algorithm<Nick>, public ChanMeanVarianceCalc
method ToBinary (line 21) | void ToBinary(Image& binaryImageOut, const Parameters& parameters = ...
FILE: Doxa/Otsu.hpp
type Doxa (line 12) | namespace Doxa
class Otsu (line 22) | class Otsu : public GlobalThreshold<Otsu>
method Pixel8 (line 27) | Pixel8 Algorithm(const unsigned int* histogram, const int N) const
method Pixel8 (line 81) | Pixel8 Threshold(const Image& grayScaleImage, const Parameters& para...
FILE: Doxa/PNM.hpp
type Doxa (line 16) | namespace Doxa
class PNM (line 37) | class PNM
method Image (line 49) | static Image Read(const std::string& fileLocation, const Parameters&...
method Write (line 67) | static void Write(const Image& image, const std::string& fileLocation)
method PNM (line 102) | PNM() {}
method Read1BitBinary (line 104) | void Read1BitBinary(std::istream& inputStream, Image& image)
method Read8BitBinary (line 125) | void Read8BitBinary(std::istream& inputStream, Image& image)
method ColorToGrayscale (line 131) | void ColorToGrayscale(std::istream& inputStream, Image& image, Grays...
method ReadPNM (line 187) | void ReadPNM(std::istream& inputStream, Image& image, const Paramete...
method WriteP4 (line 290) | void WriteP4(std::ostream &outputStream, const Image &image)
method WriteP5 (line 317) | void WriteP5(std::ostream &outputStream, const Image &image)
method WriteP6 (line 328) | void WriteP6(std::ostream& outputStream, const Image& image)
method WriteP7 (line 342) | void WriteP7(std::ostream &outputStream, const Image &image)
method GrayscaleFunc (line 359) | GrayscaleFunc GrayscaleAlgorithm(GrayscaleAlgorithms algorithm)
FILE: Doxa/Palette.hpp
type Doxa (line 10) | namespace Doxa
class Palette (line 17) | class Palette
method Red (line 21) | static inline constexpr int Red(Pixel32 rgba) { return (rgba & 0xff); }
method Green (line 22) | static inline constexpr int Green(Pixel32 rgba) { return ((rgba >> 8...
method Blue (line 23) | static inline constexpr int Blue(Pixel32 rgba) { return ((rgba >> 16...
method Alpha (line 24) | static inline constexpr int Alpha(Pixel32 rgba) { return rgba >> 24; }
method Pixel32 (line 27) | static inline constexpr Pixel32 RGB(int r, int g, int b)
method Pixel32 (line 32) | static inline constexpr Pixel32 RGBA(int r, int g, int b, int a)
method Pixel32 (line 37) | static inline constexpr Pixel32 UpdateAlpha(Pixel32 rgba, int a)
method ColorDistance (line 48) | static inline int ColorDistance(Pixel32 left, Pixel32 right)
method IsGray (line 58) | static inline constexpr bool IsGray(Pixel32 rgba)
FILE: Doxa/Parameters.hpp
type Doxa (line 12) | namespace Doxa
class Parameters (line 23) | class Parameters
method Type (line 27) | const Type Get(const std::string& name, const Type& defaultValue) const
method Set (line 48) | void Set(const std::string& name, const ParameterValue& value)
method Parameters (line 57) | Parameters(const ParameterMap& parameterMap)
method Parameters (line 67) | static Parameters FromJson(const std::string& json)
method Parameters (line 105) | Parameters() {}
FILE: Doxa/Phansalkar.hpp
type Doxa (line 11) | namespace Doxa
class Phansalkar (line 29) | class Phansalkar : public Algorithm<Phansalkar>, public ChanMeanVarian...
method ToBinary (line 33) | void ToBinary(Image& binaryImageOut, const Parameters& parameters = ...
FILE: Doxa/Region.hpp
type Doxa (line 7) | namespace Doxa
type Region (line 13) | struct Region
type Point (line 16) | struct Point
method Point (line 18) | Point() {}
method Point (line 19) | Point(int x, int y) : x(x), y(y) {}
method Region (line 30) | Region() {}
method Region (line 31) | Region(const Point& upperLeft, const Point& bottomRight) : upperLeft...
method Region (line 32) | Region(int x1, int y1, int x2, int y2) : upperLeft(x1, y1), bottomRi...
method Region (line 33) | Region(int x1, int y1, int size) : upperLeft(x1, y1), bottomRight(x1...
method Region (line 34) | Region(int width, int height) : upperLeft(0, 0), bottomRight(width -...
method InRegion (line 36) | inline bool InRegion(const Region& region) const
method Width (line 44) | inline int Width() const
method Height (line 49) | inline int Height() const
method Area (line 54) | inline int Area() const
FILE: Doxa/SIMD.h
function namespace (line 8) | namespace Doxa::SIMD
FILE: Doxa/SIMDOps.hpp
type Doxa::SIMD (line 22) | namespace Doxa::SIMD
function vec_hsum_u8 (line 96) | inline int vec_hsum_u8(vec128 v) {
FILE: Doxa/Sauvola.hpp
type Doxa (line 11) | namespace Doxa
class Sauvola (line 17) | class Sauvola : public Algorithm<Sauvola>, public ChanMeanVarianceCalc
method ToBinary (line 21) | void ToBinary(Image& binaryImageOut, const Parameters& parameters = ...
FILE: Doxa/Su.hpp
type Doxa (line 13) | namespace Doxa
class Su (line 25) | class Su : public Algorithm<Su>
method ToBinary (line 29) | void ToBinary(Image& binaryImageOut, const Parameters& parameters = ...
method AutoDetectParameters (line 57) | void AutoDetectParameters(int& windowSize, int& minN, const Image& c...
method SuCalculations (line 68) | void SuCalculations(int& Ne, double& meanE, double& stdE,
method Threshold (line 100) | void Threshold(Image& binaryImageOut, const Image& contrastImageIn, ...
FILE: Doxa/TRSingh.hpp
type Doxa (line 11) | namespace Doxa
class TRSingh (line 17) | class TRSingh : public Algorithm<TRSingh>, public ChanMeanCalc
method ToBinary (line 21) | void ToBinary(Image& binaryImageOut, const Parameters& parameters = ...
FILE: Doxa/Types.hpp
type Doxa (line 8) | namespace Doxa
type Image (line 11) | struct Image
type Region (line 12) | struct Region
type TupleTypes (line 18) | namespace TupleTypes
FILE: Doxa/Wan.hpp
type Doxa (line 12) | namespace Doxa
class Wan (line 18) | class Wan : public Algorithm<Wan>, public ChanMeanVarianceCalc
method ToBinary (line 22) | void ToBinary(Image& binaryImageOut, const Parameters& parameters = ...
FILE: Doxa/WienerFilter.hpp
type Doxa (line 12) | namespace Doxa
class WienerFilter (line 20) | class WienerFilter
method Filter (line 23) | static void Filter(Image& outputImage, const Image& inputImage, cons...
FILE: Doxa/Wolf.hpp
type Doxa (line 11) | namespace Doxa
class Wolf (line 17) | class Wolf : public Algorithm<Wolf>, public ChanMeanVarianceCalc
method ToBinary (line 21) | void ToBinary(Image& binaryImageOut, const Parameters& parameters = ...
Condensed preview — 163 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,140K chars).
[
{
"path": ".claude/settings.local.json",
"chars": 70,
"preview": "{\n \"permissions\": {\n \"allow\": [\n \"Bash(cmake:*)\"\n ]\n }\n}\n"
},
{
"path": ".gitattributes",
"chars": 98,
"preview": "## Prevent Git from affecting line endings\n\n\n## GRAPHICS\n*.psd binary\n*.ppm binary\n*.pbm binary"
},
{
"path": ".github/workflows/benchmarks.yml",
"chars": 2013,
"preview": "name: Performance Benchmarks\n\non:\n push:\n branches: [ \"master\" ]\n pull_request:\n branches: [ \"master\" ]\n\npermiss"
},
{
"path": ".github/workflows/npm-publish.yml",
"chars": 1044,
"preview": "name: Publish to npm\n\npermissions:\n contents: read\n\n# Disabled: NPM_TOKEN secret not yet configured.\n# Re-enable by unc"
},
{
"path": ".github/workflows/pythonpackage.yml",
"chars": 1843,
"preview": "name: Python Package\n\non:\n release:\n types:\n - published\n\njobs:\n build_sdist:\n name: Build SDist\n runs-o"
},
{
"path": ".github/workflows/test-and-coverage.yml",
"chars": 4736,
"preview": "# This starter workflow is for a CMake project running on multiple platforms. There is a different starter workflow if y"
},
{
"path": ".gitignore",
"chars": 4272,
"preview": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n\n# User"
},
{
"path": "Bindings/Matlab/+Doxa/Algorithms.m",
"chars": 621,
"preview": "classdef Algorithms\n %ALGORITHMS Binarization algorithms available in the Doxa framework.\n %\n % Global Thresh"
},
{
"path": "Bindings/Matlab/+Doxa/Grayscale.m",
"chars": 986,
"preview": "classdef Grayscale\n %GRAYSCALE Grayscale conversion algorithms available in the Doxa framework.\n %\n % Algorit"
},
{
"path": "Bindings/Matlab/+Doxa/Image.m",
"chars": 4049,
"preview": "classdef Image < handle\n %IMAGE Doxa image container wrapping a C++ Doxa::Image.\n % Handles the column-major (Ma"
},
{
"path": "Bindings/Matlab/+Doxa/binarize.m",
"chars": 1401,
"preview": "function outputImage = binarize(algorithm, inputImage, options)\n%BINARIZE Convert a grayscale Doxa.Image to binary.\n% "
},
{
"path": "Bindings/Matlab/+Doxa/buildParams.m",
"chars": 399,
"preview": "function params = buildParams(options)\n%BUILDPARAMS Convert name-value options to a parameter struct for MEX.\n% Intern"
},
{
"path": "Bindings/Matlab/+Doxa/calculatePerformance.m",
"chars": 904,
"preview": "function metrics = calculatePerformance(gtImage, binaryImage, options)\n%CALCULATEPERFORMANCE Calculate binarization qual"
},
{
"path": "Bindings/Matlab/+Doxa/readWeights.m",
"chars": 453,
"preview": "function weights = readWeights(filePath)\n%READWEIGHTS Load a DIBCO-format weight file for pseudo-metrics.\n% pw = Doxa."
},
{
"path": "Bindings/Matlab/+Doxa/updateToBinary.m",
"chars": 831,
"preview": "function updateToBinary(algorithm, inputImage, options)\n%UPDATETOBINARY Binarize a Doxa.Image in-place.\n% Doxa.updateT"
},
{
"path": "Bindings/Matlab/BinarizeMex.cpp",
"chars": 1464,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2026, \"Freely you have received; freely give.\" - Matt 10:8\n#include \"mex."
},
{
"path": "Bindings/Matlab/CMakeLists.txt",
"chars": 2139,
"preview": "cmake_minimum_required(VERSION 3.16)\nproject(DoxaMatlab)\n\nset(CMAKE_CXX_STANDARD 17)\nset(CMAKE_CXX_STANDARD_REQUIRED ON)"
},
{
"path": "Bindings/Matlab/CalculatePerformanceMex.cpp",
"chars": 3692,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2026, \"Freely you have received; freely give.\" - Matt 10:8\n#include \"mex."
},
{
"path": "Bindings/Matlab/Doxa.prj",
"chars": 4890,
"preview": "<deployment-project plugin=\"plugin.toolbox\" plugin-version=\"1.0\">\n <configuration file=\"${PROJECT_ROOT}\\Doxa.prj\" locat"
},
{
"path": "Bindings/Matlab/DoxaMexUtils.hpp",
"chars": 9504,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2026, \"Freely you have received; freely give.\" - Matt 10:8\n#ifndef DOXAME"
},
{
"path": "Bindings/Matlab/ImageMex.cpp",
"chars": 3273,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2026, \"Freely you have received; freely give.\" - Matt 10:8\n#include \"mex."
},
{
"path": "Bindings/Matlab/README.md",
"chars": 2869,
"preview": "# Δoxa Binarization Framework - Matlab\n\n## Introduction\nDoxa is an image binarization library focusing on local adaptive"
},
{
"path": "Bindings/Matlab/UpdateToBinaryMex.cpp",
"chars": 1446,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2026, \"Freely you have received; freely give.\" - Matt 10:8\n#include \"mex."
},
{
"path": "Bindings/Matlab/test/TestDoxa.m",
"chars": 6333,
"preview": "classdef TestDoxa < matlab.unittest.TestCase\n % Test suite for the Doxa Matlab bindings.\n\n properties\n Test"
},
{
"path": "Bindings/Python/.gitignore",
"chars": 31,
"preview": "build\ndist\nsrc/Doxa\n__pycache__"
},
{
"path": "Bindings/Python/CMakeLists.txt",
"chars": 2767,
"preview": "message(STATUS \"DoxaPy - CMake Build\")\n\ncmake_minimum_required(VERSION 3.16...3.27)\nproject(doxapy)\n\nif (CMAKE_VERSION V"
},
{
"path": "Bindings/Python/DoxaPy.ipynb",
"chars": 8815,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"id\": \"17ccb794-7c43-4c40-89ff-693df6b7b513\",\n \"metadata\": {},\n \"so"
},
{
"path": "Bindings/Python/README.md",
"chars": 5308,
"preview": "# Δoxa Binarization Framework - Python\n\n## Introduction\nDoxaPy is an image binarization library focusing on local adapti"
},
{
"path": "Bindings/Python/copy-cpp-files.py",
"chars": 646,
"preview": "import glob, os, shutil\nfrom itertools import chain\n\nsrc_dir=os.path.join(\"..\", \"..\", \"Doxa\")\ndst_dir=os.path.join(\"src\""
},
{
"path": "Bindings/Python/pyproject.toml",
"chars": 1103,
"preview": "[build-system]\nrequires = [\"cibuildwheel >= 2.22.0\",\"scikit-build-core >= 0.11.3\", \"nanobind >= 2.7.0\"]\nbuild-backend = "
},
{
"path": "Bindings/Python/requirements.txt",
"chars": 40,
"preview": "numpy>=1.20.0\nPillow>=8.0.0\nbuild>=1.2.0"
},
{
"path": "Bindings/Python/src/DoxaPy.cpp",
"chars": 10879,
"preview": "#include <nanobind/nanobind.h>\n#include <nanobind/ndarray.h>\n#include <nanobind/stl/string.h>\n#include <nanobind/stl/map"
},
{
"path": "Bindings/Python/src/doxapy/__init__.py",
"chars": 126,
"preview": "from .doxapy import *\n\n\ndef read_weights(file):\n with open(file) as f:\n return [float(x) for x in f.read().spl"
},
{
"path": "Bindings/Python/test/test_doxa.py",
"chars": 4666,
"preview": "import unittest\nfrom PIL import Image, ImageChops\nimport numpy as np\nimport os\n\nimport sys\nsys.path.append(os.path.abspa"
},
{
"path": "Bindings/Python/test/test_speed.py",
"chars": 6555,
"preview": "\"\"\"\nSpeed Tests for DoxaPy\n\nMeasures execution time for:\n- Sauvola binarization\n- WAN binarization\n- GlobalThresholding "
},
{
"path": "Bindings/WebAssembly/CMakeLists.txt",
"chars": 2080,
"preview": "cmake_minimum_required(VERSION 3.16)\nproject(doxajs)\n\nset(CMAKE_CXX_STANDARD 17)\nset(CMAKE_CXX_STANDARD_REQUIRED ON)\n\n# "
},
{
"path": "Bindings/WebAssembly/DoxaJs.nnb",
"chars": 7622,
"preview": "{\n \"cells\": [\n {\n \"language\": \"markdown\",\n \"source\": [\n \"# DoxaJs Noteboo"
},
{
"path": "Bindings/WebAssembly/DoxaWasm.cpp",
"chars": 8801,
"preview": "#include <emscripten/emscripten.h>\n#include <emscripten/bind.h>\n#include <sstream>\n//#include <iostream>\n\n#include \"../."
},
{
"path": "Bindings/WebAssembly/README.md",
"chars": 1573,
"preview": "# Δoxa Binarization Framework - WebAssembly\n\n## Introduction\nThis is an **experimental** project that exposes the ΔBF, w"
},
{
"path": "Bindings/WebAssembly/dist/doxa.js",
"chars": 7582,
"preview": "/**\n * Doxa WASM\n * A set of classes that further simplify the Doxa WASM interface.\n * This same wrapper can be run in N"
},
{
"path": "Bindings/WebAssembly/dist/doxaWasm.js",
"chars": 50954,
"preview": "var Module=typeof Module!=\"undefined\"?Module:{};var ENVIRONMENT_IS_WEB=!!globalThis.window;var ENVIRONMENT_IS_WORKER=!!g"
},
{
"path": "Bindings/WebAssembly/doxa.js",
"chars": 7582,
"preview": "/**\n * Doxa WASM\n * A set of classes that further simplify the Doxa WASM interface.\n * This same wrapper can be run in N"
},
{
"path": "Bindings/WebAssembly/package.json",
"chars": 818,
"preview": "{\n \"name\": \"doxajs\",\n \"version\": \"1.0.0\",\n \"description\": \"Doxa Binarization Framework for JavaScript/WebAssembly\",\n "
},
{
"path": "Bindings/WebAssembly/spec/binarization.spec.js",
"chars": 4008,
"preview": "const fs = require('fs');\nconst path = require('path');\nconst { loadImage, createCanvas } = require('canvas');\nconst { D"
},
{
"path": "Bindings/WebAssembly/spec/image.spec.js",
"chars": 4195,
"preview": "const { createCanvas, createImageData } = require('canvas');\nconst { Doxa } = require('../dist/doxa.js');\n\n\ndescribe(\"Do"
},
{
"path": "Bindings/WebAssembly/spec/speed.spec.js",
"chars": 5722,
"preview": "/**\n * Speed Tests for DoxaJs\n *\n * Measures execution time for:\n * - Sauvola binarization\n * - WAN binarization\n * - Gl"
},
{
"path": "Bindings/WebAssembly/spec/support/jasmine.json",
"chars": 197,
"preview": "{\n \"spec_dir\": \"spec\",\n \"spec_files\": [\n \"**/*[sS]pec.?(m)js\"\n ],\n \"helpers\": [\n \"helpers/**/*.?(m)js\"\n ],\n "
},
{
"path": "CLAUDE.md",
"chars": 11631,
"preview": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## "
},
{
"path": "CMakeLists.txt",
"chars": 3851,
"preview": "cmake_minimum_required(VERSION 3.16)\nproject(doxa VERSION 1.0.0)\n\nset(CMAKE_CXX_STANDARD 17)\nset(CMAKE_CXX_STANDARD_REQU"
},
{
"path": "CMakePresets.json",
"chars": 2949,
"preview": "{\n \"version\": 6,\n \"configurePresets\": [\n {\n \"name\": \"cpp-tests\",\n \"displayName\": \"C++ Tests\",\n \"bina"
},
{
"path": "Demo/Cpp/.gitignore",
"chars": 23,
"preview": "*.png\n*.obj\n*.dll\n*.exe"
},
{
"path": "Demo/Cpp/demo.cpp",
"chars": 2506,
"preview": "#include <iostream>\n#include \"../../Doxa/Sauvola.hpp\"\n#include \"../../Doxa/ClassifiedPerformance.hpp\"\n#include \"../../Do"
},
{
"path": "Demo/Cpp/demoOpenCV.cpp",
"chars": 2980,
"preview": "#include <opencv2/opencv.hpp>\n#include \"../../Doxa/Sauvola.hpp\"\n#include \"../../Doxa/ClassifiedPerformance.hpp\"\n#include"
},
{
"path": "Demo/Cpp/demoQt.cpp",
"chars": 3083,
"preview": "#include <QImage>\n#include <iostream>\n#include <cassert>\n#include \"../../Doxa/Sauvola.hpp\"\n#include \"../../Doxa/Classifi"
},
{
"path": "Demo/Cpp/demoQt.pro",
"chars": 1385,
"preview": "######################################################################\n# Automatically generated by qmake (3.1) Fri Jan "
},
{
"path": "Demo/Matlab/demo.m",
"chars": 1497,
"preview": "% Doxa Binarization Framework - MATLAB Demo\n%\n% This demo shows how to read an image, convert it to grayscale, binarize "
},
{
"path": "Demo/NodeJS/.gitignore",
"chars": 5,
"preview": "*.png"
},
{
"path": "Demo/NodeJS/index.js",
"chars": 2201,
"preview": "/**\n * Doxa NodeJS Demo\n * This demo uses the WASM bindings and helper classes to seemlessly call into Doxa's binarizati"
},
{
"path": "Demo/NodeJS/package.json",
"chars": 294,
"preview": "{\n \"name\": \"nodejsdemo\",\n \"version\": \"1.0.0\",\n \"description\": \"A simple demo showing how to use the Doxa library with"
},
{
"path": "Demo/Python/demo.py",
"chars": 1978,
"preview": "from PIL import Image\nimport numpy as np\nimport os\n\n# Attempt to load your local doxapy.abi3.so / doxapy.pyd build first"
},
{
"path": "Demo/WebJS/index.html",
"chars": 2545,
"preview": "<!doctype html>\n<html lang=\"en-us\">\n\t<head>\n\t\t<meta charset=\"utf-8\">\n\t\t<meta http-equiv=\"Content-Type\" content=\"text/htm"
},
{
"path": "Doxa/AdOtsu.hpp",
"chars": 3916,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2025, \"Freely you have received; freely give.\" - Matt 10:8\n#ifndef ADOTS"
},
{
"path": "Doxa/Algorithm.hpp",
"chars": 5701,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2018, \"Freely you have received; freely give.\" - Matt 10:8\n#ifndef ALGORI"
},
{
"path": "Doxa/Bataineh.hpp",
"chars": 12878,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2018, \"Freely you have received; freely give.\" - Matt 10:8\n#ifndef BATAIN"
},
{
"path": "Doxa/Bernsen.hpp",
"chars": 1413,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2018, \"Freely you have received; freely give.\" - Matt 10:8\n#ifndef BERNSE"
},
{
"path": "Doxa/BinarizationFactory.hpp",
"chars": 2010,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2025, \"Freely you have received; freely give.\" - Matt 10:8\n#ifndef BINARI"
},
{
"path": "Doxa/ChanMeanCalc.hpp",
"chars": 3719,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2020, \"Freely you have received; freely give.\" - Matt 10:8\n#ifndef CHANME"
},
{
"path": "Doxa/ChanMeanVarianceCalc.hpp",
"chars": 4726,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2020, \"Freely you have received; freely give.\" - Matt 10:8\n#ifndef CHANME"
},
{
"path": "Doxa/ClassifiedPerformance.hpp",
"chars": 11966,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2018, \"Freely you have received; freely give.\" - Matt 10:8\n#ifndef CLASSI"
},
{
"path": "Doxa/ContrastImage.hpp",
"chars": 2997,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2018, \"Freely you have received; freely give.\" - Matt 10:8\n#ifndef CONTRA"
},
{
"path": "Doxa/DIBCOUtils.hpp",
"chars": 1219,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2025, \"Freely you have received; freely give.\" - Matt 10:8\n#ifndef DIBCOU"
},
{
"path": "Doxa/DRDM.hpp",
"chars": 13377,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2026, \"Freely you have received; freely give.\" - Matt 10:8\n#ifndef DRDM_H"
},
{
"path": "Doxa/Doxa.vcxitems",
"chars": 3332,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n <Propert"
},
{
"path": "Doxa/Gatos.hpp",
"chars": 4942,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2018, \"Freely you have received; freely give.\" - Matt 10:8\n#ifndef GATOS_"
},
{
"path": "Doxa/Grayscale.hpp",
"chars": 12142,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2022, \"Freely you have received; freely give.\" - Matt 10:8\n#ifndef GRAYSC"
},
{
"path": "Doxa/GridCalc.hpp",
"chars": 7763,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2025, \"Freely you have received; freely give.\" - Matt 10:8\n#ifndef GRIDC"
},
{
"path": "Doxa/ISauvola.hpp",
"chars": 5474,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2018, \"Freely you have received; freely give.\" - Matt 10:8\n#ifndef ISAUVO"
},
{
"path": "Doxa/Image.hpp",
"chars": 3568,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2018, \"Freely you have received; freely give.\" - Matt 10:8\n#ifndef IMAGE_"
},
{
"path": "Doxa/IntegralImageMeanVarianceCalc.hpp",
"chars": 8293,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2018, \"Freely you have received; freely give.\" - Matt 10:8\n#ifndef INTEGR"
},
{
"path": "Doxa/LocalWindow.hpp",
"chars": 2311,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2018, \"Freely you have received; freely give.\" - Matt 10:8\n#ifndef LOCALW"
},
{
"path": "Doxa/Morphology.hpp",
"chars": 10685,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2018, \"Freely you have received; freely give.\" - Matt 10:8\n#ifndef MORPHO"
},
{
"path": "Doxa/MultiScale.hpp",
"chars": 2837,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2025, \"Freely you have received; freely give.\" - Matt 10:8\n#ifndef MULTIS"
},
{
"path": "Doxa/Niblack.hpp",
"chars": 974,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2018, \"Freely you have received; freely give.\" - Matt 10:8\n#ifndef NIBLAC"
},
{
"path": "Doxa/Nick.hpp",
"chars": 1018,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2018, \"Freely you have received; freely give.\" - Matt 10:8\n#ifndef NICK_H"
},
{
"path": "Doxa/Otsu.hpp",
"chars": 2456,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2022, \"Freely you have received; freely give.\" - Matt 10:8\n#ifndef OTSU_H"
},
{
"path": "Doxa/PNM.hpp",
"chars": 11878,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2022, \"Freely you have received; freely give.\" - Matt 10:8\n#ifndef PNM_HP"
},
{
"path": "Doxa/Palette.hpp",
"chars": 2161,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2018, \"Freely you have received; freely give.\" - Matt 10:8\n#ifndef PALETT"
},
{
"path": "Doxa/Parameters.hpp",
"chars": 2919,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2018, \"Freely you have received; freely give.\" - Matt 10:8\n#ifndef PARAME"
},
{
"path": "Doxa/Phansalkar.hpp",
"chars": 1923,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2026, \"Freely you have received; freely give.\" - Matt 10:8\n#ifndef PHANSA"
},
{
"path": "Doxa/Region.hpp",
"chars": 1634,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2018, \"Freely you have received; freely give.\" - Matt 10:8\n#ifndef REGION"
},
{
"path": "Doxa/SIMD.h",
"chars": 1354,
"preview": "// ?oxa Binarization Framework\n// License: CC0 2026, \"Freely you have received; freely give.\" - Matt 10:8\n// Purpose: Co"
},
{
"path": "Doxa/SIMDOps.hpp",
"chars": 4944,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2026, \"Freely you have received; freely give.\" - Matt 10:8\n// Purpose: U"
},
{
"path": "Doxa/Sauvola.hpp",
"chars": 996,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2018, \"Freely you have received; freely give.\" - Matt 10:8\n#ifndef SAUVOL"
},
{
"path": "Doxa/Su.hpp",
"chars": 4066,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2018, \"Freely you have received; freely give.\" - Matt 10:8\n#ifndef SU_HPP"
},
{
"path": "Doxa/TRSingh.hpp",
"chars": 1664,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2018, \"Freely you have received; freely give.\" - Matt 10:8\n#ifndef TRSING"
},
{
"path": "Doxa/Types.hpp",
"chars": 559,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2018, \"Freely you have received; freely give.\" - Matt 10:8\n#ifndef TYPES_"
},
{
"path": "Doxa/Wan.hpp",
"chars": 1329,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2018, \"Freely you have received; freely give.\" - Matt 10:8\n#ifndef WAN_HP"
},
{
"path": "Doxa/WienerFilter.hpp",
"chars": 1487,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2018, \"Freely you have received; freely give.\" - Matt 10:8\n#ifndef WIENER"
},
{
"path": "Doxa/Wolf.hpp",
"chars": 1564,
"preview": "// Δoxa Binarization Framework\n// License: CC0 2018, \"Freely you have received; freely give.\" - Matt 10:8\n#ifndef WOLF_H"
},
{
"path": "Doxa.Bench/BenchmarkHarness.hpp",
"chars": 1478,
"preview": "#ifndef BENCHMARKHARNESS_HPP\n#define BENCHMARKHARNESS_HPP\n\n#include \"pch.h\"\n#include \"config.hpp\"\n\n\nnamespace Doxa::Benc"
},
{
"path": "Doxa.Bench/BinarizationBenchmarks.cpp",
"chars": 5763,
"preview": "#include \"pch.h\"\n#include \"BenchmarkHarness.hpp\"\n\n\nnamespace Doxa::Benchmarks\n{\n\tstatic Image ReadTestImage()\n\t{\n\t\tretur"
},
{
"path": "Doxa.Bench/CMakeLists.txt",
"chars": 2441,
"preview": "message(STATUS \"Doxa Bench - CMake Build\")\n\ncmake_minimum_required(VERSION 3.16)\nproject(doxa_bench)\n\nset(CMAKE_CXX_STAN"
},
{
"path": "Doxa.Bench/CalculatorBenchmarks.cpp",
"chars": 1213,
"preview": "#include \"pch.h\"\n#include \"BenchmarkHarness.hpp\"\n\n\nnamespace Doxa::Benchmarks\n{\n\tstatic void BM_Niblack_Chan(benchmark::"
},
{
"path": "Doxa.Bench/ClassifiedPerformanceBenchmarks.cpp",
"chars": 1409,
"preview": "#include \"pch.h\"\n#include \"BenchmarkHarness.hpp\"\n\n\nnamespace Doxa::Benchmarks\n{\n\tstatic void BM_CompareImages_Scalar(ben"
},
{
"path": "Doxa.Bench/DRDMBenchmarks.cpp",
"chars": 1582,
"preview": "#include \"pch.h\"\n#include \"BenchmarkHarness.hpp\"\n\n\nnamespace Doxa::Benchmarks\n{\n\tstatic void BM_NUBN_STD(benchmark::Stat"
},
{
"path": "Doxa.Bench/GlobalThresholdBenchmarks.cpp",
"chars": 1294,
"preview": "#include \"pch.h\"\n#include \"BenchmarkHarness.hpp\"\n\n\nnamespace Doxa::Benchmarks\n{\n\tstatic void BM_GlobalThreshold_Scalar(b"
},
{
"path": "Doxa.Bench/config.hpp.in",
"chars": 69,
"preview": "#pragma once\n#define DOXA_BENCH_RESOURCES_DIR \"@DOXA_RESOURCES_DIR@\"\n"
},
{
"path": "Doxa.Bench/pch.h",
"chars": 375,
"preview": "//\n// pch.h\n//\n\n#pragma once\n#include <vector>\n#include <string>\n#include <cmath>\n\n#include <SIMD.h>\n#include <SIMDOps.h"
},
{
"path": "Doxa.Test/AlgorithmTests.cpp",
"chars": 1889,
"preview": "#include \"pch.h\"\n#include \"TestUtilities.hpp\"\n\n\nnamespace Doxa::UnitTests\n{\n\tclass AlgorithmTests : public ::testing::Te"
},
{
"path": "Doxa.Test/BatainehTests.cpp",
"chars": 6016,
"preview": "#include \"pch.h\"\n#include \"TestUtilities.hpp\"\n\n\nnamespace Doxa::UnitTests\n{\n\t// Exposes protected members for Unit Testi"
},
{
"path": "Doxa.Test/BinarizationTests.cpp",
"chars": 6721,
"preview": "#include \"pch.h\"\n#include \"TestUtilities.hpp\"\n#include \"ImageFixture.hpp\"\n\n\nnamespace Doxa::UnitTests\n{\n\tclass Binarizat"
},
{
"path": "Doxa.Test/CMakeLists.txt",
"chars": 2847,
"preview": "message(STATUS \"Doxa Test - CMake Build\")\n\ncmake_minimum_required(VERSION 3.16)\nproject(doxa_test)\n\nset(CMAKE_CXX_STANDA"
},
{
"path": "Doxa.Test/CalculatorTests.cpp",
"chars": 4293,
"preview": "#include \"pch.h\"\n#include \"ImageFixture.hpp\"\n#include \"TestUtilities.hpp\"\n\n\nnamespace Doxa::UnitTests\n{\n\tclass Calculato"
},
{
"path": "Doxa.Test/ClassifiedPerformanceTests.cpp",
"chars": 7225,
"preview": "#include \"pch.h\"\n#include \"TestUtilities.hpp\"\n\n\nnamespace Doxa::UnitTests\n{\n\tTEST(ClassifiedPerformanceTests, Performanc"
},
{
"path": "Doxa.Test/ContrastImageTests.cpp",
"chars": 1212,
"preview": "#include \"pch.h\"\n#include \"ImageFixture.hpp\"\n\n\nnamespace Doxa::UnitTests\n{\n\tclass ContrastImageTests : public ImageFixtu"
},
{
"path": "Doxa.Test/DIBCOUtilsTests.cpp",
"chars": 525,
"preview": "#include \"pch.h\"\n#include <sstream>\n#include <string>\n#include \"DIBCOUtils.hpp\"\n\n\nnamespace Doxa::UnitTests\n{\n\tTEST(DIBC"
},
{
"path": "Doxa.Test/DRDMTests.cpp",
"chars": 17154,
"preview": "#include \"pch.h\"\n#include \"TestUtilities.hpp\"\n\n\nnamespace Doxa::UnitTests\n{\n\t// Exposes protected members for Unit Testi"
},
{
"path": "Doxa.Test/Doxa.Test.vcxproj",
"chars": 5745,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project DefaultTargets=\"Build\" ToolsVersion=\"15.0\" xmlns=\"http://schemas.micros"
},
{
"path": "Doxa.Test/Doxa.Test.vcxproj.filters",
"chars": 1365,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild"
},
{
"path": "Doxa.Test/GrayscaleTests.cpp",
"chars": 2382,
"preview": "#include \"pch.h\"\n\n\nnamespace Doxa::UnitTests\n{\n\tTEST(GrayscaleTests, GrayscaleAlgorithmsTest)\n\t{\n\t\t// Mean, Value, and L"
},
{
"path": "Doxa.Test/GridCalcTests.cpp",
"chars": 6696,
"preview": "#include \"pch.h\"\n#include \"TestUtilities.hpp\"\n\n\nnamespace Doxa::UnitTests\n{\n\tTEST(GridCalcTests, GridCalcIterateInterwov"
},
{
"path": "Doxa.Test/ISauvolaTests.cpp",
"chars": 4873,
"preview": "#include \"pch.h\"\n#include \"TestUtilities.hpp\"\n\n\nnamespace Doxa::UnitTests\n{\n\t// Exposes protected members for Unit Testi"
},
{
"path": "Doxa.Test/ImageFixture.hpp",
"chars": 515,
"preview": "#ifndef IMAGEFIXTURE_HPP\n#define IMAGEFIXTURE_HPP\n\n#include \"pch.h\"\n#include \"TestUtilities.hpp\"\n\n\nnamespace Doxa::UnitT"
},
{
"path": "Doxa.Test/ImageTests.cpp",
"chars": 3279,
"preview": "#include \"pch.h\"\n\n\nnamespace Doxa::UnitTests\n{\n\tclass ImageTest : public ::testing::Test\n\t{\n\tprotected:\n\t\tImage image;\n\n"
},
{
"path": "Doxa.Test/LocalWindowTests.cpp",
"chars": 1124,
"preview": "#include \"pch.h\"\n\n\nnamespace Doxa::UnitTests\n{\n\tTEST(LocalWindowTests, LocalWindowIterateTest)\n\t{\n\t\t// Build dummy image"
},
{
"path": "Doxa.Test/MorphologyTests.cpp",
"chars": 4913,
"preview": "#include \"pch.h\"\n#include \"ImageFixture.hpp\"\n#include \"TestUtilities.hpp\"\n\n\nnamespace Doxa::UnitTests\n{\n\tclass Morpholog"
},
{
"path": "Doxa.Test/PNMTests.cpp",
"chars": 6818,
"preview": "#include \"pch.h\"\n#include \"TestUtilities.hpp\"\n\n\nnamespace Doxa::UnitTests\n{\n\t// Exposes protected members for Unit Testi"
},
{
"path": "Doxa.Test/PaletteTests.cpp",
"chars": 3270,
"preview": "#include \"pch.h\"\n\n\nnamespace Doxa::UnitTests\n{\n\tTEST(PaletteTests, PaletteTest)\n\t{\n\t\tconstexpr Pixel32 rgb = Palette::RG"
},
{
"path": "Doxa.Test/ParametersTests.cpp",
"chars": 1306,
"preview": "#include \"pch.h\"\n\n\nnamespace Doxa::UnitTests\n{\n\tTEST(ParametersTests, ParametersGetTest)\n\t{\n\t\tParameters param({ {\"x\", 1"
},
{
"path": "Doxa.Test/RegionTests.cpp",
"chars": 1381,
"preview": "#include \"pch.h\"\n\n\nnamespace Doxa::UnitTests\n{\n\tTEST(RegionTests, RegionConstructorTest)\n\t{\n\t\tRegion::Point upperLeft(0,"
},
{
"path": "Doxa.Test/Resources/2JohnC1V3.ppm",
"chars": 325764,
"preview": "P6\n707\n441\n255\nƳƳǴǴȵȵȵǴǴƳȵȵȵȵȵȵȵȵƳƳŲıııŲƳȶȸɹɹʸʸʷʷ˵ʴȰƮĬſľº·ºǼǿŮdzȱűŮƲȳͺ˸ǴððıǴǴɶʷʷɶɶȵ˸˸̹̹̹˸ʷɶʷɶɶʷ˸ȵðſºƾìȰɱȰȰɳʴ˴̵ʹ˳ɯǬæɿşƠʤЭ"
},
{
"path": "Doxa.Test/SIMDTests.cpp",
"chars": 3559,
"preview": "#include \"pch.h\"\n#include \"TestUtilities.hpp\"\n#include \"ImageFixture.hpp\"\n#include <iostream>\n\n\nnamespace Doxa::UnitTest"
},
{
"path": "Doxa.Test/SuTests.cpp",
"chars": 2318,
"preview": "#include \"pch.h\"\n#include \"TestUtilities.hpp\"\n\n\nnamespace Doxa::UnitTests\n{\n\t// Exposes protected members for Unit Testi"
},
{
"path": "Doxa.Test/TestUtilities.hpp",
"chars": 2583,
"preview": "#ifndef TESTUTILITIES_HPP\n#define TESTUTILITIES_HPP\n\n#include \"pch.h\"\n\n\nnamespace Doxa::UnitTests\n{\n\tclass TestUtilities"
},
{
"path": "Doxa.Test/WienerFilterTests.cpp",
"chars": 1864,
"preview": "#include \"pch.h\"\n#include \"TestUtilities.hpp\"\n#include <WienerFilter.hpp>\n\n\nnamespace Doxa::UnitTests\n{\n\t// A Wiener fil"
},
{
"path": "Doxa.Test/packages.config",
"chars": 183,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<packages>\n <package id=\"Microsoft.googletest.v140.windesktop.msvcstl.static.rt"
},
{
"path": "Doxa.Test/pch.cpp",
"chars": 35,
"preview": "//\n// pch.cpp\n//\n\n#include \"pch.h\"\n"
},
{
"path": "Doxa.Test/pch.h",
"chars": 629,
"preview": "//\n// pch.h\n//\n\n#pragma once\n#include <chrono>\n#include <functional>\n#include <vector>\n#include <tuple>\n\n#include <SIMD."
},
{
"path": "Doxa.sln",
"chars": 1433,
"preview": "\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 17\nVisualStudioVersion = 17.6.3382"
},
{
"path": "LICENSE",
"chars": 6554,
"preview": "CC0 1.0 Universal\n\nStatement of Purpose\n\nThe laws of most jurisdictions throughout the world automatically confer\nexclus"
},
{
"path": "README.md",
"chars": 7575,
"preview": "# Δoxa Binarization Framework\n\n[](h"
}
]
// ... and 22 more files (download for full content)
About this extraction
This page contains the full source code of the brandonmpetty/Doxa GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 163 files (845.9 KB), approximately 623.6k tokens, and a symbol index with 597 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.