├── include ├── fastslide │ ├── runtime │ │ ├── io │ │ │ ├── file_handle_pool.h │ │ │ ├── binary_utils.h │ │ │ └── file_reader.h │ │ └── tile_writer │ │ │ ├── fs_profile.h │ │ │ ├── blended │ │ │ ├── resample_mks.h │ │ │ ├── mks_kernel.h │ │ │ ├── srgb_linear.h │ │ │ └── blended_strategy.h │ │ │ ├── direct │ │ │ ├── fills.h │ │ │ ├── copy_planar.h │ │ │ ├── copy_rgb8.h │ │ │ └── direct_strategy.h │ │ │ └── tile_writer_strategy.h │ ├── readers │ │ ├── aperio │ │ │ ├── aperio_format_plugin.h │ │ │ └── metadata_parser.h │ │ ├── qptiff │ │ │ └── qptiff_format_plugin.h │ │ ├── readers.h │ │ ├── mrxs │ │ │ ├── mrxs_format_plugin.h │ │ │ ├── mrxs_decoder.h │ │ │ ├── mrxs_ini_parser.h │ │ │ ├── mrxs_constants.h │ │ │ └── mrxs_tile_executor.h │ │ └── tiff_based_tile_executor.h │ ├── resample │ │ └── utilities.h │ ├── python │ │ └── cache.h │ ├── c │ │ ├── fastslide.h │ │ └── registry.h │ ├── fastslide.h │ ├── utilities │ │ ├── hash.h │ │ ├── colors.h │ │ └── tiff │ │ │ └── tiff_cache_service.h │ └── core │ │ └── metadata.h └── aifocore │ ├── utilities │ ├── fmt.h │ ├── thread_pool_singleton.h │ ├── pyvips.h │ ├── vips.h │ └── temporary.h │ ├── shared │ └── exceptions.h │ └── ranges │ └── zip.h ├── third_party └── lodepng │ ├── .clang-format │ └── version.bzl ├── subprojects ├── highway.wrap ├── proxy-libintl.wrap ├── cereal.wrap ├── zlib.wrap ├── lz4.wrap ├── liblzma.wrap ├── fmt.wrap ├── libtiff.wrap ├── pugixml.wrap ├── zstd.wrap ├── pybind11.wrap ├── google-benchmark.wrap ├── libwebp.wrap ├── libjpeg-turbo.wrap └── gtest.wrap ├── docs ├── requirements.in ├── source │ ├── Makefile │ ├── make.bat │ ├── _static │ │ └── custom.css │ ├── formats │ │ ├── index.rst │ │ ├── svs.rst │ │ ├── mrxs.rst │ │ └── qptiff.rst │ ├── api │ │ └── index.rst │ └── index.rst ├── doxygen_footer.html ├── doxygen_header.html └── Makefile ├── MANIFEST.in ├── python ├── fastslide │ └── __init__.py └── test_import.py ├── src ├── fastslide │ ├── c │ │ └── fastslide.cpp │ ├── runtime │ │ ├── tile_writer │ │ │ ├── direct │ │ │ │ ├── copy_planar.cpp │ │ │ │ ├── copy_rgb8.cpp │ │ │ │ └── fills.cpp │ │ │ └── blended │ │ │ │ └── mks_kernel.cpp │ │ ├── global_cache_manager.cpp │ │ ├── io │ │ │ ├── binary_utils.cpp │ │ │ └── file_reader.cpp │ │ └── register_builtin_formats.cpp │ ├── utilities │ │ ├── tiff │ │ │ └── tiff_cache_service.cpp │ │ └── tile_cache_manager.cpp │ ├── readers │ │ ├── aperio │ │ │ └── aperio_format_plugin.cpp │ │ ├── qptiff │ │ │ └── qptiff_format_plugin.cpp │ │ └── mrxs │ │ │ └── mrxs_format_plugin.cpp │ └── python │ │ └── cache.cpp └── aifocore │ ├── utilities │ └── thread_pool_singleton.cc │ ├── shared │ └── python │ │ └── vector_module.cpp │ ├── pathology │ ├── inference.cpp │ └── inference_fimage.cpp │ └── status │ └── trace_example.cpp ├── pyproject.toml ├── meson_options.txt ├── .github └── workflows │ └── mirror-issues-to-internal-monorepo.yaml └── tools └── copy.bara.sky /include/fastslide/runtime/io/file_handle_pool.h: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /third_party/lodepng/.clang-format: -------------------------------------------------------------------------------- 1 | DisableFormat: true 2 | SortIncludes: Never 3 | -------------------------------------------------------------------------------- /subprojects/highway.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | url = https://github.com/google/highway.git 3 | revision = master 4 | 5 | [provide] 6 | hwy = hwy_dep 7 | -------------------------------------------------------------------------------- /third_party/lodepng/version.bzl: -------------------------------------------------------------------------------- 1 | LODEPNG_VERSION = "20250506" 2 | LODEPNG_COMMIT = "344b4b442d0de0787a999724dd6569461a00c92c" 3 | LODEPNG_SOURCE = "https://raw.githubusercontent.com/lvandeve/lodepng/refs/heads/master/lodepng.cpp" 4 | LODEPNG_HEADER = "https://raw.githubusercontent.com/lvandeve/lodepng/refs/heads/master/lodepng.h" 5 | -------------------------------------------------------------------------------- /subprojects/proxy-libintl.wrap: -------------------------------------------------------------------------------- 1 | [wrap-file] 2 | directory = proxy-libintl-0.5 3 | source_url = https://github.com/frida/proxy-libintl/archive/refs/tags/0.5.tar.gz 4 | source_filename = proxy-libintl-0.5.tar.gz 5 | source_hash = f7a1cbd7579baaf575c66f9d99fb6295e9b0684a28b095967cfda17857595303 6 | source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/proxy-libintl_0.5-1/proxy-libintl-0.5.tar.gz 7 | wrapdb_version = 0.5-1 8 | 9 | [provide] 10 | intl = intl_dep 11 | -------------------------------------------------------------------------------- /subprojects/cereal.wrap: -------------------------------------------------------------------------------- 1 | [wrap-file] 2 | directory = cereal-1.3.2 3 | source_url = https://github.com/USCiLab/cereal/archive/v1.3.2.tar.gz 4 | source_filename = cereal-1.3.2.tar.gz 5 | source_hash = 16a7ad9b31ba5880dac55d62b5d6f243c3ebc8d46a3514149e56b5e7ea81f85f 6 | patch_filename = cereal_1.3.2-1_patch.zip 7 | patch_url = https://wrapdb.mesonbuild.com/v2/cereal_1.3.2-1/get_patch 8 | patch_hash = fd2f047a40a0d291c643fdafe4ce743f0eadbef667b6afe43a332e1ba0862603 9 | 10 | [provide] 11 | cereal = cereal_dep 12 | 13 | -------------------------------------------------------------------------------- /docs/requirements.in: -------------------------------------------------------------------------------- 1 | # FastSlide Documentation Dependencies 2 | # Base requirements - use pip_compile to generate lock files 3 | 4 | # Core Sphinx 5 | sphinx>=7.0.0 6 | sphinx-rtd-theme>=2.0.0 7 | 8 | # Breathe (Doxygen integration) 9 | breathe>=4.35.0 10 | 11 | # Sphinx extensions 12 | sphinx-design>=0.5.0 13 | sphinx-copybutton>=0.5.0 14 | sphinx-inline-tabs>=2023.4.21 15 | sphinxcontrib-mermaid>=0.9.0 16 | sphinxext-opengraph>=0.9.0 17 | sphinx-autodoc-typehints>=2.0.0 18 | 19 | # MyST Parser (Markdown support) 20 | myst-parser>=2.0.0 21 | 22 | matplotlib>=3.10.0 -------------------------------------------------------------------------------- /subprojects/zlib.wrap: -------------------------------------------------------------------------------- 1 | [wrap-file] 2 | directory = zlib-1.3.1 3 | source_url = http://zlib.net/fossils/zlib-1.3.1.tar.gz 4 | source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/zlib_1.3.1-2/zlib-1.3.1.tar.gz 5 | source_filename = zlib-1.3.1.tar.gz 6 | source_hash = 9a93b2b7dfdac77ceba5a558a580e74667dd6fede4585b91eefb60f03b72df23 7 | patch_filename = zlib_1.3.1-2_patch.zip 8 | patch_url = https://wrapdb.mesonbuild.com/v2/zlib_1.3.1-2/get_patch 9 | patch_hash = 9cacea02e1119964bc51e92dd2359b14df723a36cfe0df1c78d55d9c9f2763ae 10 | wrapdb_version = 1.3.1-2 11 | 12 | [provide] 13 | zlib = zlib_dep 14 | -------------------------------------------------------------------------------- /subprojects/lz4.wrap: -------------------------------------------------------------------------------- 1 | [wrap-file] 2 | directory = lz4-1.10.0 3 | source_url = https://github.com/lz4/lz4/archive/v1.10.0.tar.gz 4 | source_filename = lz4-1.10.0.tgz 5 | source_hash = 537512904744b35e232912055ccf8ec66d768639ff3abe5788d90d792ec5f48b 6 | patch_filename = lz4_1.10.0-2_patch.zip 7 | patch_url = https://wrapdb.mesonbuild.com/v2/lz4_1.10.0-2/get_patch 8 | patch_hash = ce568b4e1a7a593bb233f3ec97af88ceb850524cef802fe3f916b21d8a79b6b6 9 | source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/lz4_1.10.0-2/lz4-1.10.0.tgz 10 | wrapdb_version = 1.10.0-2 11 | 12 | [provide] 13 | liblz4 = liblz4_dep 14 | -------------------------------------------------------------------------------- /subprojects/liblzma.wrap: -------------------------------------------------------------------------------- 1 | [wrap-file] 2 | directory = xz-5.2.12 3 | source_url = http://tukaani.org/xz/xz-5.2.12.tar.xz 4 | source_filename = xz-5.2.12.tar.xz 5 | source_hash = f79a92b84101d19d76be833aecc93e68e56065b61ec737610964cd4f6c54ff2e 6 | patch_filename = liblzma_5.2.12-3_patch.zip 7 | patch_url = https://wrapdb.mesonbuild.com/v2/liblzma_5.2.12-3/get_patch 8 | patch_hash = bf9eb44c6ba8d5157d5fdcc35a3d5a7a9d409a6c4ae696122bbb9e2d9d12467a 9 | source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/liblzma_5.2.12-3/xz-5.2.12.tar.xz 10 | wrapdb_version = 5.2.12-3 11 | 12 | [provide] 13 | liblzma = lzma_dep 14 | -------------------------------------------------------------------------------- /subprojects/fmt.wrap: -------------------------------------------------------------------------------- 1 | [wrap-file] 2 | directory = fmt-11.2.0 3 | source_url = https://github.com/fmtlib/fmt/archive/11.2.0.tar.gz 4 | source_filename = fmt-11.2.0.tar.gz 5 | source_hash = bc23066d87ab3168f27cef3e97d545fa63314f5c79df5ea444d41d56f962c6af 6 | patch_filename = fmt_11.2.0-2_patch.zip 7 | patch_url = https://wrapdb.mesonbuild.com/v2/fmt_11.2.0-2/get_patch 8 | patch_hash = cc555cbfc9e334d5b670763894586ad6fbaf7f85eb5e67221cfe519b919c6542 9 | source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/fmt_11.2.0-2/fmt-11.2.0.tar.gz 10 | wrapdb_version = 11.2.0-2 11 | 12 | [provide] 13 | dependency_names = fmt 14 | -------------------------------------------------------------------------------- /subprojects/libtiff.wrap: -------------------------------------------------------------------------------- 1 | [wrap-file] 2 | directory = tiff-4.7.0 3 | source_url = https://download.osgeo.org/libtiff/tiff-4.7.0.tar.xz 4 | source_filename = tiff-4.7.0.tar.gz 5 | source_hash = 273a0a73b1f0bed640afee4a5df0337357ced5b53d3d5d1c405b936501f71017 6 | patch_filename = libtiff_4.7.0-1_patch.zip 7 | patch_url = https://wrapdb.mesonbuild.com/v2/libtiff_4.7.0-1/get_patch 8 | patch_hash = 1c0f574783528f109887f8b4387c35089dc9e01ec1ca34eef75872757ab0caec 9 | source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/libtiff_4.7.0-1/tiff-4.7.0.tar.gz 10 | wrapdb_version = 4.7.0-1 11 | 12 | [provide] 13 | libtiff-4 = libtiff4_dep 14 | -------------------------------------------------------------------------------- /subprojects/pugixml.wrap: -------------------------------------------------------------------------------- 1 | [wrap-file] 2 | directory = pugixml-1.15 3 | source_url = https://github.com/zeux/pugixml/archive/v1.15.tar.gz 4 | source_filename = pugixml-1.15.tar.gz 5 | source_hash = b39647064d9e28297a34278bfb897092bf33b7c487906ddfc094c9e8868bddcb 6 | patch_filename = pugixml_1.15-1_patch.zip 7 | patch_url = https://wrapdb.mesonbuild.com/v2/pugixml_1.15-1/get_patch 8 | patch_hash = 71283431485c5e014c1e360033e84c56fda04171f9ba3bb48f37dd30c48e1d57 9 | source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/pugixml_1.15-1/pugixml-1.15.tar.gz 10 | wrapdb_version = 1.15-1 11 | 12 | [provide] 13 | pugixml = pugixml_dep 14 | -------------------------------------------------------------------------------- /subprojects/zstd.wrap: -------------------------------------------------------------------------------- 1 | [wrap-file] 2 | directory = zstd-1.5.7 3 | source_url = https://github.com/facebook/zstd/releases/download/v1.5.7/zstd-1.5.7.tar.gz 4 | source_filename = zstd-1.5.7.tar.gz 5 | source_hash = eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3 6 | patch_filename = zstd_1.5.7-3_patch.zip 7 | patch_url = https://wrapdb.mesonbuild.com/v2/zstd_1.5.7-3/get_patch 8 | patch_hash = 75ccb7b5bd26ae38b9d2db40ea4915504137835c15857ea833e70af1fe6f8686 9 | source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/zstd_1.5.7-3/zstd-1.5.7.tar.gz 10 | wrapdb_version = 1.5.7-3 11 | 12 | [provide] 13 | libzstd = libzstd_dep 14 | -------------------------------------------------------------------------------- /subprojects/pybind11.wrap: -------------------------------------------------------------------------------- 1 | [wrap-file] 2 | directory = pybind11-3.0.0 3 | source_url = https://github.com/pybind/pybind11/archive/refs/tags/v3.0.0.tar.gz 4 | source_filename = pybind11-3.0.0.tar.gz 5 | source_hash = 453b1a3e2b266c3ae9da872411cadb6d693ac18063bd73226d96cfb7015a200c 6 | patch_filename = pybind11_3.0.0-1_patch.zip 7 | patch_url = https://wrapdb.mesonbuild.com/v2/pybind11_3.0.0-1/get_patch 8 | patch_hash = 51ef27fd76207c530fb54017aaa166ff02bb49f12308d497635fefbc1bc6a560 9 | source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/pybind11_3.0.0-1/pybind11-3.0.0.tar.gz 10 | wrapdb_version = 3.0.0-1 11 | 12 | [provide] 13 | pybind11 = pybind11_dep 14 | -------------------------------------------------------------------------------- /subprojects/google-benchmark.wrap: -------------------------------------------------------------------------------- 1 | [wrap-file] 2 | directory = benchmark-1.8.4 3 | source_url = https://github.com/google/benchmark/archive/refs/tags/v1.8.4.tar.gz 4 | source_filename = benchmark-1.8.4.tar.gz 5 | source_hash = 3e7059b6b11fb1bbe28e33e02519398ca94c1818874ebed18e504dc6f709be45 6 | patch_filename = google-benchmark_1.8.4-4_patch.zip 7 | patch_url = https://wrapdb.mesonbuild.com/v2/google-benchmark_1.8.4-4/get_patch 8 | patch_hash = d1af464d29eb42442c41bc0629f94dbc2e390d9a8656461a14adfee84bcb6250 9 | source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/google-benchmark_1.8.4-4/benchmark-1.8.4.tar.gz 10 | wrapdb_version = 1.8.4-4 11 | 12 | [provide] 13 | dependency_names = benchmark, benchmark_main 14 | -------------------------------------------------------------------------------- /docs/source/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = ../build/html 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /subprojects/libwebp.wrap: -------------------------------------------------------------------------------- 1 | [wrap-file] 2 | directory = libwebp-1.5.0 3 | source_url = https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-1.5.0.tar.gz 4 | source_filename = libwebp-1.5.0.tar.gz 5 | source_hash = 7d6fab70cf844bf6769077bd5d7a74893f8ffd4dfb42861745750c63c2a5c92c 6 | patch_filename = libwebp_1.5.0-1_patch.zip 7 | patch_url = https://wrapdb.mesonbuild.com/v2/libwebp_1.5.0-1/get_patch 8 | patch_hash = 5e94fed7f748f73500a30eee83cc194a2a2b9f906145184d79a19feb14b4aca0 9 | source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/libwebp_1.5.0-1/libwebp-1.5.0.tar.gz 10 | wrapdb_version = 1.5.0-1 11 | 12 | [provide] 13 | dependency_names = libsharyuv, libwebp, libwebpdecoder, libwebpdemux, libwebpmux 14 | -------------------------------------------------------------------------------- /subprojects/libjpeg-turbo.wrap: -------------------------------------------------------------------------------- 1 | [wrap-file] 2 | directory = libjpeg-turbo-3.1.2 3 | source_url = https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/3.1.2/libjpeg-turbo-3.1.2.tar.gz 4 | source_filename = libjpeg-turbo-3.1.2.tar.gz 5 | source_hash = 8f0012234b464ce50890c490f18194f913a7b1f4e6a03d6644179fa0f867d0cf 6 | patch_filename = libjpeg-turbo_3.1.2-1_patch.zip 7 | patch_url = https://wrapdb.mesonbuild.com/v2/libjpeg-turbo_3.1.2-1/get_patch 8 | patch_hash = 5f4f4698f40f25a47709e54a5c170c305e59a3bb13e5b0c9fdfd909865357bfc 9 | source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/libjpeg-turbo_3.1.2-1/libjpeg-turbo-3.1.2.tar.gz 10 | wrapdb_version = 3.1.2-1 11 | 12 | [provide] 13 | dependency_names = libjpeg, libturbojpeg 14 | -------------------------------------------------------------------------------- /subprojects/gtest.wrap: -------------------------------------------------------------------------------- 1 | [wrap-file] 2 | directory = googletest-1.17.0 3 | source_url = https://github.com/google/googletest/archive/refs/tags/v1.17.0.tar.gz 4 | source_filename = googletest-1.17.0.tar.gz 5 | source_hash = 65fab701d9829d38cb77c14acdc431d2108bfdbf8979e40eb8ae567edf10b27c 6 | patch_filename = gtest_1.17.0-4_patch.zip 7 | patch_url = https://wrapdb.mesonbuild.com/v2/gtest_1.17.0-4/get_patch 8 | patch_hash = 3abf7662d09db706453a5b064a1e914678c74b9d9b0b19382747ca561d0d8750 9 | source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/gtest_1.17.0-4/googletest-1.17.0.tar.gz 10 | wrapdb_version = 1.17.0-4 11 | 12 | [provide] 13 | gtest = gtest_dep 14 | gtest_main = gtest_main_dep 15 | gmock = gmock_dep 16 | gmock_main = gmock_main_dep 17 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # Include build system files 2 | include pyproject.toml 3 | include meson.build 4 | include meson_options.txt 5 | 6 | # Include license and documentation 7 | include LICENSE 8 | include README_PUBLIC.md 9 | 10 | # Include all source files 11 | recursive-include src *.cpp *.c 12 | recursive-include include *.h *.hpp 13 | 14 | # Include third-party vendored code 15 | recursive-include third_party *.cpp *.c *.h *.hpp 16 | 17 | # Include subproject wraps for dependency management 18 | recursive-include subprojects *.wrap 19 | 20 | # Exclude build artifacts and caches 21 | global-exclude *.pyc 22 | global-exclude *.pyo 23 | global-exclude __pycache__ 24 | global-exclude *.so 25 | global-exclude *.dylib 26 | global-exclude *.dll 27 | prune builddir 28 | prune .git 29 | 30 | -------------------------------------------------------------------------------- /docs/doxygen_footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /python/fastslide/__init__.py: -------------------------------------------------------------------------------- 1 | """FastSlide: High-performance, thread-safe digital pathology slide reader. 2 | 3 | This package provides high-performance reading of whole slide images (WSI) commonly 4 | used in digital pathology. It supports multiple formats including Aperio SVS, 5 | 3DHISTECH MRXS, and QPTIFF. 6 | 7 | Example: 8 | >>> import fastslide 9 | >>> slide = fastslide.FastSlide.from_file_path("slide.mrxs") 10 | >>> region = slide.read_region(location=(0, 0), level=0, size=(1024, 1024)) 11 | >>> print(slide.dimensions) 12 | """ 13 | 14 | __version__ = "0.1.0" 15 | 16 | from fastslide._fastslide import * 17 | 18 | __all__ = [ 19 | "FastSlide", 20 | "CacheManager", 21 | "TileCache", 22 | "RuntimeGlobalCacheManager", 23 | "CacheInspectionStats", 24 | "CacheStats", 25 | "RuntimeCacheStats", 26 | "AssociatedImages", 27 | "AssociatedData", 28 | ] 29 | -------------------------------------------------------------------------------- /python/test_import.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Test fastslide import.""" 3 | 4 | import sys 5 | 6 | try: 7 | import fastslide 8 | 9 | print("✓ Successfully imported fastslide") 10 | print(f"✓ Available attributes: {', '.join([x for x in dir(fastslide) if not x.startswith('_')])}") 11 | 12 | # Try to check if FastSlide class is available 13 | if hasattr(fastslide, "FastSlide"): 14 | print("✓ FastSlide class is available") 15 | else: 16 | print("✗ FastSlide class not found") 17 | sys.exit(1) 18 | 19 | except ImportError as e: 20 | print(f"✗ Failed to import fastslide: {e}") 21 | import traceback 22 | 23 | traceback.print_exc() 24 | sys.exit(1) 25 | except Exception as e: 26 | print(f"✗ Unexpected error: {e}") 27 | import traceback 28 | 29 | traceback.print_exc() 30 | sys.exit(1) 31 | 32 | print("\n✓ All tests passed!") 33 | -------------------------------------------------------------------------------- /include/fastslide/runtime/tile_writer/fs_profile.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RUNTIME_TILE_WRITER_FS_PROFILE_H_ 16 | #define AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RUNTIME_TILE_WRITER_FS_PROFILE_H_ 17 | 18 | #endif // AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RUNTIME_TILE_WRITER_FS_PROFILE_H_ 19 | -------------------------------------------------------------------------------- /docs/source/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /src/fastslide/c/fastslide.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All rights reserved. 2 | // 3 | // This file is part of FastSlide. 4 | // 5 | // Use of this source code is governed by the terms found in the 6 | // LICENSE file located in the FastSlide project root. 7 | 8 | #include "fastslide/c/fastslide.h" 9 | 10 | #include // Required for printf 11 | #include // Required for strlen 12 | #include 13 | 14 | // C API version functions 15 | 16 | const char* fastslide_c_api_get_version(void) { 17 | return "1.0.0"; 18 | } 19 | 20 | int fastslide_initialize(void) { 21 | int result = fastslide_registry_initialize(); 22 | if (result) { 23 | } else { 24 | const char* error = fastslide_get_last_error(); 25 | if (error && strlen(error) > 0) { 26 | printf("[FastSlide C] ERROR: Initialization error: %s\n", error); 27 | } 28 | } 29 | return result; 30 | } 31 | 32 | void fastslide_cleanup(void) { 33 | // Clear any error state 34 | fastslide_clear_last_error(); 35 | 36 | // Note: C++ destructors will handle cleanup automatically 37 | // when global objects go out of scope 38 | } 39 | -------------------------------------------------------------------------------- /docs/source/_static/custom.css: -------------------------------------------------------------------------------- 1 | /* Custom CSS for FastSlide Documentation with RTD Theme */ 2 | 3 | /* Improve code block readability */ 4 | .rst-content pre.literal-block, 5 | .rst-content div[class^="highlight"] pre { 6 | font-size: 14px; 7 | line-height: 1.4; 8 | } 9 | 10 | /* Add some breathing room to the content */ 11 | .rst-content { 12 | max-width: 100%; 13 | } 14 | 15 | /* Style for external links to Doxygen */ 16 | a[href*="doxygen"]::after { 17 | content: " 📚"; 18 | font-size: 0.8em; 19 | } 20 | 21 | /* Improve table styling */ 22 | .rst-content table.docutils { 23 | border: none; 24 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 25 | } 26 | 27 | .rst-content table.docutils thead { 28 | background-color: #2980b9; 29 | color: white; 30 | } 31 | 32 | /* Better spacing for admonitions */ 33 | .rst-content .admonition { 34 | margin: 1.5rem 0; 35 | } 36 | 37 | /* Improve code snippet visibility */ 38 | .rst-content code.literal { 39 | background-color: #f5f5f5; 40 | border: 1px solid #e0e0e0; 41 | padding: 2px 4px; 42 | border-radius: 3px; 43 | } 44 | 45 | /* Style for version badge */ 46 | .version-badge { 47 | display: inline-block; 48 | margin-right: 0.5em; 49 | } 50 | -------------------------------------------------------------------------------- /include/aifocore/utilities/fmt.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #ifndef AIFO_AIFOCORE_INCLUDE_AIFOCORE_UTILITIES_FMT_H_ 15 | #define AIFO_AIFOCORE_INCLUDE_AIFOCORE_UTILITIES_FMT_H_ 16 | 17 | // std::format requires Clang 17+ with libc++ 18 | #if defined(__clang__) && (__clang_major__ >= 17) && __cplusplus >= 202002L && \ 19 | defined(_LIBCPP_VERSION) 20 | #include 21 | 22 | namespace aifocore::fmt { 23 | using std::format; 24 | } 25 | #else 26 | #include 27 | 28 | namespace aifocore::fmt { 29 | using ::fmt::format; // Explicit namespace for fmt to avoid conflicts 30 | } 31 | #endif 32 | 33 | #endif // AIFO_AIFOCORE_INCLUDE_AIFOCORE_UTILITIES_FMT_H_ 34 | -------------------------------------------------------------------------------- /include/fastslide/runtime/tile_writer/blended/resample_mks.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RUNTIME_TILE_WRITER_BLENDED_RESAMPLE_MKS_H_ 16 | #define AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RUNTIME_TILE_WRITER_BLENDED_RESAMPLE_MKS_H_ 17 | 18 | #include "fastslide/runtime/tile_writer/fs_profile.h" 19 | 20 | namespace fastslide::runtime { 21 | 22 | /// @brief Resample tile with subpixel shift using separable MKS convolution 23 | void ResampleTileSubpixel(const float* src_linear_planar, int w, int h, 24 | double frac_x, double frac_y, 25 | float* dst_linear_planar); 26 | 27 | } // namespace fastslide::runtime 28 | 29 | #endif // AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RUNTIME_TILE_WRITER_BLENDED_RESAMPLE_MKS_H_ 30 | -------------------------------------------------------------------------------- /docs/source/formats/index.rst: -------------------------------------------------------------------------------- 1 | Supported File Formats 2 | ====================== 3 | 4 | FastSlide supports multiple whole slide imaging formats commonly used in digital pathology. 5 | Each format has unique characteristics in terms of compression, tile organization, and metadata structure. 6 | 7 | .. toctree:: 8 | :maxdepth: 2 9 | :caption: Format Documentation: 10 | 11 | svs 12 | mrxs 13 | qptiff 14 | 15 | Format Overview 16 | --------------- 17 | 18 | .. list-table:: Supported Formats 19 | :header-rows: 1 20 | :widths: 15 20 20 25 20 21 | 22 | * - Format 23 | - File Extension 24 | - Compression 25 | - Tile Organization 26 | - Special Features 27 | * - Aperio SVS 28 | - .svs 29 | - JPEG (in TIFF) 30 | - Tiled TIFF / Strips 31 | - BigTIFF, associated images 32 | * - MIRAX (MRXS) 33 | - .mrxs 34 | - JPEG / PNG / BMP 35 | - Hierarchical DAT files 36 | - Overlapping tiles, spatial index 37 | * - QPTIFF 38 | - .tif/.tiff 39 | - LZW / JPEG / Uncompressed 40 | - Multi-page TIFF 41 | - Multi-channel spectral imaging 42 | 43 | Format Detection 44 | ---------------- 45 | 46 | FastSlide automatically detects the file format based on the file extension: 47 | 48 | - ``.svs`` → Aperio SVS reader 49 | - ``.mrxs`` → MIRAX (MRXS) reader 50 | - ``.tif``, ``.tiff``, ``.qptiff`` → QPTIFF reader 51 | 52 | The appropriate reader is selected based solely on the filename extension. 53 | -------------------------------------------------------------------------------- /include/fastslide/runtime/tile_writer/direct/fills.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RUNTIME_TILE_WRITER_DIRECT_FILLS_H_ 16 | #define AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RUNTIME_TILE_WRITER_DIRECT_FILLS_H_ 17 | 18 | #include 19 | #include 20 | 21 | #include "fastslide/runtime/tile_writer/fs_profile.h" 22 | 23 | namespace fastslide::runtime { 24 | 25 | void ZeroInit(uint8_t* buf, size_t nbytes); 26 | 27 | void FillRGB8(uint8_t* buf, int w, int h, uint8_t r, uint8_t g, uint8_t b); 28 | 29 | void FillRGBA8(uint8_t* buf, int w, int h, uint8_t r, uint8_t g, uint8_t b, 30 | uint8_t a); 31 | 32 | void FillGray8(uint8_t* buf, int w, int h, uint8_t value); 33 | 34 | } // namespace fastslide::runtime 35 | 36 | #endif // AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RUNTIME_TILE_WRITER_DIRECT_FILLS_H_ 37 | -------------------------------------------------------------------------------- /docs/source/api/index.rst: -------------------------------------------------------------------------------- 1 | API Reference 2 | ============= 3 | 4 | This section provides comprehensive documentation for both the C++ and Python APIs. 5 | 6 | .. toctree:: 7 | :maxdepth: 3 8 | :caption: API Documentation: 9 | 10 | python_api 11 | cpp_api 12 | cross_reference 13 | 14 | Overview 15 | -------- 16 | 17 | FastSlide provides two complementary APIs: 18 | 19 | - **Python API**: High-level interface with NumPy integration, perfect for research and prototyping 20 | - **C++ API**: High-performance native interface for production applications 21 | 22 | The APIs are designed to be consistent - most operations are available in both languages with similar signatures. 23 | 24 | Key Concepts 25 | ~~~~~~~~~~~~ 26 | 27 | **Slide Reader** 28 | Central class for reading whole slide images. Supports multiple formats (MRXS, Aperio SVS, QPTIFF). 29 | 30 | **Tile Cache** 31 | Memory-efficient caching system to speed up repeated tile access. 32 | 33 | **Associated Data** 34 | Additional data embedded in slides (thumbnails, labels, metadata). 35 | 36 | **Multi-level Pyramids** 37 | Slides contain multiple resolution levels for efficient zooming. 38 | 39 | Performance Notes 40 | ~~~~~~~~~~~~~~~~~ 41 | 42 | - Use tile caching for applications that read overlapping regions 43 | - The C++ API provides zero-copy operations where possible 44 | - Python API converts to NumPy arrays for easy integration with scientific libraries 45 | - Both APIs are fully thread-safe 46 | -------------------------------------------------------------------------------- /include/fastslide/runtime/tile_writer/direct/copy_planar.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RUNTIME_TILE_WRITER_DIRECT_COPY_PLANAR_H_ 16 | #define AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RUNTIME_TILE_WRITER_DIRECT_COPY_PLANAR_H_ 17 | 18 | #include 19 | 20 | #include "fastslide/runtime/tile_writer/fs_profile.h" 21 | 22 | namespace fastslide::runtime { 23 | 24 | /// @brief Copy planar tile data (e.g., QPTIFF spectral) 25 | void CopyTilePlanar(const uint8_t* tile, int tile_w, int tile_h, int src_x, 26 | int src_y, uint8_t* img, int img_w, int img_h, int dst_x, 27 | int dst_y, int copy_w, int copy_h, int target_channel, 28 | int total_channels, int bytes_per_sample); 29 | 30 | } // namespace fastslide::runtime 31 | 32 | #endif // AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RUNTIME_TILE_WRITER_DIRECT_COPY_PLANAR_H_ 33 | -------------------------------------------------------------------------------- /include/fastslide/readers/aperio/aperio_format_plugin.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_READERS_APERIO_APERIO_FORMAT_PLUGIN_H_ 16 | #define AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_READERS_APERIO_APERIO_FORMAT_PLUGIN_H_ 17 | 18 | #include 19 | 20 | #include "fastslide/runtime/format_descriptor.h" 21 | 22 | /** 23 | * @file aperio_format_plugin.h 24 | * @brief Aperio format plugin descriptor 25 | * 26 | * This header provides the format descriptor for Aperio slides. 27 | */ 28 | 29 | namespace fastslide::formats::aperio { 30 | 31 | /// @brief Create Aperio format descriptor 32 | FormatDescriptor CreateAperioFormatDescriptor(); 33 | 34 | /// @brief Helper function to register Aperio format 35 | inline FormatDescriptor RegisterAperioFormat() { 36 | return CreateAperioFormatDescriptor(); 37 | } 38 | 39 | } // namespace fastslide::formats::aperio 40 | 41 | #endif // AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_READERS_APERIO_APERIO_FORMAT_PLUGIN_H_ 42 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["meson-python>=0.15.0", "ninja"] 3 | build-backend = "mesonpy" 4 | 5 | [project] 6 | name = "fastslide" 7 | version = "0.1.0" 8 | description = "High-performance whole slide image reader for digital pathology" 9 | readme = "README.md" 10 | authors = [ 11 | {name = "Jonas Teuwen", email = "j.teuwen@nki.nl"} 12 | ] 13 | license = {text = "Apache-2.0"} 14 | requires-python = ">=3.11" 15 | classifiers = [ 16 | "Development Status :: 4 - Beta", 17 | "Intended Audience :: Science/Research", 18 | "Intended Audience :: Healthcare Industry", 19 | "License :: OSI Approved :: Apache Software License", 20 | "Programming Language :: C++", 21 | "Programming Language :: Python :: 3", 22 | "Programming Language :: Python :: 3.11", 23 | "Programming Language :: Python :: 3.12", 24 | "Topic :: Scientific/Engineering :: Medical Science Apps.", 25 | "Topic :: Scientific/Engineering :: Image Processing", 26 | ] 27 | dependencies = [ 28 | "numpy>=1.20", 29 | ] 30 | 31 | [project.optional-dependencies] 32 | dev = [ 33 | "pytest>=7.0", 34 | "pytest-cov", 35 | ] 36 | 37 | [project.urls] 38 | Homepage = "https://github.com/NKI-AI/fastslide" 39 | Documentation = "https://docs.aifo.dev/fastslide/" 40 | Repository = "https://github.com/NKI-AI/fastslide" 41 | Issues = "https://github.com/NKI-AI/fastslide/issues" 42 | 43 | [tool.meson-python.args] 44 | setup = [ 45 | "--wrap-mode=forcefallback", 46 | "--prefer-static", 47 | ] 48 | 49 | [tool.pytest.ini_options] 50 | testpaths = ["tests"] 51 | python_files = ["test_*.py", "*_test.py"] 52 | 53 | -------------------------------------------------------------------------------- /include/fastslide/readers/qptiff/qptiff_format_plugin.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_READERS_QPTIFF_QPTIFF_FORMAT_PLUGIN_H_ 16 | #define AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_READERS_QPTIFF_QPTIFF_FORMAT_PLUGIN_H_ 17 | 18 | #include 19 | 20 | #include "fastslide/runtime/format_descriptor.h" 21 | 22 | /** 23 | * @file qptiff_format_plugin.h 24 | * @brief QPTIFF format plugin descriptor 25 | * 26 | * This header provides the format descriptor for QPTIFF (PerkinElmer) slides. 27 | */ 28 | 29 | namespace fastslide { 30 | namespace formats { 31 | namespace qptiff { 32 | 33 | /// @brief Create QPTIFF format descriptor 34 | FormatDescriptor CreateQptiffFormatDescriptor(); 35 | 36 | /// @brief Helper function to register QPTIFF format 37 | inline FormatDescriptor RegisterQptiffFormat() { 38 | return CreateQptiffFormatDescriptor(); 39 | } 40 | 41 | } // namespace qptiff 42 | } // namespace formats 43 | } // namespace fastslide 44 | 45 | #endif // AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_READERS_QPTIFF_QPTIFF_FORMAT_PLUGIN_H_ 46 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | # FastSlide Meson Build Options 2 | # Configuration options for the FastSlide build system with static linking 3 | 4 | # Note: default_library, warning_level, werror are built-in Meson options 5 | # They can be set via: meson setup builddir -Ddefault_library=static -Dwarning_level=3 6 | 7 | # Feature options 8 | option('enable_tests', type: 'boolean', value: true, 9 | description: 'Enable building and running tests') 10 | 11 | option('enable_python_bindings', type: 'boolean', value: true, 12 | description: 'Enable Python bindings (requires pybind11)') 13 | 14 | option('enable_c_api', type: 'boolean', value: true, 15 | description: 'Enable C API library') 16 | 17 | option('enable_tools', type: 'boolean', value: true, 18 | description: 'Enable building tools (slidetool)') 19 | 20 | # Static linking options 21 | option('static_linking', type: 'boolean', value: true, 22 | description: 'Enable static linking for monolithic shared library') 23 | 24 | # Note: b_staticpic is a built-in Meson option 25 | # Set via: meson setup builddir -Db_staticpic=true 26 | 27 | # Installation options 28 | option('install_headers', type: 'boolean', value: true, 29 | description: 'Install header files') 30 | 31 | option('install_pkgconfig', type: 'boolean', value: true, 32 | description: 'Install pkg-config files') 33 | 34 | # Development options 35 | option('enable_debug', type: 'boolean', value: false, 36 | description: 'Enable debug symbols and assertions') 37 | 38 | option('enable_coverage', type: 'boolean', value: false, 39 | description: 'Enable code coverage instrumentation') 40 | 41 | option('enable_sanitizers', type: 'combo', choices: ['none', 'address', 'thread', 'undefined'], value: 'none', 42 | description: 'Enable sanitizers for debugging') 43 | -------------------------------------------------------------------------------- /include/fastslide/runtime/tile_writer/direct/copy_rgb8.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RUNTIME_TILE_WRITER_DIRECT_COPY_RGB8_H_ 16 | #define AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RUNTIME_TILE_WRITER_DIRECT_COPY_RGB8_H_ 17 | 18 | #include 19 | 20 | #include "fastslide/runtime/tile_writer/fs_profile.h" 21 | 22 | namespace fastslide::runtime { 23 | 24 | /// @brief Fast path: Copy RGB8 tile rectangle with row-wise memcpy 25 | void CopyTileRectRGB8(const uint8_t* tile, int tile_w, int tile_h, int src_x, 26 | int src_y, uint8_t* img, int img_w, int img_h, int dst_x, 27 | int dst_y, int copy_w, int copy_h); 28 | 29 | /// @brief Slow path: Generic interleaved copy with channel/format mismatches 30 | void CopyRectGeneral(const uint8_t* tile, int tile_w, int tile_h, 31 | int tile_channels, int src_x, int src_y, uint8_t* img, 32 | int img_w, int img_h, int img_channels, int dst_x, 33 | int dst_y, int copy_w, int copy_h, int bytes_per_sample); 34 | 35 | } // namespace fastslide::runtime 36 | 37 | #endif // AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RUNTIME_TILE_WRITER_DIRECT_COPY_RGB8_H_ 38 | -------------------------------------------------------------------------------- /include/fastslide/resample/utilities.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RESAMPLE_UTILITIES_H_ 16 | #define AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RESAMPLE_UTILITIES_H_ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | namespace fastslide::resample { 24 | 25 | template 26 | concept ArithmeticType = std::is_arithmetic_v; 27 | 28 | /// @brief Clamp a double back into type T's range 29 | template 30 | constexpr T ClampValue(double value) noexcept { 31 | if constexpr (std::is_floating_point_v) { 32 | return static_cast(value); 33 | } else if constexpr (std::is_unsigned_v) { 34 | constexpr double max_val = 35 | static_cast(std::numeric_limits::max()); 36 | return static_cast(std::clamp(value, 0.0, max_val)); 37 | } else { 38 | constexpr double min_val = 39 | static_cast(std::numeric_limits::min()); 40 | constexpr double max_val = 41 | static_cast(std::numeric_limits::max()); 42 | return static_cast(std::clamp(value, min_val, max_val)); 43 | } 44 | } 45 | 46 | } // namespace fastslide::resample 47 | 48 | #endif // AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RESAMPLE_UTILITIES_H_ 49 | -------------------------------------------------------------------------------- /.github/workflows/mirror-issues-to-internal-monorepo.yaml: -------------------------------------------------------------------------------- 1 | name: Mirror issues to internal 2 | 3 | on: 4 | issues: 5 | types: [opened] 6 | 7 | jobs: 8 | mirror: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Mirror to aiforoncology-internal using gh 12 | env: 13 | # PAT with Issues: Read/Write on NKI-AI/aiforoncology-internal 14 | GH_TOKEN: ${{ secrets.INTERNAL_REPO_PAT }} 15 | REPO_INTERNAL: NKI-AI/aiforoncology-internal 16 | 17 | SRC_REPO: ${{ github.repository }} 18 | SRC_NUM: ${{ github.event.issue.number }} 19 | SRC_TITLE: ${{ github.event.issue.title }} 20 | SRC_BODY: ${{ github.event.issue.body }} 21 | shell: bash 22 | run: | 23 | # Ensure gh is available 24 | if ! command -v gh >/dev/null 2>&1; then 25 | sudo apt-get update && sudo apt-get install -y gh 26 | fi 27 | 28 | TITLE="External[FastSlide]: ${SRC_TITLE}" 29 | 30 | # Compose body with backlink + unique marker 31 | cat > /tmp/body.md < 19 | #include 20 | #include 21 | #include 22 | 23 | #include "absl/status/status.h" 24 | #include "absl/status/statusor.h" 25 | #include "fastslide/core/tile_plan.h" 26 | #include "fastslide/image.h" 27 | 28 | namespace fastslide::runtime { 29 | 30 | /// @brief Pure virtual interface for tile writing strategies 31 | class ITileWriterStrategy { 32 | public: 33 | virtual ~ITileWriterStrategy() = default; 34 | 35 | virtual absl::Status WriteTile(const core::TileReadOp& op, 36 | std::span pixel_data, 37 | uint32_t tile_width, uint32_t tile_height, 38 | uint32_t tile_channels) = 0; 39 | 40 | virtual absl::Status Finalize() = 0; 41 | virtual ImageDimensions GetDimensions() const = 0; 42 | virtual uint32_t GetChannels() const = 0; 43 | virtual absl::StatusOr GetOutput() = 0; 44 | virtual std::string GetName() const = 0; 45 | 46 | virtual uint8_t* GetOutputBuffer() = 0; 47 | virtual size_t GetOutputBufferSize() const = 0; 48 | }; 49 | 50 | } // namespace fastslide::runtime 51 | 52 | #endif // AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RUNTIME_TILE_WRITER_TILE_WRITER_STRATEGY_H_ 53 | -------------------------------------------------------------------------------- /src/fastslide/runtime/tile_writer/direct/copy_planar.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "fastslide/runtime/tile_writer/direct/copy_planar.h" 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | namespace fastslide::runtime { 22 | 23 | void CopyTilePlanar(const uint8_t* tile, int tile_w, int tile_h, int src_x, 24 | int src_y, uint8_t* img, int img_w, int img_h, int dst_x, 25 | int dst_y, int copy_w, int copy_h, int target_channel, 26 | int total_channels, int bytes_per_sample) { 27 | const size_t pixels_per_channel = static_cast(img_w) * img_h; 28 | const size_t channel_plane_offset = 29 | target_channel * pixels_per_channel * bytes_per_sample; 30 | 31 | for (int y = 0; y < copy_h; ++y) { 32 | const int tile_row = src_y + y; 33 | const int img_row = dst_y + y; 34 | 35 | if (tile_row >= tile_h || img_row >= img_h) 36 | continue; 37 | 38 | for (int x = 0; x < copy_w; ++x) { 39 | const int tile_col = src_x + x; 40 | const int img_col = dst_x + x; 41 | 42 | if (tile_col >= tile_w || img_col >= img_w) 43 | continue; 44 | 45 | const size_t src_offset = 46 | (tile_row * tile_w + tile_col) * bytes_per_sample; 47 | const size_t dst_offset = 48 | channel_plane_offset + (img_row * img_w + img_col) * bytes_per_sample; 49 | 50 | std::memcpy(img + dst_offset, tile + src_offset, bytes_per_sample); 51 | } 52 | } 53 | } 54 | 55 | } // namespace fastslide::runtime 56 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | FastSlide Documentation 2 | ======================= 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | :caption: Contents: 7 | 8 | formats/index 9 | api/index 10 | 11 | About FastSlide 12 | --------------- 13 | 14 | FastSlide is a high-performance C++ library for reading whole slide images (WSI) in digital pathology, 15 | with native Python bindings for integration into machine learning and scientific computing workflows. 16 | 17 | **Design Goals:** 18 | 19 | - **Fast C++ Core**: Built with modern C++20 for maximum performance 20 | - **Native Python Bindings**: Zero-overhead integration using pybind11 21 | - **Thread-Safe**: Safe for multi-threaded applications and PyTorch DataLoaders 22 | - **Format Support**: MRXS (3DHistech), Aperio SVS, and QPTIFF formats 23 | 24 | **Documentation:** 25 | 26 | - **Python API**: :doc:`api/python_api` - Complete Python interface documentation 27 | - **C++ API**: :doc:`api/cpp_api` - C++ interface overview, or see `Doxygen Documentation <../doxygen/index.html>`_ for full details 28 | - **Format Support**: :doc:`formats/index` - Supported file formats 29 | 30 | Quick Start 31 | ----------- 32 | 33 | Python API 34 | ~~~~~~~~~~ 35 | 36 | .. code-block:: python 37 | 38 | import fastslide 39 | 40 | # Open a slide 41 | slide = fastslide.SlideReader("slide.mrxs") 42 | 43 | # Read a region 44 | region = slide.read_region((0, 0), 0, (1024, 1024)) 45 | 46 | # Get slide properties 47 | print(f"Slide dimensions: {slide.dimensions}") 48 | print(f"Available levels: {len(slide.level_dimensions)}") 49 | 50 | C++ API 51 | ~~~~~~~ 52 | 53 | .. code-block:: cpp 54 | 55 | #include 56 | 57 | auto reader = fastslide::SlideReader::Open("slide.mrxs"); 58 | if (!reader) { 59 | // Handle error 60 | return; 61 | } 62 | 63 | // Read a region at level 0 64 | auto image = reader->ReadRegion(0, {0, 0}, {1024, 1024}); 65 | 66 | // Get slide properties 67 | auto properties = reader->GetProperties(); 68 | std::cout << "Slide dimensions: " 69 | << properties.base_dimensions.width << "x" 70 | << properties.base_dimensions.height << std::endl; -------------------------------------------------------------------------------- /include/fastslide/readers/readers.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_READERS_READERS_H_ 16 | #define AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_READERS_READERS_H_ 17 | 18 | #include 19 | #include 20 | 21 | #include "fastslide/readers/mrxs/mrxs_format_plugin.h" 22 | #include "fastslide/readers/qptiff/qptiff_format_plugin.h" 23 | 24 | #include "fastslide/readers/aperio/aperio_format_plugin.h" // SVS format not implemented yet 25 | 26 | /** 27 | * @file readers.h 28 | * @brief Convenience header for all format plugins 29 | * 30 | * This header includes all built-in format plugins, providing a single 31 | * include point for applications that want to register all formats. 32 | */ 33 | 34 | namespace fastslide { 35 | namespace readers { 36 | 37 | /// @brief Get all built-in format descriptors 38 | /// 39 | /// Returns a vector of all built-in format descriptors that can be 40 | /// registered with the reader registry. 41 | /// 42 | /// Example usage: 43 | /// @code 44 | /// auto descriptors = GetBuiltinFormats(); 45 | /// for (const auto& desc : descriptors) { 46 | /// registry.RegisterFormat(desc); 47 | /// } 48 | /// @endcode 49 | inline std::vector GetBuiltinFormats() { 50 | return { 51 | formats::mrxs::CreateMrxsFormatDescriptor(), 52 | formats::qptiff::CreateQptiffFormatDescriptor(), 53 | formats::aperio::CreateAperioFormatDescriptor(), 54 | }; 55 | } 56 | 57 | } // namespace readers 58 | } // namespace fastslide 59 | 60 | #endif // AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_READERS_READERS_H_ 61 | -------------------------------------------------------------------------------- /include/fastslide/runtime/tile_writer/blended/mks_kernel.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RUNTIME_TILE_WRITER_BLENDED_MKS_KERNEL_H_ 16 | #define AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RUNTIME_TILE_WRITER_BLENDED_MKS_KERNEL_H_ 17 | 18 | #include 19 | 20 | namespace fastslide::runtime { 21 | 22 | constexpr int kMksRadius = 5; 23 | 24 | /// @brief Build 1D Magic Kernel for fractional shift 25 | std::array BuildKernel(double frac); 26 | 27 | /// @brief Reflect index at boundaries (symmetric extension) 28 | /// Optimized for common case where index is slightly out of bounds 29 | inline int ReflectIndex(int idx, int size) { 30 | if (size <= 0) { 31 | return 0; 32 | } 33 | if (size == 1) { 34 | return 0; 35 | } 36 | 37 | // Common case: idx is in bounds 38 | if (idx >= 0 && idx < size) { 39 | return idx; 40 | } 41 | 42 | const int period = 2 * (size - 1); 43 | 44 | // Fast reflection for slightly negative indices 45 | if (idx < 0 && idx >= -size) { 46 | return -idx; 47 | } 48 | 49 | // Fast reflection for slightly over-bounds indices 50 | if (idx >= size && idx < 2 * size) { 51 | return period - idx; 52 | } 53 | 54 | // Rare case: need full reflection with modulo 55 | int reflected_idx = (idx < 0) ? -idx : (idx - size); 56 | reflected_idx = reflected_idx % period; 57 | 58 | if (reflected_idx >= size) { 59 | reflected_idx = period - reflected_idx; 60 | } 61 | 62 | return reflected_idx; 63 | } 64 | 65 | } // namespace fastslide::runtime 66 | 67 | #endif // AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RUNTIME_TILE_WRITER_BLENDED_MKS_KERNEL_H_ 68 | -------------------------------------------------------------------------------- /include/fastslide/readers/mrxs/mrxs_format_plugin.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_READERS_MRXS_MRXS_FORMAT_PLUGIN_H_ 16 | #define AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_READERS_MRXS_MRXS_FORMAT_PLUGIN_H_ 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #include "absl/status/statusor.h" 23 | #include "fastslide/runtime/format_descriptor.h" 24 | #include "fastslide/slide_reader.h" 25 | 26 | /** 27 | * @file mrxs_format_plugin.h 28 | * @brief MRXS format plugin descriptor 29 | * 30 | * This header provides the format descriptor for MRXS (3DHISTECH) slides. 31 | * It encapsulates all MRXS-specific logic in a self-contained plugin that 32 | * can be registered with the reader registry. 33 | */ 34 | 35 | namespace fastslide::formats::mrxs { 36 | 37 | /// @brief Create MRXS format descriptor 38 | /// 39 | /// Creates a FormatDescriptor for the MRXS format with appropriate 40 | /// capabilities, extensions, and factory function. 41 | /// 42 | /// @return FormatDescriptor for MRXS format 43 | FormatDescriptor CreateMrxsFormatDescriptor(); 44 | 45 | /// @brief Helper function to register MRXS format 46 | /// 47 | /// Convenience function that creates and returns a descriptor ready for 48 | /// registration with the reader registry. 49 | /// 50 | /// Example usage: 51 | /// @code 52 | /// auto descriptor = RegisterMrxsFormat(); 53 | /// registry.RegisterFormat(descriptor); 54 | /// @endcode 55 | inline FormatDescriptor RegisterMrxsFormat() { 56 | return CreateMrxsFormatDescriptor(); 57 | } 58 | 59 | } // namespace fastslide::formats::mrxs 60 | 61 | #endif // AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_READERS_MRXS_MRXS_FORMAT_PLUGIN_H_ 62 | -------------------------------------------------------------------------------- /include/fastslide/readers/mrxs/mrxs_decoder.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_READERS_MRXS_MRXS_DECODER_H_ 16 | #define AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_READERS_MRXS_MRXS_DECODER_H_ 17 | 18 | #include 19 | #include 20 | 21 | #include "absl/status/statusor.h" 22 | #include "fastslide/image.h" 23 | #include "fastslide/readers/mrxs/mrxs_internal.h" 24 | 25 | namespace fastslide { 26 | namespace mrxs { 27 | namespace internal { 28 | 29 | /// @brief Decode compressed image data into an RGB image 30 | /// @param data Compressed image data 31 | /// @param format Image format (JPEG/PNG/BMP) 32 | /// @return StatusOr containing decoded RGB image or error 33 | absl::StatusOr DecodeImage(const std::vector& data, 34 | MrxsImageFormat format); 35 | 36 | /// @brief Decode JPEG image data 37 | /// @param data JPEG compressed data 38 | /// @return StatusOr containing decoded RGB image or error 39 | absl::StatusOr DecodeJpeg(const std::vector& data); 40 | 41 | /// @brief Decode PNG image data (using lodepng) 42 | /// @param data PNG compressed data 43 | /// @return StatusOr containing decoded RGB image or error 44 | absl::StatusOr DecodePng(const std::vector& data); 45 | 46 | /// @brief Decode BMP image data (simple, uncompressed only) 47 | /// @param data BMP data 48 | /// @return StatusOr containing decoded RGB image or error 49 | absl::StatusOr DecodeBmp(const std::vector& data); 50 | 51 | } // namespace internal 52 | } // namespace mrxs 53 | } // namespace fastslide 54 | 55 | #endif // AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_READERS_MRXS_MRXS_DECODER_H_ 56 | -------------------------------------------------------------------------------- /include/aifocore/shared/exceptions.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #ifndef AIFO_AIFOCORE_INCLUDE_AIFOCORE_SHARED_EXCEPTIONS_H_ 15 | #define AIFO_AIFOCORE_INCLUDE_AIFOCORE_SHARED_EXCEPTIONS_H_ 16 | 17 | #include 18 | #include 19 | 20 | namespace aifocore::shared::exceptions { 21 | 22 | // Base class for memory-related errors 23 | class MemoryError : public std::runtime_error { 24 | public: 25 | explicit MemoryError(const std::string& message) 26 | : std::runtime_error(message) {} 27 | }; 28 | 29 | // Specific out-of-memory error, subclass of MemoryError 30 | class OutOfMemoryError : public MemoryError { 31 | public: 32 | explicit OutOfMemoryError( 33 | const std::string& message = "Not enough memory to append array") 34 | : MemoryError(message) {} 35 | }; 36 | 37 | // For errors related to block in use 38 | class InUseError : public std::runtime_error { 39 | public: 40 | explicit InUseError( 41 | const std::string& message = "Replace blocked by writer process") 42 | : std::runtime_error(message) {} 43 | }; 44 | 45 | // For errors related to array sizes 46 | class ArraySizeError : public std::runtime_error { 47 | public: 48 | explicit ArraySizeError( 49 | const std::string& message = "Array size exceeds chunk size") 50 | : std::runtime_error(message) {} 51 | }; 52 | 53 | // For data modification errors with customizable message 54 | class DataModifiedError : public std::runtime_error { 55 | public: 56 | explicit DataModifiedError( 57 | const std::string& message = "Data was modified by another process") 58 | : std::runtime_error(message) {} 59 | }; 60 | 61 | } // namespace aifocore::shared::exceptions 62 | 63 | #endif // AIFO_AIFOCORE_INCLUDE_AIFOCORE_SHARED_EXCEPTIONS_H_ 64 | -------------------------------------------------------------------------------- /docs/source/formats/svs.rst: -------------------------------------------------------------------------------- 1 | Aperio SVS Format 2 | ================= 3 | 4 | Aperio SVS is a widely-adopted format in digital pathology, originally developed by Aperio Technologies 5 | (acquired by Leica Biosystems). SVS files are essentially BigTIFF files containing pyramidal whole slide 6 | images compressed with JPEG. 7 | 8 | Format Specification 9 | -------------------- 10 | 11 | **File Extension:** ``.svs`` 12 | 13 | **Detection:** FastSlide detects SVS files based solely on the ``.svs`` file extension. 14 | 15 | File Structure 16 | -------------- 17 | 18 | An SVS file is a multi-page TIFF containing pyramid levels and associated images: 19 | 20 | .. code-block:: text 21 | 22 | slide.svs (BigTIFF) 23 | ├── Page 0: Base level (full resolution, tiled, JPEG) 24 | ├── Page 1: Thumbnail preview 25 | ├── Page 2: Pyramid level 1 (downsampled) 26 | ├── Page 3: Pyramid level 2 (downsampled further) 27 | ├── Page 4: Macro photograph of the slide 28 | └── Page 5: Label image with barcode 29 | 30 | The first page contains the full-resolution scan, typically at 20× or 40× magnification. Subsequent pages 31 | contain progressively downsampled versions for efficient viewing at lower resolutions. Each level is typically 32 | downsampled by a factor of 2-4 from the previous level. 33 | 34 | Associated images (thumbnail, macro, label) provide additional context about the physical slide. 35 | 36 | Technical Details 37 | ----------------- 38 | 39 | **Compression and Storage:** 40 | 41 | - Tiles are typically 240×240 pixels 42 | - JPEG compression using the OLD_JPEG TIFF tag 43 | - YCbCr color space (automatically converted to RGB when reading) 44 | - 8 bits per channel, 3 channels (RGB) 45 | 46 | **Data Organization:** 47 | 48 | Main pyramid levels use a tiled format for random access, while associated images typically use a 49 | stripped format for sequential reading. 50 | 51 | Metadata 52 | -------- 53 | 54 | Metadata is stored in the ImageDescription TIFF tag as a pipe-delimited text format: 55 | 56 | .. code-block:: text 57 | 58 | Aperio Image Library v11.2.1 59 | 46000x32914 [0,100 46000x32914] (256x256) JPEG/RGB Q=30 60 | |AppMag = 20 61 | |MPP = 0.4990 62 | |Date = 12/29/09 63 | |Time = 09:59:15 64 | |ScanScope ID = CPAPERIOCS 65 | 66 | References 67 | ---------- 68 | 69 | - `OpenSlide Aperio Documentation `_ 70 | - `BigTIFF Specification `_ 71 | -------------------------------------------------------------------------------- /src/fastslide/runtime/tile_writer/blended/mks_kernel.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "fastslide/runtime/tile_writer/blended/mks_kernel.h" 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | namespace fastslide::runtime { 23 | 24 | namespace { 25 | 26 | constexpr int kMksLutRes = 2000; 27 | constexpr double kMksSupport = 4.5; 28 | 29 | constexpr int MksLutSize() { 30 | return static_cast(kMksSupport * kMksLutRes) + 2; 31 | } 32 | 33 | inline double MagicKernelSharp2021(double x) { 34 | if (x < 0.0) 35 | x = -x; 36 | if (x <= 0.5) 37 | return (577.0 / 576.0) - (239.0 / 144.0) * x * x; 38 | if (x <= 1.5) 39 | return (1.0 / 144.0) * (140.0 * x * x - 379.0 * x + 239.0); 40 | if (x <= 2.5) 41 | return -(1.0 / 144.0) * (24.0 * x * x - 113.0 * x + 130.0); 42 | if (x <= 3.5) 43 | return (1.0 / 144.0) * (4.0 * x * x - 27.0 * x + 45.0); 44 | if (x <= 4.5) { 45 | const double t = 2.0 * x - 9.0; 46 | return -(1.0 / 1152.0) * t * t; 47 | } 48 | return 0.0; 49 | } 50 | 51 | inline const std::vector& MksLut() { 52 | static std::vector lut = [] { 53 | std::vector v(MksLutSize()); 54 | for (int i = 0; i < static_cast(v.size()); ++i) { 55 | v[i] = static_cast( 56 | MagicKernelSharp2021(static_cast(i) / kMksLutRes)); 57 | } 58 | return v; 59 | }(); 60 | return lut; 61 | } 62 | 63 | } // namespace 64 | 65 | std::array BuildKernel(double frac) { 66 | std::array kernel{}; 67 | const auto& lut = MksLut(); 68 | 69 | for (int t = -kMksRadius; t <= kMksRadius; ++t) { 70 | const double dist = std::abs(static_cast(t) - frac); 71 | const int idx = std::min(static_cast(std::llround(dist * kMksLutRes)), 72 | static_cast(lut.size()) - 1); 73 | kernel[t + kMksRadius] = lut[idx]; 74 | } 75 | 76 | return kernel; 77 | } 78 | 79 | } // namespace fastslide::runtime 80 | -------------------------------------------------------------------------------- /src/aifocore/utilities/thread_pool_singleton.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "aifocore/utilities/thread_pool_singleton.h" 16 | 17 | #include 18 | #include 19 | 20 | #include "aifocore/utilities/bs_thread_pool.h" 21 | 22 | namespace aifocore { 23 | 24 | namespace { 25 | 26 | /// @brief Get thread count from environment variable or default 27 | /// 28 | /// Checks NUM_THREADS environment variable (OpenMP-style): 29 | /// - If not set: returns 0 (use hardware_concurrency) 30 | /// - If set to valid positive number: returns that value 31 | /// - If set to 0 or 1: returns 1 (single-threaded/no parallelism) 32 | /// - If invalid: returns 0 (use hardware_concurrency) 33 | /// 34 | /// @return Thread count to use (0 = hardware_concurrency) 35 | std::size_t GetThreadCountFromEnv() { 36 | const char* env_value = std::getenv("NUM_THREADS"); 37 | if (env_value == nullptr) { 38 | return 0; // Not set, use default 39 | } 40 | 41 | // Try to parse as integer 42 | char* end = nullptr; 43 | const long value = std::strtol(env_value, &end, 10); 44 | 45 | // Check if parsing was successful and value is reasonable 46 | if (end == env_value || *end != '\0' || value < 0) { 47 | // Invalid value, use default 48 | return 0; 49 | } 50 | 51 | // Return the parsed value (0 will be interpreted as hardware_concurrency by BS::thread_pool) 52 | return static_cast(value); 53 | } 54 | 55 | } // namespace 56 | 57 | BS::light_thread_pool& ThreadPoolManager::GetInstance() { 58 | return GetPool(0); 59 | } 60 | 61 | void ThreadPoolManager::SetThreadCount(std::size_t count) { 62 | GetPool(count).reset(count); 63 | } 64 | 65 | BS::light_thread_pool& ThreadPoolManager::GetPool(std::size_t count) { 66 | // Thread-safe static initialization (C++11 guaranteed) 67 | // On first call, check environment variable for thread count override 68 | static const std::size_t env_thread_count = GetThreadCountFromEnv(); 69 | static BS::light_thread_pool pool(count == 0 ? env_thread_count : count); 70 | return pool; 71 | } 72 | 73 | } // namespace aifocore 74 | -------------------------------------------------------------------------------- /include/fastslide/python/cache.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #pragma once 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "absl/status/status.h" 24 | #include "absl/status/statusor.h" 25 | #include "fastslide/utilities/cache.h" 26 | 27 | namespace fastslide::python { 28 | 29 | using fastslide::GlobalTileCache; 30 | using fastslide::TileCache; 31 | 32 | /// @brief Enhanced cache statistics for Python inspection 33 | struct CacheInspectionStats { 34 | size_t capacity; 35 | size_t size; 36 | size_t hits; 37 | size_t misses; 38 | double hit_ratio; 39 | double memory_usage_mb; 40 | std::vector recent_keys; 41 | std::unordered_map key_frequencies; 42 | }; 43 | 44 | /// @brief Cache manager with inspection capabilities 45 | class CacheManager { 46 | private: 47 | std::shared_ptr cache_; 48 | 49 | public: 50 | /// @brief Create a CacheManager with given capacity 51 | /// @param capacity Cache capacity 52 | /// @return StatusOr containing the CacheManager 53 | [[nodiscard]] static absl::StatusOr> Create( 54 | size_t capacity = 1000); 55 | 56 | [[nodiscard]] std::shared_ptr GetCache() const; 57 | 58 | void Clear(); 59 | 60 | [[nodiscard]] TileCache::Stats GetBasicStats() const; 61 | 62 | [[nodiscard]] CacheInspectionStats GetDetailedStats() const; 63 | 64 | [[nodiscard]] absl::Status Resize(size_t new_capacity); 65 | 66 | private: 67 | explicit CacheManager(std::shared_ptr cache); 68 | }; 69 | 70 | /// @brief Global cache manager singleton 71 | class GlobalCacheManager { 72 | public: 73 | [[nodiscard]] static GlobalCacheManager& Instance(); 74 | 75 | [[nodiscard]] std::shared_ptr GetCache(); 76 | 77 | [[nodiscard]] absl::Status SetCapacity(size_t capacity); 78 | 79 | [[nodiscard]] TileCache::Stats GetStats(); 80 | 81 | [[nodiscard]] CacheInspectionStats GetDetailedStats(); 82 | 83 | void Clear(); 84 | }; 85 | 86 | } // namespace fastslide::python 87 | -------------------------------------------------------------------------------- /include/fastslide/runtime/io/binary_utils.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RUNTIME_IO_BINARY_UTILS_H_ 16 | #define AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RUNTIME_IO_BINARY_UTILS_H_ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "absl/status/statusor.h" 24 | 25 | /** 26 | * @file binary_utils.h 27 | * @brief Binary I/O utility functions 28 | * 29 | * Provides common binary I/O operations like reading integers in specific 30 | * endianness and decompressing data. 31 | */ 32 | 33 | namespace fastslide { 34 | namespace runtime { 35 | namespace io { 36 | 37 | /// @brief Read a little-endian 32-bit integer from a file 38 | /// @param file File pointer (must be open for reading) 39 | /// @return 32-bit integer value or error 40 | /// @note Does not change file pointer on error 41 | absl::StatusOr ReadLeInt32(FILE* file); 42 | 43 | /// @brief Read a little-endian 32-bit unsigned integer from a file 44 | /// @param file File pointer (must be open for reading) 45 | /// @return 32-bit unsigned integer value or error 46 | /// @note Does not change file pointer on error 47 | absl::StatusOr ReadLeUInt32(FILE* file); 48 | 49 | /// @brief Decompress zlib-compressed data 50 | /// @param data Pointer to compressed data 51 | /// @param compressed_size Size of compressed data in bytes 52 | /// @param expected_size Expected size of decompressed data in bytes 53 | /// @return Decompressed data or error 54 | /// @note Uses zlib inflate() for decompression 55 | absl::StatusOr> DecompressZlib(const uint8_t* data, 56 | size_t compressed_size, 57 | size_t expected_size); 58 | 59 | } // namespace io 60 | } // namespace runtime 61 | 62 | // Import into fastslide namespace for convenience 63 | using runtime::io::DecompressZlib; 64 | using runtime::io::ReadLeInt32; 65 | using runtime::io::ReadLeUInt32; 66 | 67 | } // namespace fastslide 68 | 69 | #endif // AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RUNTIME_IO_BINARY_UTILS_H_ 70 | -------------------------------------------------------------------------------- /include/aifocore/ranges/zip.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #ifndef AIFO_AIFOCORE_INCLUDE_AIFOCORE_RANGES_ZIP_H_ 15 | #define AIFO_AIFOCORE_INCLUDE_AIFOCORE_RANGES_ZIP_H_ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | namespace aifocore::ranges { 23 | 24 | // Custom iterator for zip that stores a tuple of iterators 25 | template 26 | class zip_iterator { 27 | public: 28 | using value_type = 29 | std::tuple::reference...>; 30 | using reference = value_type; 31 | using difference_type = std::ptrdiff_t; 32 | using iterator_category = std::input_iterator_tag; 33 | 34 | explicit zip_iterator(Iterators... iterators) : iterators_(iterators...) {} 35 | 36 | zip_iterator& operator++() { 37 | std::apply([](auto&... iters) { ((++iters), ...); }, iterators_); 38 | return *this; 39 | } 40 | 41 | reference operator*() const { 42 | return std::apply([](auto&... iters) { return std::tie(*iters...); }, 43 | iterators_); 44 | } 45 | 46 | bool operator==(const zip_iterator& other) const { 47 | return iterators_ == other.iterators_; 48 | } 49 | 50 | bool operator!=(const zip_iterator& other) const { return !(*this == other); } 51 | 52 | private: 53 | std::tuple iterators_; 54 | }; 55 | 56 | // zip_view that holds the begin and end iterators 57 | template 58 | class zip_view { 59 | public: 60 | explicit zip_view(Containers&... containers) 61 | : begin_(std::begin(containers)...), end_(std::end(containers)...) {} 62 | 63 | auto begin() const { return begin_; } 64 | 65 | auto end() const { return end_; } 66 | 67 | private: 68 | zip_iterator()))...> begin_; 69 | zip_iterator()))...> end_; 70 | }; 71 | 72 | // Helper function to create a zip_view 73 | template 74 | auto zip(Containers&... containers) { 75 | return zip_view(containers...); 76 | } 77 | 78 | } // namespace aifocore::ranges 79 | 80 | #endif // AIFO_AIFOCORE_INCLUDE_AIFOCORE_RANGES_ZIP_H_ 81 | -------------------------------------------------------------------------------- /include/fastslide/c/fastslide.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All rights reserved. 2 | // 3 | // This file is part of FastSlide. 4 | // 5 | // Use of this source code is governed by the terms found in the 6 | // LICENSE file located in the FastSlide project root. 7 | 8 | #ifndef AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_C_FASTSLIDE_H_ 9 | #define AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_C_FASTSLIDE_H_ 10 | 11 | /// @file fastslide.h 12 | /// @brief Complete FastSlide C API 13 | /// 14 | /// This header provides a complete C interface to the FastSlide library, 15 | /// mirroring the C++ API structure. The C API follows these conventions: 16 | /// 17 | /// - All functions return 1 on success, 0 on failure (unless otherwise noted) 18 | /// - Error messages can be retrieved using fastslide_get_last_error() 19 | /// - Memory allocated by the library must be freed using appropriate free 20 | /// functions 21 | /// - All handles are opaque pointers and should not be accessed directly 22 | /// 23 | /// @section usage Basic Usage 24 | /// 25 | /// ```c 26 | /// #include "fastslide/c/fastslide.h" 27 | /// 28 | /// // Initialize the library 29 | /// fastslide_registry_initialize(); 30 | /// 31 | /// // Create a slide reader 32 | /// FastSlideSlideReader* reader = fastslide_create_reader("slide.svs"); 33 | /// if (!reader) { 34 | /// printf("Error: %s\n", fastslide_get_last_error()); 35 | /// return 1; 36 | /// } 37 | /// 38 | /// // Read a region 39 | /// FastSlideImage* image = fastslide_slide_reader_read_region_coords( 40 | /// reader, 0, 0, 1024, 1024, 0); 41 | /// 42 | /// // Cleanup 43 | /// fastslide_image_free(image); 44 | /// fastslide_slide_reader_free(reader); 45 | /// ``` 46 | 47 | #include "fastslide/c/image.h" 48 | #include "fastslide/c/registry.h" 49 | #include "fastslide/c/slide_reader.h" 50 | #include "fastslide/c/utilities.h" 51 | 52 | #ifdef __cplusplus 53 | extern "C" { 54 | #endif 55 | 56 | /// @brief FastSlide C API version 57 | #define FASTSLIDE_C_API_VERSION_MAJOR 1 58 | #define FASTSLIDE_C_API_VERSION_MINOR 0 59 | #define FASTSLIDE_C_API_VERSION_PATCH 0 60 | 61 | /// @brief Get C API version string 62 | /// @return Version string in format "major.minor.patch" 63 | const char* fastslide_c_api_get_version(void); 64 | 65 | /// @brief Initialize FastSlide library 66 | /// @details This function initializes the slide readers registry and sets up 67 | /// error handling. It should be called before using any other 68 | /// FastSlide functions. 69 | /// @return 1 on success, 0 on failure 70 | int fastslide_initialize(void); 71 | 72 | /// @brief Cleanup FastSlide library 73 | /// @details This function cleans up global resources. Call this when done 74 | /// with the FastSlide library. 75 | void fastslide_cleanup(void); 76 | 77 | #ifdef __cplusplus 78 | } 79 | #endif 80 | 81 | #endif // AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_C_FASTSLIDE_H_ 82 | -------------------------------------------------------------------------------- /src/fastslide/runtime/global_cache_manager.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "fastslide/runtime/global_cache_manager.h" 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | #include "absl/status/status.h" 22 | #include "aifocore/status/status_macros.h" 23 | #include "fastslide/runtime/cache_interface.h" 24 | #include "fastslide/runtime/lru_tile_cache.h" 25 | 26 | namespace fastslide { 27 | namespace runtime { 28 | 29 | GlobalCacheManager::GlobalCacheManager() { 30 | // Create default LRU cache with 1000 tile capacity 31 | auto cache_result = LRUTileCache::Create(1000); 32 | if (cache_result.ok()) { 33 | cache_ = std::move(*cache_result); 34 | } else { 35 | // This should never happen with valid capacity, but handle gracefully 36 | cache_ = nullptr; 37 | } 38 | } 39 | 40 | GlobalCacheManager& GlobalCacheManager::Instance() { 41 | static GlobalCacheManager instance; 42 | return instance; 43 | } 44 | 45 | std::shared_ptr GlobalCacheManager::GetCache() { 46 | absl::MutexLock lock(&mutex_); 47 | return cache_; 48 | } 49 | 50 | void GlobalCacheManager::SetCache(std::shared_ptr cache) { 51 | absl::MutexLock lock(&mutex_); 52 | cache_ = std::move(cache); 53 | } 54 | 55 | absl::Status GlobalCacheManager::SetCapacity(size_t capacity) { 56 | absl::MutexLock lock(&mutex_); 57 | 58 | // Create new LRU cache with new capacity 59 | auto cache_result = LRUTileCache::Create(capacity); 60 | if (!cache_result.ok()) { 61 | return cache_result.status(); 62 | } 63 | cache_ = std::move(*cache_result); 64 | 65 | return absl::OkStatus(); 66 | } 67 | 68 | size_t GlobalCacheManager::GetCapacity() const { 69 | absl::MutexLock lock(&mutex_); 70 | return cache_ ? cache_->GetCapacity() : 0; 71 | } 72 | 73 | size_t GlobalCacheManager::GetSize() const { 74 | absl::MutexLock lock(&mutex_); 75 | return cache_ ? cache_->GetSize() : 0; 76 | } 77 | 78 | ITileCache::Stats GlobalCacheManager::GetStats() const { 79 | absl::MutexLock lock(&mutex_); 80 | if (cache_) { 81 | return cache_->GetStats(); 82 | } 83 | return ITileCache::Stats{}; 84 | } 85 | 86 | void GlobalCacheManager::Clear() { 87 | absl::MutexLock lock(&mutex_); 88 | if (cache_) { 89 | cache_->Clear(); 90 | } 91 | } 92 | 93 | } // namespace runtime 94 | } // namespace fastslide 95 | -------------------------------------------------------------------------------- /src/fastslide/runtime/tile_writer/direct/copy_rgb8.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "fastslide/runtime/tile_writer/direct/copy_rgb8.h" 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | namespace fastslide::runtime { 23 | 24 | void CopyTileRectRGB8(const uint8_t* tile, int tile_w, int tile_h, int src_x, 25 | int src_y, uint8_t* img, int img_w, int img_h, int dst_x, 26 | int dst_y, int copy_w, int copy_h) { 27 | const size_t row_bytes = static_cast(copy_w) * 3; 28 | 29 | for (int y = 0; y < copy_h; ++y) { 30 | const int tile_row = src_y + y; 31 | const int img_row = dst_y + y; 32 | 33 | if (tile_row >= tile_h || img_row >= img_h) 34 | continue; 35 | 36 | const uint8_t* src = tile + (tile_row * tile_w + src_x) * 3; 37 | uint8_t* dst = img + (img_row * img_w + dst_x) * 3; 38 | 39 | std::memcpy(dst, src, row_bytes); 40 | } 41 | } 42 | 43 | void CopyRectGeneral(const uint8_t* tile, int tile_w, int tile_h, 44 | int tile_channels, int src_x, int src_y, uint8_t* img, 45 | int img_w, int img_h, int img_channels, int dst_x, 46 | int dst_y, int copy_w, int copy_h, int bytes_per_sample) { 47 | const int tile_bytes_per_pixel = bytes_per_sample * tile_channels; 48 | const int img_bytes_per_pixel = bytes_per_sample * img_channels; 49 | const int copy_channels = std::min(tile_channels, img_channels); 50 | const size_t copy_bytes = copy_channels * bytes_per_sample; 51 | 52 | for (int y = 0; y < copy_h; ++y) { 53 | const int tile_row = src_y + y; 54 | const int img_row = dst_y + y; 55 | 56 | if (tile_row >= tile_h || img_row >= img_h) 57 | continue; 58 | 59 | for (int x = 0; x < copy_w; ++x) { 60 | const int tile_col = src_x + x; 61 | const int img_col = dst_x + x; 62 | 63 | if (tile_col >= tile_w || img_col >= img_w) 64 | continue; 65 | 66 | const size_t src_offset = 67 | (tile_row * tile_w + tile_col) * tile_bytes_per_pixel; 68 | const size_t dst_offset = 69 | (img_row * img_w + img_col) * img_bytes_per_pixel; 70 | 71 | std::memcpy(img + dst_offset, tile + src_offset, copy_bytes); 72 | } 73 | } 74 | } 75 | 76 | } // namespace fastslide::runtime 77 | -------------------------------------------------------------------------------- /include/fastslide/runtime/tile_writer/direct/direct_strategy.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RUNTIME_TILE_WRITER_DIRECT_DIRECT_STRATEGY_H_ 16 | #define AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RUNTIME_TILE_WRITER_DIRECT_DIRECT_STRATEGY_H_ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "absl/status/status.h" 25 | #include "absl/status/statusor.h" 26 | #include "fastslide/core/tile_plan.h" 27 | #include "fastslide/image.h" 28 | #include "fastslide/runtime/tile_writer.h" 29 | #include "fastslide/runtime/tile_writer/tile_writer_strategy.h" 30 | 31 | namespace fastslide::runtime { 32 | 33 | /// @brief Direct tile writing strategy (fast path for aligned tiles) 34 | class DirectStrategy : public ITileWriterStrategy { 35 | public: 36 | explicit DirectStrategy(const TileWriter::Config& config); 37 | 38 | absl::Status WriteTile(const core::TileReadOp& op, 39 | std::span pixel_data, 40 | uint32_t tile_width, uint32_t tile_height, 41 | uint32_t tile_channels) override; 42 | 43 | absl::Status Finalize() override; 44 | ImageDimensions GetDimensions() const override; 45 | uint32_t GetChannels() const override; 46 | absl::StatusOr GetOutput() override; 47 | std::string GetName() const override; 48 | uint8_t* GetOutputBuffer() override; 49 | size_t GetOutputBufferSize() const override; 50 | 51 | private: 52 | absl::Status WriteTilePlanar(const core::TileReadOp& op, 53 | std::span pixel_data, 54 | uint32_t tile_width, uint32_t tile_height, 55 | uint32_t tile_channels); 56 | 57 | absl::Status WriteTileInterleaved(const core::TileReadOp& op, 58 | std::span pixel_data, 59 | uint32_t tile_width, uint32_t tile_height, 60 | uint32_t tile_channels); 61 | 62 | TileWriter::Config config_; 63 | std::unique_ptr output_image_; 64 | bool finalized_ = false; 65 | }; 66 | 67 | } // namespace fastslide::runtime 68 | 69 | #endif // AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RUNTIME_TILE_WRITER_DIRECT_DIRECT_STRATEGY_H_ 70 | -------------------------------------------------------------------------------- /src/fastslide/utilities/tiff/tiff_cache_service.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "fastslide/utilities/tiff/tiff_cache_service.h" 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | namespace fastslide::tiff { 22 | 23 | TiffCacheService::TiffCacheService(std::shared_ptr cache) 24 | : TileCacheManager(std::move(cache)) {} 25 | 26 | absl::StatusOr> TiffCacheService::GetTileFromTiff( 27 | const std::string& filename, uint16_t page, 28 | const aifocore::Size& tile_coords, TiffFile& tiff_file, 29 | const aifocore::Size& tile_dims, 30 | const PlanarInterleaver::Config& interleaver_config, 31 | uint32_t bytes_per_pixel) { 32 | 33 | // Calculate cache coordinates (tile indices) 34 | auto cache_coords = CalculateCacheCoords(tile_coords, tile_dims); 35 | 36 | // Create tile loader with interleaving 37 | auto tile_loader = CreateTileLoaderWithInterleaving( 38 | tiff_file, tile_coords, tile_dims, interleaver_config, bytes_per_pixel); 39 | 40 | // Use base class functionality for cache lookup/storage 41 | return GetTile(filename, page, cache_coords, tile_loader); 42 | } 43 | 44 | absl::StatusOr> 45 | TiffCacheService::GetScanlineFromTiff( 46 | const std::string& filename, uint16_t page, uint32_t row, 47 | TiffFile& tiff_file, uint32_t image_width, 48 | const PlanarInterleaver::Config& interleaver_config, 49 | uint32_t bytes_per_pixel) { 50 | 51 | // Create scanline loader with interleaving 52 | auto scanline_loader = CreateScanlineLoaderWithInterleaving( 53 | tiff_file, row, image_width, interleaver_config, bytes_per_pixel); 54 | 55 | // Use scanline coordinates for caching (treat as special tile coordinates) 56 | const aifocore::Size scanline_coords{0, row}; 57 | 58 | // Use base class functionality for cache lookup/storage 59 | return GetTile(filename, page, scanline_coords, scanline_loader); 60 | } 61 | 62 | aifocore::Size TiffCacheService::CalculateCacheCoords( 63 | const aifocore::Size& tile_coords, 64 | const aifocore::Size& tile_dims) const { 65 | 66 | // Convert tile coordinates to tile indices for cache keys 67 | return tile_coords / tile_dims; 68 | } 69 | 70 | } // namespace fastslide::tiff 71 | -------------------------------------------------------------------------------- /src/fastslide/runtime/io/binary_utils.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "fastslide/runtime/io/binary_utils.h" 16 | 17 | #include 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #include "absl/status/status.h" 24 | #include "absl/status/statusor.h" 25 | #include "absl/strings/str_format.h" 26 | #include "aifocore/status/status_macros.h" 27 | 28 | namespace fastslide { 29 | namespace runtime { 30 | namespace io { 31 | 32 | absl::StatusOr ReadLeInt32(FILE* file) { 33 | uint8_t buf[4]; 34 | if (fread(buf, 1, 4, file) != 4) { 35 | return MAKE_STATUS(absl::StatusCode::kInternal, 36 | "Failed to read 4 bytes for int32"); 37 | } 38 | // Little-endian byte order 39 | return static_cast(buf[0] | (buf[1] << 8) | (buf[2] << 16) | 40 | (buf[3] << 24)); 41 | } 42 | 43 | absl::StatusOr ReadLeUInt32(FILE* file) { 44 | uint8_t buf[4]; 45 | if (fread(buf, 1, 4, file) != 4) { 46 | return MAKE_STATUS(absl::StatusCode::kInternal, 47 | "Failed to read 4 bytes for uint32"); 48 | } 49 | // Little-endian byte order 50 | return static_cast(buf[0] | (buf[1] << 8) | (buf[2] << 16) | 51 | (buf[3] << 24)); 52 | } 53 | 54 | absl::StatusOr> DecompressZlib(const uint8_t* data, 55 | size_t compressed_size, 56 | size_t expected_size) { 57 | std::vector decompressed(expected_size); 58 | z_stream strm{}; 59 | strm.next_in = const_cast(data); 60 | strm.avail_in = compressed_size; 61 | strm.next_out = decompressed.data(); 62 | strm.avail_out = expected_size; 63 | 64 | if (inflateInit(&strm) != Z_OK) { 65 | return MAKE_STATUS(absl::StatusCode::kInternal, 66 | "Failed to initialize zlib"); 67 | } 68 | 69 | int ret = inflate(&strm, Z_FINISH); 70 | inflateEnd(&strm); 71 | 72 | if (ret != Z_STREAM_END) { 73 | return MAKE_STATUS( 74 | absl::StatusCode::kInternal, 75 | absl::StrFormat("Zlib decompression failed with error code: %d", ret)); 76 | } 77 | 78 | return decompressed; 79 | } 80 | 81 | } // namespace io 82 | } // namespace runtime 83 | } // namespace fastslide 84 | -------------------------------------------------------------------------------- /src/fastslide/readers/aperio/aperio_format_plugin.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "fastslide/readers/aperio/aperio_format_plugin.h" 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "absl/status/statusor.h" 25 | #include "aifocore/status/status_macros.h" 26 | #include "fastslide/readers/aperio/aperio.h" 27 | #include "fastslide/runtime/cache_interface.h" 28 | #include "fastslide/runtime/format_descriptor.h" 29 | #include "fastslide/slide_reader.h" 30 | 31 | namespace fastslide { 32 | namespace formats { 33 | namespace aperio { 34 | 35 | namespace { 36 | 37 | /// @brief Factory function for Aperio readers 38 | absl::StatusOr> CreateAperioReader( 39 | std::shared_ptr cache, std::string_view filename) { 40 | // Create the Aperio reader (using existing implementation) 41 | DECLARE_ASSIGN_OR_RETURN_MOVE(std::unique_ptr, reader, 42 | AperioReader::Create(std::string(filename))); 43 | 44 | // Apply cache if provided 45 | if (cache) { 46 | reader->SetCache(cache); 47 | } 48 | 49 | return reader; 50 | } 51 | 52 | } // namespace 53 | 54 | FormatDescriptor CreateAperioFormatDescriptor() { 55 | FormatDescriptor desc; 56 | 57 | desc.primary_extension = ".svs"; 58 | desc.format_name = "Aperio"; 59 | desc.version = "1.0.0"; 60 | 61 | // Aperio capabilities 62 | desc.capabilities = 63 | SetCapability(desc.capabilities, FormatCapability::kTiled); 64 | desc.capabilities = 65 | SetCapability(desc.capabilities, FormatCapability::kPyramidal); 66 | desc.capabilities = 67 | SetCapability(desc.capabilities, FormatCapability::kAssociatedImages); 68 | desc.capabilities = 69 | SetCapability(desc.capabilities, FormatCapability::kCompressed); 70 | desc.capabilities = 71 | SetCapability(desc.capabilities, FormatCapability::kRandomAccess); 72 | 73 | // Required capabilities (codecs) 74 | desc.required_capabilities.push_back("jpeg"); 75 | // Note: Aperio may also use JPEG2000, but it's optional depending on the file 76 | // For full compatibility, JPEG2000 support is recommended 77 | 78 | // Factory function 79 | desc.factory = CreateAperioReader; 80 | 81 | return desc; 82 | } 83 | 84 | } // namespace aperio 85 | } // namespace formats 86 | } // namespace fastslide 87 | -------------------------------------------------------------------------------- /include/fastslide/readers/mrxs/mrxs_ini_parser.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_READERS_MRXS_MRXS_INI_PARSER_H_ 16 | #define AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_READERS_MRXS_MRXS_INI_PARSER_H_ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "absl/status/statusor.h" 24 | 25 | namespace fs = std::filesystem; 26 | 27 | namespace fastslide { 28 | namespace mrxs { 29 | namespace internal { 30 | 31 | /// @brief Simple INI file parser for MRXS Slidedat.ini files 32 | /// 33 | /// This class handles the specific format used by MRXS files, including: 34 | /// - UTF-8 BOM at start of file 35 | /// - Hierarchical sections with [SECTION] markers 36 | /// - Key=Value pairs 37 | /// - Comments starting with ; or # 38 | class IniFile { 39 | public: 40 | /// @brief Load and parse an INI file 41 | /// @param path Path to the INI file 42 | /// @return StatusOr containing parsed IniFile or error 43 | static absl::StatusOr Load(const fs::path& path); 44 | 45 | /// @brief Get string value from section 46 | /// @param section Section name 47 | /// @param key Key name 48 | /// @return StatusOr containing value or error 49 | absl::StatusOr GetString(std::string_view section, 50 | std::string_view key) const; 51 | 52 | /// @brief Get integer value from section 53 | /// @param section Section name 54 | /// @param key Key name 55 | /// @return StatusOr containing integer value or error 56 | absl::StatusOr GetInt(std::string_view section, 57 | std::string_view key) const; 58 | 59 | /// @brief Get double value from section 60 | /// @param section Section name 61 | /// @param key Key name 62 | /// @return StatusOr containing double value or error 63 | absl::StatusOr GetDouble(std::string_view section, 64 | std::string_view key) const; 65 | 66 | /// @brief Check if section exists 67 | /// @param section Section name 68 | /// @return True if section exists 69 | bool HasSection(std::string_view section) const; 70 | 71 | private: 72 | std::map> data_; 73 | }; 74 | 75 | } // namespace internal 76 | } // namespace mrxs 77 | } // namespace fastslide 78 | 79 | #endif // AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_READERS_MRXS_MRXS_INI_PARSER_H_ 80 | -------------------------------------------------------------------------------- /src/fastslide/readers/qptiff/qptiff_format_plugin.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "fastslide/readers/qptiff/qptiff_format_plugin.h" 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "absl/status/statusor.h" 25 | #include "aifocore/status/status_macros.h" 26 | #include "fastslide/readers/qptiff/qptiff.h" 27 | #include "fastslide/runtime/cache_interface.h" 28 | #include "fastslide/runtime/format_descriptor.h" 29 | #include "fastslide/slide_reader.h" 30 | 31 | namespace fastslide { 32 | namespace formats { 33 | namespace qptiff { 34 | 35 | namespace { 36 | 37 | /// @brief Factory function for QPTIFF readers 38 | absl::StatusOr> CreateQptiffReader( 39 | std::shared_ptr cache, std::string_view filename) { 40 | // Create the QPTIFF reader (using existing implementation) 41 | DECLARE_ASSIGN_OR_RETURN_MOVE(std::unique_ptr, reader, 42 | QpTiffReader::Create(std::string(filename))); 43 | 44 | // Apply cache if provided 45 | if (cache) { 46 | reader->SetCache(cache); 47 | } 48 | 49 | return reader; 50 | } 51 | 52 | } // namespace 53 | 54 | FormatDescriptor CreateQptiffFormatDescriptor() { 55 | FormatDescriptor desc; 56 | 57 | desc.primary_extension = ".qptiff"; 58 | desc.format_name = "QPTIFF"; 59 | desc.version = "1.0.0"; 60 | 61 | // QPTIFF capabilities 62 | desc.capabilities = 63 | SetCapability(desc.capabilities, FormatCapability::kTiled); 64 | desc.capabilities = 65 | SetCapability(desc.capabilities, FormatCapability::kPyramidal); 66 | desc.capabilities = 67 | SetCapability(desc.capabilities, FormatCapability::kSpectral); 68 | desc.capabilities = 69 | SetCapability(desc.capabilities, FormatCapability::kAssociatedImages); 70 | desc.capabilities = 71 | SetCapability(desc.capabilities, FormatCapability::kCompressed); 72 | desc.capabilities = 73 | SetCapability(desc.capabilities, FormatCapability::kRandomAccess); 74 | 75 | // Required capabilities (codecs) 76 | // QPTIFF can use various compressions, not just JPEG 77 | // Remove requirement so it always loads 78 | // desc.required_capabilities.push_back("jpeg"); 79 | 80 | // Factory function 81 | desc.factory = CreateQptiffReader; 82 | 83 | return desc; 84 | } 85 | 86 | } // namespace qptiff 87 | } // namespace formats 88 | } // namespace fastslide 89 | -------------------------------------------------------------------------------- /include/fastslide/runtime/tile_writer/blended/srgb_linear.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RUNTIME_TILE_WRITER_BLENDED_SRGB_LINEAR_H_ 16 | #define AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RUNTIME_TILE_WRITER_BLENDED_SRGB_LINEAR_H_ 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #include "fastslide/runtime/tile_writer/fs_profile.h" 23 | 24 | namespace fastslide::runtime { 25 | 26 | /// @brief Convert sRGB8 interleaved tile to linear RGB planar (float) 27 | /// @param src_interleaved Input sRGB8 buffer (interleaved RGB, sized w * h * 3) 28 | /// @param w Tile width 29 | /// @param h Tile height 30 | /// @param dst_linear_planar Output linear RGB buffer (planar format, must be 31 | /// sized w * h * 3 + kSimdPadding to prevent SIMD 32 | /// buffer overruns) 33 | void ConvertSrgb8ToLinearPlanar(const uint8_t* src_interleaved, int w, int h, 34 | float* dst_linear_planar); 35 | 36 | /// @brief Apply gain correction to linear RGB planar data 37 | /// @param linear_planar Planar float array (R plane, G plane, B plane). 38 | /// Must be sized plane_size * 3 + kSimdPadding to prevent 39 | /// SIMD buffer overruns 40 | /// @param plane_size Size of each plane (not including padding) 41 | /// @param gain Gain factor to multiply 42 | void GainCorrectionLinearPlanar(float* linear_planar, size_t plane_size, 43 | float gain); 44 | 45 | /// @brief Convert linear RGB value to sRGB [0,1] 46 | double LinearToSrgb01(double v); 47 | 48 | // Lookup table for Linear→sRGB conversion (4096 entries for 12-bit precision) 49 | constexpr int kLinearToSrgbLutSize = 4096; 50 | extern const std::array kLinearToSrgb8Lut; 51 | 52 | /// @brief Convert linear RGB value [0,1] to sRGB8 using fast LUT 53 | /// Inline definition for zero function call overhead in hot loops 54 | inline uint8_t LinearToSrgb8Fast(float v) { 55 | // Fast path: clamp to [0, 1] 56 | if (v <= 0.0f) 57 | return 0; 58 | if (v >= 1.0f) 59 | return 255; 60 | 61 | // Direct LUT access - no function call overhead 62 | // Using float arithmetic is faster than double on most architectures 63 | const int idx = static_cast(v * kLinearToSrgbLutSize); 64 | return kLinearToSrgb8Lut[idx]; 65 | } 66 | 67 | } // namespace fastslide::runtime 68 | 69 | #endif // AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RUNTIME_TILE_WRITER_BLENDED_SRGB_LINEAR_H_ 70 | -------------------------------------------------------------------------------- /include/fastslide/fastslide.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_FASTSLIDE_H_ 16 | #define AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_FASTSLIDE_H_ 17 | 18 | /** 19 | * @file fastslide.h 20 | * @brief Main header for the FastSlide library 21 | * 22 | * This header includes all the core FastSlide functionality for reading 23 | * digital pathology slides in various formats (MRXS, QPTIFF, SVS). 24 | * 25 | * ## Architecture (v2.0) 26 | * 27 | * FastSlide has a clean 3-layer architecture: 28 | * - **Core**: Pure domain models (metadata, requests, descriptors, plans) 29 | * - **Runtime**: Services and infrastructure (caching, registry, I/O, decoders) 30 | * - **Formats**: Format-specific plugins (MRXS, QPTIFF, SVS) 31 | * 32 | * @see fastslide/core/ for domain models 33 | * @see fastslide/runtime/ for runtime services 34 | * @see fastslide/readers/ for format readers and plugins 35 | */ 36 | 37 | // ============================================================================ 38 | // Core Domain Models 39 | // ============================================================================ 40 | 41 | #include "fastslide/core/metadata.h" 42 | #include "fastslide/core/slide_descriptor.h" 43 | #include "fastslide/core/tile_plan.h" 44 | #include "fastslide/core/tile_request.h" 45 | 46 | // ============================================================================ 47 | // Runtime Services 48 | // ============================================================================ 49 | 50 | #include "fastslide/runtime/cache_interface.h" 51 | #include "fastslide/runtime/format_descriptor.h" 52 | #include "fastslide/runtime/lru_tile_cache.h" 53 | #include "fastslide/runtime/reader_registry.h" 54 | #include "fastslide/runtime/tile_writer.h" 55 | 56 | // ============================================================================ 57 | // Format Plugins 58 | // ============================================================================ 59 | 60 | #include "fastslide/readers/readers.h" 61 | 62 | // ============================================================================ 63 | // Public API 64 | // ============================================================================ 65 | 66 | #include "fastslide/image.h" 67 | #include "fastslide/metadata.h" 68 | #include "fastslide/runtime/plugin_loader.h" 69 | #include "fastslide/slide_options.h" 70 | #include "fastslide/slide_reader.h" 71 | #include "fastslide/utilities/cache.h" 72 | #include "fastslide/utilities/colors.h" 73 | 74 | #endif // AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_FASTSLIDE_H_ 75 | -------------------------------------------------------------------------------- /include/aifocore/utilities/thread_pool_singleton.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef AIFO_AIFOCORE_INCLUDE_AIFOCORE_UTILITIES_THREAD_POOL_SINGLETON_H_ 16 | #define AIFO_AIFOCORE_INCLUDE_AIFOCORE_UTILITIES_THREAD_POOL_SINGLETON_H_ 17 | 18 | #include 19 | 20 | #include "aifocore/utilities/bs_thread_pool.h" 21 | 22 | namespace aifocore { 23 | 24 | /// @brief Global thread pool manager for aifocore libraries 25 | /// 26 | /// Provides a shared thread pool instance that can be used by all libraries 27 | /// and operations. This enables efficient parallelism without creating multiple 28 | /// thread pools per component. 29 | /// 30 | /// Thread-safe singleton with configurable thread count. 31 | /// 32 | /// ## Thread Count Configuration 33 | /// 34 | /// The thread count can be controlled via environment variable (OpenMP-style): 35 | /// - `NUM_THREADS=1` : Single-threaded mode (no parallelism) 36 | /// - `NUM_THREADS=4` : Use 4 threads 37 | /// - `NUM_THREADS=0` or unset : Use hardware_concurrency() 38 | /// 39 | /// Alternatively, call SetThreadCount() before first use. 40 | class ThreadPoolManager { 41 | public: 42 | /// @brief Get the global thread pool instance 43 | /// 44 | /// Returns a reference to the singleton thread pool. The pool is created 45 | /// on first access with a default thread count determined by: 46 | /// 1. NUM_THREADS environment variable (if set) 47 | /// 2. Hardware concurrency (number of logical cores) otherwise 48 | /// 49 | /// @return Reference to the global BS::light_thread_pool 50 | static BS::light_thread_pool& GetInstance(); 51 | 52 | /// @brief Set the number of threads in the pool 53 | /// 54 | /// Resets the thread pool with the specified number of threads. Can be 55 | /// called before or after GetInstance(). If called after tasks have been 56 | /// submitted, waits for all existing tasks to complete before resetting. 57 | /// 58 | /// @param count Number of threads to use (0 = use hardware_concurrency) 59 | static void SetThreadCount(std::size_t count); 60 | 61 | private: 62 | ThreadPoolManager() = delete; 63 | ~ThreadPoolManager() = delete; 64 | ThreadPoolManager(const ThreadPoolManager&) = delete; 65 | ThreadPoolManager& operator=(const ThreadPoolManager&) = delete; 66 | 67 | /// @brief Get or create the thread pool instance 68 | /// @param count Thread count (0 = hardware concurrency) 69 | /// @return Reference to the thread pool 70 | static BS::light_thread_pool& GetPool(std::size_t count = 0); 71 | }; 72 | 73 | } // namespace aifocore 74 | 75 | #endif // AIFO_AIFOCORE_INCLUDE_AIFOCORE_UTILITIES_THREAD_POOL_SINGLETON_H_ 76 | -------------------------------------------------------------------------------- /include/fastslide/utilities/hash.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_UTILITIES_HASH_H_ 16 | #define AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_UTILITIES_HASH_H_ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "absl/status/status.h" 25 | 26 | namespace fs = std::filesystem; 27 | 28 | namespace fastslide { 29 | 30 | /// @brief SHA-256 hash builder for creating slide quickhashes 31 | /// 32 | /// This class provides a simple interface for computing SHA-256 hashes 33 | /// compatible with OpenSlide's quickhash format. It supports incremental 34 | /// hashing of files and data buffers. 35 | class QuickHashBuilder { 36 | public: 37 | /// @brief Constructor 38 | QuickHashBuilder(); 39 | 40 | /// @brief Destructor 41 | ~QuickHashBuilder(); 42 | 43 | // Delete copy/move operations (contains SHA-256 context) 44 | QuickHashBuilder(const QuickHashBuilder&) = delete; 45 | QuickHashBuilder& operator=(const QuickHashBuilder&) = delete; 46 | QuickHashBuilder(QuickHashBuilder&&) = delete; 47 | QuickHashBuilder& operator=(QuickHashBuilder&&) = delete; 48 | 49 | /// @brief Add file contents to hash 50 | /// @param file_path Path to file to hash 51 | /// @return Status indicating success or failure 52 | absl::Status HashFile(const fs::path& file_path); 53 | 54 | /// @brief Add file portion to hash 55 | /// @param file_path Path to file 56 | /// @param offset Offset in file 57 | /// @param length Number of bytes to hash 58 | /// @return Status indicating success or failure 59 | absl::Status HashFilePart(const fs::path& file_path, int64_t offset, 60 | int64_t length); 61 | 62 | /// @brief Add data buffer to hash 63 | /// @param data Pointer to data 64 | /// @param length Number of bytes 65 | /// @return Status indicating success or failure 66 | absl::Status HashData(const uint8_t* data, size_t length); 67 | 68 | /// @brief Add data buffer to hash 69 | /// @param data Vector of data 70 | /// @return Status indicating success or failure 71 | absl::Status HashData(const std::vector& data); 72 | 73 | /// @brief Finalize hash and get result as hex string 74 | /// @return Hex-encoded hash string (64 characters for SHA-256) 75 | std::string Finalize(); 76 | 77 | private: 78 | void* ctx_; // SHA-256 context (opaque pointer to avoid header dependency) 79 | std::vector hash_buffer_; // Buffer for final hash result 80 | bool finalized_; 81 | }; 82 | 83 | } // namespace fastslide 84 | 85 | #endif // AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_UTILITIES_HASH_H_ 86 | -------------------------------------------------------------------------------- /include/aifocore/utilities/pyvips.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #ifndef AIFO_AIFOCORE_INCLUDE_AIFOCORE_UTILITIES_PYVIPS_H_ 15 | #define AIFO_AIFOCORE_INCLUDE_AIFOCORE_UTILITIES_PYVIPS_H_ 16 | 17 | #include 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | namespace py = pybind11; 30 | 31 | static std::once_flag pyvips_init_flag; 32 | 33 | std::pair InitPyvips() { 34 | static py::object pyvips_module; 35 | static py::object ffi_object; 36 | 37 | std::call_once(pyvips_init_flag, []() { 38 | pyvips_module = py::module::import("pyvips"); 39 | ffi_object = pyvips_module.attr("ffi"); 40 | }); 41 | 42 | return {pyvips_module, ffi_object}; 43 | } 44 | 45 | class VImageWrapper { 46 | private: 47 | ::vips::VImage vimage; 48 | 49 | public: 50 | explicit VImageWrapper(const ::vips::VImage& img) : vimage(img) {} 51 | 52 | ~VImageWrapper() {} 53 | 54 | py::object GetImage() const { 55 | auto [pyvips_module, ffi_object] = InitPyvips(); 56 | VipsImage* image = vimage.get_image(); 57 | uintptr_t ptr_value = reinterpret_cast(image); 58 | py::object cffi_ptr = ffi_object.attr("cast")("VipsImage *", ptr_value); 59 | return pyvips_module.attr("Image")(cffi_ptr); 60 | } 61 | }; 62 | 63 | namespace pybind11::detail { 64 | template <> 65 | struct type_caster> { 66 | public: 67 | PYBIND11_TYPE_CASTER(std::shared_ptr, 68 | _("std::shared_ptr")); 69 | 70 | // Python -> C++ 71 | bool load(handle src, bool) { 72 | if (!src) { 73 | return false; 74 | } 75 | } 76 | 77 | // C++ -> Python 78 | static handle cast(const std::shared_ptr& src, 79 | return_value_policy /* policy */, handle /* parent */) { 80 | if (!src) { 81 | return py::none().release(); 82 | } 83 | 84 | auto [pyvips_module, ffi_object] = InitPyvips(); 85 | 86 | // Get the underlying VipsImage pointer 87 | VipsImage* image = src->get_image(); 88 | uintptr_t ptr_value = reinterpret_cast(image); 89 | 90 | // Convert to a pyvips.Image 91 | py::object cffi_ptr = ffi_object.attr("cast")("VipsImage *", ptr_value); 92 | return pyvips_module.attr("Image")(cffi_ptr).release(); 93 | } 94 | }; 95 | } // namespace pybind11::detail 96 | 97 | #endif // AIFO_AIFOCORE_INCLUDE_AIFOCORE_UTILITIES_PYVIPS_H_ 98 | -------------------------------------------------------------------------------- /src/aifocore/shared/python/vector_module.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include "aifocore/shared/exceptions.h" 20 | #include "aifocore/shared/python/vector.h" 21 | #include "aifocore/shared/vector.h" 22 | 23 | namespace py = pybind11; 24 | using aifocore::shared::SharedVector; 25 | using aifocore::shared::exceptions::ArraySizeError; 26 | using aifocore::shared::exceptions::DataModifiedError; 27 | using aifocore::shared::exceptions::InUseError; 28 | using aifocore::shared::exceptions::MemoryError; 29 | using aifocore::shared::exceptions::OutOfMemoryError; 30 | namespace bi = boost::interprocess; 31 | 32 | PYBIND11_MODULE(vector, m) { 33 | py::class_(m, "SharedVector") 34 | .def(py::init(), py::arg("name"), 35 | py::arg("chunk_size") = 1024 * 1024 * 1024, 36 | py::arg("max_memory_size") = 1024 * 1024 * 10) 37 | .def("append", &aifocore::shared::python::AppendPython, 38 | py::arg("array")) 39 | .def("get", &aifocore::shared::python::GetPython, py::arg("index")) 40 | .def("replace", &aifocore::shared::python::ReplacePython, 41 | py::arg("index"), py::arg("array")) 42 | .def("size", &SharedVector::size) 43 | .def("__len__", &SharedVector::size) 44 | .def("get_chunk_ref_count", &SharedVector::GetChunkRefCount, 45 | py::arg("index")) 46 | .def( 47 | "get_chunk_shape", 48 | [](const SharedVector& self, size_t index) { 49 | std::vector vec = self.GetChunkShape(index); 50 | py::tuple result = py::cast(vec); 51 | return result; 52 | }, 53 | "Get the shape of a chunk as a tuple") 54 | .def("get_chunk_pointer", &SharedVector::GetChunkPointer, 55 | py::arg("index")) 56 | .def_property_readonly("free_memory", &SharedVector::GetFreeMemory) 57 | .def_property_readonly("ref_count", &SharedVector::GetRefCount); 58 | 59 | // Expose the remove_shared_memory function 60 | m.def("remove_shared_memory", [](const std::string& name) { 61 | bi::shared_memory_object::remove(name.c_str()); 62 | }); 63 | 64 | // Register exceptions 65 | py::register_exception(m, "MemoryError"); 66 | py::register_exception(m, "OutOfMemoryError"); 67 | py::register_exception(m, "InUseError"); 68 | py::register_exception(m, "ArraySizeError"); 69 | py::register_exception(m, "DataModifiedError"); 70 | } 71 | -------------------------------------------------------------------------------- /src/aifocore/pathology/inference.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen & Joren Brunekreef. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "ahcore/pathology/config/inference_config.h" 24 | #include "ahcore/pathology/inference_engine.h" 25 | #include "aifocore/utilities/spinners.h" 26 | 27 | #include "CLI11/CLI11.hpp" 28 | 29 | namespace fs = std::filesystem; 30 | 31 | int main(int argc, char** argv) { 32 | spdlog::set_level(spdlog::level::info); 33 | spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%e][%^%l%$] %v"); 34 | 35 | // Parse image/mask using CLI11 allowing extras, then parse engine flags 36 | // with InferenceConfig::FromArgs (which also allows extras). 37 | CLI::App app{"Ahcore inference"}; 38 | app.allow_extras(true); 39 | std::string image_path_str; 40 | std::string mask_path_str; 41 | app.add_option("image", image_path_str, "Path to input image (required)") 42 | ->required(); 43 | app.add_option("--mask", mask_path_str, 44 | "Path to optional foreground TIFF mask"); 45 | try { 46 | app.parse(argc, argv); 47 | } catch (const CLI::ParseError& e) { 48 | return app.exit(e); 49 | } 50 | 51 | aifo::pathology::config::InferenceConfig config = 52 | aifo::pathology::config::InferenceConfig::FromArgs(argc, argv); 53 | 54 | fs::path image_path(image_path_str); 55 | std::optional mask_path; 56 | if (!mask_path_str.empty()) { 57 | mask_path = fs::path(mask_path_str); 58 | } 59 | 60 | // Create and run the inference engine 61 | auto engine_or = aifo::pathology::inference::InferenceEngine::Create(config); 62 | if (!engine_or.ok()) { 63 | spdlog::error("Error initializing inference engine: {}", 64 | engine_or.status().message()); 65 | return 1; 66 | } 67 | 68 | auto engine = std::move(engine_or.value()); 69 | 70 | // Setup a spinner and attach a progress callback 71 | auto spinner = std::make_unique(); 72 | spinner->Start(); 73 | engine.SetProgressCallback( 74 | [&spinner](int current_batch, int total_batches, int tile_index) { 75 | spinner->SetText("Processing batch " + std::to_string(current_batch) + 76 | "/" + std::to_string(total_batches) + " (tile " + 77 | std::to_string(tile_index) + ")"); 78 | }); 79 | 80 | absl::Status status = mask_path ? engine.ProcessImage(image_path, *mask_path) 81 | : engine.ProcessImage(image_path); 82 | spinner->Stop(); 83 | if (!status.ok()) { 84 | spdlog::error("Error running inference: {}", status.message()); 85 | return 1; 86 | } 87 | 88 | return 0; 89 | } 90 | -------------------------------------------------------------------------------- /include/fastslide/readers/mrxs/mrxs_constants.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_READERS_MRXS_MRXS_CONSTANTS_H_ 16 | #define AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_READERS_MRXS_MRXS_CONSTANTS_H_ 17 | 18 | #include 19 | #include 20 | 21 | /// @file mrxs_constants.h 22 | /// @brief Constants and limits for MRXS format processing 23 | /// 24 | /// Centralizes all magic numbers, file size limits, and format constants 25 | /// used throughout the MRXS reader implementation. This eliminates magic 26 | /// numbers scattered throughout the codebase and provides a single source 27 | /// of truth for format-specific values. 28 | 29 | namespace fastslide { 30 | namespace mrxs { 31 | namespace constants { 32 | 33 | /// @brief Maximum allowed size for a single tile in bytes (100 MB) 34 | /// 35 | /// Prevents bad_alloc from unreasonably large allocations when reading 36 | /// malformed or corrupted MRXS files. Typical JPEG tiles are <10MB, 37 | /// so 100MB provides a generous safety margin. 38 | constexpr int64_t kMaxTileSize = 100 * 1024 * 1024; 39 | 40 | /// @brief Threshold percentage for detecting text/XML content (90%) 41 | /// 42 | /// Used when analyzing binary data to determine if it contains printable 43 | /// text or XML. If more than 90% of sampled bytes are printable ASCII 44 | /// characters, the data is classified as text rather than binary. 45 | constexpr int kPrintableThreshold = 90; 46 | 47 | /// @brief Estimated maximum compression ratio for zlib (100x) 48 | /// 49 | /// Used to estimate decompressed size when actual size is unknown. 50 | /// Provides a conservative upper bound for allocating decompression buffers. 51 | constexpr int kCompressionRatioEstimate = 100; 52 | 53 | /// @brief Size of each camera position record in bytes 54 | /// 55 | /// Camera position format (9 bytes total): 56 | /// - 1 byte: flag (typically 0 or 1) 57 | /// - 4 bytes: x coordinate (little-endian int32) 58 | /// - 4 bytes: y coordinate (little-endian int32) 59 | constexpr size_t kPositionRecordSize = 9; 60 | 61 | /// @brief Number of bytes to sample when detecting text content 62 | /// 63 | /// When analyzing unknown data to determine if it's text or binary, 64 | /// we examine the first 100 bytes for printable character ratio. 65 | constexpr size_t kTextDetectionSampleSize = 100; 66 | 67 | /// @brief MRXS index file version string 68 | /// 69 | /// All MRXS index files start with this 5-byte version identifier. 70 | constexpr const char* kIndexVersion = "01.02"; 71 | 72 | /// @brief Size of MRXS index file version field in bytes 73 | constexpr size_t kIndexVersionSize = 5; 74 | 75 | } // namespace constants 76 | } // namespace mrxs 77 | } // namespace fastslide 78 | 79 | #endif // AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_READERS_MRXS_MRXS_CONSTANTS_H_ 80 | -------------------------------------------------------------------------------- /include/fastslide/readers/aperio/metadata_parser.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_READERS_APERIO_METADATA_PARSER_H_ 16 | #define AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_READERS_APERIO_METADATA_PARSER_H_ 17 | 18 | #include 19 | 20 | #include "absl/status/status.h" 21 | #include "aifocore/concepts/numeric.h" 22 | 23 | namespace fastslide { 24 | namespace formats { 25 | namespace aperio { 26 | 27 | /// @brief Metadata extracted from Aperio SVS image descriptions 28 | struct AperioMetadata { 29 | aifocore::Size mpp = {0.0, 0.0}; ///< Microns per pixel (x, y) 30 | double app_mag = 0.0; ///< Apparent magnification 31 | std::string scanner_id; ///< Scanner ID string 32 | }; 33 | 34 | /// @brief Parser for Aperio SVS metadata from TIFF image descriptions 35 | class AperioMetadataParser { 36 | public: 37 | /// @brief Parse Aperio metadata from image description 38 | /// 39 | /// Extracts metadata from Aperio-formatted image description strings 40 | /// commonly found in SVS files. The parser looks for key-value pairs 41 | /// separated by '|' characters. 42 | /// 43 | /// @param description Image description string from TIFF 44 | /// @param metadata Output metadata structure to populate 45 | /// @return Status indicating success or failure with details 46 | static absl::Status ParseFromDescription(const std::string& description, 47 | AperioMetadata& metadata); 48 | 49 | /// @brief Check if an image description contains Aperio metadata 50 | /// @param description Image description string to check 51 | /// @return true if the description appears to contain Aperio metadata 52 | static bool IsAperioFormat(const std::string& description); 53 | 54 | /// @brief Parse associated image name from Aperio description 55 | /// 56 | /// Extracts the name of associated images (like thumbnails, labels) 57 | /// from Aperio image descriptions. 58 | /// 59 | /// @param description Image description string 60 | /// @return Associated image name, or empty string if not found 61 | static std::string ParseAssociatedImageName(const std::string& description); 62 | 63 | private: 64 | /// @brief Extract key-value pairs from pipe-separated description 65 | /// @param description Full description string 66 | /// @param key Key to search for 67 | /// @return Value associated with the key, or empty string if not found 68 | static std::string ExtractValue(const std::string& description, 69 | const std::string& key); 70 | }; 71 | 72 | } // namespace aperio 73 | } // namespace formats 74 | } // namespace fastslide 75 | 76 | #endif // AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_READERS_APERIO_METADATA_PARSER_H_ 77 | -------------------------------------------------------------------------------- /src/aifocore/pathology/inference_fimage.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen & Joren Brunekreef. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "ahcore/pathology/config/inference_config.h" 24 | #include "ahcore/pathology/inference_engine_fimage.h" 25 | #include "aifocore/utilities/spinners.h" 26 | 27 | #include "CLI11/CLI11.hpp" 28 | 29 | namespace fs = std::filesystem; 30 | 31 | int main(int argc, char** argv) { 32 | spdlog::set_level(spdlog::level::info); 33 | spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%e][%^%l%$] %v"); 34 | 35 | // Parse image/mask using CLI11 allowing extras, then parse engine flags 36 | // with InferenceConfig::FromArgs (which also allows extras). 37 | CLI::App app{"Ahcore inference with FImage backend"}; 38 | app.allow_extras(true); 39 | std::string image_path_str; 40 | std::string mask_path_str; 41 | app.add_option("image", image_path_str, "Path to input image (required)") 42 | ->required(); 43 | app.add_option("--mask", mask_path_str, 44 | "Path to optional foreground TIFF mask"); 45 | try { 46 | app.parse(argc, argv); 47 | } catch (const CLI::ParseError& e) { 48 | return app.exit(e); 49 | } 50 | 51 | aifo::pathology::config::InferenceConfig config = 52 | aifo::pathology::config::InferenceConfig::FromArgs(argc, argv); 53 | 54 | fs::path image_path(image_path_str); 55 | std::optional mask_path; 56 | if (!mask_path_str.empty()) { 57 | mask_path = fs::path(mask_path_str); 58 | } 59 | 60 | // Create and run the inference engine 61 | auto engine_or = 62 | aifo::pathology::inference_fimage::InferenceEngineFImage::Create(config); 63 | if (!engine_or.ok()) { 64 | spdlog::error("Error initializing inference engine: {}", 65 | engine_or.status().message()); 66 | return 1; 67 | } 68 | 69 | auto engine = std::move(engine_or.value()); 70 | 71 | // Setup a spinner and attach a progress callback 72 | auto spinner = std::make_unique(); 73 | spinner->Start(); 74 | engine.SetProgressCallback( 75 | [&spinner](int current_batch, int total_batches, int tile_index) { 76 | spinner->SetText("Processing batch " + std::to_string(current_batch) + 77 | "/" + std::to_string(total_batches) + " (tile " + 78 | std::to_string(tile_index) + ")"); 79 | }); 80 | 81 | absl::Status status = mask_path ? engine.ProcessImage(image_path, *mask_path) 82 | : engine.ProcessImage(image_path); 83 | spinner->Stop(); 84 | if (!status.ok()) { 85 | spdlog::error("Error running inference: {}", status.message()); 86 | return 1; 87 | } 88 | 89 | return 0; 90 | } 91 | -------------------------------------------------------------------------------- /src/fastslide/runtime/io/file_reader.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "fastslide/runtime/io/file_reader.h" 16 | 17 | #include "absl/strings/str_format.h" 18 | #include "aifocore/status/status_macros.h" 19 | 20 | namespace fastslide { 21 | namespace runtime { 22 | namespace io { 23 | 24 | absl::StatusOr FileReader::Open(const fs::path& path, 25 | const char* mode) { 26 | FILE* file = fopen(path.string().c_str(), mode); 27 | if (!file) { 28 | return MAKE_STATUS(absl::StatusCode::kNotFound, 29 | absl::StrFormat("Cannot open file: %s", path.string())); 30 | } 31 | return FileReader(file); 32 | } 33 | 34 | absl::Status FileReader::Seek(int64_t offset, int whence) const { 35 | if (fseek(file_.get(), offset, whence) != 0) { 36 | return MAKE_STATUS( 37 | absl::StatusCode::kInternal, 38 | absl::StrFormat("Failed to seek to offset %lld", offset)); 39 | } 40 | return absl::OkStatus(); 41 | } 42 | 43 | absl::StatusOr FileReader::GetSize() const { 44 | const int64_t current_pos = ftell(file_.get()); 45 | if (current_pos < 0) { 46 | return MAKE_STATUS(absl::StatusCode::kInternal, 47 | "Failed to get current file position"); 48 | } 49 | 50 | if (fseek(file_.get(), 0, SEEK_END) != 0) { 51 | return MAKE_STATUS(absl::StatusCode::kInternal, 52 | "Failed to seek to end of file"); 53 | } 54 | 55 | const int64_t size = ftell(file_.get()); 56 | if (size < 0) { 57 | return MAKE_STATUS(absl::StatusCode::kInternal, 58 | "Failed to determine file size"); 59 | } 60 | 61 | // Restore original position 62 | if (fseek(file_.get(), current_pos, SEEK_SET) != 0) { 63 | return MAKE_STATUS(absl::StatusCode::kInternal, 64 | "Failed to restore file position"); 65 | } 66 | 67 | return size; 68 | } 69 | 70 | absl::Status FileReader::Read(void* buffer, size_t size) const { 71 | if (fread(buffer, 1, size, file_.get()) != size) { 72 | return MAKE_STATUS(absl::StatusCode::kInternal, 73 | absl::StrFormat("Failed to read %zu bytes", size)); 74 | } 75 | return absl::OkStatus(); 76 | } 77 | 78 | absl::StatusOr> FileReader::ReadBytes(size_t size) const { 79 | std::vector buffer(size); 80 | RETURN_IF_ERROR(Read(buffer.data(), size), "Failed to read into buffer"); 81 | return buffer; 82 | } 83 | 84 | absl::StatusOr FileReader::Tell() const { 85 | const int64_t pos = ftell(file_.get()); 86 | if (pos < 0) { 87 | return MAKE_STATUS(absl::StatusCode::kInternal, 88 | "Failed to get file position"); 89 | } 90 | return pos; 91 | } 92 | 93 | } // namespace io 94 | } // namespace runtime 95 | } // namespace fastslide 96 | -------------------------------------------------------------------------------- /src/aifocore/status/trace_example.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #include 15 | #include 16 | 17 | #include "absl/status/status.h" 18 | #include "absl/status/statusor.h" 19 | #include "aifocore/status/status_macros.h" 20 | 21 | // Function at the deepest level that generates an error 22 | absl::StatusOr ReadConfigFile(const std::string& file_path, 23 | bool simulate_error) { 24 | if (simulate_error) { 25 | // <-- USE MAKE_STATUSOR for StatusOr returns 26 | return MAKE_STATUSOR(std::string, absl::StatusCode::kNotFound, 27 | "File not found: " + file_path); 28 | } 29 | return "Configuration data loaded successfully"; 30 | } 31 | 32 | // Mid-level function that processes the config 33 | absl::StatusOr ParseConfigValue(const std::string& file_path, 34 | const std::string& key, 35 | bool simulate_error) { 36 | std::string config_content; 37 | ASSIGN_OR_RETURN(config_content, ReadConfigFile(file_path, simulate_error), 38 | "Failed to read configuration file"); 39 | 40 | if (key.empty()) { 41 | // <-- MAKE_STATUSOR again 42 | return MAKE_STATUSOR(int, absl::StatusCode::kInvalidArgument, 43 | "Empty key specified"); 44 | } 45 | 46 | return 42; 47 | } 48 | 49 | // Higher level functions stay the same… 50 | 51 | // Higher level function that uses the config value 52 | absl::Status InitializeSystem(const std::string& config_path, 53 | bool simulate_error) { 54 | int config_value; 55 | ASSIGN_OR_RETURN( 56 | config_value, 57 | ParseConfigValue(config_path, "database.port", simulate_error), 58 | "Failed to parse configuration"); 59 | 60 | // Use the config value 61 | std::cout << "System initialized with config value: " << config_value 62 | << std::endl; 63 | return absl::OkStatus(); 64 | } 65 | 66 | // Top-level function that would be called by main 67 | absl::Status StartApplication(const std::string& config_path, 68 | bool simulate_error) { 69 | absl::Status status = InitializeSystem(config_path, simulate_error); 70 | if (!status.ok()) { 71 | std::cerr << "Application failed to start with error:" << std::endl; 72 | std::cerr << status.message() << std::endl; 73 | } 74 | 75 | return status; 76 | } 77 | 78 | // Main function to run the example 79 | int main(int argc, char** argv) { 80 | bool simulate_error = true; 81 | std::string config_path = "/etc/myapp/config.json"; 82 | 83 | if (argc > 1 && std::string(argv[1]) == "--no-error") { 84 | simulate_error = false; 85 | } 86 | 87 | absl::Status result = StartApplication(config_path, simulate_error); 88 | return result.ok() ? 0 : 1; 89 | } 90 | -------------------------------------------------------------------------------- /include/aifocore/utilities/vips.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Jonas Teuwen. All Rights Reserved. 2 | // Copyright 2025 Joren Brunekreef. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | #ifndef AIFO_AIFOCORE_INCLUDE_AIFOCORE_UTILITIES_VIPS_H_ 16 | #define AIFO_AIFOCORE_INCLUDE_AIFOCORE_UTILITIES_VIPS_H_ 17 | 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include "absl/status/status.h" 25 | #include "aifocore/status/status_macros.h" 26 | #include "aifocore/utilities/fmt.h" 27 | 28 | namespace fs = std::filesystem; 29 | 30 | namespace aifocore::utilities { 31 | 32 | class VipsInitializer { 33 | public: 34 | explicit VipsInitializer(const char* argv0) { 35 | if (VIPS_INIT(argv0)) { 36 | throw std::runtime_error("Failed to initialize VIPS"); 37 | } 38 | // Force single-threaded mode (1 = single thread, 0 = auto-detect) 39 | vips_concurrency_set(1); 40 | } 41 | 42 | ~VipsInitializer() { 43 | vips_shutdown(); // Ensures proper cleanup 44 | } 45 | 46 | // Delete copy constructor and assignment operator to prevent misuse 47 | VipsInitializer(const VipsInitializer&) = delete; 48 | VipsInitializer& operator=(const VipsInitializer&) = delete; 49 | }; 50 | 51 | /** 52 | * @brief Save a Vips image to a file. 53 | * 54 | * This function saves a Vips image to a file. If an options pointer is 55 | * provided, the image will be saved with the specified options. Otherwise, the 56 | * image will be saved with the default options. Wrapper to 57 | * vips::VImage::write_to_file to separate error handling from the rest of the 58 | * code. 59 | * 60 | * @param image The Vips image to save. 61 | * @param filename The path to the file to save the image to. 62 | * @param options The options to use when saving the image. 63 | * @return An absl::Status indicating the success or failure of the operation. 64 | */ 65 | absl::Status SaveVipsImageToFile(const vips::VImage& image, 66 | const fs::path& filename, 67 | vips::VOption* options = nullptr) { 68 | // Disable VIPS threading completely 69 | vips_concurrency_set(0); 70 | try { 71 | if (options) { 72 | image.write_to_file(filename.string().c_str(), options); 73 | } else { 74 | image.write_to_file(filename.string().c_str()); 75 | } 76 | return absl::OkStatus(); 77 | } catch (const vips::VError& e) { 78 | return MAKE_STATUS(absl::StatusCode::kInternal, 79 | aifocore::fmt::format("VIPS error: {}", e.what())); 80 | } catch (const std::exception& e) { 81 | return MAKE_STATUS(absl::StatusCode::kInternal, 82 | aifocore::fmt::format("Unexpected error: {}", e.what())); 83 | } catch (...) { 84 | return MAKE_STATUS(absl::StatusCode::kInternal, "Unknown exception"); 85 | } 86 | } 87 | 88 | } // namespace aifocore::utilities 89 | 90 | #endif // AIFO_AIFOCORE_INCLUDE_AIFOCORE_UTILITIES_VIPS_H_ 91 | -------------------------------------------------------------------------------- /include/fastslide/readers/tiff_based_tile_executor.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_READERS_TIFF_BASED_TILE_EXECUTOR_H_ 16 | #define AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_READERS_TIFF_BASED_TILE_EXECUTOR_H_ 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | namespace fastslide { 23 | 24 | /// @brief CRTP base class providing thread-local buffer reuse for TIFF tile executors 25 | /// 26 | /// This class provides shared thread-local buffer management for all TIFF-based 27 | /// tile executors (Aperio, QPTIFF, etc.). Each derived class gets its own 28 | /// thread-local storage through template instantiation. 29 | /// 30 | /// Benefits: 31 | /// - Eliminates per-tile allocations (reuses buffers across tiles) 32 | /// - Improves cache locality (buffers stay hot in L1/L2 cache) 33 | /// - Thread-safe by design (each thread has its own buffers) 34 | /// - Works in both sequential and parallel execution contexts 35 | /// 36 | /// Usage: 37 | /// ```cpp 38 | /// class AperioTileExecutor : public TiffBasedTileExecutor { 39 | /// // Access buffers via GetBuffers().GetTileBuffer() and GetBuffers().GetCropBuffer() 40 | /// }; 41 | /// ``` 42 | template 43 | class TiffBasedTileExecutor { 44 | protected: 45 | /// @brief Thread-local buffer pool to avoid per-tile allocations 46 | struct ThreadLocalTileBuffers { 47 | std::vector tile_buffer; 48 | std::vector crop_buffer; 49 | 50 | /// @brief Get or resize tile buffer to required size 51 | /// @param required_size Minimum buffer size needed 52 | /// @return Pointer to buffer with at least required_size bytes 53 | uint8_t* GetTileBuffer(size_t required_size) { 54 | if (tile_buffer.size() < required_size) { 55 | tile_buffer.resize(required_size); 56 | } 57 | return tile_buffer.data(); 58 | } 59 | 60 | /// @brief Get or resize crop buffer to required size 61 | /// @param required_size Minimum buffer size needed 62 | /// @return Pointer to buffer with at least required_size bytes 63 | uint8_t* GetCropBuffer(size_t required_size) { 64 | if (crop_buffer.size() < required_size) { 65 | crop_buffer.resize(required_size); 66 | } 67 | return crop_buffer.data(); 68 | } 69 | }; 70 | 71 | /// @brief Access thread-local buffers (each derived class gets its own storage) 72 | static ThreadLocalTileBuffers& GetBuffers() { return tls_buffers_; } 73 | 74 | private: 75 | // Each template instantiation (Aperio, QPTIFF, etc.) gets its own thread-local storage 76 | static thread_local ThreadLocalTileBuffers tls_buffers_; 77 | }; 78 | 79 | // Define the thread_local storage (in header for template) 80 | template 81 | thread_local typename TiffBasedTileExecutor::ThreadLocalTileBuffers 82 | TiffBasedTileExecutor::tls_buffers_; 83 | 84 | } // namespace fastslide 85 | 86 | #endif // AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_READERS_TIFF_BASED_TILE_EXECUTOR_H_ 87 | -------------------------------------------------------------------------------- /src/fastslide/runtime/register_builtin_formats.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "absl/log/log.h" 16 | #include "fastslide/runtime/plugin_loader.h" 17 | #include "fastslide/runtime/reader_registry.h" 18 | #include "fastslide/slide_reader.h" // Complete type needed for unique_ptr 19 | 20 | /** 21 | * @file register_builtin_formats.cpp 22 | * @brief Automatic registration of built-in format plugins 23 | * 24 | * This file contains the logic to register all built-in format plugins 25 | * with the global registry. The registration happens automatically via 26 | * static initialization when the library is loaded. 27 | * 28 | * The new system uses BuiltInPluginsInitializer which supports: 29 | * - Capability filtering (only load plugins with available codecs) 30 | * - Version constraints 31 | * - Optional feature detection 32 | */ 33 | 34 | namespace fastslide::runtime { 35 | 36 | namespace { 37 | 38 | /// @brief Helper class for automatic format registration 39 | /// 40 | /// This class registers all built-in formats when the library is loaded. 41 | /// It's instantiated as a static variable, ensuring registration happens 42 | /// before main() is called. 43 | class BuiltinFormatRegistrar { 44 | public: 45 | BuiltinFormatRegistrar() { 46 | auto& registry = GetGlobalRegistry(); 47 | 48 | // Create default load context with auto-detected capabilities 49 | auto context = PluginLoadContext::CreateDefault(); 50 | 51 | // Register all built-in formats with capability filtering 52 | auto status = BuiltInPluginsInitializer::RegisterAll(registry, context); 53 | if (!status.ok()) { 54 | LOG(ERROR) << "Failed to register built-in formats: " << status.message(); 55 | } 56 | } 57 | }; 58 | 59 | // Static instance triggers registration at library load time 60 | static BuiltinFormatRegistrar g_registrar; 61 | 62 | } // namespace 63 | 64 | /// @brief Manually register built-in formats 65 | /// 66 | /// This function can be called to explicitly register built-in formats 67 | /// if automatic registration via static initialization is not desired. 68 | /// It's safe to call multiple times (formats will be re-registered). 69 | /// 70 | /// @param registry Registry to register formats with 71 | void RegisterBuiltinFormats(ReaderRegistry& registry) { 72 | // Ignore return value - we're registering all formats unconditionally 73 | (void)BuiltInPluginsInitializer::RegisterAll(registry); 74 | } 75 | 76 | /// @brief Manually register built-in formats with capability filtering 77 | /// 78 | /// This function registers only formats that can be loaded with the 79 | /// available capabilities in the provided context. 80 | /// 81 | /// @param registry Registry to register formats with 82 | /// @param context Loading context with available capabilities 83 | /// @return Status indicating success or failure 84 | [[nodiscard]] absl::Status RegisterBuiltinFormats( 85 | ReaderRegistry& registry, const PluginLoadContext& context) { 86 | return BuiltInPluginsInitializer::RegisterAll(registry, context); 87 | } 88 | 89 | } // namespace fastslide::runtime 90 | -------------------------------------------------------------------------------- /include/fastslide/utilities/colors.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_UTILITIES_COLORS_H_ 16 | #define AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_UTILITIES_COLORS_H_ 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #include "absl/status/statusor.h" 23 | 24 | namespace fastslide { 25 | 26 | /// @brief RGB color structure with uint8_t components 27 | struct ColorRGB { 28 | uint8_t r{0}; 29 | uint8_t g{0}; 30 | uint8_t b{0}; 31 | 32 | /// @brief Default constructor 33 | constexpr ColorRGB() = default; 34 | 35 | /// @brief Constructor with RGB values 36 | constexpr ColorRGB(uint8_t red, uint8_t green, uint8_t blue) 37 | : r(red), g(green), b(blue) {} 38 | 39 | /// @brief Array access operator for compatibility 40 | constexpr uint8_t& operator[](std::size_t index) { return (&r)[index]; } 41 | 42 | /// @brief Const array access operator for compatibility 43 | constexpr const uint8_t& operator[](std::size_t index) const { 44 | return (&r)[index]; 45 | } 46 | 47 | /// @brief Equality comparison 48 | constexpr bool operator==(const ColorRGB& other) const { 49 | return r == other.r && g == other.g && b == other.b; 50 | } 51 | 52 | /// @brief Inequality comparison 53 | constexpr bool operator!=(const ColorRGB& other) const { 54 | return !(*this == other); 55 | } 56 | }; 57 | 58 | /// @brief Convert HSB (Hue, Saturation, Brightness) to RGB 59 | /// @param hue Hue value in range [0.0, 1.0] 60 | /// @param saturation Saturation value in range [0.0, 1.0] 61 | /// @param brightness Brightness value in range [0.0, 1.0] 62 | /// @return RGB color triplet with values in range [0, 255] 63 | ColorRGB HSBtoRGB(float hue, float saturation, float brightness); 64 | 65 | /// @brief Get default channel color using QuPath-style algorithm 66 | /// @param channel Channel index (0-based) 67 | /// @return RGB color triplet with values in range [0, 255] 68 | /// 69 | /// This function replicates QuPath's getDefaultChannelColor algorithm: 70 | /// - Channels 0-5 use predefined colors (red, green, blue, yellow, cyan, 71 | /// magenta) 72 | /// - Higher channels use HSB color space with calculated hue, saturation, and 73 | /// brightness 74 | /// - Channels >= 360 wrap around using modulo operation 75 | ColorRGB GetDefaultChannelColor(int channel); 76 | 77 | /// @brief Pack RGB values into a 32-bit integer 78 | /// @param red Red component [0, 255] 79 | /// @param green Green component [0, 255] 80 | /// @param blue Blue component [0, 255] 81 | /// @return Packed RGB value as 32-bit integer 82 | uint32_t PackRGB(uint8_t red, uint8_t green, uint8_t blue); 83 | 84 | /// @brief Parse RGB color from a comma-separated string 85 | /// @param str String containing RGB values like "255,0,128" 86 | /// @return Array of RGB components [r, g, b] or error if parsing fails 87 | /// 88 | /// This function parses color strings in the format "R,G,B" where each 89 | /// component is an integer in the range [0, 255]. 90 | absl::StatusOr> ParseRgb(const std::string& str); 91 | 92 | } // namespace fastslide 93 | 94 | #endif // AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_UTILITIES_COLORS_H_ 95 | -------------------------------------------------------------------------------- /include/fastslide/readers/mrxs/mrxs_tile_executor.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_READERS_MRXS_MRXS_TILE_EXECUTOR_H_ 16 | #define AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_READERS_MRXS_MRXS_TILE_EXECUTOR_H_ 17 | 18 | #include "absl/status/status.h" 19 | #include "absl/synchronization/mutex.h" 20 | #include "fastslide/core/tile_plan.h" 21 | #include "fastslide/image.h" 22 | #include "fastslide/readers/mrxs/mrxs_internal.h" 23 | #include "fastslide/runtime/tile_writer.h" 24 | 25 | namespace fastslide { 26 | 27 | // Forward declaration 28 | class MrxsReader; 29 | 30 | /// @brief Helper class for executing MRXS tile read operations 31 | class MrxsTileExecutor { 32 | public: 33 | /// @brief Execute a tile plan 34 | /// @param plan The tile plan to execute 35 | /// @param reader The MRXS reader instance (for accessing tile reading 36 | /// methods) 37 | /// @param writer Tile writer for output 38 | /// @return Status indicating success or failure 39 | static absl::Status ExecutePlan(const core::TilePlan& plan, 40 | const MrxsReader& reader, 41 | runtime::TileWriter& writer); 42 | 43 | private: 44 | /// @brief Execute a single tile operation 45 | /// @param op The tile operation to execute 46 | /// @param reader The MRXS reader instance 47 | /// @param zoom_level The zoom level info 48 | /// @param writer Tile writer 49 | /// @param accumulator_mutex Mutex for thread-safe accumulation 50 | /// @return Status indicating success or failure 51 | static absl::Status ExecuteTileOperation( 52 | const core::TileReadOp& op, const MrxsReader& reader, 53 | const mrxs::SlideZoomLevel& zoom_level, runtime::TileWriter& writer, 54 | absl::Mutex& accumulator_mutex); 55 | 56 | /// @brief Read and decode tile data 57 | /// @param op The tile operation 58 | /// @param reader The MRXS reader instance 59 | /// @param zoom_level The zoom level info 60 | /// @return Decoded image or error 61 | static absl::StatusOr ReadAndDecodeTile( 62 | const core::TileReadOp& op, const MrxsReader& reader, 63 | const mrxs::SlideZoomLevel& zoom_level); 64 | 65 | /// @brief Extract sub-region from decoded tile if needed 66 | /// @param image Full decoded image 67 | /// @param op Tile operation with transform information 68 | /// @return Extracted tile region 69 | static RGBImage ExtractSubRegion(const RGBImage& image, 70 | const core::TileReadOp& op); 71 | 72 | /// @brief Check if sub-region extraction is needed 73 | /// @param image_width Full image width 74 | /// @param image_height Full image height 75 | /// @param expected_width Expected tile width 76 | /// @param expected_height Expected tile height 77 | /// @return true if extraction is needed 78 | static bool NeedsSubRegionExtraction(uint32_t image_width, 79 | uint32_t image_height, 80 | uint32_t expected_width, 81 | uint32_t expected_height); 82 | }; 83 | 84 | } // namespace fastslide 85 | 86 | #endif // AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_READERS_MRXS_MRXS_TILE_EXECUTOR_H_ 87 | -------------------------------------------------------------------------------- /include/fastslide/c/registry.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All rights reserved. 2 | // 3 | // This file is part of FastSlide. 4 | // 5 | // Use of this source code is governed by the terms found in the 6 | // LICENSE file located in the FastSlide project root. 7 | 8 | #ifndef AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_C_REGISTRY_H_ 9 | #define AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_C_REGISTRY_H_ 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include "fastslide/c/slide_reader.h" 16 | 17 | #ifdef __cplusplus 18 | extern "C" { 19 | #endif 20 | 21 | /// @brief Opaque registry handle 22 | typedef struct FastSlideRegistry FastSlideRegistry; 23 | 24 | // Registry management 25 | 26 | /// @brief Initialize slide readers registry 27 | /// @return 1 on success, 0 on failure 28 | int fastslide_registry_initialize(void); 29 | 30 | /// @brief Get the global registry instance 31 | /// @return Registry handle or NULL on failure 32 | FastSlideRegistry* fastslide_registry_get_instance(void); 33 | 34 | /// @brief Create slide reader from file 35 | /// @param registry Registry handle 36 | /// @param file_path Path to slide file 37 | /// @return Slide reader handle or NULL on failure 38 | FastSlideSlideReader* fastslide_registry_create_reader( 39 | FastSlideRegistry* registry, const char* file_path); 40 | 41 | /// @brief Create slide reader from file (convenience function using global 42 | /// registry) 43 | /// @param file_path Path to slide file 44 | /// @return Slide reader handle or NULL on failure 45 | FastSlideSlideReader* fastslide_create_reader(const char* file_path); 46 | 47 | // Utility functions 48 | 49 | /// @brief Get supported file extensions 50 | /// @param registry Registry handle 51 | /// @param extensions Output array of extension strings (allocated by function) 52 | /// @param num_extensions Output number of extensions 53 | /// @return 1 on success, 0 on failure 54 | int fastslide_registry_get_supported_extensions(FastSlideRegistry* registry, 55 | char*** extensions, 56 | int* num_extensions); 57 | 58 | /// @brief Get supported file extensions (convenience function using global 59 | /// registry) 60 | /// @param extensions Output array of extension strings (allocated by function) 61 | /// @param num_extensions Output number of extensions 62 | /// @return 1 on success, 0 on failure 63 | int fastslide_get_supported_extensions(char*** extensions, int* num_extensions); 64 | 65 | /// @brief Free extensions array 66 | /// @param extensions Extensions array 67 | /// @param num_extensions Number of extensions 68 | void fastslide_registry_free_extensions(char** extensions, int num_extensions); 69 | 70 | /// @brief Check if file is supported 71 | /// @param registry Registry handle 72 | /// @param file_path Path to file 73 | /// @return 1 if supported, 0 if not supported 74 | int fastslide_registry_is_supported(FastSlideRegistry* registry, 75 | const char* file_path); 76 | 77 | /// @brief Check if file is supported 78 | /// (convenience function using global registry) 79 | /// @param file_path Path to file 80 | /// @return 1 if supported, 0 if not supported 81 | int fastslide_is_supported(const char* file_path); 82 | 83 | // Error handling 84 | 85 | /// @brief Get last error message 86 | /// @return Error message string or NULL if no error 87 | const char* fastslide_get_last_error(void); 88 | 89 | /// @brief Clear last error message 90 | void fastslide_clear_last_error(void); 91 | 92 | // Version information 93 | 94 | /// @brief Get FastSlide version 95 | /// @return Version string 96 | const char* fastslide_get_version(void); 97 | 98 | #ifdef __cplusplus 99 | } 100 | #endif 101 | 102 | #endif // AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_C_REGISTRY_H_ 103 | -------------------------------------------------------------------------------- /src/fastslide/readers/mrxs/mrxs_format_plugin.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "fastslide/readers/mrxs/mrxs_format_plugin.h" 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "absl/status/statusor.h" 25 | #include "aifocore/status/status_macros.h" 26 | #include "fastslide/readers/mrxs/mrxs.h" 27 | #include "fastslide/runtime/cache_interface.h" 28 | #include "fastslide/runtime/format_descriptor.h" 29 | #include "fastslide/slide_reader.h" 30 | 31 | namespace fastslide { 32 | namespace formats { 33 | namespace mrxs { 34 | 35 | namespace { 36 | 37 | /// @brief Factory function for creating MRXS reader instances 38 | /// 39 | /// Creates an MrxsReader instance from a filename. This function is used by 40 | /// the format registry to instantiate readers for MRXS files. 41 | /// 42 | /// @param cache Optional tile cache (nullptr = no caching) 43 | /// @param filename Path to the .mrxs file 44 | /// @return StatusOr containing unique pointer to SlideReader or error 45 | absl::StatusOr> CreateMrxsReader( 46 | std::shared_ptr cache, std::string_view filename) { 47 | DECLARE_ASSIGN_OR_RETURN_MOVE(std::unique_ptr, reader, 48 | MrxsReader::Create(std::string(filename))); 49 | 50 | // Apply cache if provided 51 | if (cache) { 52 | reader->SetITileCache(cache); 53 | } 54 | 55 | return reader; 56 | } 57 | 58 | } // namespace 59 | 60 | /// @brief Create a format descriptor for MRXS files 61 | /// 62 | /// Constructs a FormatDescriptor that describes the MRXS format's capabilities, 63 | /// requirements, and factory function. This descriptor is used by the reader 64 | /// registry to identify and handle MRXS files. 65 | /// 66 | /// Capabilities: 67 | /// - Tiled: Images are stored as tiles 68 | /// - Pyramidal: Multiple resolution levels 69 | /// - AssociatedImages: Label, macro, thumbnail images 70 | /// - Compressed: JPEG/PNG/BMP compression 71 | /// - RandomAccess: Efficient random tile access 72 | /// 73 | /// @return Complete FormatDescriptor for MRXS format 74 | FormatDescriptor CreateMrxsFormatDescriptor() { 75 | FormatDescriptor desc; 76 | 77 | desc.primary_extension = ".mrxs"; 78 | desc.format_name = "MRXS"; 79 | desc.version = "1.0.0"; 80 | 81 | // MRXS capabilities 82 | desc.capabilities = 83 | SetCapability(desc.capabilities, FormatCapability::kTiled); 84 | desc.capabilities = 85 | SetCapability(desc.capabilities, FormatCapability::kPyramidal); 86 | desc.capabilities = 87 | SetCapability(desc.capabilities, FormatCapability::kAssociatedImages); 88 | desc.capabilities = 89 | SetCapability(desc.capabilities, FormatCapability::kCompressed); 90 | desc.capabilities = 91 | SetCapability(desc.capabilities, FormatCapability::kRandomAccess); 92 | 93 | // Required capabilities (codecs, etc.) 94 | desc.required_capabilities.push_back("jpeg"); 95 | 96 | // Factory function 97 | desc.factory = CreateMrxsReader; 98 | 99 | return desc; 100 | } 101 | 102 | } // namespace mrxs 103 | } // namespace formats 104 | } // namespace fastslide 105 | -------------------------------------------------------------------------------- /src/fastslide/utilities/tile_cache_manager.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "fastslide/utilities/tile_cache_manager.h" 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | #include "absl/log/log.h" 22 | #include "absl/status/statusor.h" 23 | 24 | namespace fastslide { 25 | 26 | TileCacheManager::TileCacheManager(std::shared_ptr cache) 27 | : cache_(std::move(cache)) {} 28 | 29 | absl::StatusOr> TileCacheManager::GetTile( 30 | const std::string& filename, uint16_t level, 31 | const aifocore::Size& tile_coords, TileLoader loader) { 32 | // Try to get from cache first 33 | auto cached_tile = 34 | GetTileFromCache(filename, level, tile_coords[0], tile_coords[1]); 35 | if (cached_tile) { 36 | return cached_tile; 37 | } 38 | 39 | auto tile_result = loader(); 40 | if (!tile_result.ok()) { 41 | return tile_result.status(); 42 | } 43 | 44 | auto tile = tile_result.value(); 45 | 46 | // Store in cache if caching is enabled 47 | if (IsCacheEnabled()) { 48 | PutTile(filename, level, tile_coords[0], tile_coords[1], tile); 49 | } 50 | 51 | return tile; 52 | } 53 | 54 | std::shared_ptr TileCacheManager::GetTileFromCache( 55 | const std::string& filename, uint16_t level, uint32_t tile_x, 56 | uint32_t tile_y) { 57 | if (!IsCacheEnabled()) { 58 | return nullptr; 59 | } 60 | 61 | runtime::TileKey key = CreateCacheKey(filename, level, tile_x, tile_y); 62 | return cache_->Get(key); 63 | } 64 | 65 | void TileCacheManager::PutTile(const std::string& filename, uint16_t level, 66 | uint32_t tile_x, uint32_t tile_y, 67 | std::shared_ptr tile) { 68 | if (!IsCacheEnabled()) { 69 | return; 70 | } 71 | 72 | runtime::TileKey key = CreateCacheKey(filename, level, tile_x, tile_y); 73 | cache_->Put(key, std::move(tile)); 74 | } 75 | 76 | bool TileCacheManager::IsCacheEnabled() const { 77 | return cache_ != nullptr; 78 | } 79 | 80 | void TileCacheManager::SetCache(std::shared_ptr cache) { 81 | cache_ = std::move(cache); 82 | } 83 | 84 | std::shared_ptr TileCacheManager::GetCache() const { 85 | return cache_; 86 | } 87 | 88 | TileCache::Stats TileCacheManager::GetStats() const { 89 | if (!IsCacheEnabled()) { 90 | return TileCache::Stats{.capacity = 0, 91 | .size = 0, 92 | .hits = 0, 93 | .misses = 0, 94 | .hit_ratio = 0.0, 95 | .memory_usage_bytes = 0}; 96 | } 97 | return cache_->GetStats(); 98 | } 99 | 100 | void TileCacheManager::Clear() { 101 | if (IsCacheEnabled()) { 102 | cache_->Clear(); 103 | } 104 | } 105 | 106 | runtime::TileKey TileCacheManager::CreateCacheKey(const std::string& filename, 107 | uint16_t level, 108 | uint32_t tile_x, 109 | uint32_t tile_y) const { 110 | return runtime::TileKey(filename, level, tile_x, tile_y); 111 | } 112 | 113 | } // namespace fastslide 114 | -------------------------------------------------------------------------------- /include/aifocore/utilities/temporary.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #ifndef AIFO_AIFOCORE_INCLUDE_AIFOCORE_UTILITIES_TEMPORARY_H_ 15 | #define AIFO_AIFOCORE_INCLUDE_AIFOCORE_UTILITIES_TEMPORARY_H_ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | namespace fs = std::filesystem; 28 | 29 | namespace aifocore::utilities { 30 | 31 | class TemporaryDirectory { 32 | public: 33 | // Default constructor creates a temporary directory 34 | TemporaryDirectory() : keep_files_(false) { InitializePath(); } 35 | 36 | // Constructor with a flag to keep temporary files 37 | explicit TemporaryDirectory(bool keep_files) : keep_files_(keep_files) { 38 | InitializePath(); 39 | } 40 | 41 | ~TemporaryDirectory() { 42 | if (!keep_files_) { 43 | try { 44 | fs::remove_all( 45 | path_.parent_path()); // Remove the entire unique directory 46 | } catch (const std::exception& e) { 47 | std::cerr << "Failed to clean up temporary folder: " << e.what() 48 | << std::endl; 49 | } 50 | } 51 | } 52 | 53 | // Disable copy semantics to prevent multiple deletions of the same directory 54 | TemporaryDirectory(const TemporaryDirectory&) = delete; 55 | TemporaryDirectory& operator=(const TemporaryDirectory&) = delete; 56 | 57 | // Allow move semantics 58 | TemporaryDirectory(TemporaryDirectory&& other) noexcept 59 | : path_(std::move(other.path_)), keep_files_(other.keep_files_) {} 60 | 61 | TemporaryDirectory& operator=(TemporaryDirectory&& other) noexcept { 62 | if (this != &other) { 63 | path_ = std::move(other.path_); 64 | keep_files_ = other.keep_files_; 65 | } 66 | return *this; 67 | } 68 | 69 | [[nodiscard]] const fs::path& Path() const { return path_; } 70 | 71 | [[nodiscard]] bool IsKept() const { return keep_files_; } 72 | 73 | void SetKeep(bool keep) { keep_files_ = keep; } 74 | 75 | private: 76 | fs::path path_; 77 | bool keep_files_; 78 | 79 | void InitializePath() { 80 | std::random_device random_device; 81 | std::mt19937_64 gen(random_device()); // Using 64-bit Mersenne Twister 82 | uint64_t unique_id = gen(); 83 | 84 | auto now = std::chrono::system_clock::now(); 85 | auto timestamp = std::chrono::duration_cast( 86 | now.time_since_epoch()) 87 | .count(); 88 | 89 | std::stringstream string_stream; 90 | string_stream << std::hex << std::setfill('0') << std::setw(16) << timestamp 91 | << "_" << unique_id; 92 | 93 | // Create path with timestamp and unique identifier 94 | path_ = fs::temp_directory_path() / string_stream.str() / "aifocore_temp"; 95 | 96 | // Throw if directory already exists 97 | if (!fs::create_directories(path_)) { 98 | throw std::runtime_error( 99 | "Failed to create temporary directory: " + path_.string() + 100 | " (directory may already exist)"); 101 | } 102 | } 103 | }; 104 | 105 | } // namespace aifocore::utilities 106 | 107 | #endif // AIFO_AIFOCORE_INCLUDE_AIFOCORE_UTILITIES_TEMPORARY_H_ 108 | -------------------------------------------------------------------------------- /docs/source/formats/mrxs.rst: -------------------------------------------------------------------------------- 1 | MIRAX (MRXS) Format 2 | =================== 3 | 4 | MIRAX is a proprietary format developed by 3DHISTECH (now Sysmex) for their PANNORAMIC line of scanners. 5 | Unlike single-file formats, MRXS slides are stored across multiple files in a directory structure. 6 | 7 | Format Specification 8 | -------------------- 9 | 10 | **File Extension:** ``.mrxs`` 11 | 12 | **Detection:** FastSlide detects MRXS files based on the ``.mrxs`` file extension. 13 | 14 | File Structure 15 | -------------- 16 | 17 | An MRXS slide consists of a main index file and a companion directory: 18 | 19 | .. code-block:: text 20 | 21 | Slide001.mrxs # Main index (INI format) 22 | Slide001/ # Data directory 23 | ├── Slidedat.ini # Metadata and configuration 24 | └── Data/ 25 | ├── Index.dat # Tile location index 26 | └── Data0000.dat, ... # Compressed tile data 27 | 28 | The ``.mrxs`` file itself is a simple INI file that points to the data directory. The real content lives in 29 | the companion directory, which contains metadata, an index of tile locations, and the actual image data 30 | packed into data files. 31 | 32 | **Storage Characteristics:** 33 | 34 | - Tiles compressed as JPEG, PNG, or BMP 35 | - Multiple data files (Data0000.dat, Data0001.dat, etc.) 36 | - Binary index maps tile coordinates to file locations 37 | - Hierarchical pyramid with 2× downsampling per level 38 | 39 | Tile Organization 40 | ----------------- 41 | 42 | MRXS uses a unique tile layout based on camera capture patterns. The scanner's camera captures overlapping 43 | photographs, which are then split into non-overlapping tiles. **Tiles from adjacent photographs overlap at their 44 | boundaries**, requiring special handling during reconstruction. 45 | 46 | FastSlide handles these overlaps by averaging pixel values in overlapping regions, 47 | after subpixel alignment using the Magic Kernel 2021 48 | 49 | Metadata 50 | -------- 51 | 52 | The Slidedat.ini file uses INI format with multiple sections: 53 | 54 | .. code-block:: ini 55 | 56 | [GENERAL] 57 | SLIDE_ID = 123456789 58 | IMAGENUMBER_X = 8 59 | IMAGENUMBER_Y = 6 60 | OBJECTIVE_MAGNIFICATION = 20 61 | MICROMETER_PER_PIXEL_X = 0.2432 62 | MICROMETER_PER_PIXEL_Y = 0.2432 63 | 64 | [HIERARCHICAL] 65 | HIER_COUNT = 9 # Number of pyramid levels 66 | HIER_0_COUNT = 48 # Tiles in level 0 67 | 68 | **Key Metadata Sections:** 69 | 70 | - **[GENERAL]**: Basic slide properties (dimensions, magnification, resolution) 71 | - **[HIERARCHICAL]**: Pyramid structure (levels, tile counts) 72 | - **[DATAFILE]**: Maps file indices to data filenames 73 | - **[NONHIER_*]**: Associated images (thumbnail, macro, label) 74 | 75 | Data Files 76 | ---------- 77 | 78 | **Index.dat** contains a binary index that maps tile coordinates to byte offsets in the data files. 79 | Each entry specifies which data file contains a tile and where in that file to find it. 80 | 81 | **Data files** (Data0000.dat, etc.) store the compressed tiles as contiguous blobs. The index provides 82 | the exact byte offset and length for each tile. 83 | 84 | Associated Images 85 | ----------------- 86 | 87 | MRXS supports optional associated images referenced in Slidedat.ini: 88 | 89 | - **thumbnail**: Preview image (ScanDataLayer_SlidePreview) 90 | - **macro**: Full slide photograph (ScanDataLayer_SlideThumbnail) 91 | - **label**: Barcode/label image (ScanDataLayer_SlideBarcode) 92 | 93 | References 94 | ---------- 95 | 96 | - `OpenSlide MIRAX Documentation `_ 97 | 98 | .. note:: 99 | MIRAX is a proprietary format. This documentation is based on reverse engineering efforts 100 | and implementation experience of many people and open source projects, especially the OpenSlide 101 | project. 102 | -------------------------------------------------------------------------------- /docs/doxygen_header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | $projectname: $title 11 | 12 | 13 | $title 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | $treeview $search $mathjax $darkmode 30 | 31 | $extrastylesheet 32 | 33 | 34 | 35 |
36 | 37 | 38 | 39 |
40 | 41 | 42 | 43 |
44 | 45 | 46 | 47 | 48 | 51 | 52 | 53 | 64 | 65 | 66 | 67 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 |
54 |
55 | $projectname $projectnumber 59 |
60 | 61 |
$projectbrief
62 | 63 |
68 |
$projectbrief
69 |
$searchbox
$searchbox
89 |
90 | 91 | 92 |
93 |
94 | 95 | 96 | -------------------------------------------------------------------------------- /include/fastslide/core/metadata.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_CORE_METADATA_H_ 16 | #define AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_CORE_METADATA_H_ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | /** 24 | * @file metadata.h 25 | * @brief Core metadata container without external dependencies 26 | * 27 | * This header defines the metadata container and standardized keys for 28 | * slide metadata. This is a pure domain model with minimal dependencies, 29 | * suitable for use in language bindings and format plugins. 30 | */ 31 | 32 | namespace fastslide { 33 | namespace core { 34 | 35 | /// @brief Metadata key constants for standardized access 36 | /// 37 | /// This namespace defines the standardized metadata keys that slide readers 38 | /// should populate. Keys are categorized as: 39 | /// - **Mandatory**: All readers MUST provide these keys 40 | /// - **Optional**: Readers SHOULD provide these if available 41 | /// - **Format-specific**: Readers MAY provide additional format-specific keys 42 | namespace MetadataKeys { 43 | 44 | // Mandatory keys (all readers must provide) 45 | inline constexpr std::string_view kFormat = 46 | "format"; ///< File format name (e.g., "MRXS", "SVS", "QPTIFF") 47 | inline constexpr std::string_view kLevels = 48 | "levels"; ///< Number of pyramid levels 49 | 50 | // Optional keys (provide if available) 51 | inline constexpr std::string_view kMppX = 52 | "mpp_x"; ///< Microns per pixel in X direction 53 | inline constexpr std::string_view kMppY = 54 | "mpp_y"; ///< Microns per pixel in Y direction 55 | inline constexpr std::string_view kMagnification = 56 | "magnification"; ///< Objective magnification 57 | inline constexpr std::string_view kObjective = "objective"; ///< Objective name 58 | inline constexpr std::string_view kScannerModel = 59 | "scanner_model"; ///< Scanner manufacturer/model 60 | inline constexpr std::string_view kScannerID = "scanner_id"; ///< Scanner ID 61 | inline constexpr std::string_view kSlideID = "slide_id"; ///< Slide identifier 62 | inline constexpr std::string_view kChannels = 63 | "channels"; ///< Number of channels 64 | inline constexpr std::string_view kAssociatedImages = 65 | "associated_images"; ///< Number of associated images 66 | 67 | // Format-specific keys (readers may add more as needed) 68 | 69 | } // namespace MetadataKeys 70 | 71 | /// @brief Metadata value type 72 | /// 73 | /// Metadata values can be strings, integers (size_t), or floating-point 74 | /// numbers (double). This variant provides type-safe storage. 75 | using MetadataValue = std::variant; 76 | 77 | /// @brief Lightweight metadata container 78 | /// 79 | /// This is a thin wrapper around std::map that provides a standardized 80 | /// interface for slide metadata. It's designed to be lightweight and 81 | /// have minimal dependencies. 82 | /// 83 | /// Note: For rich functionality (printing, validation, type conversion), 84 | /// use the full Metadata class in fastslide/metadata.h instead. 85 | using MetadataContainer = std::map; 86 | 87 | } // namespace core 88 | 89 | // Import core types into fastslide namespace 90 | using core::MetadataContainer; 91 | using core::MetadataValue; 92 | // Note: MetadataKeys is imported via metadata.h (not needed here) 93 | 94 | } // namespace fastslide 95 | 96 | #endif // AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_CORE_METADATA_H_ 97 | -------------------------------------------------------------------------------- /src/fastslide/python/cache.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "fastslide/python/cache.h" 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "absl/status/status.h" 23 | #include "absl/status/statusor.h" 24 | #include "aifocore/status/status_macros.h" 25 | 26 | namespace fastslide::python { 27 | 28 | // CacheManager implementation 29 | CacheManager::CacheManager(std::shared_ptr cache) 30 | : cache_(std::move(cache)) {} 31 | 32 | absl::StatusOr> CacheManager::Create( 33 | size_t capacity) { 34 | std::shared_ptr cache; 35 | ASSIGN_OR_RETURN(cache, TileCache::CreateShared(capacity), 36 | "Failed to create tile cache"); 37 | return std::shared_ptr(new CacheManager(std::move(cache))); 38 | } 39 | 40 | std::shared_ptr CacheManager::GetCache() const { 41 | return cache_; 42 | } 43 | 44 | void CacheManager::Clear() { 45 | cache_->Clear(); 46 | } 47 | 48 | TileCache::Stats CacheManager::GetBasicStats() const { 49 | return cache_->GetStats(); 50 | } 51 | 52 | CacheInspectionStats CacheManager::GetDetailedStats() const { 53 | auto basic = cache_->GetStats(); 54 | CacheInspectionStats detailed; 55 | detailed.capacity = basic.capacity; 56 | detailed.size = basic.size; 57 | detailed.hits = basic.hits; 58 | detailed.misses = basic.misses; 59 | detailed.hit_ratio = basic.hit_ratio; 60 | detailed.memory_usage_mb = 61 | basic.memory_usage_bytes / (1024.0 * 1024.0); // Convert bytes to MB 62 | // TODO(jonasteuwen): Add actual key tracking if needed 63 | return detailed; 64 | } 65 | 66 | absl::Status CacheManager::Resize(size_t new_capacity) { 67 | // Create new cache with new capacity 68 | std::shared_ptr new_cache; 69 | ASSIGN_OR_RETURN(new_cache, TileCache::CreateShared(new_capacity), 70 | "Failed to create new tile cache"); 71 | cache_ = std::move(new_cache); 72 | return absl::OkStatus(); 73 | } 74 | 75 | // GlobalCacheManager implementation 76 | GlobalCacheManager& GlobalCacheManager::Instance() { 77 | static GlobalCacheManager instance; 78 | return instance; 79 | } 80 | 81 | std::shared_ptr GlobalCacheManager::GetCache() { 82 | return std::shared_ptr(&GlobalTileCache::Instance().GetCache(), 83 | [](TileCache*) {}); 84 | } 85 | 86 | absl::Status GlobalCacheManager::SetCapacity(size_t capacity) { 87 | return GlobalTileCache::Instance().SetCapacity(capacity); 88 | } 89 | 90 | TileCache::Stats GlobalCacheManager::GetStats() { 91 | return GlobalTileCache::Instance().GetCache().GetStats(); 92 | } 93 | 94 | CacheInspectionStats GlobalCacheManager::GetDetailedStats() { 95 | auto basic = GlobalTileCache::Instance().GetCache().GetStats(); 96 | CacheInspectionStats detailed; 97 | detailed.capacity = basic.capacity; 98 | detailed.size = basic.size; 99 | detailed.hits = basic.hits; 100 | detailed.misses = basic.misses; 101 | detailed.hit_ratio = basic.hit_ratio; 102 | detailed.memory_usage_mb = 103 | basic.memory_usage_bytes / (1024.0 * 1024.0); // Convert bytes to MB 104 | // TODO(jonasteuwen): Add actual key tracking if needed 105 | return detailed; 106 | } 107 | 108 | void GlobalCacheManager::Clear() { 109 | GlobalTileCache::Instance().GetCache().Clear(); 110 | } 111 | 112 | } // namespace fastslide::python 113 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for building FastSlide documentation (Sphinx + Doxygen) 2 | # For deployment to docs.aifo.dev/fastslide 3 | 4 | .PHONY: help clean html doxygen all serve install-deps 5 | 6 | # Directories 7 | SOURCEDIR = source 8 | BUILDDIR = build 9 | HTMLDIR = $(BUILDDIR)/html 10 | DOXYGENDIR = $(BUILDDIR)/doxygen 11 | ROOTDIR = .. 12 | 13 | # Commands 14 | SPHINXBUILD = sphinx-build 15 | SPHINXOPTS = 16 | DOXYGEN = doxygen 17 | 18 | # Default target 19 | help: 20 | @echo "FastSlide Documentation Build System" 21 | @echo "" 22 | @echo "Usage:" 23 | @echo " make html Build Sphinx HTML documentation" 24 | @echo " make doxygen Build Doxygen C++ API documentation" 25 | @echo " make all Build both Sphinx and Doxygen documentation" 26 | @echo " make clean Remove all build artifacts" 27 | @echo " make serve Start a local HTTP server to preview docs" 28 | @echo " make install-deps Install Python dependencies for building docs" 29 | @echo "" 30 | @echo "Output structure for docs.aifo.dev/fastslide:" 31 | @echo " $(HTMLDIR)/ - Sphinx documentation (main site)" 32 | @echo " $(HTMLDIR)/doxygen/ - Doxygen C++ API reference" 33 | 34 | # Install Python dependencies 35 | install-deps: 36 | @echo "Installing documentation dependencies..." 37 | uv pip install sphinx sphinx_rtd_theme breathe myst-parser sphinx-design \ 38 | sphinx-copybutton sphinx-inline-tabs sphinxcontrib-mermaid \ 39 | sphinxext-opengraph sphinx-autodoc-typehints 40 | 41 | # Clean all build artifacts 42 | clean: 43 | @echo "Cleaning build directory..." 44 | rm -rf $(BUILDDIR) 45 | @echo "Clean complete." 46 | 47 | # Build Doxygen documentation 48 | doxygen: 49 | @echo "Building Doxygen C++ API documentation..." 50 | @mkdir -p $(DOXYGENDIR) 51 | cd $(ROOTDIR) && $(DOXYGEN) Doxyfile 52 | @echo "Doxygen documentation built in: $(DOXYGENDIR)" 53 | 54 | # Build Sphinx documentation 55 | html: doxygen 56 | @echo "Building Sphinx documentation..." 57 | @mkdir -p $(HTMLDIR) 58 | $(SPHINXBUILD) -b html $(SOURCEDIR) $(HTMLDIR) $(SPHINXOPTS) 59 | @echo "" 60 | @echo "Copying Doxygen output to HTML build directory..." 61 | @mkdir -p $(HTMLDIR)/doxygen 62 | @cp -r $(DOXYGENDIR)/html/* $(HTMLDIR)/doxygen/ 63 | @echo "" 64 | @echo "Build complete. The HTML pages are in $(HTMLDIR)" 65 | @echo "" 66 | @echo "Documentation structure:" 67 | @echo " - Sphinx docs: $(HTMLDIR)/index.html" 68 | @echo " - Doxygen C++ API: $(HTMLDIR)/doxygen/index.html" 69 | 70 | # Build all documentation 71 | all: clean html 72 | @echo "" 73 | @echo "===================================================" 74 | @echo "All documentation built successfully!" 75 | @echo "===================================================" 76 | @echo "" 77 | @echo "To deploy to docs.aifo.dev/fastslide, sync the contents of:" 78 | @echo " $(HTMLDIR)/" 79 | @echo "" 80 | @echo "This will make available:" 81 | @echo " - https://docs.aifo.dev/fastslide/ (Sphinx)" 82 | @echo " - https://docs.aifo.dev/fastslide/doxygen/ (Doxygen)" 83 | 84 | # Copy Doxygen output to HTML build directory 85 | copy-doxygen: 86 | @echo "Copying Doxygen output to HTML build directory..." 87 | @mkdir -p $(HTMLDIR)/doxygen 88 | @if [ -d "$(DOXYGENDIR)/html" ]; then \ 89 | cp -r $(DOXYGENDIR)/html/* $(HTMLDIR)/doxygen/; \ 90 | echo "Doxygen documentation copied to $(HTMLDIR)/doxygen/"; \ 91 | else \ 92 | echo "Error: Doxygen output not found. Run 'make doxygen' first."; \ 93 | exit 1; \ 94 | fi 95 | 96 | # Serve documentation locally for testing 97 | serve: 98 | @if [ ! -d "$(HTMLDIR)" ]; then \ 99 | echo "Build directory not found. Running 'make all' first..."; \ 100 | $(MAKE) all; \ 101 | fi 102 | @echo "" 103 | @echo "Starting local HTTP server..." 104 | @echo "Documentation will be available at:" 105 | @echo " - http://localhost:8000/ (Sphinx)" 106 | @echo " - http://localhost:8000/doxygen/ (Doxygen)" 107 | @echo "" 108 | @echo "Press Ctrl+C to stop the server" 109 | @cd $(HTMLDIR) && python3 -m http.server 8000 110 | 111 | -------------------------------------------------------------------------------- /tools/copy.bara.sky: -------------------------------------------------------------------------------- 1 | # copy.bara.sky 2 | 3 | # This configuration file sets up a Copybara workflow to sync a specific part of NKI-AI/aiforoncology-internal 4 | # to the NKI-AI/aiforoncology repository. 5 | 6 | # Update the following fields according to your needs: 7 | # - In 'origin', change the URL and ref to match your source repository and branch. 8 | # - In 'destination', ensure the URL and push branch point to the target repository. 9 | # - The 'origin_files' glob restricts the sync to the specific part of the project you want to mirror. Adjust it 10 | # (e.g., "sync_folder/**") as necessary. 11 | 12 | REFERENCE_REPO_URL = "git@github.com:NKI-AI/aiforoncology-internal.git" # The source repository URL to sync from. 13 | REFERENCE_BRANCH = "feature/slideinsight-api" # The branch in the source repository to sync from. 14 | 15 | TARGET_REPO_URL = "git@github.com:NKI-AI/fastslide.git" # The destination repository URL to sync to. 16 | TARGET_BRANCH = "sync/internal" # The branch in the destination repository to sync to. 17 | 18 | # Configurable list of files (e.g. README.md) 19 | GENERAL_FILES = [ 20 | "aifo/fastslide/README_PUBLIC.md", 21 | "aifo/fastslide/meson.build", 22 | "aifo/fastslide/LICENSE", 23 | "aifo/fastslide/meson_options.txt", 24 | "aifo/fastslide/MANIFEST.in", 25 | "aifo/fastslide/pyproject.toml", 26 | "aifo/fastslide/requirements_linux.txt", 27 | "aifo/fastslide/requirements_darwin.txt", 28 | ] 29 | 30 | # Configurable list of source globs (e.g. src/python) 31 | SRC_GLOBS = [ 32 | "aifo/fastslide/src/**", 33 | "aifo/fastslide/include/**", 34 | "aifo/fastslide/python/**", 35 | "aifo/aifocore/src/**", 36 | "aifo/aifocore/include/**", 37 | "third_party/lodepng/**", 38 | ] 39 | 40 | # Configurable list of other globs for projects to copy over. 41 | OTHER_GLOBS = [ 42 | "aifo/fastslide/benchmarks/**", 43 | "aifo/fastslide/docs/**", 44 | "aifo/fastslide/examples/**", 45 | "aifo/fastslide/tests/**", 46 | "aifo/fastslide/tools/**", 47 | "aifo/fastslide/subprojects/**", 48 | ] 49 | 50 | EXCLUDED_FILES = [ 51 | "**/*.bazel", 52 | "**/*.BUILD", 53 | ] 54 | 55 | core.workflow( 56 | name = "default", 57 | origin = git.origin( 58 | url = REFERENCE_REPO_URL, 59 | ref = REFERENCE_BRANCH, 60 | partial_fetch = True, # Use partial clone to avoid downloading all LFS files 61 | ), 62 | destination = git.destination( 63 | url = TARGET_REPO_URL, 64 | fetch = TARGET_BRANCH, 65 | push = TARGET_BRANCH, 66 | ), 67 | 68 | origin_files = GENERAL_FILES + glob(SRC_GLOBS + OTHER_GLOBS, exclude = EXCLUDED_FILES), 69 | destination_files = glob(["**"]), 70 | transformations = [ 71 | core.move( 72 | before = "aifo/fastslide/src/", 73 | after = "src/fastslide/" 74 | ), 75 | core.move( 76 | before = "aifo/aifocore/src/", 77 | after = "src/aifocore/" 78 | ), 79 | core.move( 80 | before = "aifo/fastslide/", 81 | after = "" 82 | ), 83 | core.move( 84 | before = "aifo/aifocore/", 85 | after = "" 86 | ), 87 | core.replace( 88 | before = "\"aifo/fastslide/src/", 89 | after = "\"src/fastslide/", 90 | regex_groups = {}, 91 | paths = glob(["**/*.cpp", "**/*.h"]) 92 | ), 93 | core.move( 94 | before = "README_PUBLIC.md", 95 | after = "README.md" 96 | ) 97 | ], 98 | authoring = authoring.pass_thru("Jonas Teuwen ") 99 | ) 100 | 101 | # To execute the workflow, run: 102 | # bazel run //tools:copybara -- migrate 103 | # If it's your first time running Copybara in specific target, you may need to initialize it first: 104 | # bazel run //tools:copybara -- migrate --init-history 105 | # You can also use the `--force` flag to overwrite existing files in the destination repository. 106 | 107 | # Ensure you have the necessary permissions and that the branches exist in both repositories. 108 | -------------------------------------------------------------------------------- /include/fastslide/runtime/tile_writer/blended/blended_strategy.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RUNTIME_TILE_WRITER_BLENDED_BLENDED_STRATEGY_H_ 16 | #define AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RUNTIME_TILE_WRITER_BLENDED_BLENDED_STRATEGY_H_ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "absl/status/status.h" 26 | #include "absl/status/statusor.h" 27 | #include "absl/synchronization/mutex.h" 28 | #include "fastslide/core/tile_plan.h" 29 | #include "fastslide/image.h" 30 | #include "fastslide/runtime/tile_writer.h" 31 | #include "fastslide/runtime/tile_writer/tile_writer_strategy.h" 32 | #include "hwy/aligned_allocator.h" 33 | 34 | namespace fastslide::runtime { 35 | 36 | // SIMD padding for buffer allocations (AVX-512 = 16 floats = 64 bytes) 37 | // Prevents buffer overruns with unaligned vector stores 38 | constexpr size_t kSimdPadding = 16; 39 | 40 | /// @brief Blended tile writing strategy (weighted composition in linear RGB) 41 | class BlendedStrategy : public ITileWriterStrategy { 42 | public: 43 | explicit BlendedStrategy(const TileWriter::Config& config); 44 | 45 | absl::Status WriteTile(const core::TileReadOp& op, 46 | std::span pixel_data, 47 | uint32_t tile_width, uint32_t tile_height, 48 | uint32_t tile_channels) override; 49 | 50 | /// @brief Write tile with explicit mutex for thread-safe accumulation 51 | absl::Status WriteTile(const core::TileReadOp& op, 52 | std::span pixel_data, 53 | uint32_t tile_width, uint32_t tile_height, 54 | uint32_t tile_channels, 55 | absl::Mutex& accumulator_mutex); 56 | 57 | absl::Status Finalize() override; 58 | ImageDimensions GetDimensions() const override; 59 | uint32_t GetChannels() const override; 60 | absl::StatusOr GetOutput() override; 61 | std::string GetName() const override; 62 | uint8_t* GetOutputBuffer() override; 63 | size_t GetOutputBufferSize() const override; 64 | 65 | private: 66 | absl::Status WriteRGBTileBlended(const core::TileReadOp& op, 67 | std::span pixel_data, 68 | uint32_t tile_width, uint32_t tile_height, 69 | absl::Mutex& accumulator_mutex); 70 | 71 | absl::Status WriteMultiChannelTileSimple(const core::TileReadOp& op, 72 | std::span pixel_data, 73 | uint32_t tile_width, 74 | uint32_t tile_height, 75 | uint32_t tile_channels); 76 | 77 | TileWriter::Config config_; 78 | std::unique_ptr output_image_; 79 | // Use aligned allocator for SIMD (AVX-512 requires 64-byte alignment) 80 | // Planar storage for better cache locality and sequential memory access 81 | hwy::AlignedVector accumulator_r_; // Linear R channel accumulator 82 | hwy::AlignedVector accumulator_g_; // Linear G channel accumulator 83 | hwy::AlignedVector accumulator_b_; // Linear B channel accumulator 84 | hwy::AlignedVector weight_sum_; // Weight sum per pixel 85 | absl::Mutex internal_mutex_; // Mutex for non-parallel WriteTile calls 86 | bool finalized_ = false; 87 | }; 88 | 89 | } // namespace fastslide::runtime 90 | 91 | #endif // AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RUNTIME_TILE_WRITER_BLENDED_BLENDED_STRATEGY_H_ 92 | -------------------------------------------------------------------------------- /include/fastslide/runtime/io/file_reader.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RUNTIME_IO_FILE_READER_H_ 16 | #define AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RUNTIME_IO_FILE_READER_H_ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "absl/status/status.h" 25 | #include "absl/status/statusor.h" 26 | 27 | namespace fs = std::filesystem; 28 | 29 | namespace fastslide { 30 | namespace runtime { 31 | namespace io { 32 | 33 | /// @brief RAII wrapper for FILE* operations 34 | /// 35 | /// Provides automatic file handle cleanup and error-checked operations. 36 | /// Prevents resource leaks and reduces boilerplate in file I/O code. 37 | /// 38 | /// Example usage: 39 | /// ```cpp 40 | /// ASSIGN_OR_RETURN(auto reader, FileReader::Open(path, "rb")); 41 | /// ASSIGN_OR_RETURN(int64_t size, reader.GetSize()); 42 | /// RETURN_IF_ERROR(reader.Seek(offset)); 43 | /// ``` 44 | class FileReader { 45 | public: 46 | /// @brief Default constructor (creates invalid reader) 47 | FileReader() : file_(nullptr, fclose) {} 48 | 49 | /// @brief Open a file for reading/writing 50 | /// @param path Path to file 51 | /// @param mode File open mode ("rb", "wb", etc.) 52 | /// @return FileReader instance or error 53 | /// @retval absl::NotFoundError if file cannot be opened 54 | static absl::StatusOr Open(const fs::path& path, 55 | const char* mode); 56 | 57 | /// @brief Move constructor 58 | FileReader(FileReader&& other) noexcept = default; 59 | 60 | /// @brief Move assignment 61 | FileReader& operator=(FileReader&& other) noexcept = default; 62 | 63 | /// @brief Destructor (automatic via unique_ptr) 64 | ~FileReader() = default; 65 | 66 | /// @brief Deleted copy constructor (unique ownership) 67 | FileReader(const FileReader&) = delete; 68 | 69 | /// @brief Deleted copy assignment (unique ownership) 70 | FileReader& operator=(const FileReader&) = delete; 71 | 72 | /// @brief Get raw FILE pointer 73 | /// @return FILE pointer (never null) 74 | FILE* Get() const { return file_.get(); } 75 | 76 | /// @brief Seek to position in file 77 | /// @param offset Byte offset 78 | /// @param whence SEEK_SET, SEEK_CUR, or SEEK_END 79 | /// @return OkStatus or error 80 | absl::Status Seek(int64_t offset, int whence = SEEK_SET) const; 81 | 82 | /// @brief Get file size 83 | /// @return File size in bytes or error 84 | absl::StatusOr GetSize() const; 85 | 86 | /// @brief Read data from file 87 | /// @param buffer Buffer to read into 88 | /// @param size Number of bytes to read 89 | /// @return OkStatus or error 90 | absl::Status Read(void* buffer, size_t size) const; 91 | 92 | /// @brief Read data into vector 93 | /// @param size Number of bytes to read 94 | /// @return Vector of bytes or error 95 | absl::StatusOr> ReadBytes(size_t size) const; 96 | 97 | /// @brief Get current file position 98 | /// @return Current position or error 99 | absl::StatusOr Tell() const; 100 | 101 | private: 102 | /// @brief Private constructor (use Open factory method) 103 | explicit FileReader(FILE* file) : file_(file, fclose) {} 104 | 105 | std::unique_ptr file_; 106 | }; 107 | 108 | } // namespace io 109 | } // namespace runtime 110 | 111 | // Import into fastslide namespace for convenience 112 | using runtime::io::FileReader; 113 | 114 | } // namespace fastslide 115 | 116 | #endif // AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_RUNTIME_IO_FILE_READER_H_ 117 | -------------------------------------------------------------------------------- /docs/source/formats/qptiff.rst: -------------------------------------------------------------------------------- 1 | QPTIFF Format 2 | ============= 3 | 4 | QPTIFF (PerkinElmer Quantitative Pathology TIFF) is a multi-channel imaging format designed for 5 | spectral microscopy applications. Unlike standard RGB imaging, QPTIFF stores each spectral channel 6 | as a separate grayscale image, enabling quantitative analysis of immunofluorescence and hyperspectral data. 7 | 8 | Format Specification 9 | -------------------- 10 | 11 | **File Extensions:** ``.tif``, ``.tiff``, ``.qptiff`` 12 | 13 | **Detection:** FastSlide detects QPTIFF files based on the file extension matching ``.tif``, ``.tiff``, 14 | or ``.qptiff``. 15 | 16 | File Structure 17 | -------------- 18 | 19 | QPTIFF organizes channels as separate TIFF pages in a page-per-channel layout: 20 | 21 | .. code-block:: text 22 | 23 | multispectral.tif 24 | ├── Page 0: Channel 0, Full resolution 25 | ├── Page 1: Channel 1, Full resolution 26 | ├── Page 2: Channel 2, Full resolution 27 | ├── ... 28 | ├── Page N: Channel 0, Downsampled 2× 29 | ├── Page N+1: Channel 1, Downsampled 2× 30 | └── ... 31 | 32 | Each channel is stored separately as a grayscale (single-sample) image. For a multi-level pyramid, 33 | all channels at level 0 come first, followed by all channels at level 1, and so on. 34 | 35 | **Key Characteristics:** 36 | 37 | - One TIFF page per channel per pyramid level 38 | - Grayscale images (1 sample per pixel) 39 | - Supports 8-bit, 12-bit, 16-bit, and 32-bit float data 40 | - Compressed with LZW, JPEG, or stored uncompressed 41 | - 3-100+ spectral channels typical 42 | 43 | Channel Types 44 | ------------- 45 | 46 | **Multispectral Imaging** (3-10 channels): 47 | 48 | Specific fluorescent markers like DAPI (nuclei), FITC, Cy3, Cy5. Each channel captures emission 49 | from a particular fluorophore at a defined wavelength. 50 | 51 | **Hyperspectral Imaging** (50-100+ channels): 52 | 53 | Dense wavelength sampling across the visible or near-infrared spectrum. Used for material identification 54 | through spectral signatures and advanced unmixing algorithms. 55 | 56 | Metadata 57 | -------- 58 | 59 | Channel information is embedded in XML within each page's ImageDescription tag: 60 | 61 | .. code-block:: xml 62 | 63 | 64 | 65 | Channel_DAPI 66 | FullResolution 67 | 0 68 | 69 | DAPI 70 | Fluorophore 71 | 72 | 73 | 461 74 | 100 75 | 76 | 20 77 | 0.3250 78 | 0.3250 79 | 80 | 81 | **Key Metadata:** 82 | 83 | - **Name**: Channel identifier 84 | - **ChannelID**: Numeric index starting from 0 85 | - **Wavelength**: Emission wavelength in nanometers 86 | - **ExposureTime**: Camera exposure in milliseconds 87 | - **PhysicalSizeX/Y**: Microns per pixel (resolution) 88 | 89 | Pyramid Structure 90 | ----------------- 91 | 92 | The pyramid follows a by-channel grouping: 93 | 94 | .. code-block:: text 95 | 96 | Level 0 (Full resolution, e.g., 40000×30000): 97 | Pages 0-2: Channels 0, 1, 2 98 | 99 | Level 1 (2× downsampled, 20000×15000): 100 | Pages 3-5: Channels 0, 1, 2 101 | 102 | Level 2 (4× downsampled, 10000×7500): 103 | Pages 6-8: Channels 0, 1, 2 104 | 105 | All channels at a given level have identical dimensions. Downsampling is typically 2× per level, 106 | with 4-6 levels total. 107 | 108 | Compatibility 109 | ------------- 110 | 111 | FastSlide's QPTIFF reader works with files from PerkinElmer Vectra systems and is compatible with 112 | Bio-Formats, QuPath, and ImageJ/Fiji. 113 | 114 | References 115 | ---------- 116 | 117 | - `TIFF Specification `_ 118 | - `Bio-Formats QPTIFF Support `_ 119 | 120 | .. note:: 121 | The QPTIFF specification is not publicly documented. This documentation is based on 122 | reverse engineering and practical implementation experience. 123 | -------------------------------------------------------------------------------- /include/fastslide/utilities/tiff/tiff_cache_service.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_UTILITIES_TIFF_TIFF_CACHE_SERVICE_H_ 16 | #define AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_UTILITIES_TIFF_TIFF_CACHE_SERVICE_H_ 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #include "absl/status/statusor.h" 23 | #include "aifocore/concepts/numeric.h" 24 | #include "fastslide/utilities/cache.h" 25 | #include "fastslide/utilities/tiff/planar_interleaver.h" 26 | #include "fastslide/utilities/tiff/tiff_file.h" 27 | #include "fastslide/utilities/tile_cache_manager.h" 28 | 29 | namespace fastslide { 30 | namespace tiff { 31 | 32 | /// @brief Specialized cache service for TIFF-based readers 33 | /// 34 | /// This class extends TileCacheManager with TIFF-specific caching functionality, 35 | /// providing high-level methods that handle both tiled and strip-based TIFF data 36 | /// with automatic planar interleaving and cache management. 37 | class TiffCacheService : public TileCacheManager { 38 | public: 39 | /// @brief Constructor 40 | /// @param cache Optional tile cache (nullptr to disable caching) 41 | explicit TiffCacheService(std::shared_ptr cache = nullptr); 42 | 43 | /// @brief Get a tile from cache or load it using TIFF file 44 | /// 45 | /// This method handles the complete tile loading workflow including 46 | /// cache lookup, planar interleaving, and cache storage. 47 | /// 48 | /// @param filename TIFF file path (for cache key generation) 49 | /// @param page TIFF page number (for cache key generation) 50 | /// @param tile_coords Tile coordinates 51 | /// @param tiff_file TiffFile wrapper for reading 52 | /// @param tile_dims Tile dimensions 53 | /// @param interleaver_config Planar interleaving configuration 54 | /// @param bytes_per_pixel Total bytes per pixel 55 | /// @return Cached tile or error status 56 | absl::StatusOr> GetTileFromTiff( 57 | const std::string& filename, uint16_t page, 58 | const aifocore::Size& tile_coords, TiffFile& tiff_file, 59 | const aifocore::Size& tile_dims, 60 | const PlanarInterleaver::Config& interleaver_config, 61 | uint32_t bytes_per_pixel); 62 | 63 | /// @brief Get a scanline from cache or load it using TIFF file 64 | /// 65 | /// This method treats scanlines as single-row tiles for caching purposes 66 | /// and handles planar interleaving for strip-based TIFF data. 67 | /// 68 | /// @param filename TIFF file path (for cache key generation) 69 | /// @param page TIFF page number (for cache key generation) 70 | /// @param row Scanline row number 71 | /// @param tiff_file TiffFile wrapper for reading 72 | /// @param image_width Image width 73 | /// @param interleaver_config Planar interleaving configuration 74 | /// @param bytes_per_pixel Total bytes per pixel 75 | /// @return Cached tile representing the scanline or error status 76 | absl::StatusOr> GetScanlineFromTiff( 77 | const std::string& filename, uint16_t page, uint32_t row, 78 | TiffFile& tiff_file, uint32_t image_width, 79 | const PlanarInterleaver::Config& interleaver_config, 80 | uint32_t bytes_per_pixel); 81 | 82 | private: 83 | /// @brief Calculate tile cache coordinates from tile coordinates 84 | /// @param tile_coords Tile coordinates in image space 85 | /// @param tile_dims Tile dimensions 86 | /// @return Cache coordinates (tile indices) 87 | aifocore::Size CalculateCacheCoords( 88 | const aifocore::Size& tile_coords, 89 | const aifocore::Size& tile_dims) const; 90 | }; 91 | 92 | } // namespace tiff 93 | } // namespace fastslide 94 | 95 | #endif // AIFO_FASTSLIDE_INCLUDE_FASTSLIDE_UTILITIES_TIFF_TIFF_CACHE_SERVICE_H_ 96 | -------------------------------------------------------------------------------- /src/fastslide/runtime/tile_writer/direct/fills.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Jonas Teuwen. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "fastslide/runtime/tile_writer/direct/fills.h" 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | namespace fastslide::runtime { 23 | 24 | void ZeroInit(uint8_t* buf, size_t nbytes) { 25 | std::memset(buf, 0, nbytes); 26 | } 27 | 28 | void FillRGB8(uint8_t* buf, int w, int h, uint8_t r, uint8_t g, uint8_t b) { 29 | if (w <= 0 || h <= 0) 30 | return; 31 | const size_t total_bytes = static_cast(w) * h * 3; 32 | 33 | // Fast path for uniform colors (r == g == b) 34 | // This handles black (0), white (255), and any gray value 35 | if (r == g && g == b) { 36 | std::memset(buf, r, total_bytes); 37 | return; 38 | } 39 | 40 | // Non-uniform color path (rare in medical imaging) 41 | // Use exponential doubling for efficiency 42 | const size_t row_bytes = static_cast(w) * 3; 43 | uint8_t* row0 = buf; 44 | 45 | // Build first row via seed + exponential doubling 46 | const int seed_px = std::min(w, 64); // 64 px -> 192 bytes 47 | for (int i = 0; i < seed_px; ++i) { 48 | row0[3 * i + 0] = r; 49 | row0[3 * i + 1] = g; 50 | row0[3 * i + 2] = b; 51 | } 52 | size_t filled = static_cast(seed_px) * 3; 53 | while (filled < row_bytes) { 54 | const size_t chunk = std::min(filled, row_bytes - filled); 55 | std::memcpy(row0 + filled, row0, chunk); 56 | filled += chunk; 57 | } 58 | 59 | if (h == 1) 60 | return; 61 | 62 | // Exponential doubling across rows 63 | size_t filled_rows = 1; 64 | while (filled_rows < static_cast(h)) { 65 | const size_t block_rows = 66 | std::min(filled_rows, static_cast(h) - filled_rows); 67 | const size_t block_bytes = block_rows * row_bytes; 68 | std::memcpy(buf + filled_rows * row_bytes, buf, block_bytes); 69 | filled_rows += block_rows; 70 | } 71 | } 72 | 73 | void FillRGBA8(uint8_t* buf, int w, int h, uint8_t r, uint8_t g, uint8_t b, 74 | uint8_t a) { 75 | if (w <= 0 || h <= 0) 76 | return; 77 | const size_t total_bytes = static_cast(w) * h * 4; 78 | 79 | // Fast path for uniform colors (r == g == b == a) 80 | if (r == g && g == b && b == a) { 81 | std::memset(buf, r, total_bytes); 82 | return; 83 | } 84 | 85 | // Non-uniform color path (rare in medical imaging) 86 | // Use exponential doubling for efficiency 87 | const size_t row_bytes = static_cast(w) * 4; 88 | uint8_t* row0 = buf; 89 | 90 | // Build first row via seed + exponential doubling 91 | const int seed_px = std::min(w, 64); // 64 px -> 256 bytes 92 | for (int i = 0; i < seed_px; ++i) { 93 | row0[4 * i + 0] = r; 94 | row0[4 * i + 1] = g; 95 | row0[4 * i + 2] = b; 96 | row0[4 * i + 3] = a; 97 | } 98 | size_t filled = static_cast(seed_px) * 4; 99 | while (filled < row_bytes) { 100 | const size_t chunk = std::min(filled, row_bytes - filled); 101 | std::memcpy(row0 + filled, row0, chunk); 102 | filled += chunk; 103 | } 104 | 105 | if (h == 1) 106 | return; 107 | 108 | // Exponential doubling across rows 109 | size_t filled_rows = 1; 110 | while (filled_rows < static_cast(h)) { 111 | const size_t block_rows = 112 | std::min(filled_rows, static_cast(h) - filled_rows); 113 | const size_t block_bytes = block_rows * row_bytes; 114 | std::memcpy(buf + filled_rows * row_bytes, buf, block_bytes); 115 | filled_rows += block_rows; 116 | } 117 | } 118 | 119 | void FillGray8(uint8_t* buf, int w, int h, uint8_t value) { 120 | const size_t total_pixels = static_cast(w) * h; 121 | std::memset(buf, value, total_pixels); 122 | } 123 | 124 | } // namespace fastslide::runtime 125 | --------------------------------------------------------------------------------