Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 27 additions & 27 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ jobs:
name: Build source distribution
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: "3.12"
python-version: "3.14"
cache: "pip"
- name: Install dependencies
run: |
Expand All @@ -24,7 +24,7 @@ jobs:
run: python -m build --sdist
- name: Check the package
run: python -m twine check dist/*
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v7
with:
name: cibw-sdist
path: dist/*.tar.gz
Expand All @@ -34,30 +34,32 @@ jobs:
name: Test source distribution
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: "3.12"
python-version: "3.14"
cache: "pip"
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v7
with:
name: cibw-sdist
path: dist
- name: Install debian packages needed
run: sudo apt-get install libxkbcommon0 libegl1
- name: Install from sdist
run: |
pip install "$(ls dist/fabio-*.tar.gz)"
pip install pyqt5 matplotlib
pip install qtpy pyside6 matplotlib
- name: Run tests
run: python -c "import fabio.test, sys; sys.exit(fabio.test.run_tests())"

build_doc:
name: Build documentation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: "3.12"
python-version: "3.14"
cache: "pip"
- name: Install pandoc&graphviz
run: sudo apt-get install pandoc graphviz
Expand All @@ -72,7 +74,7 @@ jobs:
export FABIO_VERSION="$(python -c 'import fabio; print(fabio.strictversion)')"
sphinx-build doc/source/ "fabio-${FABIO_VERSION}_documentation/"
zip -r "fabio-${FABIO_VERSION}_documentation.zip" "fabio-${FABIO_VERSION}_documentation/"
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v7
with:
name: documentation
path: fabio-*_documentation.zip
Expand All @@ -93,40 +95,38 @@ jobs:
cibw_archs: "ppc64le"
- os: windows-2022
cibw_archs: "auto64"
- os: macos-13
cibw_archs: "x86_64"
macos_target: "11.0"
- os: macos-14
# - os: macos-13
# cibw_archs: "x86_64"
# macos_target: "11.0"
- os: macos-latest
cibw_archs: "arm64"
macos_target: "11.0"

steps:
- uses: actions/checkout@v4
- uses: docker/setup-qemu-action@v3
- uses: actions/checkout@v5
- uses: docker/setup-qemu-action@v4
if: runner.os == 'Linux'
with:
platforms: all
- uses: pypa/cibuildwheel@v3.2.1
- uses: pypa/cibuildwheel@v3.3.1
env:
# Use silx wheelhouse: needed for ppc64le
CIBW_ENVIRONMENT_LINUX: "PIP_FIND_LINKS=https://www.silx.org/pub/wheelhouse/ PIP_TRUSTED_HOST=www.silx.org"
CIBW_BEFORE_BUILD: "pip install --prefer-binary h5py"
CIBW_BEFORE_TEST: "pip install --prefer-binary h5py"
CIBW_BEFORE_TEST: "pip install --prefer-binary h5py qtpy pyside6 matplotlib"
CIBW_BUILD_VERBOSITY: 1
CIBW_BUILD: cp311-* cp312-* cp313-* cp314-*
# Do not build for pypy and muslinux
CIBW_SKIP: pp* *-musllinux_*
CIBW_ARCHS: ${{ matrix.cibw_archs }}

MACOSX_DEPLOYMENT_TARGET: "${{ matrix.macos_target }}"

# Install test dependencies
CIBW_BEFORE_TEST_LINUX: yum install -y mesa-libEGL mesa-libGL libxkbcommon-x11 libxkbcommon xcb-util-image xcb-util-keysyms libXrandr xcb-util-renderutil libXcursor libxcb
CIBW_TEST_COMMAND: python -c "import fabio.test, sys; sys.exit(fabio.test.run_tests())"
# Skip tests for emulated architectures
# and Python3.8 on macos/arm64 (https://github.com/pypa/cibuildwheel/pull/1169)
CIBW_TEST_SKIP: "*-*linux_{aarch64,ppc64le,s390x} cp38-macosx_*"

- uses: actions/upload-artifact@v4
CIBW_TEST_SKIP: "*-*linux_{aarch64,ppc64le,s390x}"
- uses: actions/upload-artifact@v7
with:
name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}
path: ./wheelhouse/*.whl
Expand All @@ -143,7 +143,7 @@ jobs:
# or, alternatively, upload to PyPI on every tag starting with 'v' (remove on: release above to use this)
# if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
steps:
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v7
with:
pattern: cibw-*
path: dist
Expand Down
21 changes: 17 additions & 4 deletions doc/source/Changelog.rst
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
Changelog
=========

FabIO-2026.xx.y:
................
- Migrate viewer application to `qtpy`
- Supports Python 3.11+ through Python's limited API
FabIO-2026.6.0:
...............
- Migrate viewer application to `qtpy` for Qt-version independence (PySide6/PyQt5/PyQt6)
- Introduce ``ENDIANNESS`` enum and ``FabioImage.get_stype()`` helper to build dtypes with
explicit byte order; migrate all codec and image-format modules away from
``ndarray.byteswap()`` to endian-typed reads/writes (#310)
- Support reading images from byte-streams and ``BytesIO`` objects (#612)
- Privatise ``_do_magic()`` in ``openimage`` for consistency
- All remaining GPL-licensed source files converted to MIT license
- Helper function to raise the open-file ``ulimit`` on Linux systems (#568)
- Fix resource-download locking strategy using ``filelock``
- Fix compatibility with NumPy ≥ 2.5 (deprecation of in-place shape assignment)
- Fix deprecation warnings introduced in Python 3.14
- Update ``lxml`` requirement to ≥ 6.1.1
- Code clean-up: ``bruker100image`` and ``kcdimage`` format handlers simplified
- Ruff linting now enforced in CI
- Drop Python 3.10; supports Python 3.11 - 3.14+ (Python limited API)

FabIO-2025.10.0:
................
Expand Down
56 changes: 25 additions & 31 deletions doc/source/coverage.rst
Original file line number Diff line number Diff line change
@@ -1,90 +1,84 @@
Test coverage report for fabio
==============================

Measured on *fabio* version 2026.3.0a0, 16/06/2026
Measured on *fabio* version 2026.6.0, 17/06/2026

.. csv-table:: Test suite coverage
:header: "Name", "Stmts", "Exec", "Cover"
:widths: 35, 8, 8, 8

"GEimage.py", "115", "89", "77.4 %"
"GEimage.py", "113", "88", "77.9 %"
"HiPiCimage.py", "60", "53", "88.3 %"
"OXDimage.py", "351", "324", "92.3 %"
"TiffIO.py", "781", "643", "82.3 %"
"OXDimage.py", "341", "323", "94.7 %"
"TiffIO.py", "778", "641", "82.4 %"
"__init__.py", "33", "27", "81.8 %"
"adscimage.py", "3", "3", "100.0 %"
"binaryimage.py", "61", "43", "70.5 %"
"bruker100image.py", "285", "242", "84.9 %"
"brukerimage.py", "203", "169", "83.3 %"
"bruker100image.py", "283", "241", "85.2 %"
"brukerimage.py", "201", "169", "84.1 %"
"cbfimage.py", "643", "422", "65.6 %"
"converters.py", "16", "13", "81.2 %"
"datIO.py", "30", "7", "23.3 %"
"directories.py", "22", "17", "77.3 %"
"dm3image.py", "152", "143", "94.1 %"
"dtrekimage.py", "169", "140", "82.8 %"
"edfimage.py", "920", "726", "78.9 %"
"dm3image.py", "156", "146", "93.6 %"
"dtrekimage.py", "155", "130", "83.9 %"
"edfimage.py", "927", "726", "78.3 %"
"eigerimage.py", "195", "139", "71.3 %"
"esperantoimage.py", "158", "137", "86.7 %"
"fabioformats.py", "88", "65", "73.9 %"
"fabioimage.py", "469", "387", "82.5 %"
"fabioutils.py", "391", "311", "79.5 %"
"fabioimage.py", "474", "392", "82.7 %"
"fabioutils.py", "409", "328", "80.2 %"
"file_series.py", "369", "279", "75.6 %"
"fit2dimage.py", "90", "75", "83.3 %"
"fit2dmaskimage.py", "78", "73", "93.6 %"
"fit2dimage.py", "88", "73", "83.0 %"
"fit2dmaskimage.py", "74", "72", "97.3 %"
"fit2dspreadsheetimage.py", "46", "39", "84.8 %"
"hdf5image.py", "97", "66", "68.0 %"
"jpeg2kimage.py", "83", "55", "66.3 %"
"jpegimage.py", "46", "44", "95.7 %"
"kcdimage.py", "106", "75", "70.8 %"
"kcdimage.py", "103", "73", "70.9 %"
"lambdaimage.py", "146", "115", "78.8 %"
"limaimage.py", "179", "150", "83.8 %"
"mar345image.py", "270", "250", "92.6 %"
"mar345image.py", "265", "243", "91.7 %"
"marccdimage.py", "65", "57", "87.7 %"
"mpaimage.py", "55", "50", "90.9 %"
"mrcimage.py", "84", "64", "76.2 %"
"nexus.py", "231", "128", "55.4 %"
"numpyimage.py", "76", "52", "68.4 %"
"openimage.py", "132", "113", "85.6 %"
"openimage.py", "135", "116", "85.9 %"
"pilatusimage.py", "43", "38", "88.4 %"
"pixiimage.py", "106", "90", "84.9 %"
"pnmimage.py", "136", "85", "62.5 %"
"raxisimage.py", "102", "90", "88.2 %"
"pnmimage.py", "131", "83", "63.4 %"
"raxisimage.py", "94", "83", "88.3 %"
"sparseimage.py", "155", "105", "67.7 %"
"speimage.py", "161", "156", "96.9 %"
"templateimage.py", "24", "16", "66.7 %"
"tifimage.py", "127", "120", "94.5 %"
"version.py", "39", "38", "97.4 %"
"version.py", "39", "34", "87.2 %"
"xcaliburimage.py", "565", "441", "78.1 %"
"xsdimage.py", "93", "69", "74.2 %"
"xsdimage.py", "91", "68", "74.7 %"
"app/__init__.py", "0", "0", "0.0 %"
"app/convert.py", "203", "27", "13.3 %"
"app/densify.py", "178", "36", "20.2 %"
"app/eiger2cbf.py", "303", "36", "11.9 %"
"app/eiger2crysalis.py", "370", "41", "11.1 %"
"app/hdf2neggia.py", "203", "32", "15.8 %"
"app/viewer.py", "27", "15", "55.6 %"
"app/viewer_qtpy.py", "840", "62", "7.4 %"
"benchmark/__init__.py", "30", "15", "50.0 %"
"compression/__init__.py", "1", "1", "100.0 %"
"compression/agi_bitfield.py", "171", "148", "86.5 %"
"compression/compression.py", "244", "187", "76.6 %"
"compression/agi_bitfield.py", "170", "148", "87.1 %"
"compression/compression.py", "231", "183", "79.2 %"
"ext/__init__.py", "0", "0", "0.0 %"
"qt/__init__.py", "0", "0", "0.0 %"
"qt/_pyqt6.py", "30", "28", "93.3 %"
"qt/_pyside_dynamic.py", "75", "5", "6.7 %"
"qt/_qt.py", "144", "58", "40.3 %"
"qt/_utils.py", "21", "9", "42.9 %"
"qt/dialogs.py", "126", "15", "11.9 %"
"qt/inspect.py", "25", "12", "48.0 %"
"qt/matplotlib.py", "70", "25", "35.7 %"
"qt/viewer.py", "823", "58", "7.0 %"
"test/__init__.py", "19", "12", "63.2 %"
"test/profile_all.py", "41", "23", "56.1 %"
"test/codecs/__init__.py", "86", "80", "93.0 %"
"utils/ExternalResources.py", "266", "176", "66.2 %"
"utils/__init__.py", "0", "0", "0.0 %"
"utils/cli.py", "59", "47", "79.7 %"
"utils/cli.py", "75", "58", "77.3 %"
"utils/deprecation.py", "66", "62", "93.9 %"
"utils/pilutils.py", "48", "38", "79.2 %"
"utils/pilutils.py", "51", "39", "76.5 %"

"fabio total", "13318", "8281", "62.2 %"
"fabio total", "12163", "8102", "66.6 %"
19 changes: 2 additions & 17 deletions src/fabio/utils/testutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,18 +121,13 @@ class LoggingCounter(logging.Handler):
:type logger: str or :class:`logging.Logger`
"""

def __init__(
self,
logger=None,
):
def __init__(self, logger=None):
if logger is None:
logger = logging.getLogger()
elif not isinstance(logger, logging.Logger):
logger = logging.getLogger(logger)
self.logger = logger

self.records = []

super(LoggingCounter, self).__init__()

def __enter__(self):
Expand Down Expand Up @@ -198,7 +193,6 @@ class TestLogging(LoggingCounter):
Default: Do not check.
:raises RuntimeError: If the message counts are the expected ones.
"""

def __init__(
self,
logger=None,
Expand All @@ -209,14 +203,7 @@ def __init__(
debug=None,
notset=None,
):
if logger is None:
logger = logging.getLogger()
elif not isinstance(logger, logging.Logger):
logger = logging.getLogger(logger)
self.logger = logger

self.records = []

super(TestLogging, self).__init__(logger)
self.count_by_level = {
logging.CRITICAL: critical,
logging.ERROR: error,
Expand All @@ -226,8 +213,6 @@ def __init__(
logging.NOTSET: notset,
}

super(TestLogging, self).__init__()


def __exit__(self, exc_type, exc_value, traceback):
"""Context (i.e., with) support"""
Expand Down
Loading