diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 636ab21b..89e818cb 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.10', '3.11', '3.12', '3.13', '3.14.0-rc.3'] + python-version: ['3.10', '3.11', '3.12', '3.13', '3.14.0'] steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 53ff5486..96226c67 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -64,7 +64,7 @@ jobs: - name: Install fabio run: pip install . - name: Install documentation dependencies - run: pip install -r requirements.txt + run: pip install -r requirements.txt ci/requirements_rtd.txt - name: Build doc env: READTHEDOCS: "True" # To skip checking that fabio is installed locally @@ -85,13 +85,13 @@ jobs: fail-fast: false matrix: include: - - os: ubuntu-20.04 + - os: ubuntu-24.04 cibw_archs: "auto64" - - os: ubuntu-20.04 + - os: ubuntu-24.04 cibw_archs: "aarch64" - - os: ubuntu-20.04 + - os: ubuntu-24.04 cibw_archs: "ppc64le" - - os: windows-2019 + - os: windows-2022 cibw_archs: "auto64" - os: macos-13 cibw_archs: "x86_64" @@ -111,7 +111,7 @@ jobs: # 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_BUILD_VERBOSITY: 1 - CIBW_BUILD: cp38-* cp39-* cp310-* cp311-* cp312-* cp313-* + CIBW_BUILD: cp310-* cp311-* cp312-* cp313-* cp314-* # Do not build for pypy and muslinux CIBW_SKIP: pp* *-musllinux_* CIBW_ARCHS: ${{ matrix.cibw_archs }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 32a5bc7f..527b67cd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.2.0 + rev: v6.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -10,3 +10,17 @@ repos: - id: check-json - id: check-toml - id: check-added-large-files + +repos: +- repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.14.1 + hooks: + # Run the linter. + - id: ruff-check + types_or: [ python, pyi ] + args: [ --fix ] + files: ^src/ + # Run the formatter. + #- id: ruff-format + # types_or: [ python, pyi ] diff --git a/ci/appveyor.yml b/ci/appveyor.yml index 0aebbdfd..c3ccac99 100644 --- a/ci/appveyor.yml +++ b/ci/appveyor.yml @@ -34,21 +34,28 @@ environment: # PYTHON_ARCH: "64" # Python 3.9 - - PYTHON_DIR: "C:\\Python39-x64" - PYTHON_ARCH: "64" + #- PYTHON_DIR: "C:\\Python39-x64" + # PYTHON_ARCH: "64" # Python 3.10 - PYTHON_DIR: "C:\\Python310-x64" PYTHON_ARCH: "64" # Python 3.11 - - PYTHON_DIR: "C:\\Python311-x64" - PYTHON_ARCH: "64" + #- PYTHON_DIR: "C:\\Python311-x64" + # PYTHON_ARCH: "64" # Python 3.12 - PYTHON_DIR: "C:\\Python312-x64" PYTHON_ARCH: "64" + + # Python 3.13 + - PYTHON_DIR: "C:\\Python313-x64" + PYTHON_ARCH: "64" + # Python 3.14 + #- PYTHON_DIR: "C:\\Python314-x64" + # PYTHON_ARCH: "64" install: # Add Python to PATH diff --git a/ci/requirements_rtd.txt b/ci/requirements_rtd.txt index 1dc1fdc9..15ebf992 100644 --- a/ci/requirements_rtd.txt +++ b/ci/requirements_rtd.txt @@ -1,4 +1,4 @@ -numpy; python_version>= '3.6' +numpy h5py sphinx cython @@ -6,3 +6,4 @@ sphinxcontrib-programoutput nbsphinx lxml>=4.6.3 pillow +pydata-sphinx-theme diff --git a/doc/source/conf.py b/doc/source/conf.py index fa2b0a68..c22e40e3 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -14,6 +14,7 @@ import sys import os +on_rtd = os.environ.get('READTHEDOCS') == 'True' # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. @@ -125,7 +126,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'sphinx_rtd_theme' +html_theme = "pydata_sphinx_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the diff --git a/doc/source/coverage.rst b/doc/source/coverage.rst index d7b6c52e..6f37ab58 100644 --- a/doc/source/coverage.rst +++ b/doc/source/coverage.rst @@ -1,69 +1,87 @@ Test coverage report for fabio ============================== -Measured on *fabio* version 2024.4.0, 11/04/2024 +Measured on *fabio* version 2025.10.0b1, 28/10/2025 .. csv-table:: Test suite coverage :header: "Name", "Stmts", "Exec", "Cover" :widths: 35, 8, 8, 8 - "GEimage.py", "116", "90", "77.6 %" - "HiPiCimage.py", "61", "54", "88.5 %" - "OXDimage.py", "352", "325", "92.3 %" + "GEimage.py", "115", "89", "77.4 %" + "HiPiCimage.py", "60", "53", "88.3 %" + "OXDimage.py", "351", "324", "92.3 %" "TiffIO.py", "781", "643", "82.3 %" - "__init__.py", "34", "28", "82.4 %" - "adscimage.py", "4", "4", "100.0 %" - "binaryimage.py", "60", "44", "73.3 %" - "bruker100image.py", "286", "243", "85.0 %" + "__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 %" - "cbfimage.py", "641", "422", "65.8 %" - "converters.py", "17", "14", "82.4 %" + "cbfimage.py", "644", "423", "65.7 %" + "converters.py", "16", "13", "81.2 %" + "datIO.py", "30", "7", "23.3 %" + "directories.py", "22", "17", "77.3 %" "dm3image.py", "153", "144", "94.1 %" - "dtrekimage.py", "170", "141", "82.9 %" - "edfimage.py", "916", "703", "76.7 %" - "eigerimage.py", "194", "139", "71.6 %" + "dtrekimage.py", "169", "140", "82.8 %" + "edfimage.py", "920", "725", "78.8 %" + "eigerimage.py", "195", "139", "71.3 %" "esperantoimage.py", "158", "137", "86.7 %" - "fabioformats.py", "90", "65", "72.2 %" - "fabioimage.py", "468", "387", "82.7 %" - "fabioutils.py", "392", "312", "79.6 %" - "file_series.py", "370", "280", "75.7 %" - "fit2dimage.py", "91", "76", "83.5 %" - "fit2dmaskimage.py", "79", "75", "94.9 %" - "fit2dspreadsheetimage.py", "47", "40", "85.1 %" - "hdf5image.py", "98", "67", "68.4 %" - "jpeg2kimage.py", "82", "74", "90.2 %" - "jpegimage.py", "47", "45", "95.7 %" - "kcdimage.py", "107", "76", "71.0 %" - "limaimage.py", "180", "151", "83.9 %" - "mar345image.py", "271", "251", "92.6 %" - "marccdimage.py", "66", "58", "87.9 %" - "mpaimage.py", "56", "51", "91.1 %" - "mrcimage.py", "85", "65", "76.5 %" - "nexus.py", "233", "130", "55.8 %" - "numpyimage.py", "77", "53", "68.8 %" - "openimage.py", "130", "112", "86.2 %" + "fabioformats.py", "88", "65", "73.9 %" + "fabioimage.py", "469", "387", "82.5 %" + "fabioutils.py", "391", "311", "79.5 %" + "file_series.py", "369", "279", "75.6 %" + "fit2dimage.py", "90", "75", "83.3 %" + "fit2dmaskimage.py", "78", "73", "93.6 %" + "fit2dspreadsheetimage.py", "46", "39", "84.8 %" + "hdf5image.py", "97", "66", "68.0 %" + "jpeg2kimage.py", "83", "71", "85.5 %" + "jpegimage.py", "46", "44", "95.7 %" + "kcdimage.py", "106", "75", "70.8 %" + "lambdaimage.py", "146", "115", "78.8 %" + "limaimage.py", "179", "150", "83.8 %" + "mar345image.py", "270", "250", "92.6 %" + "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 %" "pilatusimage.py", "43", "38", "88.4 %" - "pixiimage.py", "107", "91", "85.0 %" - "pnmimage.py", "137", "86", "62.8 %" - "raxisimage.py", "103", "91", "88.3 %" - "sparseimage.py", "146", "97", "66.4 %" - "speimage.py", "162", "157", "96.9 %" - "tifimage.py", "128", "121", "94.5 %" - "version.py", "40", "35", "87.5 %" - "xcaliburimage.py", "586", "462", "78.8 %" - "xsdimage.py", "94", "70", "74.5 %" + "pixiimage.py", "106", "90", "84.9 %" + "pnmimage.py", "136", "85", "62.5 %" + "raxisimage.py", "102", "90", "88.2 %" + "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 %" + "xcaliburimage.py", "584", "460", "78.8 %" + "xsdimage.py", "93", "69", "74.2 %" "app/__init__.py", "0", "0", "0.0 %" - "app/convert.py", "204", "28", "13.7 %" + "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", "201", "32", "15.9 %" + "app/viewer.py", "1099", "71", "6.5 %" + "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", "245", "188", "76.7 %" + "compression/compression.py", "244", "187", "76.6 %" "ext/__init__.py", "0", "0", "0.0 %" - "test/__init__.py", "22", "15", "68.2 %" - "test/codecs/__init__.py", "87", "81", "93.1 %" + "qt/__init__.py", "4", "3", "75.0 %" + "qt/_pyqt6.py", "30", "9", "30.0 %" + "qt/_pyside_dynamic.py", "75", "5", "6.7 %" + "qt/_qt.py", "144", "54", "37.5 %" + "qt/_utils.py", "21", "9", "42.9 %" + "qt/inspect.py", "25", "11", "44.0 %" + "qt/matplotlib.py", "65", "24", "36.9 %" + "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", "181", "134", "74.0 %" "utils/__init__.py", "0", "0", "0.0 %" - "utils/cli.py", "60", "48", "80.0 %" - "utils/deprecation.py", "67", "63", "94.0 %" - "utils/pilutils.py", "49", "39", "79.6 %" + "utils/cli.py", "59", "47", "79.7 %" + "utils/deprecation.py", "66", "62", "93.9 %" + "utils/pilutils.py", "48", "38", "79.2 %" - "fabio total", "9748", "7661", "78.6 %" + "fabio total", "12534", "8174", "65.2 %" diff --git a/meson.build b/meson.build index f813632d..785aff72 100644 --- a/meson.build +++ b/meson.build @@ -12,13 +12,19 @@ if meson.backend() != 'ninja' error('Ninja backend required') endif +# C-compiler cc = meson.get_compiler('c') -cy = meson.get_compiler('cython') m_dep = cc.find_library('m', required : false) if m_dep.found() add_project_link_arguments('-lm', language : 'c') endif +# Cython compiler +cy = meson.get_compiler('cython') +if cy.version().version_compare('>=3.1.0') + add_project_arguments('-Xfreethreading_compatible=true', language : 'cython') +endif + # https://mesonbuild.com/Python-module.html py_mod = import('python') py = py_mod.find_installation() diff --git a/pyproject.toml b/pyproject.toml index c0a86e52..51ae60fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,8 +59,8 @@ requires = [ "tomli>=1.0.0; python_version < '3.11'" ] [project.optional-dependencies] -gui = [ "pyqt5", "matplotlib" ] -all = ["pyqt5", "matplotlib"] +gui = [ "pyside6", "matplotlib", "packaging" ] +all = [ "pyside6", "matplotlib", "packaging" ] [project.urls] homepage = 'http://www.silx.org' diff --git a/src/fabio/GEimage.py b/src/fabio/GEimage.py index 047acaf4..50fe83f6 100644 --- a/src/fabio/GEimage.py +++ b/src/fabio/GEimage.py @@ -31,8 +31,8 @@ c:\\adept\\core\\DefaultImageInfoConfig.csv """ -__authors__ = ["Antonino Miceli", "Jon Wright", - "Jérôme Kieffer", "Joel Bernier"] + +__authors__ = ["Antonino Miceli", "Jon Wright", "Jérôme Kieffer", "Joel Bernier"] __date__ = "03/04/2020" __status__ = "production" __copyright__ = "2007-2020 APS; 2010-2020 ESRF" @@ -46,166 +46,166 @@ from .fabioutils import next_filename, previous_filename from .openimage import MAGIC_NUMBERS -EDF_MAGIC_NUMBERS = [(x, y) for x, y in MAGIC_NUMBERS if y == 'edf'] +EDF_MAGIC_NUMBERS = [(x, y) for x, y in MAGIC_NUMBERS if y == "edf"] GE_HEADER_INFO = [ # Name, length in bytes, format for struct (None means string) - ('ImageFormat', 10, None), - ('VersionOfStandardHeader', 2, ' 0: - raw16 = infile.read(self.header['OI'] * 2) - if self.header['OL'] > 0: - raw32 = infile.read(self.header['OL'] * 4) + if self.header["OI"] > 0: + raw16 = infile.read(self.header["OI"] * 2) + if self.header["OL"] > 0: + raw32 = infile.read(self.header["OL"] * 4) # endianess is handled at the decompression level raw_data = decTY1(raw8, raw16, raw32) - elif self.header['Compression'] == 'TY5': + elif self.header["Compression"] == "TY5": logger.info("Compressed with the TY5 compression") dtype = numpy.dtype(numpy.int8) raw8 = infile.read(dim1 * dim2) - if self.header['OI'] > 0: - self.raw16 = infile.read(self.header['OI'] * 2) + if self.header["OI"] > 0: + self.raw16 = infile.read(self.header["OI"] * 2) else: self.raw16 = b"" - if self.header['OL'] > 0: - self.raw32 = infile.read(self.header['OL'] * 4) + if self.header["OL"] > 0: + self.raw32 = infile.read(self.header["OL"] * 4) else: self.raw32 = b"" self.rest = infile.read() @@ -286,7 +305,7 @@ def read(self, fname, frame=None): if not numpy.little_endian: raw_data.byteswap(True) - logger.debug('OVER_SHORT2: %s', raw_data.dtype) + logger.debug("OVER_SHORT2: %s", raw_data.dtype) logger.debug("%s" % (raw_data < 0).sum()) logger.debug("BYTECODE: %s", raw_data.dtype.type) self.data = raw_data.reshape((dim2, dim1)) @@ -304,134 +323,220 @@ def _writeheader(self): if "NX" not in self.header.keys() or "NY" not in self.header.keys(): dim2, dim1 = self.shape - self.header['NX'] = dim1 - self.header['NY'] = dim2 - ascii_headers = [self.header['Header Version'], - "COMPRESSION=%s (%5.1f)" % (self.header["Compression"], self.getCompressionRatio()), - "NX=%4i NY=%4i OI=%7i OL=%7i " % (self.header["NX"], self.header["NY"], self.header["OI"], self.header["OL"]), - "NHEADER=%7i NG=%7i NS=%7i NK=%7i NS=%7i NH=%7i" % (self.header['Header Size In Bytes'], - self.header['General Section size in Byte'], - self.header['Special Section size in Byte'], - self.header['KM4 Section size in Byte'], - self.header['Statistic Section in Byte'], - self.header['History Section in Byte']), - "NSUPPLEMENT=%7i" % (self.header["NSUPPLEMENT"])] + self.header["NX"] = dim1 + self.header["NY"] = dim2 + ascii_headers = [ + self.header["Header Version"], + "COMPRESSION=%s (%5.1f)" + % (self.header["Compression"], self.getCompressionRatio()), + "NX=%4i NY=%4i OI=%7i OL=%7i " + % ( + self.header["NX"], + self.header["NY"], + self.header["OI"], + self.header["OL"], + ), + "NHEADER=%7i NG=%7i NS=%7i NK=%7i NS=%7i NH=%7i" + % ( + self.header["Header Size In Bytes"], + self.header["General Section size in Byte"], + self.header["Special Section size in Byte"], + self.header["KM4 Section size in Byte"], + self.header["Statistic Section in Byte"], + self.header["History Section in Byte"], + ), + "NSUPPLEMENT=%7i" % (self.header["NSUPPLEMENT"]), + ] if "Time" in self.header: ascii_headers.append("TIME=%s" % self.header["Time"]) else: - ascii_headers.append("TIME=%s" % time.ctime()) header = (linesep.join(ascii_headers)).ljust(256).encode("ASCII") - NG = Section(self.header['General Section size in Byte'], self.header) - NG.setData('Binning in x', 0, numpy.uint16) - NG.setData('Binning in y', 2, numpy.uint16) - NG.setData('Detector size x', 22, numpy.uint16) - NG.setData('Detector size y', 24, numpy.uint16) - NG.setData('Pixels in x', 26, numpy.uint16) - NG.setData('Pixels in y', 28, numpy.uint16) - NG.setData('No of pixels', 36, numpy.uint32) + NG = Section(self.header["General Section size in Byte"], self.header) + NG.setData("Binning in x", 0, numpy.uint16) + NG.setData("Binning in y", 2, numpy.uint16) + NG.setData("Detector size x", 22, numpy.uint16) + NG.setData("Detector size y", 24, numpy.uint16) + NG.setData("Pixels in x", 26, numpy.uint16) + NG.setData("Pixels in y", 28, numpy.uint16) + NG.setData("No of pixels", 36, numpy.uint32) header += NG.__repr__() - NS = Section(self.header['Special Section size in Byte'], self.header) - NS.setData('Gain', 56, numpy.float64) - NS.setData('Overflows flag', 464, numpy.int16) - NS.setData('Overflow after remeasure flag', 466, numpy.int16) - NS.setData('Overflow threshold', 472, numpy.int32) - NS.setData('Exposure time in sec', 480, numpy.float64) - NS.setData('Overflow time in sec', 488, numpy.float64) - NS.setData('Monitor counts of raw image 1', 528, numpy.int32) - NS.setData('Monitor counts of raw image 2', 532, numpy.int32) - NS.setData('Monitor counts of overflow raw image 1', 536, numpy.int32) - NS.setData('Monitor counts of overflow raw image 2', 540, numpy.int32) - NS.setData('Unwarping', 544, numpy.int32) - if 'Detector type' in self.header: + NS = Section(self.header["Special Section size in Byte"], self.header) + NS.setData("Gain", 56, numpy.float64) + NS.setData("Overflows flag", 464, numpy.int16) + NS.setData("Overflow after remeasure flag", 466, numpy.int16) + NS.setData("Overflow threshold", 472, numpy.int32) + NS.setData("Exposure time in sec", 480, numpy.float64) + NS.setData("Overflow time in sec", 488, numpy.float64) + NS.setData("Monitor counts of raw image 1", 528, numpy.int32) + NS.setData("Monitor counts of raw image 2", 532, numpy.int32) + NS.setData("Monitor counts of overflow raw image 1", 536, numpy.int32) + NS.setData("Monitor counts of overflow raw image 2", 540, numpy.int32) + NS.setData("Unwarping", 544, numpy.int32) + if "Detector type" in self.header: for key, value in DETECTOR_TYPES.items(): - if value == self.header['Detector type']: + if value == self.header["Detector type"]: NS.setData(None, 548, numpy.int32, default=key) - NS.setData('Real pixel size x (mm)', 568, numpy.float64) - NS.setData('Real pixel size y (mm)', 576, numpy.float64) + NS.setData("Real pixel size x (mm)", 568, numpy.float64) + NS.setData("Real pixel size y (mm)", 576, numpy.float64) header += NS.__repr__() - KM = Section(self.header['KM4 Section size in Byte'], self.header) - KM.setData('Spatial correction file date', 0, "|S26") - KM.setData('Spatial correction file', 26, "|S246") + KM = Section(self.header["KM4 Section size in Byte"], self.header) + KM.setData("Spatial correction file date", 0, "|S26") + KM.setData("Spatial correction file", 26, "|S246") # Angles are in steps due to stepper motors - conversion factor RAD # angle[0] = omega, angle[1] = theta, angle[2] = kappa, angle[3] = phi, - if self.header.get('Omega step in deg', None): - KM.setData(None, 368, numpy.float64, deg2rad(self.header["Omega step in deg"])) - if self.header.get('Omega start in deg', None): - KM.setData(None, 284, numpy.int32, self.header["Omega start in deg"] / self.header["Omega step in deg"]) - if self.header.get('Omega end in deg', None): - KM.setData(None, 324, numpy.int32, self.header["Omega end in deg"] / self.header["Omega step in deg"]) - if self.header.get('Omega zero corr. in deg', None): - KM.setData(None, 512, numpy.int32, self.header['Omega zero corr. in deg'] / self.header["Omega step in deg"]) - - if self.header.get('Theta step in deg', None): - KM.setData(None, 368 + 8, numpy.float64, deg2rad(self.header["Theta step in deg"])) - if self.header.get('Theta start in deg', None): - KM.setData(None, 284 + 4, numpy.int32, self.header["Theta start in deg"] / self.header["Theta step in deg"]) - if self.header.get('Theta end in deg', None): - KM.setData(None, 324 + 4, numpy.int32, self.header["Theta end in deg"] / self.header["Theta step in deg"]) - if self.header.get('Theta zero corr. in deg', None): - KM.setData(None, 512 + 4, numpy.int32, self.header['Theta zero corr. in deg'] / self.header["Theta step in deg"]) - - if self.header.get('Kappa step in deg', None): - KM.setData(None, 368 + 16, numpy.float64, deg2rad(self.header["Kappa step in deg"])) - if self.header.get('Kappa start in deg', None): - KM.setData(None, 284 + 8, numpy.int32, self.header["Kappa start in deg"] / self.header["Kappa step in deg"]) - if self.header.get('Kappa end in deg', None): - KM.setData(None, 324 + 8, numpy.int32, self.header["Kappa end in deg"] / self.header["Kappa step in deg"]) - if self.header.get('Kappa zero corr. in deg', None): - KM.setData(None, 512 + 8, numpy.int32, self.header['Kappa zero corr. in deg'] / self.header["Kappa step in deg"]) - - if self.header.get('Phi step in deg', None): - KM.setData(None, 368 + 24, numpy.float64, deg2rad(self.header["Phi step in deg"])) - if self.header.get('Phi start in deg', None): - KM.setData(None, 284 + 12, numpy.int32, self.header["Phi start in deg"] / self.header["Phi step in deg"]) - if self.header.get('Phi end in deg', None): - KM.setData(None, 324 + 12, numpy.int32, self.header["Phi end in deg"] / self.header["Phi step in deg"]) - if self.header.get('Phi zero corr. in deg', None): - KM.setData(None, 512 + 12, numpy.int32, self.header['Phi zero corr. in deg'] / self.header["Phi step in deg"]) + if self.header.get("Omega step in deg", None): + KM.setData( + None, 368, numpy.float64, deg2rad(self.header["Omega step in deg"]) + ) + if self.header.get("Omega start in deg", None): + KM.setData( + None, + 284, + numpy.int32, + self.header["Omega start in deg"] + / self.header["Omega step in deg"], + ) + if self.header.get("Omega end in deg", None): + KM.setData( + None, + 324, + numpy.int32, + self.header["Omega end in deg"] / self.header["Omega step in deg"], + ) + if self.header.get("Omega zero corr. in deg", None): + KM.setData( + None, + 512, + numpy.int32, + self.header["Omega zero corr. in deg"] + / self.header["Omega step in deg"], + ) + + if self.header.get("Theta step in deg", None): + KM.setData( + None, 368 + 8, numpy.float64, deg2rad(self.header["Theta step in deg"]) + ) + if self.header.get("Theta start in deg", None): + KM.setData( + None, + 284 + 4, + numpy.int32, + self.header["Theta start in deg"] + / self.header["Theta step in deg"], + ) + if self.header.get("Theta end in deg", None): + KM.setData( + None, + 324 + 4, + numpy.int32, + self.header["Theta end in deg"] / self.header["Theta step in deg"], + ) + if self.header.get("Theta zero corr. in deg", None): + KM.setData( + None, + 512 + 4, + numpy.int32, + self.header["Theta zero corr. in deg"] + / self.header["Theta step in deg"], + ) + + if self.header.get("Kappa step in deg", None): + KM.setData( + None, 368 + 16, numpy.float64, deg2rad(self.header["Kappa step in deg"]) + ) + if self.header.get("Kappa start in deg", None): + KM.setData( + None, + 284 + 8, + numpy.int32, + self.header["Kappa start in deg"] + / self.header["Kappa step in deg"], + ) + if self.header.get("Kappa end in deg", None): + KM.setData( + None, + 324 + 8, + numpy.int32, + self.header["Kappa end in deg"] / self.header["Kappa step in deg"], + ) + if self.header.get("Kappa zero corr. in deg", None): + KM.setData( + None, + 512 + 8, + numpy.int32, + self.header["Kappa zero corr. in deg"] + / self.header["Kappa step in deg"], + ) + + if self.header.get("Phi step in deg", None): + KM.setData( + None, 368 + 24, numpy.float64, deg2rad(self.header["Phi step in deg"]) + ) + if self.header.get("Phi start in deg", None): + KM.setData( + None, + 284 + 12, + numpy.int32, + self.header["Phi start in deg"] / self.header["Phi step in deg"], + ) + if self.header.get("Phi end in deg", None): + KM.setData( + None, + 324 + 12, + numpy.int32, + self.header["Phi end in deg"] / self.header["Phi step in deg"], + ) + if self.header.get("Phi zero corr. in deg", None): + KM.setData( + None, + 512 + 12, + numpy.int32, + self.header["Phi zero corr. in deg"] + / self.header["Phi step in deg"], + ) # Beam rotation about e2,e3 - KM.setData('Beam rot in deg (e2)', 552, numpy.float64) - KM.setData('Beam rot in deg (e3)', 560, numpy.float64) + KM.setData("Beam rot in deg (e2)", 552, numpy.float64) + KM.setData("Beam rot in deg (e3)", 560, numpy.float64) # Wavelenghts alpha1, alpha2, beta - KM.setData('Wavelength alpha1', 568, numpy.float64) - KM.setData('Wavelength alpha2', 576, numpy.float64) - KM.setData('Wavelength alpha', 584, numpy.float64) - KM.setData('Wavelength beta', 592, numpy.float64) + KM.setData("Wavelength alpha1", 568, numpy.float64) + KM.setData("Wavelength alpha2", 576, numpy.float64) + KM.setData("Wavelength alpha", 584, numpy.float64) + KM.setData("Wavelength beta", 592, numpy.float64) # Detector tilts around e1,e2,e3 in deg - KM.setData('Detector tilt e1 in deg', 640, numpy.float64) - KM.setData('Detector tilt e2 in deg', 648, numpy.float64) - KM.setData('Detector tilt e3 in deg', 656, numpy.float64) + KM.setData("Detector tilt e1 in deg", 640, numpy.float64) + KM.setData("Detector tilt e2 in deg", 648, numpy.float64) + KM.setData("Detector tilt e3 in deg", 656, numpy.float64) # Beam center - KM.setData('Beam center x', 664, numpy.float64) - KM.setData('Beam center y', 672, numpy.float64) + KM.setData("Beam center x", 664, numpy.float64) + KM.setData("Beam center y", 672, numpy.float64) # Angle (alpha) between kappa rotation axis and e3 (ideally 50 deg) - KM.setData('Alpha angle in deg', 680, numpy.float64) + KM.setData("Alpha angle in deg", 680, numpy.float64) # Angle (beta) between phi rotation axis and e3 (ideally 0 deg) - KM.setData('Beta angle in deg', 688, numpy.float64) + KM.setData("Beta angle in deg", 688, numpy.float64) # Detector distance - KM.setData('Distance in mm', 712, numpy.float64) + KM.setData("Distance in mm", 712, numpy.float64) header += KM.__repr__() - SS = Section(self.header['Statistic Section in Byte'], self.header) - SS.setData('Stat: Min ', 0, numpy.int32) - SS.setData('Stat: Max ', 4, numpy.int32) - SS.setData('Stat: Average ', 24, numpy.float64) - if self.header.get('Stat: Stddev ', None): - SS.setData(None, 32, numpy.float64, self.header['Stat: Stddev '] ** 2) - SS.setData('Stat: Skewness ', 40, numpy.float64) + SS = Section(self.header["Statistic Section in Byte"], self.header) + SS.setData("Stat: Min ", 0, numpy.int32) + SS.setData("Stat: Max ", 4, numpy.int32) + SS.setData("Stat: Average ", 24, numpy.float64) + if self.header.get("Stat: Stddev ", None): + SS.setData(None, 32, numpy.float64, self.header["Stat: Stddev "] ** 2) + SS.setData("Stat: Skewness ", 40, numpy.float64) header += SS.__repr__() - HS = Section(self.header['History Section in Byte'], self.header) - HS.setData('Flood field image', 99, "|S27") + HS = Section(self.header["History Section in Byte"], self.header) + HS.setData("Flood field image", 99, "|S27") header += HS.__repr__() return header @@ -456,7 +561,11 @@ def write(self, fname): def getCompressionRatio(self): "calculate the compression factor obtained vs raw data" - return 100.0 * (self.data.size + 2 * self.header["OI"] + 4 * self.header["OL"]) / (self.data.size * 4) + return ( + 100.0 + * (self.data.size + 2 * self.header["OI"] + 4 * self.header["OL"]) + / (self.data.size * 4) + ) @staticmethod def checkData(data=None): @@ -495,7 +604,7 @@ def dec_TY5(self, stream): ex1 += 1 # this is the special case 1: # if the marker 254 is found the next 2 bytes encode one pixel - value = raw[pos_inp + 1:pos_inp + 3].view(numpy.int16) + value = raw[pos_inp + 1 : pos_inp + 3].view(numpy.int16) if not numpy.little_endian: value = value.byteswap(True) current = last + value[0] @@ -505,8 +614,8 @@ def dec_TY5(self, stream): # this is the special case 2: # if the marker 255 is found the next 4 bytes encode one pixel ex2 += 1 - logger.info('special case 32 bits.') - value = raw[pos_inp + 1:pos_inp + 5].view(numpy.int32) + logger.info("special case 32 bits.") + value = raw[pos_inp + 1 : pos_inp + 5].view(numpy.int32) if not numpy.little_endian: value = value.byteswap(True) current = last + value[0] @@ -562,4 +671,4 @@ def setData(self, key, offset, dtype, default=None): value = numpy.array(value).astype(dtype).tobytes() else: value = numpy.array(value).astype(dtype).byteswap().tobytes() - self.lstChr[offset:offset + self.getSize(dtype)] = value + self.lstChr[offset : offset + self.getSize(dtype)] = value diff --git a/src/fabio/TiffIO.py b/src/fabio/TiffIO.py index bca6a1dd..c44051e7 100644 --- a/src/fabio/TiffIO.py +++ b/src/fabio/TiffIO.py @@ -37,22 +37,23 @@ ALLOW_MULTIPLE_STRIPS = False -TAG_ID = {256: "NumberOfColumns", # S or L ImageWidth - 257: "NumberOfRows", # S or L ImageHeight - 258: "BitsPerSample", # S Number of bits per component - 259: "Compression", # SHORT (1 - NoCompression, ... - 262: "PhotometricInterpretation", # SHORT (0 - WhiteIsZero, 1 -BlackIsZero, 2 - RGB, 3 - Palette color - 270: "ImageDescription", # ASCII - 272: "Model", # ASCII - 273: "StripOffsets", # S or L, for each strip, the byte offset of the strip - 277: "SamplesPerPixel", # SHORT (>=3) only for RGB images - 278: "RowsPerStrip", # S or L, number of rows in each back may be not for the last - 279: "StripByteCounts", # S or L, The number of bytes in the strip AFTER any compression - 305: "Software", # ASCII - 306: "Date", # ASCII - 320: "Colormap", # Colormap of Palette-color Images - 339: "SampleFormat", # SHORT Interpretation of data in each pixel - } +TAG_ID = { + 256: "NumberOfColumns", # S or L ImageWidth + 257: "NumberOfRows", # S or L ImageHeight + 258: "BitsPerSample", # S Number of bits per component + 259: "Compression", # SHORT (1 - NoCompression, ... + 262: "PhotometricInterpretation", # SHORT (0 - WhiteIsZero, 1 -BlackIsZero, 2 - RGB, 3 - Palette color + 270: "ImageDescription", # ASCII + 272: "Model", # ASCII + 273: "StripOffsets", # S or L, for each strip, the byte offset of the strip + 277: "SamplesPerPixel", # SHORT (>=3) only for RGB images + 278: "RowsPerStrip", # S or L, number of rows in each back may be not for the last + 279: "StripByteCounts", # S or L, The number of bytes in the strip AFTER any compression + 305: "Software", # ASCII + 306: "Date", # ASCII + 320: "Colormap", # Colormap of Palette-color Images + 339: "SampleFormat", # SHORT Interpretation of data in each pixel +} # TILES ARE TO BE SUPPORTED TOO ... TAG_NUMBER_OF_COLUMNS = 256 @@ -71,30 +72,34 @@ TAG_COLORMAP = 320 TAG_SAMPLE_FORMAT = 339 -FIELD_TYPE = {1: ('BYTE', "B"), - 2: ('ASCII', "s"), # string ending with binary zero - 3: ('SHORT', "H"), - 4: ('LONG', "I"), - 5: ('RATIONAL', "II"), - 6: ('SBYTE', "b"), - 7: ('UNDEFINED', "B"), - 8: ('SSHORT', "h"), - 9: ('SLONG', "i"), - 10: ('SRATIONAL', "ii"), - 11: ('FLOAT', "f"), - 12: ('DOUBLE', "d")} - -FIELD_TYPE_OUT = {'B': 1, - 's': 2, - 'H': 3, - 'I': 4, - 'II': 5, - 'b': 6, - 'h': 8, - 'i': 9, - 'ii': 10, - 'f': 11, - 'd': 12} +FIELD_TYPE = { + 1: ("BYTE", "B"), + 2: ("ASCII", "s"), # string ending with binary zero + 3: ("SHORT", "H"), + 4: ("LONG", "I"), + 5: ("RATIONAL", "II"), + 6: ("SBYTE", "b"), + 7: ("UNDEFINED", "B"), + 8: ("SSHORT", "h"), + 9: ("SLONG", "i"), + 10: ("SRATIONAL", "ii"), + 11: ("FLOAT", "f"), + 12: ("DOUBLE", "d"), +} + +FIELD_TYPE_OUT = { + "B": 1, + "s": 2, + "H": 3, + "I": 4, + "II": 5, + "b": 6, + "h": 8, + "i": 9, + "ii": 10, + "f": 11, + "d": 12, +} # sample formats (http://www.awaresystems.be/imaging/tiff/tiffflags/sampleformat.html) SAMPLE_FORMAT_UINT = 1 @@ -108,20 +113,18 @@ class TiffIO(object): - def __init__(self, filename, mode=None, cache_length=20, mono_output=False): if mode is None: - mode = 'rb' - if 'b' not in mode: - mode = mode + 'b' - if 'a' in mode.lower(): + mode = "rb" + if "b" not in mode: + mode = mode + "b" + if "a" in mode.lower(): raise IOError("Mode %s makes no sense on TIFF files. Consider 'rb+'" % mode) - if ('w' in mode): - if '+' not in mode: - mode += '+' + if "w" in mode: + if "+" not in mode: + mode += "+" - if hasattr(filename, "seek") and\ - hasattr(filename, "read"): + if hasattr(filename, "seek") and hasattr(filename, "read"): fd = filename self._access = None else: @@ -152,11 +155,11 @@ def _initInternalVariables(self, fd=None): if order == b"II": # intel, little endian fileOrder = "little" - self._structChar = '<' + self._structChar = "<" elif order == b"MM": # motorola, high endian fileOrder = "big" - self._structChar = '>' + self._structChar = ">" else: raise IOError("File is not a Mar CCD file, nor a TIFF file") a = fd.read(2) @@ -171,9 +174,9 @@ def _initInternalVariables(self, fd=None): swap = False else: if sys.byteorder == "little": - self._structChar = '<' + self._structChar = "<" else: - self._structChar = '>' + self._structChar = ">" swap = False self._swap = swap self._IFD = [] @@ -191,7 +194,7 @@ def __makeSureFileIsOpen(self): if self._access is None: # we do not own the file # open in read mode - newFile = open(fileName, 'rb') + newFile = open(fileName, "rb") else: newFile = open(fileName, self._access) self.fd = newFile @@ -227,7 +230,7 @@ def getImageFileDirectories(self, fd=None): fd.seek(4) self._IFD = [] nImages = 0 - fmt = st + 'I' + fmt = st + "I" inStr = fd.read(struct.calcsize(fmt)) if not len(inStr): offsetToIFD = 0 @@ -238,11 +241,13 @@ def getImageFileDirectories(self, fd=None): self._IFD.append(offsetToIFD) nImages += 1 fd.seek(offsetToIFD) - fmt = st + 'H' - numberOfDirectoryEntries = struct.unpack(fmt, fd.read(struct.calcsize(fmt)))[0] + fmt = st + "H" + numberOfDirectoryEntries = struct.unpack( + fmt, fd.read(struct.calcsize(fmt)) + )[0] logger.debug("Number of directory entries = %d", numberOfDirectoryEntries) - fmt = st + 'I' + fmt = st + "I" fd.seek(offsetToIFD + 2 + 12 * numberOfDirectoryEntries) offsetToIFD = struct.unpack(fmt, fd.read(struct.calcsize(fmt)))[0] logger.debug("Next Offset to IFD = %d", offsetToIFD) @@ -255,11 +260,11 @@ def _parseImageFileDirectory(self, nImage): st = self._structChar fd = self.fd fd.seek(offsetToIFD) - fmt = st + 'H' + fmt = st + "H" numberOfDirectoryEntries = struct.unpack(fmt, fd.read(struct.calcsize(fmt)))[0] logger.debug("Number of directory entries = %d", numberOfDirectoryEntries) - fmt = st + 'HHI4s' + fmt = st + "HHI4s" tagIDList = [] fieldTypeList = [] nValuesList = [] @@ -271,12 +276,14 @@ def _parseImageFileDirectory(self, nImage): nValuesList.append(nValues) if nValues == 1: ftype, vfmt = FIELD_TYPE[fieldType] - if ftype not in ['ASCII', 'RATIONAL', 'SRATIONAL']: + if ftype not in ["ASCII", "RATIONAL", "SRATIONAL"]: vfmt = st + vfmt - data = valueOffset[0: struct.calcsize(vfmt)] + data = valueOffset[0 : struct.calcsize(vfmt)] if struct.calcsize(vfmt) > len(data): # Add a 0 padding to have the expected size - logger.warning("Data at tag id '%s' is smaller than expected", tagID) + logger.warning( + "Data at tag id '%s' is smaller than expected", tagID + ) data = data + b"\x00" * (struct.calcsize(vfmt) - len(data)) actualValue = struct.unpack(vfmt, data)[0] valueOffsetList.append(actualValue) @@ -285,7 +292,9 @@ def _parseImageFileDirectory(self, nImage): elif (nValues < 5) and (fieldType == 2): ftype, vfmt = FIELD_TYPE[fieldType] vfmt = st + "%d%s" % (nValues, vfmt) - actualValue = struct.unpack(vfmt, valueOffset[0: struct.calcsize(vfmt)])[0] + actualValue = struct.unpack( + vfmt, valueOffset[0 : struct.calcsize(vfmt)] + )[0] valueOffsetList.append(actualValue) else: valueOffsetList.append(valueOffset) @@ -300,7 +309,9 @@ def _parseImageFileDirectory(self, nImage): # logger.debug("valueOffset = %s", valueOffset) return tagIDList, fieldTypeList, nValuesList, valueOffsetList - def _readIFDEntry(self, tag, tagIDList, fieldTypeList, nValuesList, valueOffsetList): + def _readIFDEntry( + self, tag, tagIDList, fieldTypeList, nValuesList, valueOffsetList + ): fd = self.fd st = self._structChar idx = tagIDList.index(tag) @@ -329,10 +340,12 @@ def _readIFDEntry(self, tag, tagIDList, fieldTypeList, nValuesList, valueOffsetL try: text = raw.decode("utf-8") except UnicodeDecodeError: - logger.warning("TIFF file tag %d contains non ASCII/UTF-8 characters. ", tag) - text = raw.decode("utf-8", errors='replace') + logger.warning( + "TIFF file tag %d contains non ASCII/UTF-8 characters. ", tag + ) + text = raw.decode("utf-8", errors="replace") # Use a valid ASCII character to limit ferther encoding error - text = text.replace(u"\ufffd", "?") + text = text.replace("\ufffd", "?") cleaned_output.append(text) if isinstance(output, tuple): @@ -364,7 +377,9 @@ def _readInfo(self, nImage, close=True): # read the header self.__makeSureFileIsOpen() - tagIDList, fieldTypeList, nValuesList, valueOffsetList = self._parseImageFileDirectory(nImage) + tagIDList, fieldTypeList, nValuesList, valueOffsetList = ( + self._parseImageFileDirectory(nImage) + ) # rows and columns nColumns = valueOffsetList[tagIDList.index(TAG_NUMBER_OF_COLUMNS)] @@ -375,22 +390,27 @@ def _readInfo(self, nImage, close=True): nBits = valueOffsetList[idx] if nValuesList[idx] != 1: # this happens with RGB and friends, nBits is not a single value - nBits = self._readIFDEntry(TAG_BITS_PER_SAMPLE, - tagIDList, fieldTypeList, nValuesList, - valueOffsetList) + nBits = self._readIFDEntry( + TAG_BITS_PER_SAMPLE, + tagIDList, + fieldTypeList, + nValuesList, + valueOffsetList, + ) if TAG_COLORMAP in tagIDList: idx = tagIDList.index(TAG_COLORMAP) - tmpColormap = self._readIFDEntry(TAG_COLORMAP, - tagIDList, fieldTypeList, nValuesList, valueOffsetList) + tmpColormap = self._readIFDEntry( + TAG_COLORMAP, tagIDList, fieldTypeList, nValuesList, valueOffsetList + ) if max(tmpColormap) > 255: tmpColormap = numpy.array(tmpColormap, dtype=numpy.uint16) - tmpColormap = (tmpColormap / 256.).astype(numpy.uint8) + tmpColormap = (tmpColormap / 256.0).astype(numpy.uint8) else: tmpColormap = numpy.array(tmpColormap, dtype=numpy.uint8) tmpColormap.shape = 3, -1 colormap = numpy.zeros((tmpColormap.shape[-1], 3), tmpColormap.dtype) - colormap[:,:] = tmpColormap.T + colormap[:, :] = tmpColormap.T tmpColormap = None else: colormap = None @@ -415,30 +435,41 @@ def _readInfo(self, nImage, close=True): # photometric interpretation interpretation = 1 if TAG_PHOTOMETRIC_INTERPRETATION in tagIDList: - interpretation = valueOffsetList[tagIDList.index(TAG_PHOTOMETRIC_INTERPRETATION)] + interpretation = valueOffsetList[ + tagIDList.index(TAG_PHOTOMETRIC_INTERPRETATION) + ] else: - logger.debug("WARNING: Non standard TIFF. Photometric interpretation TAG missing") + logger.debug( + "WARNING: Non standard TIFF. Photometric interpretation TAG missing" + ) helpString = "" if TAG_IMAGE_DESCRIPTION in tagIDList: - imageDescription = self._readIFDEntry(TAG_IMAGE_DESCRIPTION, - tagIDList, fieldTypeList, nValuesList, valueOffsetList) + imageDescription = self._readIFDEntry( + TAG_IMAGE_DESCRIPTION, + tagIDList, + fieldTypeList, + nValuesList, + valueOffsetList, + ) if type(imageDescription) in [type([1]), type((1,))]: imageDescription = helpString.join(imageDescription) else: imageDescription = "%d/%d" % (nImage + 1, len(self._IFD)) if TAG_MODEL in tagIDList: - model = self._readIFDEntry(TAG_MODEL, - tagIDList, fieldTypeList, nValuesList, valueOffsetList) + model = self._readIFDEntry( + TAG_MODEL, tagIDList, fieldTypeList, nValuesList, valueOffsetList + ) else: model = None defaultSoftware = "Unknown Software" if TAG_SOFTWARE in tagIDList: - software = self._readIFDEntry(TAG_SOFTWARE, - tagIDList, fieldTypeList, nValuesList, valueOffsetList) + software = self._readIFDEntry( + TAG_SOFTWARE, tagIDList, fieldTypeList, nValuesList, valueOffsetList + ) if isinstance(software, (tuple, list)): software = helpString.join(software) else: @@ -452,37 +483,40 @@ def _readInfo(self, nImage, close=True): pass if TAG_DATE in tagIDList: - date = self._readIFDEntry(TAG_DATE, - tagIDList, fieldTypeList, nValuesList, valueOffsetList) + date = self._readIFDEntry( + TAG_DATE, tagIDList, fieldTypeList, nValuesList, valueOffsetList + ) if type(date) in [type([1]), type((1,))]: date = helpString.join(date) else: date = "Unknown Date" - stripOffsets = self._readIFDEntry(TAG_STRIP_OFFSETS, - tagIDList, - fieldTypeList, - nValuesList, - valueOffsetList) + stripOffsets = self._readIFDEntry( + TAG_STRIP_OFFSETS, tagIDList, fieldTypeList, nValuesList, valueOffsetList + ) if TAG_ROWS_PER_STRIP in tagIDList: - rowsPerStrip = self._readIFDEntry(TAG_ROWS_PER_STRIP, - tagIDList, - fieldTypeList, - nValuesList, - valueOffsetList)[0] + rowsPerStrip = self._readIFDEntry( + TAG_ROWS_PER_STRIP, + tagIDList, + fieldTypeList, + nValuesList, + valueOffsetList, + )[0] else: rowsPerStrip = nRows logger.warning("Non standard TIFF. Rows per strip TAG missing") if TAG_STRIP_BYTE_COUNTS in tagIDList: - stripByteCounts = self._readIFDEntry(TAG_STRIP_BYTE_COUNTS, - tagIDList, - fieldTypeList, - nValuesList, - valueOffsetList) + stripByteCounts = self._readIFDEntry( + TAG_STRIP_BYTE_COUNTS, + tagIDList, + fieldTypeList, + nValuesList, + valueOffsetList, + ) else: logger.warning("Non standard TIFF. Strip byte counts TAG missing") - if hasattr(nBits, 'index'): + if hasattr(nBits, "index"): expectedSum = 0 for n in nBits: expectedSum += int(nRows * nColumns * n / 8) @@ -512,9 +546,13 @@ def _readInfo(self, nImage, close=True): info["compression"] = compression info["compression_type"] = compression_type info["imageDescription"] = imageDescription - info["stripOffsets"] = stripOffsets # This contains the file offsets to the data positions + info["stripOffsets"] = ( + stripOffsets # This contains the file offsets to the data positions + ) info["rowsPerStrip"] = rowsPerStrip - info["stripByteCounts"] = stripByteCounts # bytes in strip since I do not support compression + info["stripByteCounts"] = ( + stripByteCounts # bytes in strip since I do not support compression + ) info["software"] = software info["date"] = date info["colormap"] = colormap @@ -524,36 +562,38 @@ def _readInfo(self, nImage, close=True): info["model"] = model infoDict = {} - testString = 'PyMca' + testString = "PyMca" if software.startswith(testString): # str to make sure python 2.x sees it as string and not unicode descriptionString = imageDescription # interpret the image description in terms of supplied # information at writing time - items = descriptionString.split('=') + items = descriptionString.split("=") for i in range(int(len(items) / 2)): key = "%s" % items[i * 2] # get rid of the \n at the end of the value value = "%s" % items[i * 2 + 1][:-1] infoDict[key] = value - info['info'] = infoDict + info["info"] = infoDict if (self._maxImageCacheLength > 0) and useInfoCache: self._imageInfoCacheIndex.insert(0, nImage) self._imageInfoCache.insert(0, info) if len(self._imageInfoCacheIndex) > self._maxImageCacheLength: - self._imageInfoCacheIndex = self._imageInfoCacheIndex[:self._maxImageCacheLength] - self._imageInfoCache = self._imageInfoCache[:self._maxImageCacheLength] + self._imageInfoCacheIndex = self._imageInfoCacheIndex[ + : self._maxImageCacheLength + ] + self._imageInfoCache = self._imageInfoCache[: self._maxImageCacheLength] return info def _readImage(self, nImage, **kw): logger.debug("Reading image %d", nImage) - if 'close' in kw: - close = kw['close'] + if "close" in kw: + close = kw["close"] else: close = True - rowMin = kw.get('rowMin', None) - rowMax = kw.get('rowMax', None) + rowMin = kw.get("rowMin", None) + rowMax = kw.get("rowMax", None) if nImage in self._imageDataCacheIndex: logger.debug("Reading image data from cache") return self._imageDataCache[self._imageDataCacheIndex.index(nImage)] @@ -571,8 +611,8 @@ def _readImage(self, nImage, **kw): logger.debug("Backtrace", exc_info=True) self._forceMonoOutput = oldMono raise - compression = info['compression'] - compression_type = info['compression_type'] + compression = info["compression"] + compression_type = info["compression_type"] if compression: if compression_type != 32773: raise IOError("Compressed TIFF images not supported except packbits") @@ -638,7 +678,9 @@ def _readImage(self, nImage, **kw): elif nBits in [64, (64, 64, 64), [64, 64, 64]]: dtype = numpy.uint64 else: - raise ValueError("Unsupported number of bits for unsigned int: %s" % (nBits,)) + raise ValueError( + "Unsupported number of bits for unsigned int: %s" % (nBits,) + ) elif sampleFormat == SAMPLE_FORMAT_INT: if nBits in [8, (8, 8, 8), [8, 8, 8]]: dtype = numpy.int8 @@ -649,10 +691,15 @@ def _readImage(self, nImage, **kw): elif nBits in [64, (64, 64, 64), [64, 64, 64]]: dtype = numpy.int64 else: - raise ValueError("Unsupported number of bits for signed int: %s" % (nBits,)) + raise ValueError( + "Unsupported number of bits for signed int: %s" % (nBits,) + ) else: - raise ValueError("Unsupported combination. Bits = %s Format = %d" % (nBits, sampleFormat)) - if hasattr(nBits, 'index'): + raise ValueError( + "Unsupported combination. Bits = %s Format = %d" + % (nBits, sampleFormat) + ) + if hasattr(nBits, "index"): image = numpy.zeros((nRows, nColumns, len(nBits)), dtype=dtype) elif colormap is not None and interpretation == 3: # should I use colormap dtype? @@ -661,9 +708,13 @@ def _readImage(self, nImage, **kw): image = numpy.zeros((nRows, nColumns), dtype=dtype) fd = self.fd - stripOffsets = info["stripOffsets"] # This contains the file offsets to the data positions + stripOffsets = info[ + "stripOffsets" + ] # This contains the file offsets to the data positions rowsPerStrip = info["rowsPerStrip"] - stripByteCounts = info["stripByteCounts"] # bytes in strip since I do not support compression + stripByteCounts = info[ + "stripByteCounts" + ] # bytes in strip since I do not support compression rowStart = 0 if len(stripOffsets) == 1: @@ -679,9 +730,9 @@ def _readImage(self, nImage, **kw): readout = numpy.frombuffer(fd.read(nBytes), dtype).copy() if self._swap: readout.byteswap(True) - if hasattr(nBits, 'index'): + if hasattr(nBits, "index"): readout.shape = -1, nColumns, len(nBits) - elif info['colormap'] is not None and interpretation == 3: + elif info["colormap"] is not None and interpretation == 3: readout = colormap[readout] readout.shape = -1, nColumns, 3 else: @@ -695,7 +746,7 @@ def _readImage(self, nImage, **kw): if rowEnd < rowMin: rowStart += nRowsToRead continue - if (rowStart > rowMax): + if rowStart > rowMax: break # we are in position fd.seek(stripOffsets[i]) @@ -712,17 +763,20 @@ def _readImage(self, nImage, **kw): # intermediate buffer tmpBuffer = fd.read(nBytes) while readBytes < nBytes: - n = struct.unpack('b', tmpBuffer[readBytes:(readBytes + 1)])[0] + n = struct.unpack("b", tmpBuffer[readBytes : (readBytes + 1)])[ + 0 + ] readBytes += 1 if n >= 0: # should I prevent reading more than the # length of the chain? Let's python raise # the exception... - bufferBytes += tmpBuffer[readBytes: - readBytes + (n + 1)] - readBytes += (n + 1) + bufferBytes += tmpBuffer[readBytes : readBytes + (n + 1)] + readBytes += n + 1 elif n > -128: - bufferBytes += (-n + 1) * tmpBuffer[readBytes:(readBytes + 1)] + bufferBytes += (-n + 1) * tmpBuffer[ + readBytes : (readBytes + 1) + ] readBytes += 1 else: # if read -128 ignore the byte @@ -731,27 +785,27 @@ def _readImage(self, nImage, **kw): if self._swap: readout.byteswap(True) - if hasattr(nBits, 'index'): + if hasattr(nBits, "index"): readout.shape = -1, nColumns, len(nBits) - elif info['colormap'] is not None: + elif info["colormap"] is not None: readout = colormap[readout] readout.shape = -1, nColumns, 3 else: readout.shape = -1, nColumns - image[rowStart:rowEnd,:] = readout + image[rowStart:rowEnd, :] = readout else: readout = numpy.frombuffer(fd.read(nBytes), dtype).copy() if self._swap: readout.byteswap(True) - if hasattr(nBits, 'index'): + if hasattr(nBits, "index"): readout.shape = -1, nColumns, len(nBits) elif colormap is not None: readout = colormap[readout] readout.shape = -1, nColumns, 3 else: readout.shape = -1, nColumns - image[rowStart:rowEnd,:] = readout + image[rowStart:rowEnd, :] = readout rowStart += nRowsToRead if close: self.__makeSureFileIsClosed() @@ -760,22 +814,26 @@ def _readImage(self, nImage, **kw): # color image if self._forceMonoOutput: # color image, convert to monochrome - image = (image[:,:, 0] * 0.114 + - image[:,:, 1] * 0.587 + - image[:,:, 2] * 0.299).astype(numpy.float32) + image = ( + image[:, :, 0] * 0.114 + + image[:, :, 1] * 0.587 + + image[:, :, 2] * 0.299 + ).astype(numpy.float32) if (rowMin == 0) and (rowMax == (nRows - 1)): self._imageDataCacheIndex.insert(0, nImage) self._imageDataCache.insert(0, image) if len(self._imageDataCacheIndex) > self._maxImageCacheLength: - self._imageDataCacheIndex = self._imageDataCacheIndex[:self._maxImageCacheLength] - self._imageDataCache = self._imageDataCache[:self._maxImageCacheLength] + self._imageDataCacheIndex = self._imageDataCacheIndex[ + : self._maxImageCacheLength + ] + self._imageDataCache = self._imageDataCache[: self._maxImageCacheLength] return image def writeImage(self, image0, info=None, software=None, date=None): if software is None: - software = 'PyMca.TiffIO' + software = "PyMca.TiffIO" # if date is None: # date = time.ctime() @@ -796,13 +854,13 @@ def writeImage(self, image0, info=None, software=None, date=None): fd.seek(0) mode = fd.mode name = fd.name - if 'w' in mode: + if "w" in mode: # we have to overwrite the file self.__makeSureFileIsClosed() fd = None if os.path.exists(name): os.remove(name) - fd = open(name, mode='wb+') + fd = open(name, mode="wb+") self._initEmptyFile(fd) self.fd = fd @@ -825,16 +883,18 @@ def writeImage(self, image0, info=None, software=None, date=None): logger.debug("File contains %d images", nImages) if nImages == 0: fd.seek(4) - fmt = st + 'I' + fmt = st + "I" fd.write(struct.pack(fmt, endOfFile)) else: fd.seek(self._IFD[-1]) - fmt = st + 'H' - numberOfDirectoryEntries = struct.unpack(fmt, fd.read(struct.calcsize(fmt)))[0] - fmt = st + 'I' + fmt = st + "H" + numberOfDirectoryEntries = struct.unpack( + fmt, fd.read(struct.calcsize(fmt)) + )[0] + fmt = st + "I" pos = self._IFD[-1] + 2 + 12 * numberOfDirectoryEntries fd.seek(pos) - fmt = st + 'I' + fmt = st + "I" fd.write(struct.pack(fmt, endOfFile)) fd.flush() @@ -850,9 +910,9 @@ def writeImage(self, image0, info=None, software=None, date=None): description += "%s=%s\n" % (key, info[key]) # get the image file directory - outputIFD = self._getOutputIFD(image, description=description, - software=software, - date=date) + outputIFD = self._getOutputIFD( + image, description=description, software=software, date=date + ) # write the new IFD fd.write(outputIFD) @@ -874,21 +934,21 @@ def _initEmptyFile(self, fd=None): order = "II" # intel, little endian fileOrder = "little" - self._structChar = '<' + self._structChar = "<" else: order = "MM" # motorola, high endian fileOrder = "big" - self._structChar = '>' + self._structChar = ">" st = self._structChar if fileOrder == sys.byteorder: self._swap = False else: self._swap = True fd.seek(0) - fd.write(struct.pack(st + '2s', bytes(order, 'utf-8'))) - fd.write(struct.pack(st + 'H', 42)) - fd.write(struct.pack(st + 'I', 0)) + fd.write(struct.pack(st + "2s", bytes(order, "utf-8"))) + fd.write(struct.pack(st + "H", 42)) + fd.write(struct.pack(st + "I", 0)) fd.flush() def _getOutputIFD(self, image, description=None, software=None, date=None): @@ -917,9 +977,9 @@ def _getOutputIFD(self, image, description=None, software=None, date=None): descriptionLength = len(description) if isinstance(description, str): - raw = description.encode('utf-8') + raw = description.encode("utf-8") else: - raw = bytes(description, 'utf-8') + raw = bytes(description, "utf-8") imageDescription = struct.pack("%ds" % len(raw), raw) nDirectoryEntries += 1 @@ -929,7 +989,7 @@ def _getOutputIFD(self, image, description=None, software=None, date=None): while softwareLength < 4: software = software + " " softwareLength = len(software) - software = bytes(software, 'utf-8') + software = bytes(software, "utf-8") softwarePackedString = struct.pack("%ds" % softwareLength, software) nDirectoryEntries += 1 else: @@ -937,7 +997,7 @@ def _getOutputIFD(self, image, description=None, software=None, date=None): if date is not None: dateLength = len(date) - date = bytes(date, 'utf-8') + date = bytes(date, "utf-8") datePackedString = struct.pack("%ds" % dateLength, date) dateLength = len(datePackedString) nDirectoryEntries += 1 @@ -967,7 +1027,8 @@ def _getOutputIFD(self, image, description=None, software=None, date=None): nDirectoryEntries += 1 # For SamplesPerPixel else: raise RuntimeError( - "Image with %d color channel(s) not supported" % nChannels) + "Image with %d color channel(s) not supported" % nChannels + ) # image description if imageDescription is not None: @@ -1003,15 +1064,19 @@ def _getOutputIFD(self, image, description=None, software=None, date=None): rowsPerStrip = nRows # stripByteCounts - stripByteCounts = int(nColumns * rowsPerStrip * - bitsPerSample * nChannels / 8) + stripByteCounts = int(nColumns * rowsPerStrip * bitsPerSample * nChannels / 8) if descriptionLength > 4: - stripOffsets0 = (endOfFile + dateLength + descriptionLength + - 2 + 12 * nDirectoryEntries + 4) + stripOffsets0 = ( + endOfFile + + dateLength + + descriptionLength + + 2 + + 12 * nDirectoryEntries + + 4 + ) else: - stripOffsets0 = (endOfFile + dateLength + - 2 + 12 * nDirectoryEntries + 4) + stripOffsets0 = endOfFile + dateLength + 2 + 12 * nDirectoryEntries + 4 if softwareLength > 4: stripOffsets0 += softwareLength @@ -1026,7 +1091,7 @@ def _getOutputIFD(self, image, description=None, software=None, date=None): if rowsPerStrip != nRows: nStripOffsets = int(nRows / rowsPerStrip) - fmt = st + 'I' + fmt = st + "I" stripOffsetsLength = struct.calcsize(fmt) * nStripOffsets stripOffsets0 += stripOffsetsLength # the length for the stripByteCounts will be the same @@ -1045,8 +1110,7 @@ def _getOutputIFD(self, image, description=None, software=None, date=None): logger.debug("IMAGE WILL START AT %d", stripOffsets[0]) # sample format - if dtype in [numpy.float32, numpy.float64] or\ - dtype.str[-2] == 'f': + if dtype in [numpy.float32, numpy.float64] or dtype.str[-2] == "f": sampleFormat = SAMPLE_FORMAT_FLOAT elif dtype in [numpy.uint8, numpy.uint16, numpy.uint32, numpy.uint64]: sampleFormat = SAMPLE_FORMAT_UINT @@ -1075,116 +1139,169 @@ def _getOutputIFD(self, image, description=None, software=None, date=None): outputIFD += struct.pack(fmt, nDirectoryEntries) fmt = st + "HHII" - outputIFD += struct.pack(fmt, TAG_NUMBER_OF_COLUMNS, - FIELD_TYPE_OUT['I'], 1, info["nColumns"]) - outputIFD += struct.pack(fmt, TAG_NUMBER_OF_ROWS, - FIELD_TYPE_OUT['I'], 1, info["nRows"]) + outputIFD += struct.pack( + fmt, TAG_NUMBER_OF_COLUMNS, FIELD_TYPE_OUT["I"], 1, info["nColumns"] + ) + outputIFD += struct.pack( + fmt, TAG_NUMBER_OF_ROWS, FIELD_TYPE_OUT["I"], 1, info["nRows"] + ) if info["photometricInterpretation"] == 1: - fmt = st + 'HHIHH' - outputIFD += struct.pack(fmt, TAG_BITS_PER_SAMPLE, - FIELD_TYPE_OUT['H'], 1, info["nBits"], 0) + fmt = st + "HHIHH" + outputIFD += struct.pack( + fmt, TAG_BITS_PER_SAMPLE, FIELD_TYPE_OUT["H"], 1, info["nBits"], 0 + ) elif info["photometricInterpretation"] == 2: - fmt = st + 'HHII' - outputIFD += struct.pack(fmt, TAG_BITS_PER_SAMPLE, - FIELD_TYPE_OUT['H'], 3, - info["stripOffsets"][0] - 2 * stripOffsetsLength - descriptionLength - - dateLength - softwareLength - bitsPerSampleLength) + fmt = st + "HHII" + outputIFD += struct.pack( + fmt, + TAG_BITS_PER_SAMPLE, + FIELD_TYPE_OUT["H"], + 3, + info["stripOffsets"][0] + - 2 * stripOffsetsLength + - descriptionLength + - dateLength + - softwareLength + - bitsPerSampleLength, + ) else: raise RuntimeError("Unsupported photometric interpretation") - fmt = st + 'HHIHH' - outputIFD += struct.pack(fmt, TAG_COMPRESSION, - FIELD_TYPE_OUT['H'], 1, info["compression"], 0) - fmt = st + 'HHIHH' - outputIFD += struct.pack(fmt, TAG_PHOTOMETRIC_INTERPRETATION, - FIELD_TYPE_OUT['H'], 1, info["photometricInterpretation"], 0) + fmt = st + "HHIHH" + outputIFD += struct.pack( + fmt, TAG_COMPRESSION, FIELD_TYPE_OUT["H"], 1, info["compression"], 0 + ) + fmt = st + "HHIHH" + outputIFD += struct.pack( + fmt, + TAG_PHOTOMETRIC_INTERPRETATION, + FIELD_TYPE_OUT["H"], + 1, + info["photometricInterpretation"], + 0, + ) if imageDescription is not None: descriptionLength = len(imageDescription) if descriptionLength > 4: - fmt = st + 'HHII' - outputIFD += struct.pack(fmt, TAG_IMAGE_DESCRIPTION, - FIELD_TYPE_OUT['s'], descriptionLength, - info["stripOffsets"][0] - 2 * stripOffsetsLength - - descriptionLength) + fmt = st + "HHII" + outputIFD += struct.pack( + fmt, + TAG_IMAGE_DESCRIPTION, + FIELD_TYPE_OUT["s"], + descriptionLength, + info["stripOffsets"][0] + - 2 * stripOffsetsLength + - descriptionLength, + ) else: # it has to have length 4 - fmt = st + 'HHI%ds' % descriptionLength - outputIFD += struct.pack(fmt, TAG_IMAGE_DESCRIPTION, - FIELD_TYPE_OUT['s'], - descriptionLength, - imageDescription) + fmt = st + "HHI%ds" % descriptionLength + outputIFD += struct.pack( + fmt, + TAG_IMAGE_DESCRIPTION, + FIELD_TYPE_OUT["s"], + descriptionLength, + imageDescription, + ) if len(stripOffsets) == 1: - fmt = st + 'HHII' - outputIFD += struct.pack(fmt, TAG_STRIP_OFFSETS, - FIELD_TYPE_OUT['I'], 1, - info["stripOffsets"][0]) + fmt = st + "HHII" + outputIFD += struct.pack( + fmt, TAG_STRIP_OFFSETS, FIELD_TYPE_OUT["I"], 1, info["stripOffsets"][0] + ) else: - fmt = st + 'HHII' - outputIFD += struct.pack(fmt, TAG_STRIP_OFFSETS, - FIELD_TYPE_OUT['I'], - len(stripOffsets), - info["stripOffsets"][0] - 2 * stripOffsetsLength) + fmt = st + "HHII" + outputIFD += struct.pack( + fmt, + TAG_STRIP_OFFSETS, + FIELD_TYPE_OUT["I"], + len(stripOffsets), + info["stripOffsets"][0] - 2 * stripOffsetsLength, + ) if info["photometricInterpretation"] == 2: - fmt = st + 'HHIHH' - outputIFD += struct.pack(fmt, TAG_SAMPLES_PER_PIXEL, - FIELD_TYPE_OUT['H'], 1, - info["samplesPerPixel"], 0) + fmt = st + "HHIHH" + outputIFD += struct.pack( + fmt, + TAG_SAMPLES_PER_PIXEL, + FIELD_TYPE_OUT["H"], + 1, + info["samplesPerPixel"], + 0, + ) - fmt = st + 'HHII' - outputIFD += struct.pack(fmt, TAG_ROWS_PER_STRIP, - FIELD_TYPE_OUT['I'], 1, - info["rowsPerStrip"]) + fmt = st + "HHII" + outputIFD += struct.pack( + fmt, TAG_ROWS_PER_STRIP, FIELD_TYPE_OUT["I"], 1, info["rowsPerStrip"] + ) if len(stripOffsets) == 1: - fmt = st + 'HHII' - outputIFD += struct.pack(fmt, TAG_STRIP_BYTE_COUNTS, - FIELD_TYPE_OUT['I'], 1, - info["stripByteCounts"]) + fmt = st + "HHII" + outputIFD += struct.pack( + fmt, + TAG_STRIP_BYTE_COUNTS, + FIELD_TYPE_OUT["I"], + 1, + info["stripByteCounts"], + ) else: - fmt = st + 'HHII' - outputIFD += struct.pack(fmt, TAG_STRIP_BYTE_COUNTS, - FIELD_TYPE_OUT['I'], len(stripOffsets), - info["stripOffsets"][0] - stripOffsetsLength) + fmt = st + "HHII" + outputIFD += struct.pack( + fmt, + TAG_STRIP_BYTE_COUNTS, + FIELD_TYPE_OUT["I"], + len(stripOffsets), + info["stripOffsets"][0] - stripOffsetsLength, + ) if software is not None: if softwareLength > 4: - fmt = st + 'HHII' - outputIFD += struct.pack(fmt, TAG_SOFTWARE, - FIELD_TYPE_OUT['s'], - softwareLength, - info["stripOffsets"][0] - - 2 * stripOffsetsLength - - descriptionLength - softwareLength - dateLength) + fmt = st + "HHII" + outputIFD += struct.pack( + fmt, + TAG_SOFTWARE, + FIELD_TYPE_OUT["s"], + softwareLength, + info["stripOffsets"][0] + - 2 * stripOffsetsLength + - descriptionLength + - softwareLength + - dateLength, + ) else: # it has to have length 4 - fmt = st + 'HHI%ds' % softwareLength - outputIFD += struct.pack(fmt, TAG_SOFTWARE, - FIELD_TYPE_OUT['s'], - softwareLength, - softwarePackedString) + fmt = st + "HHI%ds" % softwareLength + outputIFD += struct.pack( + fmt, + TAG_SOFTWARE, + FIELD_TYPE_OUT["s"], + softwareLength, + softwarePackedString, + ) if date is not None: - fmt = st + 'HHII' - outputIFD += struct.pack(fmt, TAG_DATE, - FIELD_TYPE_OUT['s'], - dateLength, - info["stripOffsets"][0] - - 2 * stripOffsetsLength - - descriptionLength - dateLength) - - fmt = st + 'HHIHH' - outputIFD += struct.pack(fmt, TAG_SAMPLE_FORMAT, - FIELD_TYPE_OUT['H'], 1, - info["sampleFormat"], 0) - fmt = st + 'I' + fmt = st + "HHII" + outputIFD += struct.pack( + fmt, + TAG_DATE, + FIELD_TYPE_OUT["s"], + dateLength, + info["stripOffsets"][0] + - 2 * stripOffsetsLength + - descriptionLength + - dateLength, + ) + + fmt = st + "HHIHH" + outputIFD += struct.pack( + fmt, TAG_SAMPLE_FORMAT, FIELD_TYPE_OUT["H"], 1, info["sampleFormat"], 0 + ) + fmt = st + "I" outputIFD += struct.pack(fmt, 0) if info["photometricInterpretation"] == 2: - outputIFD += struct.pack('HHH', info["nBits"], - info["nBits"], info["nBits"]) + outputIFD += struct.pack("HHH", info["nBits"], info["nBits"], info["nBits"]) if softwareLength > 4: outputIFD += softwarePackedString diff --git a/src/fabio/__init__.py b/src/fabio/__init__.py index 486a4e8e..b29fc3a3 100644 --- a/src/fabio/__init__.py +++ b/src/fabio/__init__.py @@ -28,40 +28,44 @@ __contact__ = "Jerome.Kieffer@ESRF.eu" __license__ = "GPLv3+" __copyright__ = "European Synchrotron Radiation Facility, Grenoble, France" -__date__ = "15/03/2024" +__date__ = "27/10/2025" __status__ = "stable" import sys import logging +from .version import __date__ as date, version, version_info, hexversion, strictversion # noqa +from . import fabioformats as _fabioformats +from . import fabioimage # noqa +from . import openimage # noqa +from .fabioutils import ( + jump_filename, + FilenameObject, + previous_filename, + next_filename, + deconstruct_filename, + extract_filenumber, + getnum, + construct_filename, + exists, +) # noqa +from .compression import COMPRESSORS # noqa +from .openimage import openimage as open # noqa +from .openimage import open_series as open_series # noqa +from .openimage import openheader as openheader # noqa if "ps1" in dir(sys): # configure logging with interactive console logging.basicConfig() -import os -from .version import __date__ as date, version, version_info, hexversion, strictversion # noqa -from . import fabioformats as _fabioformats - # provide a global fabio API factory = _fabioformats.factory # feed the library with all the available formats _fabioformats.register_default_formats() -from . import fabioimage -from . import openimage - -from .fabioutils import COMPRESSORS, jump_filename, FilenameObject, \ - previous_filename, next_filename, deconstruct_filename, \ - extract_filenumber, getnum, construct_filename, exists - # Compatibility with outside world: filename_object = FilenameObject -from .openimage import openimage as open -from .openimage import open_series as open_series -from .openimage import openheader as openheader - def register(codec_class): """ @@ -85,7 +89,7 @@ def register(codec_class): class MyCodec(fabio.fabioimage.FabioImage): pass """ - assert(issubclass(codec_class, fabioimage.FabioImage)) + assert issubclass(codec_class, fabioimage.FabioImage) _fabioformats.register(codec_class) return codec_class @@ -102,6 +106,7 @@ def tests(): export http_proxy=http://proxy.site.com:3128 """ from . import test + test.run_tests() @@ -110,5 +115,6 @@ def benchmarks(): Run the benchmarks """ from . import benchmark + res = benchmark.run() return res diff --git a/src/fabio/adscimage.py b/src/fabio/adscimage.py index 7bc27fef..1a6431ef 100644 --- a/src/fabio/adscimage.py +++ b/src/fabio/adscimage.py @@ -28,5 +28,6 @@ """ from . import dtrekimage + AdscImage = dtrekimage.DtrekImage adscimage = dtrekimage.DtrekImage diff --git a/src/fabio/app/convert.py b/src/fabio/app/convert.py index ca77360d..2248bd78 100644 --- a/src/fabio/app/convert.py +++ b/src/fabio/app/convert.py @@ -28,18 +28,15 @@ # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. -"""Portable image converter based on FabIO library. -""" +"""Portable image converter based on FabIO library.""" __author__ = "Valentin Valls" __copyright__ = "European Synchrotron Radiation Facility, Grenoble, France" __licence__ = "MIT" -__date__ = "23/04/2021" +__date__ = "27/10/2025" __status__ = "production" import logging -logging.basicConfig() - import sys import os import fabio @@ -47,11 +44,12 @@ from fabio.utils.cli import expand_args import argparse +logging.basicConfig() logger = logging.getLogger("fabio-convert") def get_default_extension_from_format(format_name): - """" + """ " Get a default file extension from a fabio format :param str format: String format like "edfimage" @@ -131,7 +129,9 @@ def convert_one(input_filename, output_filename, options): print("Converting file '%s' to '%s'" % (input_filename, output_filename)) if not input_exists: - logger.error("Input file '%s' do not exists. Conversion skipped.", input_filename) + logger.error( + "Input file '%s' do not exists. Conversion skipped.", input_filename + ) return False skip_conversion = False @@ -153,7 +153,7 @@ def convert_one(input_filename, output_filename, options): else: remove_file = True elif is_user_want_to_overwrite_filename(output_filename): - remove_file = True + remove_file = True else: skip_conversion = True @@ -164,7 +164,11 @@ def convert_one(input_filename, output_filename, options): if not options.dry_run: os.remove(output_filename) except OSError as e: - logger.error("Removing previous file %s failed cause: \"%s\". Conversion skipped.", e.message, output_filename) + logger.error( + 'Removing previous file %s failed cause: "%s". Conversion skipped.', + e.message, + output_filename, + ) logger.debug("Backtrace", exc_info=True) return False @@ -179,7 +183,11 @@ def convert_one(input_filename, output_filename, options): except KeyboardInterrupt: raise except Exception as e: - logger.error("Loading input file '%s' failed cause: \"%s\". Conversion skipped.", input_filename, e.message) + logger.error( + "Loading input file '%s' failed cause: \"%s\". Conversion skipped.", + input_filename, + e.message, + ) logger.debug("Backtrace", exc_info=True) return False @@ -189,7 +197,11 @@ def convert_one(input_filename, output_filename, options): except KeyboardInterrupt: raise except Exception as e: - logger.error("Converting input file '%s' failed cause: \"%s\". Conversion skipped.", input_filename, e.message) + logger.error( + "Converting input file '%s' failed cause: \"%s\". Conversion skipped.", + input_filename, + e.message, + ) logger.debug("Backtrace", exc_info=True) return False @@ -200,7 +212,11 @@ def convert_one(input_filename, output_filename, options): except KeyboardInterrupt: raise except Exception as e: - logger.error("Saving output file '%s' failed cause: \"%s\". Conversion skipped.", output_filename, e.message) + logger.error( + "Saving output file '%s' failed cause: \"%s\". Conversion skipped.", + output_filename, + e.message, + ) logger.debug("Backtrace", exc_info=True) return False @@ -217,7 +233,6 @@ def convert_all(options): """ succeeded = True for filename in options.images: - if options.output is None: output_filename = get_output_filename(filename, options.format) elif os.path.isdir(options.output): @@ -275,51 +290,111 @@ def is_format_supported(format_name): def main(): - epilog = """return codes: 0 means a success. 1 means the conversion contains a failure, 2 means there was an error in the arguments""" - parser = argparse.ArgumentParser(prog="fabio-convert", - description=__doc__, - epilog=epilog) - parser.add_argument("IMAGE", nargs="*", - help="Input file images") - parser.add_argument("-V", "--version", action='version', version=fabio.version, - help="output version and exit") - parser.add_argument("-v", "--verbose", action='store_true', dest="verbose", default=False, - help="show information for each conversions") - parser.add_argument("--debug", action='store_true', dest="debug", default=False, - help="show debug information") + parser = argparse.ArgumentParser( + prog="fabio-convert", description=__doc__, epilog=epilog + ) + parser.add_argument("IMAGE", nargs="*", help="Input file images") + parser.add_argument( + "-V", + "--version", + action="version", + version=fabio.version, + help="output version and exit", + ) + parser.add_argument( + "-v", + "--verbose", + action="store_true", + dest="verbose", + default=False, + help="show information for each conversions", + ) + parser.add_argument( + "--debug", + action="store_true", + dest="debug", + default=False, + help="show debug information", + ) group = parser.add_argument_group("main arguments") - group.add_argument("-l", "--list", action="store_true", dest="list", default=None, - help="show the list of available formats and exit") - group.add_argument("-o", "--output", dest='output', type=str, - help="output file or directory") - group.add_argument("-F", "--output-format", dest="format", type=str, default=None, - help="output format") + group.add_argument( + "-l", + "--list", + action="store_true", + dest="list", + default=None, + help="show the list of available formats and exit", + ) + group.add_argument( + "-o", "--output", dest="output", type=str, help="output file or directory" + ) + group.add_argument( + "-F", + "--output-format", + dest="format", + type=str, + default=None, + help="output format", + ) group = parser.add_argument_group("optional behaviour arguments") - group.add_argument("-f", "--force", dest="force", action="store_true", default=False, - help="if an existing destination file cannot be" + - " opened, remove it and try again (this option" + - " is ignored when the -n option is also used)") - group.add_argument("-n", "--no-clobber", dest="no_clobber", action="store_true", default=False, - help="do not overwrite an existing file (this option" + - " is ignored when the -i option is also used)") - group.add_argument("--remove-destination", dest="remove_destination", action="store_true", default=False, - help="remove each existing destination file before" + - " attempting to open it (contrast with --force)") - group.add_argument("-u", "--update", dest="update", action="store_true", default=False, - help="copy only when the SOURCE file is newer" + - " than the destination file or when the" + - " destination file is missing") - group.add_argument("-i", "--interactive", dest="interactive", action="store_true", default=False, - help="prompt before overwrite (overrides a previous -n" + - " option)") - group.add_argument("--dry-run", dest="dry_run", action="store_true", default=False, - help="do everything except modifying the file system") + group.add_argument( + "-f", + "--force", + dest="force", + action="store_true", + default=False, + help="if an existing destination file cannot be" + + " opened, remove it and try again (this option" + + " is ignored when the -n option is also used)", + ) + group.add_argument( + "-n", + "--no-clobber", + dest="no_clobber", + action="store_true", + default=False, + help="do not overwrite an existing file (this option" + + " is ignored when the -i option is also used)", + ) + group.add_argument( + "--remove-destination", + dest="remove_destination", + action="store_true", + default=False, + help="remove each existing destination file before" + + " attempting to open it (contrast with --force)", + ) + group.add_argument( + "-u", + "--update", + dest="update", + action="store_true", + default=False, + help="copy only when the SOURCE file is newer" + + " than the destination file or when the" + + " destination file is missing", + ) + group.add_argument( + "-i", + "--interactive", + dest="interactive", + action="store_true", + default=False, + help="prompt before overwrite (overrides a previous -n" + " option)", + ) + group.add_argument( + "--dry-run", + dest="dry_run", + action="store_true", + default=False, + help="do everything except modifying the file system", + ) try: args = parser.parse_args() @@ -339,10 +414,11 @@ def main(): args.images.sort() if args.format is None or not args.format.endswith("image"): - if args.format is None: if args.output is None: - raise argparse.ArgumentError(None, "No format specified. Use -F or -o.") + raise argparse.ArgumentError( + None, "No format specified. Use -F or -o." + ) dummy_filename = args.output else: # format looks to be an extension @@ -352,15 +428,26 @@ def main(): filename = fabioutils.FilenameObject(filename=dummy_filename) if filename.format is None or len(filename.format) == 0: - raise argparse.ArgumentError(None, "This file extension is unknown. You have also to specify a format using -F.") + raise argparse.ArgumentError( + None, + "This file extension is unknown. You have also to specify a format using -F.", + ) elif filename.format is None or len(filename.format) > 1: formats = [i + "image" for i in filename.format] - formats = ', '.join(formats) - raise argparse.ArgumentError(None, "This file extension correspond to different file formats: '%s'. You have to specify it using -F." % formats) + formats = ", ".join(formats) + raise argparse.ArgumentError( + None, + "This file extension correspond to different file formats: '%s'. You have to specify it using -F." + % formats, + ) args.format = filename.format[0] + "image" if not is_format_supported(args.format): - raise argparse.ArgumentError(None, "Format '%s' is unknown. Use -l to list all available formats." % args.format) + raise argparse.ArgumentError( + None, + "Format '%s' is unknown. Use -l to list all available formats." + % args.format, + ) except argparse.ArgumentError as e: logger.error(e.message) logger.debug("Backtrace", exc_info=True) @@ -368,7 +455,9 @@ def main(): succeeded = convert_all(args) if not succeeded: - print("Conversion or part of it failed. You can try with --debug to have more output information.") + print( + "Conversion or part of it failed. You can try with --debug to have more output information." + ) return EXIT_FAILURE return EXIT_SUCCESS diff --git a/src/fabio/app/densify.py b/src/fabio/app/densify.py index b6368c9b..dc4b3323 100644 --- a/src/fabio/app/densify.py +++ b/src/fabio/app/densify.py @@ -29,22 +29,18 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. -"""Convert a sparse fileformat (Generated by sparsify-Bragg from pyFAI) to a dense +"""Convert a sparse fileformat (Generated by sparsify-Bragg from pyFAI) to a dense stack of frames in Eiger, Lima ... images. """ -__author__ = "Jerome Kieffer" +__author__ = "Jéröme Kieffer" __copyright__ = "European Synchrotron Radiation Facility, Grenoble, France" __licence__ = "MIT" -__date__ = "02/09/2024" +__date__ = "27/10/2025" __status__ = "production" -FOOTER = """ -""" import logging -logging.basicConfig() -logger = logging.getLogger("densify") import sys import argparse import os @@ -60,10 +56,17 @@ from ..nexus import Nexus, h5py try: - import hdf5plugin + import hdf5plugin # noqa except ImportError: pass + +logging.basicConfig() +logger = logging.getLogger("densify") + + +FOOTER = """ +""" EXIT_SUCCESS = 0 EXIT_FAILURE = 1 EXIT_ARGUMENT_FAILURE = 2 @@ -76,58 +79,104 @@ def parse_args(): contains a failure, 2 means there was an error in the arguments""" - parser = argparse.ArgumentParser(prog="densify", - description=__doc__, - epilog=epilog) - parser.add_argument("IMAGE", nargs="*", - help="File with input images") - parser.add_argument("-V", "--version", action='version', version=fabio_version, - help="output version and exit") - parser.add_argument("-v", "--verbose", action='store_true', dest="verbose", default=False, - help="show information for each conversions") - parser.add_argument("--debug", action='store_true', dest="debug", default=False, - help="show debug information") + parser = argparse.ArgumentParser(prog="densify", description=__doc__, epilog=epilog) + parser.add_argument("IMAGE", nargs="*", help="File with input images") + parser.add_argument( + "-V", + "--version", + action="version", + version=fabio_version, + help="output version and exit", + ) + parser.add_argument( + "-v", + "--verbose", + action="store_true", + dest="verbose", + default=False, + help="show information for each conversions", + ) + parser.add_argument( + "--debug", + action="store_true", + dest="debug", + default=False, + help="show debug information", + ) group = parser.add_argument_group("main arguments") - group.add_argument("-l", "--list", action="store_true", dest="list", default=None, - help="show the list of available output formats and exit") - group.add_argument("-o", "--output", default=None, type=str, - help="output filename, by default {basename}_densify.h5") - group.add_argument("-O", "--output-format", dest="format", default='lima', type=str, - help="output format among 'lima', 'eiger' ...") - group.add_argument("-D", "--dummy", type=int, default=None, - help="Set masked values to this dummy value") + group.add_argument( + "-l", + "--list", + action="store_true", + dest="list", + default=None, + help="show the list of available output formats and exit", + ) + group.add_argument( + "-o", + "--output", + default=None, + type=str, + help="output filename, by default {basename}_densify.h5", + ) + group.add_argument( + "-O", + "--output-format", + dest="format", + default="lima", + type=str, + help="output format among 'lima', 'eiger' ...", + ) + group.add_argument( + "-D", + "--dummy", + type=int, + default=None, + help="Set masked values to this dummy value", + ) group = parser.add_argument_group("optional behaviour arguments") -# group.add_argument("-f", "--force", dest="force", action="store_true", default=False, -# help="if an existing destination file cannot be" + -# " opened, remove it and try again (this option" + -# " is ignored when the -n option is also used)") -# group.add_argument("-n", "--no-clobber", dest="no_clobber", action="store_true", default=False, -# help="do not overwrite an existing file (this option" + -# " is ignored when the -i option is also used)") -# group.add_argument("--remove-destination", dest="remove_destination", action="store_true", default=False, -# help="remove each existing destination file before" + -# " attempting to open it (contrast with --force)") -# group.add_argument("-u", "--update", dest="update", action="store_true", default=False, -# help="copy only when the SOURCE file is newer" + -# " than the destination file or when the" + -# " destination file is missing") -# group.add_argument("-i", "--interactive", dest="interactive", action="store_true", default=False, -# help="prompt before overwrite (overrides a previous -n" + -# " option)") - group.add_argument("--dry-run", dest="dry_run", action="store_true", default=False, - help="do everything except modifying the file system") - group.add_argument("-N", "--noise", type=float, dest="noisy", default=1.0, - help="Noise scaling factor, from 0 to 1, set to 0 to disable the noise reconstruction") -# group = parser.add_argument_group("Image preprocessing (Important: applied in this order!)") -# group.add_argument("--rotation", type=int, default=180, -# help="Rotate the initial image by this value in degrees. Must be a multiple of 90°. By default 180 deg (flip_up with origin=lower and flip_lr because the image is seen from the sample).") -# group.add_argument("--transpose", default=False, action="store_true", -# help="Flip the x/y axis") -# group.add_argument("--flip-ud", dest="flip_ud", default=False, action="store_true", -# help="Flip the image upside-down") -# group.add_argument("--flip-lr", dest="flip_lr", default=False, action="store_true", -# help="Flip the image left-right") + # group.add_argument("-f", "--force", dest="force", action="store_true", default=False, + # help="if an existing destination file cannot be" + + # " opened, remove it and try again (this option" + + # " is ignored when the -n option is also used)") + # group.add_argument("-n", "--no-clobber", dest="no_clobber", action="store_true", default=False, + # help="do not overwrite an existing file (this option" + + # " is ignored when the -i option is also used)") + # group.add_argument("--remove-destination", dest="remove_destination", action="store_true", default=False, + # help="remove each existing destination file before" + + # " attempting to open it (contrast with --force)") + # group.add_argument("-u", "--update", dest="update", action="store_true", default=False, + # help="copy only when the SOURCE file is newer" + + # " than the destination file or when the" + + # " destination file is missing") + # group.add_argument("-i", "--interactive", dest="interactive", action="store_true", default=False, + # help="prompt before overwrite (overrides a previous -n" + + # " option)") + group.add_argument( + "--dry-run", + dest="dry_run", + action="store_true", + default=False, + help="do everything except modifying the file system", + ) + group.add_argument( + "-N", + "--noise", + type=float, + dest="noisy", + default=1.0, + help="Noise scaling factor, from 0 to 1, set to 0 to disable the noise reconstruction", + ) + # group = parser.add_argument_group("Image preprocessing (Important: applied in this order!)") + # group.add_argument("--rotation", type=int, default=180, + # help="Rotate the initial image by this value in degrees. Must be a multiple of 90°. By default 180 deg (flip_up with origin=lower and flip_lr because the image is seen from the sample).") + # group.add_argument("--transpose", default=False, action="store_true", + # help="Flip the x/y axis") + # group.add_argument("--flip-ud", dest="flip_ud", default=False, action="store_true", + # help="Flip the image upside-down") + # group.add_argument("--flip-lr", dest="flip_lr", default=False, action="store_true", + # help="Flip the image left-right") try: args = parser.parse_args() @@ -204,8 +253,12 @@ def save_master(outfile, sparsefile): detector["pixel_mask_applied"] = numpy.int32(1) detector["x_pixel_size"] = numpy.float32(ai.detector.pixel2) detector["y_pixel_size"] = numpy.float32(ai.detector.pixel2) - nxs.h5["/entry/instrument/detector/detectorSpecific/pixel_mask"] = d["mask"] - nxs.h5["/entry/instrument/detector/detectorSpecific/nimages"] = numpy.uint32(d["nframes"]) + nxs.h5["/entry/instrument/detector/detectorSpecific/pixel_mask"] = d[ + "mask" + ] + nxs.h5["/entry/instrument/detector/detectorSpecific/nimages"] = ( + numpy.uint32(d["nframes"]) + ) class Converter: @@ -237,12 +290,14 @@ def decompress_one(self, filename): pool = multiprocessing.pool.ThreadPool(multiprocessing.cpu_count()) self.pb.update(1, "Populate thread pool") - future_frames = {idx: pool.apply_async(sparse._generate_data, (idx,)) - for idx in range(sparse.nframes)} + future_frames = { + idx: pool.apply_async(sparse._generate_data, (idx,)) + for idx in range(sparse.nframes) + } pool.close() for idx in range(sparse.nframes): self.pb.update(idx, f"Decompress frame #{idx:04d}") - future_frame = future_frames.pop(idx) + future_frame = future_frames.pop(idx) dest.set_data(future_frame.get(), idx) pool.join() @@ -255,27 +310,30 @@ def decompress_one(self, filename): elif self.args.format.startswith("eiger"): output = os.path.splitext(filename)[0] + "_000001.h5" else: - base = os.path.basename(os.path.splitext(filename)[0]) - output = output.replace("{basename}", base) + base = os.path.basename(os.path.splitext(filename)[0]) + output = output.replace("{basename}", base) self.pb.update(self.pb.max_value, f"Save {output}") dest.save(output) - # link peaks to destination files + # link peaks to destination files if sparse.peaks: - relpath = os.path.relpath(os.path.abspath(filename), - os.path.dirname(os.path.abspath(output))) + relpath = os.path.relpath( + os.path.abspath(filename), os.path.dirname(os.path.abspath(output)) + ) with h5py.File(output, "a") as h: - key = posixpath.join(h.attrs['default'], posixpath.split(sparse.peaks)[-1]) + key = posixpath.join( + h.attrs["default"], posixpath.split(sparse.peaks)[-1] + ) h[key] = h5py.ExternalLink(relpath, sparse.peaks) - + if self.args.format.startswith("eiger"): save_master(output, filename) t3 = time.perf_counter() self.pb.clear() print(f"Densify of {filename} --> {output} took:") - print(f"Read input: {t1-t0:.3f}s") - print(f"Decompress: {t2-t1:.3f}s") - print(f"Write outp: {t3-t2:.3f}s") + print(f"Read input: {t1 - t0:.3f}s") + print(f"Decompress: {t2 - t1:.3f}s") + print(f"Write outp: {t3 - t2:.3f}s") def decompress(self): "Decompress all input files" @@ -291,7 +349,7 @@ def main(): c = Converter(args) c.decompress() except Exception as err: - logger.error("%s: %s", err.__class__.__name__,str(err)) + logger.error("%s: %s", err.__class__.__name__, str(err)) logger.error("Backtrace", exc_info=True) return EXIT_FAILURE else: diff --git a/src/fabio/app/eiger2cbf.py b/src/fabio/app/eiger2cbf.py index 78e591fc..5d50720c 100644 --- a/src/fabio/app/eiger2cbf.py +++ b/src/fabio/app/eiger2cbf.py @@ -34,35 +34,34 @@ to CBF and mimic the header from Dectris Pilatus. """ -__author__ = "Jerome Kieffer" +__author__ = "Jérôme Kieffer" __copyright__ = "European Synchrotron Radiation Facility, Grenoble, France" __licence__ = "MIT" -__date__ = "07/10/2024" +__date__ = "27/10/2025" __status__ = "production" import logging -logging.basicConfig() -logger = logging.getLogger("eiger2cbf") import codecs import sys import os - from ..utils.cli import expand_args from ..openimage import openimage as fabio_open from .. import cbfimage, limaimage, eigerimage, version as fabio_version import numpy import argparse + +logging.basicConfig() +logger = logging.getLogger("eiger2cbf") try: - import hdf5plugin + import hdf5plugin # noqa except ImportError: pass try: import numexpr -except: +except ImportError: logger.error("Numexpr is needed to interpret formula ...") -logger = logging.getLogger("eiger2cbf") EXIT_SUCCESS = 0 EXIT_FAILURE = 1 EXIT_ARGUMENT_FAILURE = 2 @@ -114,26 +113,27 @@ def __init__(self, title, max_value, bar_width): encoding = sys.stdout.encoding if encoding is None: # We uses the safer aproch: a valid ASCII character. - self.progress_char = '#' + self.progress_char = "#" else: try: import datetime + if str(datetime.datetime.now())[5:10] == "02-14": - self.progress_char = u'\u2665' + self.progress_char = "\u2665" else: - self.progress_char = u'\u25A0' + self.progress_char = "\u25a0" _byte = codecs.encode(self.progress_char, encoding) except (ValueError, TypeError, LookupError): # In case the char is not supported by the encoding, # or if the encoding does not exists - self.progress_char = '#' + self.progress_char = "#" def clear(self): """ Remove the progress bar from the display and move the cursor at the beginning of the line using carriage return. """ - sys.stdout.write('\r' + " " * self.last_size + "\r") + sys.stdout.write("\r" + " " * self.last_size + "\r") sys.stdout.flush() def display(self): @@ -171,7 +171,13 @@ def update(self, value, message="", max_value=None): bar_position = self.bar_width # line to display - line = '\r%15s [%s%s] % 3d%% %s' % (self.title, self.progress_char * bar_position, ' ' * (self.bar_width - bar_position), percent, message) + line = "\r%15s [%s%s] % 3d%% %s" % ( + self.title, + self.progress_char * bar_position, + " " * (self.bar_width - bar_position), + percent, + message, + ) # trailing to mask the previous message line_size = len(line) @@ -187,30 +193,30 @@ def update(self, value, message="", max_value=None): def select_pilatus_detecor(shape): """CrysalisPro only accepts some detector shapes as CBF inputs. Those shaped correspond to the one of the dectris detector Pilatus - - This function takes the input shape and return the smallest Dectris shape which is large enough. + + This function takes the input shape and return the smallest Dectris shape which is large enough. """ assert len(shape) >= 2 valid_detectors = { # (514, 1030): 'eiger_500k', - # (1065, 1030): 'eiger_1m', - # (2167, 2070): 'eiger_4m', - # (3269, 3110): 'eiger_9m', - # (4371, 4150): 'eiger_16m', - # (512, 1028): 'eiger2cdte500k', - # (1062, 1028): 'eiger2cdte1m', - # (2162, 2068): 'eiger2cdte4m', - # (3262, 3108): 'eiger2cdte9m', - # (4362, 4148): 'eiger2cdte16m', - # (1, 1280): 'mythen1280', - (195, 487): 'pilatus_100k', - (407, 487): 'pilatus_200k', - (619, 487): 'pilatus_300k', - (195, 1475): 'pilatus_300kw', - (1043, 981): 'pilatus_1m', - (1679, 1475): 'pilatus_2m', - (2527, 2463): 'pilatus_6m', - # (195, 4439): 'pilatus_900kw' - } + # (1065, 1030): 'eiger_1m', + # (2167, 2070): 'eiger_4m', + # (3269, 3110): 'eiger_9m', + # (4371, 4150): 'eiger_16m', + # (512, 1028): 'eiger2cdte500k', + # (1062, 1028): 'eiger2cdte1m', + # (2162, 2068): 'eiger2cdte4m', + # (3262, 3108): 'eiger2cdte9m', + # (4362, 4148): 'eiger2cdte16m', + # (1, 1280): 'mythen1280', + (195, 487): "pilatus_100k", + (407, 487): "pilatus_200k", + (619, 487): "pilatus_300k", + (195, 1475): "pilatus_300kw", + (1043, 981): "pilatus_1m", + (1679, 1475): "pilatus_2m", + (2527, 2463): "pilatus_6m", + # (195, 4439): 'pilatus_900kw' + } best = None for k in valid_detectors: if k[0] >= shape[-2] and k[1] >= shape[-1]: @@ -242,7 +248,9 @@ def convert_one(input_filename, options, start_at=0): print("Converting file '%s'" % (input_filename)) if not input_exists: - logger.error("Input file '%s' do not exists. Conversion skipped.", input_filename) + logger.error( + "Input file '%s' do not exists. Conversion skipped.", input_filename + ) return -1 try: @@ -251,7 +259,11 @@ def convert_one(input_filename, options, start_at=0): except KeyboardInterrupt: raise except Exception as e: - logger.error("Loading input file '%s' failed cause: \"%s\". Conversion skipped.", input_filename, e.message) + logger.error( + "Loading input file '%s' failed cause: \"%s\". Conversion skipped.", + input_filename, + e.message, + ) logger.debug("Backtrace", exc_info=True) return -1 @@ -272,45 +284,73 @@ def convert_one(input_filename, options, start_at=0): if data_grp: nxdetector = data_grp.parent try: - detector = "%s, S/N %s" % (nxdetector["detector_information/model"][()], - nxdetector["detector_information/name"][()]) + detector = "%s, S/N %s" % ( + nxdetector["detector_information/model"][()], + nxdetector["detector_information/name"][()], + ) pilatus_headers["Detector"] = detector except Exception as e: - logger.warning("Error in searching for detector definition (%s): %s", type(e), e) + logger.warning( + "Error in searching for detector definition (%s): %s", + type(e), + e, + ) try: - pilatus_headers["Pixel_size"] = (nxdetector["detector_information/pixel_size/xsize"][()], - nxdetector["detector_information/pixel_size/ysize"][()]) + pilatus_headers["Pixel_size"] = ( + nxdetector["detector_information/pixel_size/xsize"][()], + nxdetector["detector_information/pixel_size/ysize"][()], + ) except Exception as e: - logger.warning("Error in searching for pixel size (%s): %s", type(e), e) + logger.warning( + "Error in searching for pixel size (%s): %s", type(e), e + ) try: t1 = nxdetector["acquisition/exposure_time"][()] t2 = nxdetector["acquisition/latency_time"][()] pilatus_headers["Exposure_time"] = t1 pilatus_headers["Exposure_period"] = t1 + t2 except Exception as e: - logger.warning("Error in searching for exposure time (%s): %s", type(e), e) + logger.warning( + "Error in searching for exposure time (%s): %s", + type(e), + e, + ) elif isinstance(source, eigerimage.EigerImage): entry_name = source.h5.attrs.get("default") if entry_name: entry = source.h5.get(entry_name) try: nxdetector = entry["instrument/detector"] - except: - logger.error("No detector definition in Eiger file, is this a master file ?") + except Exception: + logger.error( + "No detector definition in Eiger file, is this a master file ?" + ) else: - detector = "%s, S/N %s" % (nxdetector["description"][()].decode(), - nxdetector["detector_number"][()].decode()) + detector = "%s, S/N %s" % ( + nxdetector["description"][()].decode(), + nxdetector["detector_number"][()].decode(), + ) pilatus_headers["Detector"] = detector - pilatus_headers["Pixel_size"] = (nxdetector["x_pixel_size"][()], - nxdetector["y_pixel_size"][()]) + pilatus_headers["Pixel_size"] = ( + nxdetector["x_pixel_size"][()], + nxdetector["y_pixel_size"][()], + ) pilatus_headers["Exposure_time"] = nxdetector["count_time"][()] pilatus_headers["Exposure_period"] = nxdetector["frame_time"][()] - pilatus_headers["Wavelength"] = entry["instrument/beam/incident_wavelength"][()] - pilatus_headers["Detector_distance"] = nxdetector["detector_distance"][()] - pilatus_headers["Beam_xy"] = (nxdetector["beam_center_x"][()], - nxdetector["beam_center_y"][()]) - pilatus_headers["sensor"] = (nxdetector["sensor_material"][()].decode(), - nxdetector["sensor_thickness"][()]) + pilatus_headers["Wavelength"] = entry[ + "instrument/beam/incident_wavelength" + ][()] + pilatus_headers["Detector_distance"] = nxdetector["detector_distance"][ + () + ] + pilatus_headers["Beam_xy"] = ( + nxdetector["beam_center_x"][()], + nxdetector["beam_center_y"][()], + ) + pilatus_headers["sensor"] = ( + nxdetector["sensor_material"][()].decode(), + nxdetector["sensor_thickness"][()], + ) else: raise NotImplementedError("Unsupported format: %s" % source.__class__.__name__) @@ -388,7 +428,7 @@ def convert_one(input_filename, options, start_at=0): if options.flip_lr: input_data = numpy.fliplr(input_data) - data[:input_data.shape[0],:input_data.shape[1]] = input_data + data[: input_data.shape[0], : input_data.shape[1]] = input_data mask = numpy.where(input_data == numpy.iinfo(frame.data.dtype).max) data[mask] = options.dummy @@ -396,12 +436,14 @@ def convert_one(input_filename, options, start_at=0): if formula and destination: position = formula(idx) - delta = (formula(idx + 1) - position) + delta = formula(idx + 1) - position pilatus_headers["Start_angle"] = pilatus_headers[destination] = position - pilatus_headers["Angle_increment"] = pilatus_headers[destination + "_increment"] = delta + pilatus_headers["Angle_increment"] = pilatus_headers[ + destination + "_increment" + ] = delta converted.pilatus_headers = pilatus_headers - output_filename = options.output.format(index=((idx + options.offset))) + output_filename = options.output.format(index=(idx + options.offset)) os.makedirs(os.path.dirname(output_filename), exist_ok=True) try: logger.debug("Write '%s'", output_filename) @@ -410,7 +452,12 @@ def convert_one(input_filename, options, start_at=0): except KeyboardInterrupt: raise except Exception as e: - logger.error("Saving output file '%s' failed cause: \"%s: %s\". Conversion skipped.", output_filename, type(e), e) + logger.error( + "Saving output file '%s' failed cause: \"%s: %s\". Conversion skipped.", + output_filename, + type(e), + e, + ) logger.debug("Backtrace", exc_info=True) return -1 return source.nframes @@ -436,90 +483,177 @@ def convert_all(options): def main(): - epilog = """return codes: 0 means a success. 1 means the conversion contains a failure, 2 means there was an error in the arguments""" - parser = argparse.ArgumentParser(prog="eiger2cbf", - description=__doc__, - epilog=epilog) - parser.add_argument("IMAGE", nargs="*", - help="File with input images") - parser.add_argument("-V", "--version", action='version', version=fabio_version, - help="output version and exit") - parser.add_argument("-v", "--verbose", action='store_true', dest="verbose", default=False, - help="show information for each conversions") - parser.add_argument("--debug", action='store_true', dest="debug", default=False, - help="show debug information") + parser = argparse.ArgumentParser( + prog="eiger2cbf", description=__doc__, epilog=epilog + ) + parser.add_argument("IMAGE", nargs="*", help="File with input images") + parser.add_argument( + "-V", + "--version", + action="version", + version=fabio_version, + help="output version and exit", + ) + parser.add_argument( + "-v", + "--verbose", + action="store_true", + dest="verbose", + default=False, + help="show information for each conversions", + ) + parser.add_argument( + "--debug", + action="store_true", + dest="debug", + default=False, + help="show debug information", + ) group = parser.add_argument_group("main arguments") -# group.add_argument("-l", "--list", action="store_true", dest="list", default=None, -# help="show the list of available formats and exit") - group.add_argument("-o", "--output", default='eiger2cbf/frame_{index:04d}.cbf', type=str, - help="output directory and filename template: eiger2cbf/frame_{index:04d}.cbf") - group.add_argument("-m", "--mask", type=str, default=None, - help="Read masked pixel from this file") - group.add_argument("-O", "--offset", type=int, default=0, - help="index offset, CrysalisPro likes indexes to start at 1, Python starts at 0") - group.add_argument("-D", "--dummy", type=int, default=-1, - help="Set masked values to this dummy value") - group.add_argument("--pilatus", action="store_true", default=False, - help="Select an image shape similar to Pilatus detectors for compatibiliy with Crysalis") + # group.add_argument("-l", "--list", action="store_true", dest="list", default=None, + # help="show the list of available formats and exit") + group.add_argument( + "-o", + "--output", + default="eiger2cbf/frame_{index:04d}.cbf", + type=str, + help="output directory and filename template: eiger2cbf/frame_{index:04d}.cbf", + ) + group.add_argument( + "-m", "--mask", type=str, default=None, help="Read masked pixel from this file" + ) + group.add_argument( + "-O", + "--offset", + type=int, + default=0, + help="index offset, CrysalisPro likes indexes to start at 1, Python starts at 0", + ) + group.add_argument( + "-D", + "--dummy", + type=int, + default=-1, + help="Set masked values to this dummy value", + ) + group.add_argument( + "--pilatus", + action="store_true", + default=False, + help="Select an image shape similar to Pilatus detectors for compatibiliy with Crysalis", + ) group = parser.add_argument_group("optional behaviour arguments") -# group.add_argument("-f", "--force", dest="force", action="store_true", default=False, -# help="if an existing destination file cannot be" + -# " opened, remove it and try again (this option" + -# " is ignored when the -n option is also used)") -# group.add_argument("-n", "--no-clobber", dest="no_clobber", action="store_true", default=False, -# help="do not overwrite an existing file (this option" + -# " is ignored when the -i option is also used)") -# group.add_argument("--remove-destination", dest="remove_destination", action="store_true", default=False, -# help="remove each existing destination file before" + -# " attempting to open it (contrast with --force)") -# group.add_argument("-u", "--update", dest="update", action="store_true", default=False, -# help="copy only when the SOURCE file is newer" + -# " than the destination file or when the" + -# " destination file is missing") -# group.add_argument("-i", "--interactive", dest="interactive", action="store_true", default=False, -# help="prompt before overwrite (overrides a previous -n" + -# " option)") - group.add_argument("--dry-run", dest="dry_run", action="store_true", default=False, - help="do everything except modifying the file system") + # group.add_argument("-f", "--force", dest="force", action="store_true", default=False, + # help="if an existing destination file cannot be" + + # " opened, remove it and try again (this option" + + # " is ignored when the -n option is also used)") + # group.add_argument("-n", "--no-clobber", dest="no_clobber", action="store_true", default=False, + # help="do not overwrite an existing file (this option" + + # " is ignored when the -i option is also used)") + # group.add_argument("--remove-destination", dest="remove_destination", action="store_true", default=False, + # help="remove each existing destination file before" + + # " attempting to open it (contrast with --force)") + # group.add_argument("-u", "--update", dest="update", action="store_true", default=False, + # help="copy only when the SOURCE file is newer" + + # " than the destination file or when the" + + # " destination file is missing") + # group.add_argument("-i", "--interactive", dest="interactive", action="store_true", default=False, + # help="prompt before overwrite (overrides a previous -n" + + # " option)") + group.add_argument( + "--dry-run", + dest="dry_run", + action="store_true", + default=False, + help="do everything except modifying the file system", + ) group = parser.add_argument_group("Experimental setup options") - group.add_argument("-e", "--energy", type=float, default=None, - help="Energy of the incident beam in keV") - group.add_argument("-w", "--wavelength", type=float, default=None, - help="Wavelength of the incident beam in Å") - group.add_argument("-d", "--distance", type=float, default=None, - help="Detector distance in meters") - group.add_argument("-b", "--beam", nargs=2, type=float, default=None, - help="Direct beam in pixels x, y") + group.add_argument( + "-e", + "--energy", + type=float, + default=None, + help="Energy of the incident beam in keV", + ) + group.add_argument( + "-w", + "--wavelength", + type=float, + default=None, + help="Wavelength of the incident beam in Å", + ) + group.add_argument( + "-d", "--distance", type=float, default=None, help="Detector distance in meters" + ) + group.add_argument( + "-b", + "--beam", + nargs=2, + type=float, + default=None, + help="Direct beam in pixels x, y", + ) group = parser.add_argument_group("Goniometer setup") -# group.add_argument("--axis", type=str, default=None, -# help="Goniometer angle used for scanning: 'omega', 'phi' or 'chi'") - group.add_argument("--alpha", type=float, default=None, - help="Goniometer angle alpha value in deg.") - group.add_argument("--kappa", type=float, default=None, - help="Goniometer angle kappa value in deg.") - group.add_argument("--chi", type=str, default=None, - help="Goniometer angle chi value in deg. or formula f(index)") - group.add_argument("--phi", type=str, default=None, - help="Goniometer angle phi value in deg. or formula f(index)") - group.add_argument("--omega", type=str, default=None, - help="Goniometer angle omega value in deg. or formula f(index) like '-180+index*0.1") - - group = parser.add_argument_group("Image preprocessing (Important: applied in this order!)") - group.add_argument("--rotation", type=int, default=0, - help="Rotate the initial image by this value in degrees. Must be a multiple of 90°.") - group.add_argument("--transpose", default=False, action="store_true", - help="Flip the x/y axis") - group.add_argument("--flip-ud", dest="flip_ud", default=False, action="store_true", - help="Flip the image upside-down") - group.add_argument("--flip-lr", dest="flip_lr", default=False, action="store_true", - help="Flip the image left-right") + # group.add_argument("--axis", type=str, default=None, + # help="Goniometer angle used for scanning: 'omega', 'phi' or 'chi'") + group.add_argument( + "--alpha", type=float, default=None, help="Goniometer angle alpha value in deg." + ) + group.add_argument( + "--kappa", type=float, default=None, help="Goniometer angle kappa value in deg." + ) + group.add_argument( + "--chi", + type=str, + default=None, + help="Goniometer angle chi value in deg. or formula f(index)", + ) + group.add_argument( + "--phi", + type=str, + default=None, + help="Goniometer angle phi value in deg. or formula f(index)", + ) + group.add_argument( + "--omega", + type=str, + default=None, + help="Goniometer angle omega value in deg. or formula f(index) like '-180+index*0.1", + ) + + group = parser.add_argument_group( + "Image preprocessing (Important: applied in this order!)" + ) + group.add_argument( + "--rotation", + type=int, + default=0, + help="Rotate the initial image by this value in degrees. Must be a multiple of 90°.", + ) + group.add_argument( + "--transpose", default=False, action="store_true", help="Flip the x/y axis" + ) + group.add_argument( + "--flip-ud", + dest="flip_ud", + default=False, + action="store_true", + help="Flip the image upside-down", + ) + group.add_argument( + "--flip-lr", + dest="flip_lr", + default=False, + action="store_true", + help="Flip the image left-right", + ) try: args = parser.parse_args() @@ -540,7 +674,9 @@ def main(): succeeded = convert_all(args) if not succeeded: - print("Conversion or part of it failed. You can try with --debug to have more output information.") + print( + "Conversion or part of it failed. You can try with --debug to have more output information." + ) return EXIT_FAILURE return EXIT_SUCCESS diff --git a/src/fabio/app/eiger2crysalis.py b/src/fabio/app/eiger2crysalis.py index 8ea4a96b..9febe1d4 100644 --- a/src/fabio/app/eiger2crysalis.py +++ b/src/fabio/app/eiger2crysalis.py @@ -31,25 +31,18 @@ """Portable image converter based on FabIO library to export Eiger frames (including the one from LImA) -to a set of esperanto frames which can be imported +to a set of esperanto frames which can be imported into CrysalisPro. """ -__author__ = "Jerome Kieffer" +__author__ = "Jérôme Kieffer" __copyright__ = "European Synchrotron Radiation Facility, Grenoble, France" __licence__ = "MIT" -__date__ = "09/10/2024" +__date__ = "27/10/2025" __status__ = "production" -FOOTER = """To import your files as a project: -* Start CrysalisPro and open any project -* press "F5" to open the console -* Type `esperanto createrunlist` and select your first and last frame -""" import logging -logging.basicConfig() -logger = logging.getLogger("eiger2crysalis") import sys import os import shutil @@ -60,16 +53,26 @@ from ..utils.cli import ProgressBar, expand_args import numpy import argparse + try: - import hdf5plugin + import hdf5plugin # noqa except ImportError: pass +logging.basicConfig() +logger = logging.getLogger("eiger2crysalis") + try: import numexpr except ImportError: logger.error("Numexpr is needed to interpret formula ...") +FOOTER = """To import your files as a project: +* Start CrysalisPro and open any project +* press "F5" to open the console +* Type `esperanto createrunlist` and select your first and last frame +""" + EXIT_SUCCESS = 0 EXIT_FAILURE = 1 EXIT_ARGUMENT_FAILURE = 2 @@ -92,7 +95,6 @@ def as_str(smth): class Converter: - def __init__(self, options): self.options = options self.mask = None @@ -106,9 +108,11 @@ def __init__(self, options): else: self.dirname = os.path.dirname(os.path.abspath(self.options.output)) if "{prefix}" in self.options.output: - self.prefix = os.path.basename(prefix)+"_1" + self.prefix = os.path.basename(prefix) + "_1" else: - self.prefix = os.path.basename(os.path.abspath(self.options.output)).split("{")[0] + self.prefix = os.path.basename(os.path.abspath(self.options.output)).split( + "{" + )[0] self.headers = None self.processed_frames = None self.scan_type = None @@ -128,7 +132,7 @@ def geometry_transform(self, image): def new_beam_center(self, x, y, shape): """Calculate the position of the beam after all transformations: - + :param x, y: position in the initial image :shape: shape of the input image :return: x, y, coordinated of the new beam center within the esperanto frame. @@ -155,7 +159,9 @@ def finish(self): if not self.succeeded: if not self.options.verbose: self.progress.clear() - print("Conversion or part of it failed. You can try with --debug to have more output information.") + print( + "Conversion or part of it failed. You can try with --debug to have more output information." + ) return EXIT_FAILURE else: if not self.options.verbose: @@ -165,66 +171,66 @@ def finish(self): def common_headers(self): headers = { - # SPECIAL_CCD_1 - "delectronsperadu": 1, - "ldarkcorrectionswitch": 0, - "lfloodfieldcorrectionswitch/mode": 0, - "dsystemdcdb2gain": 1.0, - "ddarksignal": 0, - "dreadnoiserms": 0, - # SPECIAL_CCD_2 - "ioverflowflag":0 , - "ioverflowafterremeasureflag":0, - "inumofdarkcurrentimages":0, - "inumofmultipleimages":0, - "loverflowthreshold": 1000000, - # SPECIAL_CCD_3 - # SPECIAL_CCD_4 - # SPECIAL_CCD_5 - # TIME - # "dexposuretimeinsec": 0.2, - "doverflowtimeinsec": 0 , - "doverflowfilter":0, - # MONITOR - # PIXELSIZE - # "drealpixelsizex": 0.075, - # "drealpixelsizey": 0.075, - "dsithicknessmmforpixeldetector": 1, - # TIMESTAMP - "timestampstring": get_isotime(), - # GRIDPATTERN - # STARTANGLESINDEG - # "dom_s":-180 + i, - # "dth_s":0, - # "dka_s":0, - # "dph_s":0, - # ENDANGLESINDEG - # "dom_e":-179 + i, - # "dth_e": 0, - # "dka_e": 0, - # "dph_e": 0, - # GONIOMODEL_1 - "dbeam2indeg":0, - "dbeam3indeg":0, - "detectorrotindeg_x":0, - "detectorrotindeg_y":0, - "detectorrotindeg_z":0, - # "dxorigininpix": img.data.shape[1] - (img.data.shape[1] - data.shape[1]) / 2 - center_x, - # "dyorigininpix": img.data.shape[0] - center_y, - "dalphaindeg": 50, - "dbetaindeg": 0, -# "ddistanceinmm": 117, - # GONIOMODEL_2 - # WAVELENGTH - # "dalpha1": wl, - # "dalpha2": wl, - # "dalpha12": wl, - # "dbeta1": wl, - # MONOCHROMATOR - "ddvalue-prepolfac": 0.98, - "orientation-type": "SYNCHROTRON", - # ABSTORUN - } + # SPECIAL_CCD_1 + "delectronsperadu": 1, + "ldarkcorrectionswitch": 0, + "lfloodfieldcorrectionswitch/mode": 0, + "dsystemdcdb2gain": 1.0, + "ddarksignal": 0, + "dreadnoiserms": 0, + # SPECIAL_CCD_2 + "ioverflowflag": 0, + "ioverflowafterremeasureflag": 0, + "inumofdarkcurrentimages": 0, + "inumofmultipleimages": 0, + "loverflowthreshold": 1000000, + # SPECIAL_CCD_3 + # SPECIAL_CCD_4 + # SPECIAL_CCD_5 + # TIME + # "dexposuretimeinsec": 0.2, + "doverflowtimeinsec": 0, + "doverflowfilter": 0, + # MONITOR + # PIXELSIZE + # "drealpixelsizex": 0.075, + # "drealpixelsizey": 0.075, + "dsithicknessmmforpixeldetector": 1, + # TIMESTAMP + "timestampstring": get_isotime(), + # GRIDPATTERN + # STARTANGLESINDEG + # "dom_s":-180 + i, + # "dth_s":0, + # "dka_s":0, + # "dph_s":0, + # ENDANGLESINDEG + # "dom_e":-179 + i, + # "dth_e": 0, + # "dka_e": 0, + # "dph_e": 0, + # GONIOMODEL_1 + "dbeam2indeg": 0, + "dbeam3indeg": 0, + "detectorrotindeg_x": 0, + "detectorrotindeg_y": 0, + "detectorrotindeg_z": 0, + # "dxorigininpix": img.data.shape[1] - (img.data.shape[1] - data.shape[1]) / 2 - center_x, + # "dyorigininpix": img.data.shape[0] - center_y, + "dalphaindeg": 50, + "dbetaindeg": 0, + # "ddistanceinmm": 117, + # GONIOMODEL_2 + # WAVELENGTH + # "dalpha1": wl, + # "dalpha2": wl, + # "dalpha12": wl, + # "dbeta1": wl, + # MONOCHROMATOR + "ddvalue-prepolfac": 0.98, + "orientation-type": "SYNCHROTRON", + # ABSTORUN + } with fabio_open(self.options.images[0]) as source: shape = source.data.shape @@ -243,32 +249,69 @@ def common_headers(self): if data_grp: nxdetector = data_grp.parent try: - headers["drealpixelsizex"] = nxdetector["detector_information/pixel_size/xsize"][()] * 1e3 - headers["drealpixelsizey"] = nxdetector["detector_information/pixel_size/ysize"][()] * 1e3 + headers["drealpixelsizex"] = ( + nxdetector[ + "detector_information/pixel_size/xsize" + ][()] + * 1e3 + ) + headers["drealpixelsizey"] = ( + nxdetector[ + "detector_information/pixel_size/ysize" + ][()] + * 1e3 + ) except Exception as e: - logger.warning("Error in searching for pixel size (%s): %s", type(e), e) + logger.warning( + "Error in searching for pixel size (%s): %s", + type(e), + e, + ) try: t1 = nxdetector["acquisition/exposure_time"][()] headers["dexposuretimeinsec"] = t1 except Exception as e: - logger.warning("Error in searching for exposure time (%s): %s", type(e), e) + logger.warning( + "Error in searching for exposure time (%s): %s", + type(e), + e, + ) elif isinstance(source, sparseimage.SparseImage): entry_name = source.h5.attrs.get("default") if entry_name: entry = source.h5.get(entry_name) if entry: - instruments = [i for i in entry.values() if as_str(i.attrs.get("NX_class", "")) == "NXinstrument"] + instruments = [ + i + for i in entry.values() + if as_str(i.attrs.get("NX_class", "")) == "NXinstrument" + ] if instruments: instrument = instruments[0] - detectors = [i for i in instrument.values() if as_str(i.attrs.get("NX_class", "")) == "NXdetector"] + detectors = [ + i + for i in instrument.values() + if as_str(i.attrs.get("NX_class", "")) == "NXdetector" + ] if detectors: detector = detectors[0] - headers["drealpixelsizex"] = detector["x_pixel_size"][()] * 1e3 - headers["drealpixelsizey"] = detector["y_pixel_size"][()] * 1e3 + headers["drealpixelsizex"] = ( + detector["x_pixel_size"][()] * 1e3 + ) + headers["drealpixelsizey"] = ( + detector["y_pixel_size"][()] * 1e3 + ) headers["dxorigininpix"] = detector["beam_center_x"][()] headers["dyorigininpix"] = detector["beam_center_y"][()] - headers["ddistanceinmm"] = detector["distance"][()] * 1e3 - monchromators = [i for i in instrument.values() if as_str(i.attrs.get("NX_class", "")) == "NXmonochromator"] + headers["ddistanceinmm"] = ( + detector["distance"][()] * 1e3 + ) + monchromators = [ + i + for i in instrument.values() + if as_str(i.attrs.get("NX_class", "")) + == "NXmonochromator" + ] if monchromators: wavelength = monchromators[0]["wavelength"][()] self.mask = numpy.logical_not(numpy.isfinite(source.mask)) @@ -281,8 +324,10 @@ def common_headers(self): entry = source.h5.get(entry_name) try: nxdetector = entry["instrument/detector"] - except: - logger.error("No detector definition in Eiger file, is this a master file ?") + except Exception: + logger.error( + "No detector definition in Eiger file, is this a master file ?" + ) else: headers["drealpixelsizex"] = nxdetector["x_pixel_size"][()] * 1e3 headers["drealpixelsizey"] = nxdetector["y_pixel_size"][()] * 1e3 @@ -290,9 +335,11 @@ def common_headers(self): headers["dyorigininpix"] = nxdetector["beam_center_y"][()] headers["ddistanceinmm"] = nxdetector["detector_distance"][()] * 1e3 headers["dexposuretimeinsec"] = nxdetector["count_time"][()] - wavelength = entry["instrument/beam/incident_wavelength"][()] + wavelength = entry["instrument/beam/incident_wavelength"][()] else: - raise NotImplementedError("Unsupported format: %s" % source.__class__.__name__) + raise NotImplementedError( + "Unsupported format: %s" % source.__class__.__name__ + ) if self.mask is None: self.mask = numpy.zeros(shape, dtype=dtype) # Parse option for headers @@ -300,7 +347,9 @@ def common_headers(self): wavelength = CONST_hc / self.options.energy elif self.options.wavelength: wavelength = self.options.wavelength - headers["dalpha1"] = headers["dalpha2"] = headers["dalpha12"] = headers["dbeta1"] = wavelength + headers["dalpha1"] = headers["dalpha2"] = headers["dalpha12"] = headers[ + "dbeta1" + ] = wavelength if self.options.distance: headers["ddistanceinmm"] = self.options.distance if self.options.beam: @@ -339,13 +388,13 @@ def common_headers(self): value = numexpr.NumExpr(self.options.omega) self.scan_type = "omega" headers["dom_s"] = headers["dom_e"] = value - + return headers def convert_one(self, input_filename, start_at=0): """ Convert a single file using options - + :param str input_filename: The input filename :param object options: List of options provided from the command line :param start_at: index to start at for given file @@ -360,7 +409,9 @@ def convert_one(self, input_filename, start_at=0): print("Converting file '%s'" % (input_filename)) if not input_exists: - logger.error("Input file '%s' do not exists. Conversion skipped.", input_filename) + logger.error( + "Input file '%s' do not exists. Conversion skipped.", input_filename + ) return -1 try: @@ -369,7 +420,11 @@ def convert_one(self, input_filename, start_at=0): except KeyboardInterrupt: raise except Exception as e: - logger.error("Loading input file '%s' failed cause: \"%s\". Conversion skipped.", input_filename, e.message) + logger.error( + "Loading input file '%s' failed cause: \"%s\". Conversion skipped.", + input_filename, + e.message, + ) logger.debug("Backtrace", exc_info=True) return -1 @@ -379,8 +434,12 @@ def convert_one(self, input_filename, start_at=0): input_data = frame.data numpy.maximum(self.mask, input_data, out=self.mask) input_data = input_data.astype(numpy.int32) - input_data[input_data == numpy.iinfo(frame.data.dtype).max] = self.options.dummy - converted = esperantoimage.EsperantoImage(data=input_data) # This changes the shape + input_data[input_data == numpy.iinfo(frame.data.dtype).max] = ( + self.options.dummy + ) + converted = esperantoimage.EsperantoImage( + data=input_data + ) # This changes the shape converted.data = self.geometry_transform(converted.data) for k, v in self.headers.items(): if callable(v): @@ -396,9 +455,11 @@ def convert_one(self, input_filename, start_at=0): else: self.angle_ranges[k] = (min(v0, v1), max(v0, v1)) - output_filename = self.options.output.format(index=((idx + self.options.offset)), - prefix=self.prefix, - dirname=self.dirname) + output_filename = self.options.output.format( + index=(idx + self.options.offset), + prefix=self.prefix, + dirname=self.dirname, + ) os.makedirs(os.path.dirname(output_filename), exist_ok=True) try: logger.debug("Write '%s'", output_filename) @@ -407,7 +468,12 @@ def convert_one(self, input_filename, start_at=0): except KeyboardInterrupt: raise except Exception as e: - logger.error("Saving output file '%s' failed cause: \"%s: %s\". Conversion skipped.", output_filename, type(e), e) + logger.error( + "Saving output file '%s' failed cause: \"%s: %s\". Conversion skipped.", + output_filename, + type(e), + e, + ) logger.debug("Backtrace", exc_info=True) return -1 return source.nframes @@ -416,183 +482,327 @@ def treat_mask(self, full=False): ":param full: complete/slow mask analysis" if self.progress: self.progress.update(self.progress.max_value - 1, "Generate mask") - dummy_value = numpy.asarray(-numpy.ones(1), dtype=self.mask.dtype)[-1] #-1 in the given datatype - mask = (self.mask==dummy_value).astype(numpy.int8) + dummy_value = numpy.asarray(-numpy.ones(1), dtype=self.mask.dtype)[ + -1 + ] # -1 in the given datatype + mask = (self.mask == dummy_value).astype(numpy.int8) esperantoimage.EsperantoImage.DUMMY = 1 - new_mask = self.geometry_transform(esperantoimage.EsperantoImage(data=mask).data) - esperantoimage.EsperantoImage.DUMMY = -1 # restore the class ! + new_mask = self.geometry_transform( + esperantoimage.EsperantoImage(data=mask).data + ) + esperantoimage.EsperantoImage.DUMMY = -1 # restore the class ! if self.progress: - self.progress.update(self.progress.max_value - 1, f"Decompose mask full={full}") + self.progress.update( + self.progress.max_value - 1, f"Decompose mask full={full}" + ) xci = xcaliburimage.XcaliburImage(data=new_mask) ccd = xci.decompose(full) if self.progress: - self.progress.update(self.progress.max_value - 0.5, "Exporting mask as CCD/SET file") - dummy_filename = self.options.output.format(index=self.options.offset, - prefix=self.prefix, - dirname=self.dirname) + self.progress.update( + self.progress.max_value - 0.5, "Exporting mask as CCD/SET file" + ) + dummy_filename = self.options.output.format( + index=self.options.offset, prefix=self.prefix, dirname=self.dirname + ) dirname = os.path.dirname(dummy_filename) prefix = self.prefix.split("_")[0] numpy.save(os.path.join(dirname, prefix + "_mask.npy"), new_mask) ccd.save(os.path.join(dirname, prefix + ".ccd")) with open(os.path.join(dirname, prefix + ".set"), mode="wb") as maskfile: - maskfile.write(b'#XCALIBUR SYSTEM\r\n') - maskfile.write(b'#XCALIBUR SETUP FILE\r\n') - maskfile.write(b'#*******************************************************************************************************\r\n') - maskfile.write(b'# CHIP CHARACTERISTICS e_19_020609.ccd D A T E Wed-Sep-16-10-00-59-2009\r\n') - maskfile.write(b'# This program produces version 1.9\r\n') - maskfile.write(b'#******************************************************************************************************\r\n') - maskfile.write(b'#THIS FILE IS USER READABLE - BUT SHOULD NOT BE TOUCHED BY THE USER\r\n') - maskfile.write(b'#ANY CHANGES TO THIS FILE WILL RESULT IN LOSS OF WARRANTY!\r\n') - maskfile.write(b'#CHIP IDCODE producer type serial\r\n') + maskfile.write(b"#XCALIBUR SYSTEM\r\n") + maskfile.write(b"#XCALIBUR SETUP FILE\r\n") + maskfile.write( + b"#*******************************************************************************************************\r\n" + ) + maskfile.write( + b"# CHIP CHARACTERISTICS e_19_020609.ccd D A T E Wed-Sep-16-10-00-59-2009\r\n" + ) + maskfile.write(b"# This program produces version 1.9\r\n") + maskfile.write( + b"#******************************************************************************************************\r\n" + ) + maskfile.write( + b"#THIS FILE IS USER READABLE - BUT SHOULD NOT BE TOUCHED BY THE USER\r\n" + ) + maskfile.write( + b"#ANY CHANGES TO THIS FILE WILL RESULT IN LOSS OF WARRANTY!\r\n" + ) + maskfile.write(b"#CHIP IDCODE producer type serial\r\n") maskfile.write(b'CHIP IDCODE "n/a" "n/a" "n/a\r\n') - maskfile.write(b'#CHIP TAPER producer type serial\r\n') + maskfile.write(b"#CHIP TAPER producer type serial\r\n") maskfile.write(b'CHIP TAPER "" "" ""\r\n') - maskfile.write(b'#ALL COORDINATES GO FROM 0 TO N-1\r\n') - maskfile.write(b'#CHIP BADPOINT treatment options: IGNORE,REPLACE,AVERAGE\r\n') - maskfile.write(b'#CHIP BADPOINT x1x1 y1x1 treatment r1x1x1 r1y1x1 r2x1x1 r2y1x1\r\n') - maskfile.write(b'#CHIP BADPOINT 630 422 REPLACE 632 422 0 0\r\n') - maskfile.write(b'#CHIP BADRECTANGLE xl xr yb yt\r\n') + maskfile.write(b"#ALL COORDINATES GO FROM 0 TO N-1\r\n") + maskfile.write( + b"#CHIP BADPOINT treatment options: IGNORE,REPLACE,AVERAGE\r\n" + ) + maskfile.write( + b"#CHIP BADPOINT x1x1 y1x1 treatment r1x1x1 r1y1x1 r2x1x1 r2y1x1\r\n" + ) + maskfile.write(b"#CHIP BADPOINT 630 422 REPLACE 632 422 0 0\r\n") + maskfile.write(b"#CHIP BADRECTANGLE xl xr yb yt\r\n") for r in ccd.pschipbadpolygon: - maskfile.write(f"CHIP BADRECTANGLE {r.iax[0]} {r.iax[1]} {r.iay[0]} {r.iay[1]}\r\n".encode()) + maskfile.write( + f"CHIP BADRECTANGLE {r.iax[0]} {r.iax[1]} {r.iay[0]} {r.iay[1]}\r\n".encode() + ) for r in ccd.pschipbadpoint: - maskfile.write(f"CHIP BADPOINT {r.spt.ix} {r.spt.iy} IGNORE {r.spt.ix} {r.spt.iy} {r.spt.ix} {r.spt.iy}\r\n".encode()) - maskfile.write(b'#END OF XCALIBUR CHIP CHARACTERISTICS FILE\r\n') + maskfile.write( + f"CHIP BADPOINT {r.spt.ix} {r.spt.iy} IGNORE {r.spt.ix} {r.spt.iy} {r.spt.ix} {r.spt.iy}\r\n".encode() + ) + maskfile.write(b"#END OF XCALIBUR CHIP CHARACTERISTICS FILE\r\n") # Make a backup as the original could be overwritten by Crysalis at import - shutil.copyfile(os.path.join(dirname, prefix + ".set"), os.path.join(dirname, prefix + ".set.orig")) - + shutil.copyfile( + os.path.join(dirname, prefix + ".set"), + os.path.join(dirname, prefix + ".set.orig"), + ) + # save the ".run" file rundescription = xcaliburimage.RunDescription(prefix, dirname, pssweep=[]) - if self.scan_type=="phi": + if self.scan_type == "phi": iscantype = xcaliburimage.SCAN_TYPE.Phi.value dstart = self.headers["dph_s"](0) dend = self.headers["dph_s"](self.processed_frames) dphi = 0.0 domega = self.headers["dom_s"] - elif self.scan_type=="omega": + elif self.scan_type == "omega": iscantype = xcaliburimage.SCAN_TYPE.Omega.value dstart = self.headers["dom_s"](0) dend = self.headers["dom_s"](self.processed_frames) dphi = self.headers["dph_s"] domega = 0.0 else: - raise RuntimeError("This program only supports Omega and Phi scans, please provide the scan type in the command-line. See the output of the `--help` options.") - oscil = (dend-dstart)/self.processed_frames - sweep = xcaliburimage.Sweep(0, - iscantype, - domega=domega, - dtheta=self.headers["dth_s"], - dkappa=self.headers["dka_s"], - dphi=dphi, - dstart=dstart, dend=dend, - dwidth=oscil, - dunknown2=0.0, - iunknown3=self.processed_frames, - iunknown4=0, - iunknown5=self.processed_frames, iunknown6=0, - dexposure=self.headers["dexposuretimeinsec"] - ) + raise RuntimeError( + "This program only supports Omega and Phi scans, please provide the scan type in the command-line. See the output of the `--help` options." + ) + oscil = (dend - dstart) / self.processed_frames + sweep = xcaliburimage.Sweep( + 0, + iscantype, + domega=domega, + dtheta=self.headers["dth_s"], + dkappa=self.headers["dka_s"], + dphi=dphi, + dstart=dstart, + dend=dend, + dwidth=oscil, + dunknown2=0.0, + iunknown3=self.processed_frames, + iunknown4=0, + iunknown5=self.processed_frames, + iunknown6=0, + dexposure=self.headers["dexposuretimeinsec"], + ) rundescription.pssweep.append(sweep) - rundescription.save(os.path.join(dirname,prefix+".run")) + rundescription.save(os.path.join(dirname, prefix + ".run")) # Finally save the ".par" file - xci.save_par(dirname, prefix, - wavelength=self.options.wavelength, - alpha=self.options.alpha, - polarization=self.options.polarization, - distance=self.options.distance, - oscil=oscil, - center_x=self.headers["dxorigininpix"], - center_y=self.headers["dyorigininpix"] - ) + xci.save_par( + dirname, + prefix, + wavelength=self.options.wavelength, + alpha=self.options.alpha, + polarization=self.options.polarization, + distance=self.options.distance, + oscil=oscil, + center_x=self.headers["dxorigininpix"], + center_y=self.headers["dyorigininpix"], + ) def main(): - epilog = """return codes: 0 means a success. 1 means the conversion contains a failure, 2 means there was an error in the arguments""" - parser = argparse.ArgumentParser(prog="eiger2crysalis", - description=__doc__, - epilog=epilog) - parser.add_argument("IMAGE", nargs="*", - help="File with input images") - parser.add_argument("-V", "--version", action='version', version=fabio_version, - help="output version and exit") - parser.add_argument("-v", "--verbose", action='store_true', dest="verbose", default=False, - help="show information for each conversions") - parser.add_argument("--debug", action='store_true', dest="debug", default=False, - help="show debug information") + parser = argparse.ArgumentParser( + prog="eiger2crysalis", description=__doc__, epilog=epilog + ) + parser.add_argument("IMAGE", nargs="*", help="File with input images") + parser.add_argument( + "-V", + "--version", + action="version", + version=fabio_version, + help="output version and exit", + ) + parser.add_argument( + "-v", + "--verbose", + action="store_true", + dest="verbose", + default=False, + help="show information for each conversions", + ) + parser.add_argument( + "--debug", + action="store_true", + dest="debug", + default=False, + help="show debug information", + ) group = parser.add_argument_group("main arguments") - group.add_argument("-l", "--list", action="store_true", dest="list", default=None, - help="show the list of available formats and exit") - group.add_argument("-o", "--output", default='{dirname}/{prefix}/{prefix}_1_{index}.esperanto', type=str, - help="output directory and filename template") - group.add_argument("-O", "--offset", type=int, default=1, - help="index offset, CrysalisPro likes indexes to start at 1, Python starts at 0") - group.add_argument("-D", "--dummy", type=int, default=-1, - help="Set masked values to this dummy value") + group.add_argument( + "-l", + "--list", + action="store_true", + dest="list", + default=None, + help="show the list of available formats and exit", + ) + group.add_argument( + "-o", + "--output", + default="{dirname}/{prefix}/{prefix}_1_{index}.esperanto", + type=str, + help="output directory and filename template", + ) + group.add_argument( + "-O", + "--offset", + type=int, + default=1, + help="index offset, CrysalisPro likes indexes to start at 1, Python starts at 0", + ) + group.add_argument( + "-D", + "--dummy", + type=int, + default=-1, + help="Set masked values to this dummy value", + ) group = parser.add_argument_group("optional behaviour arguments") -# group.add_argument("-f", "--force", dest="force", action="store_true", default=False, -# help="if an existing destination file cannot be" + -# " opened, remove it and try again (this option" + -# " is ignored when the -n option is also used)") -# group.add_argument("-n", "--no-clobber", dest="no_clobber", action="store_true", default=False, -# help="do not overwrite an existing file (this option" + -# " is ignored when the -i option is also used)") -# group.add_argument("--remove-destination", dest="remove_destination", action="store_true", default=False, -# help="remove each existing destination file before" + -# " attempting to open it (contrast with --force)") -# group.add_argument("-u", "--update", dest="update", action="store_true", default=False, -# help="copy only when the SOURCE file is newer" + -# " than the destination file or when the" + -# " destination file is missing") -# group.add_argument("-i", "--interactive", dest="interactive", action="store_true", default=False, -# help="prompt before overwrite (overrides a previous -n" + -# " option)") - group.add_argument("--dry-run", dest="dry_run", action="store_true", default=False, - help="do everything except modifying the file system") - group.add_argument("--calc-mask", dest="calc_mask", default=False, action="store_true", - help="Generate a fine mask from pixels marked as invalid. By default, only treats gaps") + # group.add_argument("-f", "--force", dest="force", action="store_true", default=False, + # help="if an existing destination file cannot be" + + # " opened, remove it and try again (this option" + + # " is ignored when the -n option is also used)") + # group.add_argument("-n", "--no-clobber", dest="no_clobber", action="store_true", default=False, + # help="do not overwrite an existing file (this option" + + # " is ignored when the -i option is also used)") + # group.add_argument("--remove-destination", dest="remove_destination", action="store_true", default=False, + # help="remove each existing destination file before" + + # " attempting to open it (contrast with --force)") + # group.add_argument("-u", "--update", dest="update", action="store_true", default=False, + # help="copy only when the SOURCE file is newer" + + # " than the destination file or when the" + + # " destination file is missing") + # group.add_argument("-i", "--interactive", dest="interactive", action="store_true", default=False, + # help="prompt before overwrite (overrides a previous -n" + + # " option)") + group.add_argument( + "--dry-run", + dest="dry_run", + action="store_true", + default=False, + help="do everything except modifying the file system", + ) + group.add_argument( + "--calc-mask", + dest="calc_mask", + default=False, + action="store_true", + help="Generate a fine mask from pixels marked as invalid. By default, only treats gaps", + ) group = parser.add_argument_group("Experimental setup options") - group.add_argument("-e", "--energy", type=float, default=None, - help="Energy of the incident beam in keV") - group.add_argument("-w", "--wavelength", type=float, default=None, - help="Wavelength of the incident beam in Å") - group.add_argument("-d", "--distance", type=float, default=None, - help="Detector distance in millimeters") - group.add_argument("-b", "--beam", nargs=2, type=float, default=None, - help="Direct beam in pixels x, y") - group.add_argument("-p", "--polarization", type=float, default=0.99, - help="Polarization factor (0.99 by default on synchrotron)") + group.add_argument( + "-e", + "--energy", + type=float, + default=None, + help="Energy of the incident beam in keV", + ) + group.add_argument( + "-w", + "--wavelength", + type=float, + default=None, + help="Wavelength of the incident beam in Å", + ) + group.add_argument( + "-d", + "--distance", + type=float, + default=None, + help="Detector distance in millimeters", + ) + group.add_argument( + "-b", + "--beam", + nargs=2, + type=float, + default=None, + help="Direct beam in pixels x, y", + ) + group.add_argument( + "-p", + "--polarization", + type=float, + default=0.99, + help="Polarization factor (0.99 by default on synchrotron)", + ) group = parser.add_argument_group("Goniometer setup") -# group.add_argument("--axis", type=str, default=None, -# help="Goniometer angle used for scanning: 'omega', 'phi' or 'kappa'") - group.add_argument("--alpha", type=float, default=50, - help="Goniometer angle alpha value in deg. Constant, angle between kappa/omega.") - group.add_argument("--kappa", type=str, default=0, - help="Goniometer angle kappa value in deg or formula f(index).") -# group.add_argument("--chi", type=str, default=0, -# help="Goniometer angle chi value in deg. or formula f(index).") - group.add_argument("--phi", type=str, default=0, - help="Goniometer angle phi value in deg. or formula f(index). Inner-most rotation.") - group.add_argument("--omega", type=str, default=0, - help="Goniometer angle omega value in deg. or formula f(index). Outer-most rotation.") - group.add_argument("--theta", type=str, default=0, - help="Goniometer angle theta value in deg. or formula f(index). Tilt angle of the detector.") - - group = parser.add_argument_group("Image preprocessing (Important: applied in this order!)") - group.add_argument("--rotation", type=int, default=180, - help="Rotate the initial image by this value in degrees. Must be a multiple of 90°. By default 180 deg (flip_up with origin=lower and flip_lr because the image is seen from the sample).") - group.add_argument("--transpose", default=False, action="store_true", - help="Flip the x/y axis") - group.add_argument("--flip-ud", dest="flip_ud", default=False, action="store_true", - help="Flip the image upside-down") - group.add_argument("--flip-lr", dest="flip_lr", default=False, action="store_true", - help="Flip the image left-right") + # group.add_argument("--axis", type=str, default=None, + # help="Goniometer angle used for scanning: 'omega', 'phi' or 'kappa'") + group.add_argument( + "--alpha", + type=float, + default=50, + help="Goniometer angle alpha value in deg. Constant, angle between kappa/omega.", + ) + group.add_argument( + "--kappa", + type=str, + default=0, + help="Goniometer angle kappa value in deg or formula f(index).", + ) + # group.add_argument("--chi", type=str, default=0, + # help="Goniometer angle chi value in deg. or formula f(index).") + group.add_argument( + "--phi", + type=str, + default=0, + help="Goniometer angle phi value in deg. or formula f(index). Inner-most rotation.", + ) + group.add_argument( + "--omega", + type=str, + default=0, + help="Goniometer angle omega value in deg. or formula f(index). Outer-most rotation.", + ) + group.add_argument( + "--theta", + type=str, + default=0, + help="Goniometer angle theta value in deg. or formula f(index). Tilt angle of the detector.", + ) + + group = parser.add_argument_group( + "Image preprocessing (Important: applied in this order!)" + ) + group.add_argument( + "--rotation", + type=int, + default=180, + help="Rotate the initial image by this value in degrees. Must be a multiple of 90°. By default 180 deg (flip_up with origin=lower and flip_lr because the image is seen from the sample).", + ) + group.add_argument( + "--transpose", default=False, action="store_true", help="Flip the x/y axis" + ) + group.add_argument( + "--flip-ud", + dest="flip_ud", + default=False, + action="store_true", + help="Flip the image upside-down", + ) + group.add_argument( + "--flip-lr", + dest="flip_lr", + default=False, + action="store_true", + help="Flip the image left-right", + ) try: args = parser.parse_args() diff --git a/src/fabio/app/hdf2neggia.py b/src/fabio/app/hdf2neggia.py index cff4632c..41f7d1e7 100755 --- a/src/fabio/app/hdf2neggia.py +++ b/src/fabio/app/hdf2neggia.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -__date__ = "26/05/2025" +__date__ = "27/10/2025" __author__ = "Jérôme Kieffer" __license__ = "MIT" @@ -9,15 +9,17 @@ import argparse from io import StringIO import logging -application_name = os.path.splitext(os.path.basename(__file__))[0] -logger = logging.getLogger(application_name) -logging.basicConfig() import numpy from .. import version as fabio_version, date as fabio_date from ..openimage import openimage as fabio_open from ..nexus import Nexus import h5py import hdf5plugin + +application_name = os.path.splitext(os.path.basename(__file__))[0] +logger = logging.getLogger(application_name) +logging.basicConfig() + try: from pyFAI import load except ImportError: @@ -25,49 +27,79 @@ logger.error("Unable to import pyFAI, won't be able to parse PONI-file!") - - class XDSbuilder: def __init__(self): "Constructor of the class" - + self.poni = None self.options = None - self.frames = None + self.frames = None self.h5_filename = "fabio_master.h5" def parse(self, argv=None): - "Parse command line arguments and return " + "Parse command line arguments and return" if argv is None: argv = [] - parser = argparse.ArgumentParser(prog=application_name, - description='Convert any HDF5 file containing images to a file processable by XDS using the neggia plugin from Dectris.' - 'Do not forget to specify LIB=/path/to/plugin/dectris-neggia.so in XDS.INP', - epilog='Requires hdf5plugin and pyFAI to parse configuration file. Geometry can be calibrated with pyFAI-calib2',) - parser.add_argument('--version', action='version', version=f'%(prog)s {fabio_version} from {fabio_date}') - parser.add_argument("input", nargs='+', help="Space separated list of input files (HDF5)") - parser.add_argument("--verbose", "-v", help="increase output verbosity", - action="count") - parser.add_argument("--force", "-f", help="force overwrite output file", - action="store_true") - parser.add_argument("--copy", "-c", help="copy dataset instead of using external links", - action="store_true") - parser.add_argument("--geometry", "-g", help="PONI-file containing the geometry (pyFAI format, MANDATORY)") - parser.add_argument("--output", "-o", help=f"output directory, the data will be in {self.h5_filename}", default="fabio_xds") - parser.add_argument("--CdTe", help="The detector is made of CdTe", default=False, action="store_true") - parser.add_argument("--neggia", help="Path of the neggia plugin", default="dectris-neggia.so") - parser.add_argument("--oscillation", help="Oscillation range used.", default=0.5) - + parser = argparse.ArgumentParser( + prog=application_name, + description="Convert any HDF5 file containing images to a file processable by XDS using the neggia plugin from Dectris." + "Do not forget to specify LIB=/path/to/plugin/dectris-neggia.so in XDS.INP", + epilog="Requires hdf5plugin and pyFAI to parse configuration file. Geometry can be calibrated with pyFAI-calib2", + ) + parser.add_argument( + "--version", + action="version", + version=f"%(prog)s {fabio_version} from {fabio_date}", + ) + parser.add_argument( + "input", nargs="+", help="Space separated list of input files (HDF5)" + ) + parser.add_argument( + "--verbose", "-v", help="increase output verbosity", action="count" + ) + parser.add_argument( + "--force", "-f", help="force overwrite output file", action="store_true" + ) + parser.add_argument( + "--copy", + "-c", + help="copy dataset instead of using external links", + action="store_true", + ) + parser.add_argument( + "--geometry", + "-g", + help="PONI-file containing the geometry (pyFAI format, MANDATORY)", + ) + parser.add_argument( + "--output", + "-o", + help=f"output directory, the data will be in {self.h5_filename}", + default="fabio_xds", + ) + parser.add_argument( + "--CdTe", + help="The detector is made of CdTe", + default=False, + action="store_true", + ) + parser.add_argument( + "--neggia", help="Path of the neggia plugin", default="dectris-neggia.so" + ) + parser.add_argument( + "--oscillation", help="Oscillation range used.", default=0.5 + ) + self.options = parser.parse_args(argv) return self.options - + def configure_verboseness(self): if self.options and self.options.verbose: - if self.options.verbose>1: + if self.options.verbose > 1: logger.setLevel(logging.DEBUG) else: logger.setLevel(logging.INFO) - + def load_poni(self): "return 1 if poni-file cannot be found" if self.options is None: @@ -81,26 +113,25 @@ def load_poni(self): logger.error("Unable to parse PONI-file: %s", self.options.geometry) raise err return 1 - + def load_input(self): - if len(self.options.input)==0: + if len(self.options.input) == 0: logger.error("No input HDF5 file provided. Aborting") return 2 self.frames = [fabio_open(i) for i in self.options.input] return 0 - + def _distance_center(self): - """return distance, centerx, center_y - """ + """return distance, centerx, center_y""" width = self.poni.detector.shape[-1] - distance = self.poni.dist*1e3 #mm + distance = self.poni.dist * 1e3 # mm r_array = self.poni.array_from_unit(unit="r_m") x_center = numpy.argmin(r_array) % width - y_center = numpy.argmin(r_array) // width + y_center = numpy.argmin(r_array) // width return distance, x_center, y_center def build_neggia(self): - """Build the neggia file, i.e. the HDF5 file with data + metadata for analysis""" + """Build the neggia file, i.e. the HDF5 file with data + metadata for analysis""" if not os.path.exists(self.options.output): os.makedirs(self.options.output) dest_h5 = os.path.join(self.options.output, self.h5_filename) @@ -114,19 +145,37 @@ def build_neggia(self): mode = "w" distance, center_x, center_y = self._distance_center() with Nexus(dest_h5, mode) as nxs: - entry = nxs.new_entry(entry="entry", program_name=application_name, force_name=True) + entry = nxs.new_entry( + entry="entry", program_name=application_name, force_name=True + ) instrument = nxs.new_instrument(entry=entry, instrument_name="instrument") if self.poni.wavelength: beam = nxs.new_class(instrument, "beam", "NXbeam") - beam.create_dataset("incident_wavelength", data=self.poni.wavelength*1e10).attrs["unit"] = "A" + beam.create_dataset( + "incident_wavelength", data=self.poni.wavelength * 1e10 + ).attrs["unit"] = "A" detector = nxs.new_class(instrument, "detector", "NXdetector") - detector.create_dataset("x_pixel_size", data=float(self.poni.pixel2)).attrs["unit"] = "m" - detector.create_dataset("y_pixel_size", data=float(self.poni.pixel1)).attrs["unit"] = "m" - detector.create_dataset("beam_center_x", data=float(center_x)).attrs["unit"] = "pixel" - detector.create_dataset("beam_center_y", data=float(center_y)).attrs["unit"] = "pixel" - detector.create_dataset("detector_distance", data=distance*1e-3).attrs["unit"] = "m" - detectorSpecific = nxs.new_class(detector, "detectorSpecific", "NXcollection") - detectorSpecific.create_dataset("nimages", data=sum(i.nframes for i in self.frames)) + detector.create_dataset("x_pixel_size", data=float(self.poni.pixel2)).attrs[ + "unit" + ] = "m" + detector.create_dataset("y_pixel_size", data=float(self.poni.pixel1)).attrs[ + "unit" + ] = "m" + detector.create_dataset("beam_center_x", data=float(center_x)).attrs[ + "unit" + ] = "pixel" + detector.create_dataset("beam_center_y", data=float(center_y)).attrs[ + "unit" + ] = "pixel" + detector.create_dataset("detector_distance", data=distance * 1e-3).attrs[ + "unit" + ] = "m" + detectorSpecific = nxs.new_class( + detector, "detectorSpecific", "NXcollection" + ) + detectorSpecific.create_dataset( + "nimages", data=sum(i.nframes for i in self.frames) + ) detectorSpecific.create_dataset("ntrigger", data=1) mask = self.poni.detector.mask if mask is None: @@ -140,117 +189,154 @@ def build_neggia(self): if isinstance(fimg.dataset, h5py.Dataset): cnt += 1 if self.options.copy: - data.create_dataset(f"data_{cnt:06d}", data=fimg.dataset[()], - chunks=(1,)+fimg.shape, - **hdf5plugin.Bitshuffle(nelems=0, cname='lz4')) + data.create_dataset( + f"data_{cnt:06d}", + data=fimg.dataset[()], + chunks=(1,) + fimg.shape, + **hdf5plugin.Bitshuffle(nelems=0, cname="lz4"), + ) else: - data[f"data_{cnt:06d}"] = h5py.ExternalLink(os.path.relpath(fimg.dataset.file.filename, self.options.output), fimg.dataset.name) + data[f"data_{cnt:06d}"] = h5py.ExternalLink( + os.path.relpath( + fimg.dataset.file.filename, self.options.output + ), + fimg.dataset.name, + ) elif isinstance(fimg.dataset, numpy.ndarray): cnt += 1 if fimg.dataset.ndim < 3: dataset = numpy.atleast_3d(fimg.dataset) - dataset.shape = (1,)*(3-fimg.dataset.ndim)+fimg.dataset.shape + dataset.shape = (1,) * ( + 3 - fimg.dataset.ndim + ) + fimg.dataset.shape else: dataset = fimg.dataset - data.create_dataset(f"data_{cnt:06d}", data=dataset, - chunks=(1,)+fimg.shape, - **hdf5plugin.Bitshuffle(nelems=0, cname='lz4')) - else: # assume it is a list + data.create_dataset( + f"data_{cnt:06d}", + data=dataset, + chunks=(1,) + fimg.shape, + **hdf5plugin.Bitshuffle(nelems=0, cname="lz4"), + ) + else: # assume it is a list for item in fimg.dataset: # each item can be either a dataset or a numpy array if isinstance(item, h5py.Dataset): cnt += 1 if self.options.copy: - data.create_dataset(f"data_{cnt:06d}", data=item[()], - chunks=(1,)+fimg.shape, - **hdf5plugin.Bitshuffle(nelems=0, cname='lz4')) + data.create_dataset( + f"data_{cnt:06d}", + data=item[()], + chunks=(1,) + fimg.shape, + **hdf5plugin.Bitshuffle(nelems=0, cname="lz4"), + ) else: - data[f"data_{cnt:06d}"] = h5py.ExternalLink(os.path.relpath(item.file.filename, self.options.output), item.name) - + data[f"data_{cnt:06d}"] = h5py.ExternalLink( + os.path.relpath( + item.file.filename, self.options.output + ), + item.name, + ) + elif isinstance(fimg.dataset, numpy.ndarray): cnt += 1 if fimg.dataset.ndim < 3: dataset = numpy.atleast_3d(item) - dataset.shape = (1,)*(3-item.ndim)+item.shape + dataset.shape = (1,) * (3 - item.ndim) + item.shape else: dataset = item - data.create_dataset(f"data_{cnt:06d}", data=dataset, - chunks=(1,)+fimg.shape, - **hdf5plugin.Bitshuffle(nelems=0, cname='lz4')) + data.create_dataset( + f"data_{cnt:06d}", + data=dataset, + chunks=(1,) + fimg.shape, + **hdf5plugin.Bitshuffle(nelems=0, cname="lz4"), + ) else: - logger.warning("Don't know how to handle %s, skipping", item) - os.link(dest_h5, dest_h5.replace("master","000000")) + logger.warning( + "Don't know how to handle %s, skipping", item + ) + os.link(dest_h5, dest_h5.replace("master", "000000")) logger.info(f"Successfully created Neggia file {dest_h5}.") return 0 - + def build_XDS(self): "Create XDS.INP file suitable for data reduction" if not os.path.exists(self.options.output): os.makedirs(self.options.output) dest_xds = os.path.join(self.options.output, "XDS.INP") - - xds = ["JOB= XYCORR INIT COLSPOT IDXREF DEFPIX INTEGRATE CORRECT", - "! JOB= DEFPIX INTEGRATE CORRECT", - "", - f"NAME_TEMPLATE_OF_DATA_FRAMES= {self.h5_filename.replace('master','??????')}", - f"LIB= {self.options.neggia}", - "", - "SPACE_GROUP_NUMBER=0 ! 0 if unknown", - "FRIEDEL'S_LAW=FALSE ! This acts only on the CORRECT step", - "", - "FRACTION_OF_POLARIZATION=0.99", - "ROTATION_AXIS=0 -1 0", - "POLARIZATION_PLANE_NORMAL=0 1 0", - "DIRECTION_OF_DETECTOR_X-AXIS=1 0 0", - "DIRECTION_OF_DETECTOR_Y-AXIS=0 1 0", - "INCIDENT_BEAM_DIRECTION=0 0 1", - f"OSCILLATION_RANGE= {self.options.oscillation}", - "", - "OVERLOAD=100000000", - ] + + xds = [ + "JOB= XYCORR INIT COLSPOT IDXREF DEFPIX INTEGRATE CORRECT", + "! JOB= DEFPIX INTEGRATE CORRECT", + "", + f"NAME_TEMPLATE_OF_DATA_FRAMES= {self.h5_filename.replace('master', '??????')}", + f"LIB= {self.options.neggia}", + "", + "SPACE_GROUP_NUMBER=0 ! 0 if unknown", + "FRIEDEL'S_LAW=FALSE ! This acts only on the CORRECT step", + "", + "FRACTION_OF_POLARIZATION=0.99", + "ROTATION_AXIS=0 -1 0", + "POLARIZATION_PLANE_NORMAL=0 1 0", + "DIRECTION_OF_DETECTOR_X-AXIS=1 0 0", + "DIRECTION_OF_DETECTOR_Y-AXIS=0 1 0", + "INCIDENT_BEAM_DIRECTION=0 0 1", + f"OSCILLATION_RANGE= {self.options.oscillation}", + "", + "OVERLOAD=100000000", + ] xds.append(f"DATA_RANGE= 1 {sum(i.nframes for i in self.frames)}") - + shape = self.poni.detector.shape pixel1, pixel2 = self.poni.detector.pixel1, self.poni.detector.pixel2 - xds.append(f"NX= {shape[1]:d} NY= {shape[0]:d} QX= {pixel2*1000:f} QY= {pixel1*1000:f}") + xds.append( + f"NX= {shape[1]:d} NY= {shape[0]:d} QX= {pixel2 * 1000:f} QY= {pixel1 * 1000:f}" + ) - distance, center_x, center_y = self._distance_center() + distance, center_x, center_y = self._distance_center() xds.append("DETECTOR= EIGER") xds.append(f"DETECTOR_DISTANCE= {distance:.4f}") xds.append(f"ORGX= {center_x:.3f} ORGY= {center_y:.3f}") - xds.append(f"X-RAY_WAVELENGTH={1e10*self.poni.wavelength}") + xds.append(f"X-RAY_WAVELENGTH={1e10 * self.poni.wavelength}") xds.append("") - mask = self.poni.detector.mask>0 + mask = self.poni.detector.mask > 0 empty = numpy.where(numpy.std(mask, axis=0) == 0)[0] if len(empty): start = 0 - for stop in numpy.where(empty[1:]-empty[:-1]!=1)[0]: - xds.append(f"UNTRUSTED_RECTANGLE= {empty[start]:4d} {empty[stop]:4d} 0 {shape[0]:4d} ") + for stop in numpy.where(empty[1:] - empty[:-1] != 1)[0]: + xds.append( + f"UNTRUSTED_RECTANGLE= {empty[start]:4d} {empty[stop]:4d} 0 {shape[0]:4d} " + ) start = stop + 1 - xds.append(f"UNTRUSTED_RECTANGLE= {empty[start]:4d} {empty[-1]:4d} 0 {shape[0]:4d} ") + xds.append( + f"UNTRUSTED_RECTANGLE= {empty[start]:4d} {empty[-1]:4d} 0 {shape[0]:4d} " + ) empty = numpy.where(numpy.std(mask, axis=1) == 0)[0] if len(empty): start = 0 - for stop in numpy.where(empty[1:]-empty[:-1]!=1)[0]: - xds.append(f"UNTRUSTED_RECTANGLE= 0 {shape[1]:4d} {empty[start]:4d} {empty[stop]:4d}") + for stop in numpy.where(empty[1:] - empty[:-1] != 1)[0]: + xds.append( + f"UNTRUSTED_RECTANGLE= 0 {shape[1]:4d} {empty[start]:4d} {empty[stop]:4d}" + ) start = stop + 1 - xds.append(f"UNTRUSTED_RECTANGLE= 0 {shape[1]:4d} {empty[start]:4d} {empty[-1]:4d}") + xds.append( + f"UNTRUSTED_RECTANGLE= 0 {shape[1]:4d} {empty[start]:4d} {empty[-1]:4d}" + ) xds.append("") if self.options.CdTe: xds.append("SENSOR_THICKNESS=0.75 !mm") nrj = self.poni.energy - e, mu = numpy.loadtxt(StringIO(Attenuations_CdTe),unpack=True) + e, mu = numpy.loadtxt(StringIO(Attenuations_CdTe), unpack=True) xds.append(f"SILICON= {numpy.interp(nrj, e, mu):.3f} !1/mm") - mode = "w" + mode = "w" if os.path.exists(dest_xds): if self.options.force: mode = "w" else: logger.error("Output file exist, not overwriting it. Aborting") return 1 - with open(dest_xds, "w") as w: + with open(dest_xds, mode) as w: w.write(os.linesep.join(xds)) logger.info(f"Successfully created XDS file {dest_xds}.") return 0 @@ -258,27 +344,29 @@ def build_XDS(self): def process(self): "pipeline processing, returns a error code as integer" self.configure_verboseness() - + rc = self.load_poni() - if rc: return rc - + if rc: + return rc + rc = self.load_input() - if rc: return rc - + if rc: + return rc + rc = self.build_neggia() - if rc: return rc - rc = self.build_XDS() - if rc: return rc - return rc - - + if rc: + return rc + + return self.build_XDS() + + def main(argv=None): xds = XDSbuilder() xds.parse(sys.argv[1:] if argv is None else argv) return xds.process() -if __name__=="__main__": +if __name__ == "__main__": sys.exit(main()) diff --git a/src/fabio/app/viewer.py b/src/fabio/app/viewer.py index 1df89703..4f27b9ae 100755 --- a/src/fabio/app/viewer.py +++ b/src/fabio/app/viewer.py @@ -38,36 +38,41 @@ __version__ = "1.0" __author__ = "Gaël Goret, Jérôme Kieffer" __copyright__ = "2015 ESRF" -__licence__ = "GPL" +__licence__ = "MIT" import sys import os import time - from .. import qt from ..qt.matplotlib import FigureCanvasQTAgg from ..qt.matplotlib import NavigationToolbar2QT from matplotlib.figure import Figure - import numpy -numpy.seterr(divide='ignore') - import fabio - from fabio.nexus import Nexus from argparse import ArgumentParser -output_format = ['*.bin', '*.cbf', '*.edf', '*.h5', '*.img', - '*.mar2300', '*.mar3450', '*.marccd', '*.tiff', "*.sfrm"] +numpy.seterr(divide="ignore") +output_format = [ + "*.bin", + "*.cbf", + "*.edf", + "*.h5", + "*.img", + "*.mar2300", + "*.mar3450", + "*.marccd", + "*.tiff", + "*.sfrm", +] -class AppForm(qt.QMainWindow): +class AppForm(qt.QMainWindow): def __init__(self, parent=None): - # Main window qt.QMainWindow.__init__(self, parent) - self.setWindowTitle('FabIO Viewer') + self.setWindowTitle("FabIO Viewer") self.setSizePolicy(qt.QSizePolicy().Expanding, qt.QSizePolicy().Expanding) # Menu and widget self.create_menu() @@ -91,7 +96,7 @@ def __init__(self, parent=None): self.transform_list = [] self.sequential_file_mode = False self.h5_loaded = False - self.counter_format = '%03d' + self.counter_format = "%03d" def format_header(self, d): """ @@ -100,7 +105,7 @@ def format_header(self, d): """ keys = list(d.keys()) keys.sort() - res = " \n".join(['%s: %s' % (k, d[k]) for k in keys]) + " \n" + res = " \n".join(["%s: %s" % (k, d[k]) for k in keys]) + " \n" return res def _open(self, filename): @@ -109,27 +114,33 @@ def _open(self, filename): try: img = fabio.open(filename) except Exception as _: - message = 'Automatic format recognition procedure failed or '\ - 'pehaps you are trying to open a binary data block...\n\n'\ - 'Switch to manual procedure.' - qt.QMessageBox.warning(self, 'Message', message) + message = ( + "Automatic format recognition procedure failed or " + "pehaps you are trying to open a binary data block...\n\n" + "Switch to manual procedure." + ) + qt.QMessageBox.warning(self, "Message", message) dial = BinDialog(self) dim1, dim2, offset, bytecode, endian = dial.exec_() if dim1 is not None and dim2 is not None: - if endian == 'Short': - endian = '<' + if endian == "Short": + endian = "<" else: - endian = '>' + endian = ">" img = fabio.binaryimage.binaryimage() img.read(filename, dim1, dim2, offset, bytecode, endian) - img.header = {'Info': 'No header information available in binary data blocks'} + img.header = { + "Info": "No header information available in binary data blocks" + } else: return return img def open_data_series(self, series=None): if not series: - series = qt.QFileDialog.getOpenFileNames(self, 'Select and open series of files') + series = qt.QFileDialog.getOpenFileNames( + self, "Select and open series of files" + ) if isinstance(series, tuple): # PyQt5 compatibility series = series[0] @@ -153,16 +164,22 @@ def open_data_series(self, series=None): if fname: extract_fname = self.extract_fname_from_path(fname) if self.sequential_file_mode: - self.statusBar().showMessage('Adding path %s to batch image list, please wait...' % fname) - self.log.appendPlainText('Adding path %s to batch image list' % fname) + self.statusBar().showMessage( + "Adding path %s to batch image list, please wait..." % fname + ) + self.log.appendPlainText( + "Adding path %s to batch image list" % fname + ) qt.QCoreApplication.processEvents() self.imagelistWidget.addItem(extract_fname) self.sequential_file_list += [extract_fname] self.sequential_file_dict[extract_fname] = fname iid += 1 else: - self.statusBar().showMessage('Opening file %s, please wait...' % fname) - self.log.appendPlainText('Opening file %s' % fname) + self.statusBar().showMessage( + "Opening file %s, please wait..." % fname + ) + self.log.appendPlainText("Opening file %s" % fname) qt.QCoreApplication.processEvents() img = self._open(fname) if img is None: @@ -189,15 +206,15 @@ def open_data_series(self, series=None): self.sequential_file_list += [extract_fname] self.sequential_file_dict[extract_fname] = fname iid += 1 - self.progressBar.setValue(int((iid + 1) / total * 100.)) + self.progressBar.setValue(int((iid + 1) / total * 100.0)) self.statusBar().clearMessage() self.progressBar.setValue(0) - self.log.appendPlainText('Opening procedure: Complete') + self.log.appendPlainText("Opening procedure: Complete") if self.data_series: self.select_new_image(None, imgID=0) def open_h5_data_series(self): # TODO batch mode compatibility - fname = qt.QFileDialog.getOpenFileName(self, 'Select and open series of files') + fname = qt.QFileDialog.getOpenFileName(self, "Select and open series of files") if isinstance(fname, tuple): # PyQt5 compatibility fname = fname[0] @@ -209,8 +226,8 @@ def open_h5_data_series(self): # TODO batch mode compatibility self.filecheckBox.setCheckState(False) self.sequential_file_mode = False self.filecheckBox.stateChanged.connect(self.sequential_option) - message = 'Sequential file mode is not compatible with hdf5 input file: option removed' - qt.QMessageBox.warning(self, 'Message', message) + message = "Sequential file mode is not compatible with hdf5 input file: option removed" + qt.QMessageBox.warning(self, "Message", message) if fname: self.data_series = [] self.header_series = [] @@ -218,7 +235,7 @@ def open_h5_data_series(self): # TODO batch mode compatibility self.sequential_file_dict = {} self.imagelistWidget.clear() self.headerTextEdit.clear() - with Nexus(fname, 'r') as nxs: + with Nexus(fname, "r") as nxs: entry = nxs.get_entries()[0] nxdata = nxs.get_class(entry, class_type="NXdata")[0] dataset = nxdata.get("data", numpy.zeros(shape=(1, 1, 1))) @@ -228,17 +245,21 @@ def open_h5_data_series(self): # TODO batch mode compatibility self.images_list.clear() safeiid = 0 for iid in range(total): - self.progressBar.setValue(int((iid + 1.0) / total * 100.)) - self.log.appendPlainText('Extracting data from hdf5 archive, image number %d' % iid) + self.progressBar.setValue(int((iid + 1.0) / total * 100.0)) + self.log.appendPlainText( + "Extracting data from hdf5 archive, image number %d" % iid + ) qt.QCoreApplication.processEvents() self.data_series.append(dataset[iid]) - self.header_series += [{'Info': 'No header information available in hdf5 Archive'}] + self.header_series += [ + {"Info": "No header information available in hdf5 Archive"} + ] imgDict[extract_fname + str(iid)] = safeiid self.images_list.addItem(extract_fname + str(iid)) safeiid += 1 self.statusBar().clearMessage() self.progressBar.setValue(0) - self.log.appendPlainText('Hdf5 Extraction: Complete') + self.log.appendPlainText("Hdf5 Extraction: Complete") self.imgDict = imgDict.copy() if self.data_series: self.select_new_image(None, imgID=0) @@ -246,23 +267,26 @@ def open_h5_data_series(self): # TODO batch mode compatibility def extract_fname_from_path(self, name): posslash = name.rfind("/") if posslash > -1: - return name[posslash + 1:] + return name[posslash + 1 :] else: return name - defaultSaveFilter = ""\ - "binary data block (*.bin);;"\ - "cbf image (*.cbf);;"\ - "edf image (*.edf);;"\ - "oxford diffraction image (*.img);;"\ - "mar2300 image(*.mar2300);;"\ - "mar3450 image (*.mar3450);;"\ - "marccd image (*.marccd));;"\ - "tiff image (*.tiff);;"\ + defaultSaveFilter = ( + "" + "binary data block (*.bin);;" + "cbf image (*.cbf);;" + "edf image (*.edf);;" + "oxford diffraction image (*.img);;" + "mar2300 image(*.mar2300);;" + "mar3450 image (*.mar3450);;" + "marccd image (*.marccd));;" + "tiff image (*.tiff);;" "bruker image (*.sfrm)" + ) - def _getSaveFileNameAndFilter(self, parent=None, caption='', directory='', - filter=''): + def _getSaveFileNameAndFilter( + self, parent=None, caption="", directory="", filter="" + ): dialog = qt.QFileDialog(parent, caption=caption, directory=directory) dialog.setAcceptMode(qt.QFileDialog.AcceptSave) dialog.setNameFilter(filter) @@ -272,57 +296,70 @@ def _getSaveFileNameAndFilter(self, parent=None, caption='', directory='', return dialog.selectedFiles()[0], dialog.selectedNameFilter() def save_as(self): - info = self._getSaveFileNameAndFilter(self, "Save active image as", - qt.QDir.currentPath(), - filter=self.tr(self.defaultSaveFilter)) + info = self._getSaveFileNameAndFilter( + self, + "Save active image as", + qt.QDir.currentPath(), + filter=self.tr(self.defaultSaveFilter), + ) if self.data.any(): - if str(info[0]) != '' and str(info[1]) != '': + if str(info[0]) != "" and str(info[1]) != "": format_ = self.extract_format_from_string(str(info[1])) fname = self.add_extention_if_absent(str(info[0]), format_) self.convert_and_write(fname, format_, self.data, self.header) else: - if str(info[0]) != '' and str(info[1]) != '': + if str(info[0]) != "" and str(info[1]) != "": message = "Could not save image as file if no data have been loaded" - qt.QMessageBox.warning(self, 'Warning', message) + qt.QMessageBox.warning(self, "Warning", message) def save_data_series_as_multiple_file(self): - info = self._getSaveFileNameAndFilter(self, "Save data series as multiple files", - qt.QDir.currentPath(), - filter=self.tr(self.defaultSaveFilter)) + info = self._getSaveFileNameAndFilter( + self, + "Save data series as multiple files", + qt.QDir.currentPath(), + filter=self.tr(self.defaultSaveFilter), + ) if self.data_series or self.sequential_file_list: - if str(info[0]) != '' and str(info[1]) != '': + if str(info[0]) != "" and str(info[1]) != "": format_ = self.extract_format_from_string(str(info[1])) fname = self.os.path.splitext(str(info[0]))[0] self.convert_and_write_multiple_files(fname, format_) else: - if str(info[0]) != '' and str(info[1]) != '': + if str(info[0]) != "" and str(info[1]) != "": message = "Could not save image as file if no data have been loaded" - qt.QMessageBox.warning(self, 'Warning', message) + qt.QMessageBox.warning(self, "Warning", message) def save_data_series_as_singlehdf(self): - info = self._getSaveFileNameAndFilter(self, "Save data series as single high density file", - qt.QDir.currentPath(), - filter=self.tr("HDF5 archive (*.h5)")) + info = self._getSaveFileNameAndFilter( + self, + "Save data series as single high density file", + qt.QDir.currentPath(), + filter=self.tr("HDF5 archive (*.h5)"), + ) if self.data_series or self.sequential_file_list: - if str(info[0]) != '' and str(info[1]) != '': + if str(info[0]) != "" and str(info[1]) != "": format_ = self.extract_format_from_string(str(info[1])) fname = self.add_extention_if_absent(str(info[0]), format_) - if format_ == '*.h5': + if format_ == "*.h5": self.convert_and_save_to_h5(fname) else: - qt.QMessageBox.warning(self, 'Warning', "Unknown format: %s" % format_) + qt.QMessageBox.warning( + self, "Warning", "Unknown format: %s" % format_ + ) return else: - if str(info[0]) != '' and str(info[1]) != '': + if str(info[0]) != "" and str(info[1]) != "": message = "Could not save image as file if no data have been loaded" - qt.QMessageBox.warning(self, 'Warning', message) + qt.QMessageBox.warning(self, "Warning", message) def convert_and_save_to_h5(self, fname): """ Save a stack as Nexus entry (create a new entry in the file each time) """ with Nexus(fname) as nxs: - entry = nxs.new_entry(entry="entry", program_name="fabio_viewer", title="FabIO Viewer") + entry = nxs.new_entry( + entry="entry", program_name="fabio_viewer", title="FabIO Viewer" + ) nxdata = nxs.new_class(entry, "fabio", class_type="NXdata") # Read shape: if self.sequential_file_mode: @@ -340,11 +377,13 @@ def convert_and_save_to_h5(self, fname): else: tmpdata = data shape = tmpdata.shape - dataset = nxdata.create_dataset("data", - shape=(total,) + shape, - dtype=numpy.float32, - chunks=(1,) + shape, - compression="gzip") + dataset = nxdata.create_dataset( + "data", + shape=(total,) + shape, + dtype=numpy.float32, + chunks=(1,) + shape, + compression="gzip", + ) dataset.attrs["interpretation"] = "image" dataset.attrs["signal"] = "1" if self.sequential_file_mode: @@ -353,8 +392,8 @@ def convert_and_save_to_h5(self, fname): img = self._open(tmpfname) if img is None: continue - self.progressBar.setValue(int((iid + 1) / total * 100.)) - template = 'Converting and saving file %s. saving file number %d' + self.progressBar.setValue(int((iid + 1) / total * 100.0)) + template = "Converting and saving file %s. saving file number %d" self.log.appendPlainText(template % (tmpfname, iid)) qt.QCoreApplication.processEvents() if self.transform_data_series: @@ -364,8 +403,8 @@ def convert_and_save_to_h5(self, fname): dataset[iid] = tmpdata else: for iid, data in enumerate(self.data_series): - self.log.appendPlainText('Saving file number %d' % iid) - self.progressBar.setValue(int((iid + 1) / total * 100.)) + self.log.appendPlainText("Saving file number %d" % iid) + self.progressBar.setValue(int((iid + 1) / total * 100.0)) qt.QCoreApplication.processEvents() if self.transform_data_series: tmpdata = self.apply_queued_transformations(data) @@ -374,7 +413,7 @@ def convert_and_save_to_h5(self, fname): dataset[iid] = tmpdata self.statusBar().clear() self.progressBar.setValue(0) - self.log.appendPlainText('Hdf5 Recording: Complete') + self.log.appendPlainText("Hdf5 Recording: Complete") def add_extention_if_absent(self, fname, format_): posslash = fname.rfind("/") @@ -385,36 +424,35 @@ def add_extention_if_absent(self, fname, format_): return fname + format_[1:] def convert_and_write(self, fname, format_, data, header): - if format_ == '*.bin': + if format_ == "*.bin": with open(fname, mode="wb") as out: data.tofile(out) return - elif format_ == '*.marccd': + elif format_ == "*.marccd": out = fabio.marccdimage.marccdimage(data=data, header=header) - elif format_ == '*.edf': + elif format_ == "*.edf": out = fabio.edfimage.edfimage(data=data, header=header) - elif format_ == '*.tiff': + elif format_ == "*.tiff": out = fabio.tifimage.tifimage(data=data, header=header) - elif format_ == '*.cbf': + elif format_ == "*.cbf": out = fabio.cbfimage.cbfimage(data=data, header=header) - elif format_ in ['*.mar3450', '*.mar2300']: + elif format_ in ["*.mar3450", "*.mar2300"]: data = self.padd_mar(data, format_) out = fabio.mar345image.mar345image(data=data, header=header) - elif format_ == '*.img': + elif format_ == "*.img": out = fabio.OXDimage.OXDimage(data=data, header=header) - elif format_ == '*.sfrm': + elif format_ == "*.sfrm": out = fabio.brukerimage.brukerimage(data=data, header=header) else: raise Warning("Unknown format: %s" % format_) - template = 'Writing file %s to %s format, please wait...' + template = "Writing file %s to %s format, please wait..." self.statusBar().showMessage(template % (fname, format_[2:])) - self.log.appendPlainText('Writing file %s to %s format' % (fname, format_[2:])) + self.log.appendPlainText("Writing file %s to %s format" % (fname, format_[2:])) qt.QCoreApplication.processEvents() out.write(fname) self.statusBar().clearMessage() def convert_and_write_multiple_files(self, fname, format_): - if self.sequential_file_mode: total = len(self.sequential_file_list) ii = 0 @@ -423,14 +461,14 @@ def convert_and_write_multiple_files(self, fname, format_): img = self._open(tmpfname) if img is None: continue - self.progressBar.setValue(int((ii + 1) / total * 100.)) - self.log.appendPlainText('Converting file %s' % tmpfname) + self.progressBar.setValue(int((ii + 1) / total * 100.0)) + self.log.appendPlainText("Converting file %s" % tmpfname) qt.QCoreApplication.processEvents() if self.transform_data_series: tmpdata = self.apply_queued_transformations(img.data) else: tmpdata = img.data - filename = ('%s_%s%s' % (fname, self.counter_format, format_[1:])) % ii + filename = ("%s_%s%s" % (fname, self.counter_format, format_[1:])) % ii self.convert_and_write(filename, format_, tmpdata, img.header) ii += 1 else: @@ -438,15 +476,15 @@ def convert_and_write_multiple_files(self, fname, format_): for i in range(len(self.data_series)): tmpdata = self.data_series[i] tmpheader = self.header_series[i] - tmpfname = ('%s_%s%s' % (fname, self.counter_format, format_[1:])) % i - self.progressBar.setValue(int((i + 1) / total * 100.)) - self.log.appendPlainText('Converting file %s' % i) + tmpfname = ("%s_%s%s" % (fname, self.counter_format, format_[1:])) % i + self.progressBar.setValue(int((i + 1) / total * 100.0)) + self.log.appendPlainText("Converting file %s" % i) qt.QCoreApplication.processEvents() if self.transform_data_series: tmpdata = self.apply_queued_transformations(tmpdata) self.convert_and_write(tmpfname, format_, tmpdata, tmpheader) self.progressBar.setValue(0) - self.log.appendPlainText('Convertion to %s: Complete' % format_[2:]) + self.log.appendPlainText("Convertion to %s: Complete" % format_[2:]) def extract_format_from_string(self, format_long): for fmt in output_format: @@ -457,53 +495,58 @@ def extract_format_from_string(self, format_long): def horizontal_mirror(self): if self.transform_data_series: if self.sequential_file_mode: - self.transformation_queue.addItem('horizontal_mirror') - self.transform_list += ['horizontal_mirror'] - self.log.appendPlainText('Add horizontal mirror to transformations queue') + self.transformation_queue.addItem("horizontal_mirror") + self.transform_list += ["horizontal_mirror"] + self.log.appendPlainText( + "Add horizontal mirror to transformations queue" + ) qt.QCoreApplication.processEvents() else: total = len(self.data_series) if not total: message = "Could not transform image if no data have been loaded" - qt.QMessageBox.warning(self, 'Warning', message) + qt.QMessageBox.warning(self, "Warning", message) return for i in range(len(self.data_series)): self.data_series[i] = numpy.flipud(self.data_series[i])[:] - self.progressBar.setValue(int((i + 1) / total * 100.)) - self.log.appendPlainText('Applying horizontal mirror to data series: image %d' % i) + self.progressBar.setValue(int((i + 1) / total * 100.0)) + self.log.appendPlainText( + "Applying horizontal mirror to data series: image %d" % i + ) qt.QCoreApplication.processEvents() iid = self.imgDict[str(self.images_list.currentText())] self.select_new_image(None, imgID=iid) else: - if self.data.any(): self.data = numpy.flipud(self.data)[:] iid = self.imgDict[str(self.images_list.currentText())] self.data_series[iid] = self.data[:] - self.log.appendPlainText('Applying horizontal mirror to current data') + self.log.appendPlainText("Applying horizontal mirror to current data") self.on_draw() else: message = "Could not transform image if no data have been loaded" - qt.QMessageBox.warning(self, 'Warning', message) + qt.QMessageBox.warning(self, "Warning", message) self.progressBar.setValue(0) def vertical_mirror(self): if self.transform_data_series: if self.sequential_file_mode: - self.transformation_queue.addItem('vertical_mirror') - self.transform_list += ['vertical_mirror'] - self.log.appendPlainText('Add vertical mirror to transformations queue') + self.transformation_queue.addItem("vertical_mirror") + self.transform_list += ["vertical_mirror"] + self.log.appendPlainText("Add vertical mirror to transformations queue") qt.QCoreApplication.processEvents() else: total = len(self.data_series) if not total: message = "Could not transform image if no data have been loaded" - qt.QMessageBox.warning(self, 'Warning', message) + qt.QMessageBox.warning(self, "Warning", message) return for i in range(len(self.data_series)): self.data_series[i] = numpy.fliplr(self.data_series[i])[:] - self.progressBar.setValue(int((i + 1) / total * 100.)) - self.log.appendPlainText('Applying vertical mirror to data series: image %d' % i) + self.progressBar.setValue(int((i + 1) / total * 100.0)) + self.log.appendPlainText( + "Applying vertical mirror to data series: image %d" % i + ) qt.QCoreApplication.processEvents() iid = self.imgDict[str(self.images_list.currentText())] self.select_new_image(None, imgID=iid) @@ -512,30 +555,32 @@ def vertical_mirror(self): self.data = numpy.fliplr(self.data)[:] iid = self.imgDict[str(self.images_list.currentText())] self.data_series[iid] = self.data[:] - self.log.appendPlainText('Applying vertical mirror to current data') + self.log.appendPlainText("Applying vertical mirror to current data") self.on_draw() else: message = "Could not transform image if no data have been loaded" - qt.QMessageBox.warning(self, 'Warning', message) + qt.QMessageBox.warning(self, "Warning", message) self.progressBar.setValue(0) def transposition(self): if self.transform_data_series: if self.sequential_file_mode: - self.transformation_queue.addItem('transposition') - self.transform_list += ['transposition'] - self.log.appendPlainText('Add transposition to transformations queue') + self.transformation_queue.addItem("transposition") + self.transform_list += ["transposition"] + self.log.appendPlainText("Add transposition to transformations queue") qt.QCoreApplication.processEvents() else: total = len(self.data_series) if not total: message = "Could not transform image if no data have been loaded" - qt.QMessageBox.warning(self, 'Warning', message) + qt.QMessageBox.warning(self, "Warning", message) return for i in range(len(self.data_series)): self.data_series[i] = self.data_series[i].transpose()[:] - self.progressBar.setValue(int((i + 1) / total * 100.)) - self.log.appendPlainText('Applying transposition to data series: image %d' % i) + self.progressBar.setValue(int((i + 1) / total * 100.0)) + self.log.appendPlainText( + "Applying transposition to data series: image %d" % i + ) qt.QCoreApplication.processEvents() iid = self.imgDict[str(self.images_list.currentText())] self.select_new_image(None, imgID=iid) @@ -544,30 +589,32 @@ def transposition(self): self.data = self.data.transpose()[:] iid = self.imgDict[str(self.images_list.currentText())] self.data_series[iid] = self.data[:] - self.log.appendPlainText('Applying transposition to current data') + self.log.appendPlainText("Applying transposition to current data") self.on_draw() else: message = "Could not transform image if no data have been loaded" - qt.QMessageBox.warning(self, 'Warning', message) + qt.QMessageBox.warning(self, "Warning", message) self.progressBar.setValue(0) def rotation_90(self): if self.transform_data_series: if self.sequential_file_mode: - self.transformation_queue.addItem('rotation(+90)') - self.transform_list += ['rotation(+90)'] - self.log.appendPlainText('Add + 90 rotation to transformations queue') + self.transformation_queue.addItem("rotation(+90)") + self.transform_list += ["rotation(+90)"] + self.log.appendPlainText("Add + 90 rotation to transformations queue") qt.QCoreApplication.processEvents() else: total = len(self.data_series) if not total: message = "Could not transform image if no data have been loaded" - qt.QMessageBox.warning(self, 'Warning', message) + qt.QMessageBox.warning(self, "Warning", message) return for i in range(len(self.data_series)): self.data_series[i] = numpy.rot90(self.data_series[i])[:] - self.progressBar.setValue(int((i + 1) / total * 100.)) - self.log.appendPlainText('Applying + 90 rotation to data series: image %d' % i) + self.progressBar.setValue(int((i + 1) / total * 100.0)) + self.log.appendPlainText( + "Applying + 90 rotation to data series: image %d" % i + ) qt.QCoreApplication.processEvents() iid = self.imgDict[str(self.images_list.currentText())] self.select_new_image(None, imgID=iid) @@ -576,30 +623,32 @@ def rotation_90(self): self.data = numpy.rot90(self.data)[:] iid = self.imgDict[str(self.images_list.currentText())] self.data_series[iid] = self.data[:] - self.log.appendPlainText('Applying + 90 rotation to current data') + self.log.appendPlainText("Applying + 90 rotation to current data") self.on_draw() else: message = "Could not transform image if no data have been loaded" - qt.QMessageBox.warning(self, 'Warning', message) + qt.QMessageBox.warning(self, "Warning", message) self.progressBar.setValue(0) def rotation_180(self): if self.transform_data_series: if self.sequential_file_mode: - self.transformation_queue.addItem('rotation(+180)') - self.transform_list += ['rotation(+180)'] - self.log.appendPlainText('Add + 180 rotation to transformations queue') + self.transformation_queue.addItem("rotation(+180)") + self.transform_list += ["rotation(+180)"] + self.log.appendPlainText("Add + 180 rotation to transformations queue") qt.QCoreApplication.processEvents() else: total = len(self.data_series) if not total: message = "Could not transform image if no data have been loaded" - qt.QMessageBox.warning(self, 'Warning', message) + qt.QMessageBox.warning(self, "Warning", message) return for i in range(len(self.data_series)): self.data_series[i] = numpy.rot90(self.data_series[i], 2)[:] - self.progressBar.setValue(int((i + 1) / total * 100.)) - self.log.appendPlainText('Applying + 180 rotation to data series: image %d' % i) + self.progressBar.setValue(int((i + 1) / total * 100.0)) + self.log.appendPlainText( + "Applying + 180 rotation to data series: image %d" % i + ) qt.QCoreApplication.processEvents() iid = self.imgDict[str(self.images_list.currentText())] self.select_new_image(None, imgID=iid) @@ -608,30 +657,32 @@ def rotation_180(self): self.data = numpy.rot90(self.data, 2)[:] iid = self.imgDict[str(self.images_list.currentText())] self.data_series[iid] = self.data[:] - self.log.appendPlainText('Applying + 180 rotation to current data') + self.log.appendPlainText("Applying + 180 rotation to current data") self.on_draw() else: message = "Could not transform image if no data have been loaded" - qt.QMessageBox.warning(self, 'Warning', message) + qt.QMessageBox.warning(self, "Warning", message) self.progressBar.setValue(0) def rotation_270(self): if self.transform_data_series: if self.sequential_file_mode: - self.transformation_queue.addItem('rotation(-90)') - self.transform_list += ['rotation(-90)'] - self.log.appendPlainText('Add - 90 rotation to transformations queue') + self.transformation_queue.addItem("rotation(-90)") + self.transform_list += ["rotation(-90)"] + self.log.appendPlainText("Add - 90 rotation to transformations queue") qt.QCoreApplication.processEvents() else: total = len(self.data_series) if not total: message = "Could not transform image if no data have been loaded" - qt.QMessageBox.warning(self, 'Warning', message) + qt.QMessageBox.warning(self, "Warning", message) return for i in range(len(self.data_series)): self.data_series[i] = numpy.rot90(self.data_series[i], 3)[:] - self.progressBar.setValue(int((i + 1) / total * 100.)) - self.log.appendPlainText('Applying - 90 rotation to data series: image %d' % i) + self.progressBar.setValue(int((i + 1) / total * 100.0)) + self.log.appendPlainText( + "Applying - 90 rotation to data series: image %d" % i + ) qt.QCoreApplication.processEvents() iid = self.imgDict[str(self.images_list.currentText())] self.select_new_image(None, imgID=iid) @@ -640,15 +691,15 @@ def rotation_270(self): self.data = numpy.rot90(self.data, 3)[:] iid = self.imgDict[str(self.images_list.currentText())] self.data_series[iid] = self.data[:] - self.log.appendPlainText('Applying - 90 rotation to current data') + self.log.appendPlainText("Applying - 90 rotation to current data") self.on_draw() else: message = "Could not transform image if no data have been loaded" - qt.QMessageBox.warning(self, 'Warning', message) + qt.QMessageBox.warning(self, "Warning", message) self.progressBar.setValue(0) def mask(self): - message = 'Select and import a boolean mask from binary data block file' + message = "Select and import a boolean mask from binary data block file" fname = qt.QFileDialog.getOpenFileName(self, message) if isinstance(fname, tuple): # PyQt5 compatibility @@ -659,34 +710,41 @@ def mask(self): dial = BinDialog(self) dim1, dim2, offset, bytecode, endian = dial.exec_() if dim1 is not None and dim2 is not None: - if endian == 'Short': - endian = '<' + if endian == "Short": + endian = "<" else: - endian = '>' + endian = ">" img = fabio.binaryimage.binaryimage() img.read(fname, dim1, dim2, offset, bytecode, endian) self.mask = img.data[:] if self.transform_data_series: if self.sequential_file_mode: - self.transformation_queue.addItem('masking') - self.transform_list += ['masking'] - self.log.appendPlainText('Add masking to transformations queue') + self.transformation_queue.addItem("masking") + self.transform_list += ["masking"] + self.log.appendPlainText("Add masking to transformations queue") qt.QCoreApplication.processEvents() else: total = len(self.data_series) if not total: - message = "Could not transform image if no data have been loaded" - qt.QMessageBox.warning(self, 'Warning', message) + message = ( + "Could not transform image if no data have been loaded" + ) + qt.QMessageBox.warning(self, "Warning", message) return for i in range(len(self.data_series)): if self.data_series[i].shape != self.mask.shape: - message = "Mask and image have different shapes, skipping image %d" % i - qt.QMessageBox.warning(self, 'Warning', message) + message = ( + "Mask and image have different shapes, skipping image %d" + % i + ) + qt.QMessageBox.warning(self, "Warning", message) self.log.appendPlainText(message) else: self.data_series[i] = self.mask * self.data_series[i] - self.progressBar.setValue(int((i + 1) / total * 100.)) - self.log.appendPlainText('Applying mask to data series: image %d' % i) + self.progressBar.setValue(int((i + 1) / total * 100.0)) + self.log.appendPlainText( + "Applying mask to data series: image %d" % i + ) qt.QCoreApplication.processEvents() iid = self.imgDict[str(self.images_list.currentText())] self.select_new_image(None, imgID=iid) @@ -696,46 +754,55 @@ def mask(self): iid = self.imgDict[str(self.images_list.currentText())] self.data_series[iid] = self.data[:] self.on_draw() - message = 'Binary boolean mask loaded and applied' + message = "Binary boolean mask loaded and applied" self.statusBar().showMessage(message, 2000) self.log.appendPlainText(message) qt.QCoreApplication.processEvents() else: - message = "Could not transform image if no data have been loaded" - qt.QMessageBox.warning(self, 'Warning', message) + message = ( + "Could not transform image if no data have been loaded" + ) + qt.QMessageBox.warning(self, "Warning", message) else: return self.progressBar.setValue(0) def apply_queued_transformations(self, data): - transformations = ['horizontal_mirror', 'vertical_mirror', 'transposition', - 'rotation(+90)', 'rotation(+180)', 'rotation(-90)', - 'masking', 'downsampling'] + transformations = [ + "horizontal_mirror", + "vertical_mirror", + "transposition", + "rotation(+90)", + "rotation(+180)", + "rotation(-90)", + "masking", + "downsampling", + ] for t in self.transform_list: - if t in transformations: - if t == 'horizontal_mirror': - data = numpy.flipud(data)[:] - self.log.appendPlainText('horizontal_mirror Done') - elif t == 'vertical_mirror': - data = numpy.fliplr(data)[:] - self.log.appendPlainText('vertical_mirror Done') - elif t == 'transposition': - data = data.transpose()[:] - self.log.appendPlainText('transposition Done') - elif t == 'rotation(+90)': - data = numpy.rot90(data)[:] - self.log.appendPlainText('rotation(+90) Done') - elif t == 'rotation(+180)': - data = numpy.rot90(data, 2)[:] - self.log.appendPlainText('rotation(+180) Done') - elif t == 'rotation(-90)': - data = numpy.rot90(data, 3)[:] - self.log.appendPlainText('rotation(-90) Done') - elif t == 'masking': - data = self.mask * data - self.log.appendPlainText('masking Done') - else: - raise Warning('Unknown transformation %s' % t) + if t in transformations: + if t == "horizontal_mirror": + data = numpy.flipud(data)[:] + self.log.appendPlainText("horizontal_mirror Done") + elif t == "vertical_mirror": + data = numpy.fliplr(data)[:] + self.log.appendPlainText("vertical_mirror Done") + elif t == "transposition": + data = data.transpose()[:] + self.log.appendPlainText("transposition Done") + elif t == "rotation(+90)": + data = numpy.rot90(data)[:] + self.log.appendPlainText("rotation(+90) Done") + elif t == "rotation(+180)": + data = numpy.rot90(data, 2)[:] + self.log.appendPlainText("rotation(+180) Done") + elif t == "rotation(-90)": + data = numpy.rot90(data, 3)[:] + self.log.appendPlainText("rotation(-90) Done") + elif t == "masking": + data = self.mask * data + self.log.appendPlainText("masking Done") + else: + raise Warning("Unknown transformation %s" % t) return data def transformation_options(self): @@ -752,18 +819,22 @@ def downsample(self): dial = DownSamplingDialog() thick, start_angle, step_angle = dial.exec_() if thick is not None: - info = self._getSaveFileNameAndFilter(self, - "Save downsampled data series as multiple files", - qt.QDir.currentPath(), - filter=self.tr(self.defaultSaveFilter)) + info = self._getSaveFileNameAndFilter( + self, + "Save downsampled data series as multiple files", + qt.QDir.currentPath(), + filter=self.tr(self.defaultSaveFilter), + ) if self.data_series or self.sequential_file_list: - if str(info[0]) != '' and str(info[1]) != '': + if str(info[0]) != "" and str(info[1]) != "": format_ = self.extract_format_from_string(str(info[1])) fname = self.os.path.splitext(str(info[0]))[0] if self.sequential_file_mode: total = len(self.sequential_file_list) - img = fabio.open(self.sequential_file_dict[self.sequential_file_list[0]]) + img = fabio.open( + self.sequential_file_dict[self.sequential_file_list[0]] + ) stack = numpy.zeros_like(img.data) t0 = time.time() subtotal = (total // thick) * thick @@ -776,23 +847,37 @@ def downsample(self): if img is None: continue if img.data.shape != stack.shape: - message = "Error image shape: %s summed data shape: %s" % (img.data.shape, stack.shape) + message = ( + "Error image shape: %s summed data shape: %s" + % (img.data.shape, stack.shape) + ) self.log.appendPlainText(message) continue numpy.add(stack, img.data, stack) - self.progressBar.setValue(int((i + 1) / subtotal * 100.)) - self.log.appendPlainText('File %s stacked' % imgkey) + self.progressBar.setValue(int((i + 1) / subtotal * 100.0)) + self.log.appendPlainText("File %s stacked" % imgkey) qt.QCoreApplication.processEvents() if j == thick - 1: - self.log.appendPlainText('stack number %d summing up' % k) + self.log.appendPlainText( + "stack number %d summing up" % k + ) qt.QCoreApplication.processEvents() - if format_ in ['*.mar3450', '*.mar2300']: - img.header["PHI_START"] = '%.3f' % (start_angle + step_angle * (i - thick + 1)) - img.header["PHI_END"] = '%.3f' % (start_angle + step_angle * (i)) - filename = ('%s_%s%s' % (fname, self.counter_format, format_[1:])) % k - self.convert_and_write(filename, format_, stack, img.header) + if format_ in ["*.mar3450", "*.mar2300"]: + img.header["PHI_START"] = "%.3f" % ( + start_angle + step_angle * (i - thick + 1) + ) + img.header["PHI_END"] = "%.3f" % ( + start_angle + step_angle * (i) + ) + filename = ( + "%s_%s%s" + % (fname, self.counter_format, format_[1:]) + ) % k + self.convert_and_write( + filename, format_, stack, img.header + ) t1 = time.time() - print('time: %s' % (t1 - t0)) + print("time: %s" % (t1 - t0)) stack = numpy.zeros_like(img.data) t0 = time.time() else: @@ -804,29 +889,43 @@ def downsample(self): k = i // thick data = self.data_series[i] if data.shape != stack.shape: - message = "Error image shape: %s summed data shape: %s" % (img.data.shape, stack.shape) + message = ( + "Error image shape: %s summed data shape: %s" + % (img.data.shape, stack.shape) + ) self.log.appendPlainText(message) continue numpy.add(stack, data, stack) - self.progressBar.setValue(int((i + 1) / subtotal * 100.)) - self.log.appendPlainText('File number %d stacked' % i) + self.progressBar.setValue(int((i + 1) / subtotal * 100.0)) + self.log.appendPlainText("File number %d stacked" % i) qt.QCoreApplication.processEvents() if j == thick - 1: - self.log.appendPlainText('stack number %d summing up' % k) + self.log.appendPlainText( + "stack number %d summing up" % k + ) qt.QCoreApplication.processEvents() - if format_ in ['*.mar3450', '*.mar2300']: - self.header_series[i]["PHI_START"] = '%.3f' % (start_angle + step_angle * (i - thick + 1)) - self.header_series[i]["PHI_END"] = '%.3f' % (start_angle + step_angle * (i)) - filename = ('%s_%s%s' % (fname, self.counter_format, format_[1:])) % k - self.convert_and_write(filename, format_, stack, self.header_series[i]) + if format_ in ["*.mar3450", "*.mar2300"]: + self.header_series[i]["PHI_START"] = "%.3f" % ( + start_angle + step_angle * (i - thick + 1) + ) + self.header_series[i]["PHI_END"] = "%.3f" % ( + start_angle + step_angle * (i) + ) + filename = ( + "%s_%s%s" + % (fname, self.counter_format, format_[1:]) + ) % k + self.convert_and_write( + filename, format_, stack, self.header_series[i] + ) stack = numpy.zeros_like(data) self.progressBar.setValue(0) - self.log.appendPlainText('Downsampling: Complete') + self.log.appendPlainText("Downsampling: Complete") qt.QCoreApplication.processEvents() else: - if str(info[0]) != '' and str(info[1]) != '': + if str(info[0]) != "" and str(info[1]) != "": message = "Could not save image as file if no data have been loaded" - qt.QMessageBox.warning(self, 'Warning', message) + qt.QMessageBox.warning(self, "Warning", message) def select_new_image(self, name, imgID=None): if imgID is not None: @@ -845,20 +944,27 @@ def on_pick(self, event): y = int(round(event.ydata)) if x < self.data.shape[1] and y < self.data.shape[0]: i = self.data[y, x] - self.pix_coords_label.setText("Pixel coordinates and intensity: x =%6d, y =%6d, i =%6g" % (x, y, i)) + self.pix_coords_label.setText( + "Pixel coordinates and intensity: x =%6d, y =%6d, i =%6g" + % (x, y, i) + ) else: - self.pix_coords_label.setText("Pixel coordinates and intensity: x = None , y = None , i = None ") + self.pix_coords_label.setText( + "Pixel coordinates and intensity: x = None , y = None , i = None " + ) else: - self.pix_coords_label.setText("Pixel coordinates and intensity: x = None , y = None , i = None ") + self.pix_coords_label.setText( + "Pixel coordinates and intensity: x = None , y = None , i = None " + ) def on_draw(self): - """ Redraws the figure""" - self.statusBar().showMessage('Loading display...') + """Redraws the figure""" + self.statusBar().showMessage("Loading display...") qt.QCoreApplication.processEvents() # clear the axes and redraw a new plot self.axes.clear() # self.axes.imshow(numpy.log(numpy.clip(self.data,1.0e-12,1.0e260) ),interpolation = 'nearest') - self.axes.imshow(numpy.log(self.data), interpolation='nearest') + self.axes.imshow(numpy.log(self.data), interpolation="nearest") self.axes.set_visible(True) if self.axes.get_ylim()[0] < self.axes.get_ylim()[1]: @@ -871,10 +977,12 @@ def batch_to_view(self): iid = 0 item = items[0] item = str(item.text()) - hdfxtens = ['.h5', '.H5', '.hdf', '.HDF', 'hdf5', '.HDF5'] + hdfxtens = [".h5", ".H5", ".hdf", ".HDF", "hdf5", ".HDF5"] for xtens in hdfxtens: if xtens in item: - qt.QMessageBox.warning(self, 'Message', "Can't display hdf archive from batch mode ") + qt.QMessageBox.warning( + self, "Message", "Can't display hdf archive from batch mode " + ) return if self.sequential_file_mode: self.data_series = [] @@ -884,8 +992,10 @@ def batch_to_view(self): self.headerTextEdit.clear() self.axes.clear() self.canvas.draw() - self.statusBar().showMessage('Import image %s in the View Mode tab, please wait...' % item) - self.log.appendPlainText('Import image %s in the View Mode tab' % item) + self.statusBar().showMessage( + "Import image %s in the View Mode tab, please wait..." % item + ) + self.log.appendPlainText("Import image %s in the View Mode tab" % item) qt.QCoreApplication.processEvents() fname = self.sequential_file_dict[item] extract_fname = os.path.splitext(os.path.basename(fname))[0] @@ -928,7 +1038,7 @@ def set_counter_format_option(self): def padd_mar(self, data, format_): dim1, dim2 = data.shape - if format_ == '*.mar2300': + if format_ == "*.mar2300": size = 2300 else: size = 3450 @@ -979,11 +1089,10 @@ def sequential_option(self, state): self.filecheckBox.setCheckState(False) self.sequential_file_mode = False self.filecheckBox.stateChanged.connect(self.sequential_option) - message = 'Sequential file mode is not compatible with hdf5 input file: option removed' - qt.QMessageBox.warning(self, 'Message', message) + message = "Sequential file mode is not compatible with hdf5 input file: option removed" + qt.QMessageBox.warning(self, "Message", message) def create_main_frame(self): - self.tabWidget = qt.QTabWidget() tab1 = qt.QWidget() self.tabWidget.addTab(tab1, "View Mode") @@ -1008,18 +1117,20 @@ def create_main_frame(self): self.axes = self.fig.add_subplot(111) self.axes.set_visible(False) # Bind the 'pick' event for clicking on one of the bars - self.canvas.mpl_connect('motion_notify_event', self.on_pick) + self.canvas.mpl_connect("motion_notify_event", self.on_pick) # Create the navigation toolbar, tied to the canvas self.mpl_toolbar = NavigationToolbar2QT(self.canvas, tab1, coordinates=False) # Other GUI controls - selector_label = qt.QLabel('Active Image:') + selector_label = qt.QLabel("Active Image:") self.images_list = qt.QComboBox(self) self.images_list.activated[str].connect(self.select_new_image) viewer_label = qt.QLabel("Images Viewer: ", self) - self.pix_coords_label = qt.QLabel("Pixel coordinates and intensity: x = None , y = None , i = None ", self) + self.pix_coords_label = qt.QLabel( + "Pixel coordinates and intensity: x = None , y = None , i = None ", self + ) self.mpl_toolbar.addWidget(self.pix_coords_label) self.headerTextEdit = qt.QPlainTextEdit(tab1) @@ -1061,7 +1172,7 @@ def create_main_frame(self): imagelistlabel = qt.QLabel(tab2) imagelistlabel.setText("Images List:") self.imagelistWidget = qt.QListWidget(tab2) - import_view_button = qt.QPushButton('Export image to View Mode', tab2) + import_view_button = qt.QPushButton("Export image to View Mode", tab2) import_view_button.clicked.connect(self.batch_to_view) imagelistvbox.addWidget(imagelistlabel) @@ -1083,7 +1194,9 @@ def create_main_frame(self): self.butttonGroup.addButton(self.filecheckBox) self.filecheckBox2 = qt.QCheckBox() - self.filecheckBox2.setText("Direct access (all images are store in memory simultaneously)") + self.filecheckBox2.setText( + "Direct access (all images are store in memory simultaneously)" + ) self.filecheckBox2.setChecked(True) self.butttonGroup.addButton(self.filecheckBox2) @@ -1104,7 +1217,7 @@ def create_main_frame(self): queuebox = qt.QVBoxLayout() self.transformation_queue = qt.QListWidget(tab2) queuebox.addWidget(self.transformation_queue) - clear_trans_list_button = qt.QPushButton('Clear Transformation List', tab2) + clear_trans_list_button = qt.QPushButton("Clear Transformation List", tab2) clear_trans_list_button.clicked.connect(self.clear_transform_list) queuebox.addWidget(clear_trans_list_button) @@ -1146,18 +1259,20 @@ def create_main_frame(self): self.setCentralWidget(self.tabWidget) def create_status_bar(self): - self.status_text = qt.QLabel('') + self.status_text = qt.QLabel("") self.statusBar().addWidget(self.status_text, 1) - self.statusBar().showMessage('Thanks for using FabIO viewer.', 5000) + self.statusBar().showMessage("Thanks for using FabIO viewer.", 5000) def on_about(self): - msg = [__doc__, - "", - "Version: \t\t%s" % __version__, - "FabIO version: \t%s" % fabio.version, - "Author: \t\t%s" % __author__, - "Copyright: \t\t%s" % __copyright__, - "License: \t\t%s" % __licence__] + msg = [ + __doc__, + "", + "Version: \t\t%s" % __version__, + "FabIO version: \t%s" % fabio.version, + "Author: \t\t%s" % __author__, + "Copyright: \t\t%s" % __copyright__, + "License: \t\t%s" % __licence__, + ] qt.QMessageBox.about(self, "About FabIO Viewer", os.linesep.join(msg)) @@ -1165,123 +1280,147 @@ def create_menu(self): self.file_menu = self.menuBar().addMenu("&File") self.open_menu = self.file_menu.addMenu("&Open") - action = self.create_action("&Image(s)", - shortcut="", - slot=self.open_data_series, - tip="Load single file and data series (files sequence)") + action = self.create_action( + "&Image(s)", + shortcut="", + slot=self.open_data_series, + tip="Load single file and data series (files sequence)", + ) self.add_actions(self.open_menu, (action,)) - action = self.create_action("&Hdf5 data series", - shortcut="", - slot=self.open_h5_data_series, - tip="Load single file and data series (files sequence)") + action = self.create_action( + "&Hdf5 data series", + shortcut="", + slot=self.open_h5_data_series, + tip="Load single file and data series (files sequence)", + ) self.add_actions(self.open_menu, (action,)) self.save_as_menu = self.file_menu.addMenu("&Save") - action = self.create_action("&Active image", - slot=self.save_as, - shortcut="", - tip="Save/Convert the image which is currently displayed") + action = self.create_action( + "&Active image", + slot=self.save_as, + shortcut="", + tip="Save/Convert the image which is currently displayed", + ) self.add_actions(self.save_as_menu, (action,)) self.save_data_series_menu = self.save_as_menu.addMenu("&Data series as") - action = self.create_action("&Multiple files", - slot=self.save_data_series_as_multiple_file, - shortcut="", - tip="Save/Convert the set of images currently loaded into the images list") + action = self.create_action( + "&Multiple files", + slot=self.save_data_series_as_multiple_file, + shortcut="", + tip="Save/Convert the set of images currently loaded into the images list", + ) self.add_actions(self.save_data_series_menu, (action,)) - action = self.create_action("&Hdf5 archive", - slot=self.save_data_series_as_singlehdf, - shortcut="", - tip="Save/Convert the set of images currently loaded into the images list") + action = self.create_action( + "&Hdf5 archive", + slot=self.save_data_series_as_singlehdf, + shortcut="", + tip="Save/Convert the set of images currently loaded into the images list", + ) self.add_actions(self.save_data_series_menu, (action,)) - action = self.create_action("&Quit", - slot=self.close, - shortcut="Ctrl+Q", - tip="Close the application") + action = self.create_action( + "&Quit", slot=self.close, shortcut="Ctrl+Q", tip="Close the application" + ) self.add_actions(self.file_menu, (action,)) self.transform_menu = self.menuBar().addMenu("&Transform") self.mirror_menu = self.transform_menu.addMenu("&Mirror") - action = self.create_action("&Horizontal", - shortcut='', - slot=self.horizontal_mirror, - tip="Horizontal mirror") + action = self.create_action( + "&Horizontal", + shortcut="", + slot=self.horizontal_mirror, + tip="Horizontal mirror", + ) self.add_actions(self.mirror_menu, (action,)) - action = self.create_action("&Vertical", - shortcut='', - slot=self.vertical_mirror, - tip="Vertical mirror") + action = self.create_action( + "&Vertical", shortcut="", slot=self.vertical_mirror, tip="Vertical mirror" + ) self.add_actions(self.mirror_menu, (action,)) - action = self.create_action("&Transposition", - shortcut='', - slot=self.transposition, - tip="Transposition") + action = self.create_action( + "&Transposition", shortcut="", slot=self.transposition, tip="Transposition" + ) self.add_actions(self.mirror_menu, (action,)) self.rotation_menu = self.transform_menu.addMenu("&Rotation") - action = self.create_action("+90", - shortcut='', - slot=self.rotation_90, - tip="Rotation of +90 degrees (counter-clockwise)") + action = self.create_action( + "+90", + shortcut="", + slot=self.rotation_90, + tip="Rotation of +90 degrees (counter-clockwise)", + ) self.add_actions(self.rotation_menu, (action,)) - action = self.create_action("+180", - shortcut='', - slot=self.rotation_180, - tip="Rotation of +180 degrees (counter-clockwise)") + action = self.create_action( + "+180", + shortcut="", + slot=self.rotation_180, + tip="Rotation of +180 degrees (counter-clockwise)", + ) self.add_actions(self.rotation_menu, (action,)) - action = self.create_action("- 90", - shortcut='', - slot=self.rotation_270, - tip="Rotation of -90 degrees (counter-clockwise)") + action = self.create_action( + "- 90", + shortcut="", + slot=self.rotation_270, + tip="Rotation of -90 degrees (counter-clockwise)", + ) self.add_actions(self.rotation_menu, (action,)) - action = self.create_action("&Mask", - shortcut='', - slot=self.mask, - tip="Import a mask from file and apply it to image(s)") + action = self.create_action( + "&Mask", + shortcut="", + slot=self.mask, + tip="Import a mask from file and apply it to image(s)", + ) self.add_actions(self.transform_menu, (action,)) - action = self.create_action("&Downsample", - shortcut='', - slot=self.downsample, - tip="Summation over groups of images") + action = self.create_action( + "&Downsample", + shortcut="", + slot=self.downsample, + tip="Summation over groups of images", + ) self.add_actions(self.transform_menu, (action,)) - tip = "Define if transformations are applied to the whole data series (checked)"\ + tip = ( + "Define if transformations are applied to the whole data series (checked)" " or only to the active image (unchecked) " - action = self.create_action("&Apply transform to the whole data series", - shortcut='', - slot=self.transformation_options, - tip=tip) + ) + action = self.create_action( + "&Apply transform to the whole data series", + shortcut="", + slot=self.transformation_options, + tip=tip, + ) action.setCheckable(True) self.add_actions(self.transform_menu, (action,)) self.transform_option_action = action self.options = self.menuBar().addMenu("&Options") - action = self.create_action("&Counter format", - shortcut='', - slot=self.set_counter_format_option, - tip='Allow to define the format for the counter in multiple saving') + action = self.create_action( + "&Counter format", + shortcut="", + slot=self.set_counter_format_option, + tip="Allow to define the format for the counter in multiple saving", + ) self.add_actions(self.options, (action,)) self.help_menu = self.menuBar().addMenu("&Help") - action = self.create_action("&About", - shortcut='F1', - slot=self.on_about, - tip='About Images Converter') + action = self.create_action( + "&About", shortcut="F1", slot=self.on_about, tip="About Images Converter" + ) self.add_actions(self.help_menu, (action,)) def add_actions(self, target, actions): @@ -1291,7 +1430,16 @@ def add_actions(self, target, actions): else: target.addAction(action) - def create_action(self, text, slot=None, shortcut=None, icon=None, tip=None, checkable=False, signal="triggered"): + def create_action( + self, + text, + slot=None, + shortcut=None, + icon=None, + tip=None, + checkable=False, + signal="triggered", + ): action = qt.QAction(text, self) if icon is not None: action.setIcon(qt.QIcon(":/%s.png" % icon)) @@ -1308,18 +1456,22 @@ def create_action(self, text, slot=None, shortcut=None, icon=None, tip=None, che return action -class CounterFormatOptionDialog(qt.QDialog): # option doivent refleter l etat des couche du dessous +class CounterFormatOptionDialog( + qt.QDialog +): # option doivent refleter l etat des couche du dessous """Dialog containing entry for down sampling""" def __init__(self, counter_format, parent=None): qt.QDialog.__init__(self, parent) self.resize(350, 100) - self.setWindowTitle('Options') + self.setWindowTitle("Options") self.counter_format = counter_format buttonBox = qt.QDialogButtonBox(self) buttonBox.setGeometry(qt.QRect(0, 60, 341, 32)) buttonBox.setOrientation(qt.Qt.Horizontal) - buttonBox.setStandardButtons(qt.QDialogButtonBox.Cancel | qt.QDialogButtonBox.Ok) + buttonBox.setStandardButtons( + qt.QDialogButtonBox.Cancel | qt.QDialogButtonBox.Ok + ) label = qt.QLabel(self) label.setGeometry(qt.QRect(38, 23, 181, 16)) @@ -1334,11 +1486,11 @@ def __init__(self, counter_format, parent=None): def exec_(self): if qt.QDialog.exec_(self) == qt.QDialog.Accepted: - if str(self.lineEdit.text()) != '': + if str(self.lineEdit.text()) != "": return str(self.lineEdit.text()) else: message = "All informations are mandatory, please fill the blanks" - qt.QMessageBox.warning(self, 'Warning', message) + qt.QMessageBox.warning(self, "Warning", message) else: return self.counter_format @@ -1349,11 +1501,13 @@ class DownSamplingDialog(qt.QDialog): def __init__(self, parent=None): qt.QDialog.__init__(self, parent) self.resize(407, 250) - self.setWindowTitle('Downsampling') + self.setWindowTitle("Downsampling") buttonBox = qt.QDialogButtonBox(self) buttonBox.setGeometry(qt.QRect(45, 200, 341, 32)) buttonBox.setOrientation(qt.Qt.Horizontal) - buttonBox.setStandardButtons(qt.QDialogButtonBox.Cancel | qt.QDialogButtonBox.Ok) + buttonBox.setStandardButtons( + qt.QDialogButtonBox.Cancel | qt.QDialogButtonBox.Ok + ) label = qt.QLabel(self) label.setGeometry(qt.QRect(38, 63, 181, 16)) @@ -1381,11 +1535,19 @@ def __init__(self, parent=None): def exec_(self): if qt.QDialog.exec_(self) == qt.QDialog.Accepted: - if str(self.lineEdit.text()) != '' and str(self.lineEdit2.text()) != '' and str(self.lineEdit3.text()) != '': - return int(str(self.lineEdit.text())), float(str(self.lineEdit2.text())), float(str(self.lineEdit3.text())) + if ( + str(self.lineEdit.text()) != "" + and str(self.lineEdit2.text()) != "" + and str(self.lineEdit3.text()) != "" + ): + return ( + int(str(self.lineEdit.text())), + float(str(self.lineEdit2.text())), + float(str(self.lineEdit3.text())), + ) else: message = "All informations are mandatory, please fill the blanks" - qt.QMessageBox.warning(self, 'Warning', message) + qt.QMessageBox.warning(self, "Warning", message) else: return None, None, None @@ -1407,7 +1569,9 @@ def __init__(self, parent=None): buttonBox = qt.QDialogButtonBox(self) buttonBox.setGeometry(qt.QRect(50, 230, 341, 32)) buttonBox.setOrientation(qt.Qt.Horizontal) - buttonBox.setStandardButtons(qt.QDialogButtonBox.Cancel | qt.QDialogButtonBox.Ok) + buttonBox.setStandardButtons( + qt.QDialogButtonBox.Cancel | qt.QDialogButtonBox.Ok + ) groupBox = qt.QGroupBox(self) groupBox.setGeometry(qt.QRect(10, 10, 370, 191)) @@ -1427,15 +1591,24 @@ def __init__(self, parent=None): label_5.setText("Offset:") self.lineEdit_3 = qt.QLineEdit(self) self.lineEdit_3.setGeometry(qt.QRect(184, 100, 91, 25)) - self.lineEdit_3.setText('0') + self.lineEdit_3.setText("0") label_3 = qt.QLabel(groupBox) label_3.setGeometry(qt.QRect(70, 130, 91, 16)) label_3.setText("ByteCode:") self.comboBox = qt.QComboBox(groupBox) self.comboBox.setGeometry(qt.QRect(173, 123, 91, 25)) - bytecodes = ["int8", "int16", "int32", "int64", - "uint8", "uint16", "uint32", "uint64", - "float32", "float64"] + bytecodes = [ + "int8", + "int16", + "int32", + "int64", + "uint8", + "uint16", + "uint32", + "uint64", + "float32", + "float64", + ] for bytecode in bytecodes: self.comboBox.addItem(bytecode) self.comboBox.setCurrentIndex(2) @@ -1451,13 +1624,17 @@ def __init__(self, parent=None): buttonBox.accepted.connect(self.binary_block_info) def binary_block_info(self): - if str(self.lineEdit.text()) != '' and str(self.lineEdit_2.text()) != '' and str(self.lineEdit_3.text()) != '': + if ( + str(self.lineEdit.text()) != "" + and str(self.lineEdit_2.text()) != "" + and str(self.lineEdit_3.text()) != "" + ): self.dim1 = int(str(self.lineEdit.text())) self.dim2 = int(str(self.lineEdit_2.text())) self.offset = int(str(self.lineEdit_3.text())) else: message = "All informations are mandatory, please fill the blanks" - qt.QMessageBox.warning(self, 'Warning', message) + qt.QMessageBox.warning(self, "Warning", message) return self.bytecode = str(self.comboBox.currentText()) self.endian = str(self.comboBox_2.currentText()) @@ -1474,11 +1651,20 @@ def exec_(self): def main(): - parser = ArgumentParser(prog="fabio_viewer", usage="fabio_viewer img1 img2... imgn", - description=__doc__, - epilog="Based on FabIO version %s" % fabio.version) + parser = ArgumentParser( + prog="fabio_viewer", + usage="fabio_viewer img1 img2... imgn", + description=__doc__, + epilog="Based on FabIO version %s" % fabio.version, + ) parser.add_argument("images", nargs="*") - parser.add_argument("-V", "--version", action='version', version=__version__, help="Print version & quit") + parser.add_argument( + "-V", + "--version", + action="version", + version=__version__, + help="Print version & quit", + ) args = parser.parse_args() qt.QApplication.setStyle(qt.QStyleFactory.create("Cleanlooks")) app = qt.QApplication([]) diff --git a/src/fabio/benchmark/__init__.py b/src/fabio/benchmark/__init__.py index 3e50236a..1433fa4c 100644 --- a/src/fabio/benchmark/__init__.py +++ b/src/fabio/benchmark/__init__.py @@ -24,43 +24,37 @@ """Benchmark for file reading""" __author__ = "Jérôme Kieffer" -__date__ = "10/02/2023" +__date__ = "27/10/2025" __license__ = "MIT" __copyright__ = "2016-2020 European Synchrotron Radiation Facility, Grenoble, France" -import json import sys -import time import timeit import os -import platform -import subprocess -import numpy -import fabio -import os.path as op -import logging +from ..test import utilstest # To use use the locally build version of PyFAI, use ../bootstrap.py try: from .. import open as fabio_open, version, date except ImportError: from fabio import open as fabio_open, version, date -from ..test import utilstest -datasets = ["mb_LP_1_001.img", - "Cr8F8140k103.0026", - "run2_1_00148.cbf", - "F2K_Seb_Lyso0675.edf", - "fit2d_click.msk", - "GE_aSI_detector_image_1529", - "i01f0001.kcd", - "example.mar2300", - "corkcont2_H_0089.mccd", - "b191_1_9_1.img", - "image0001.pgm", - "mgzn-20hpt.img", - "oPPA_5grains_0001.tif", - "XSDataImage.xml", ] +datasets = [ + "mb_LP_1_001.img", + "Cr8F8140k103.0026", + "run2_1_00148.cbf", + "F2K_Seb_Lyso0675.edf", + "fit2d_click.msk", + "GE_aSI_detector_image_1529", + "i01f0001.kcd", + "example.mar2300", + "corkcont2_H_0089.mccd", + "b191_1_9_1.img", + "image0001.pgm", + "mgzn-20hpt.img", + "oPPA_5grains_0001.tif", + "XSDataImage.xml", +] setup = """ import fabio @@ -78,16 +72,22 @@ def run_benchmark(number=10, repeat=3): print(f"Python {sys.version}") print(f"FabIO {version} ({date})") print("#" * 80) - print(" Module filename \t file size \t image size \t read time (ms) \t ms/Mpix") + print( + " Module filename \t file size \t image size \t read time (ms) \t ms/Mpix" + ) for img in datasets: fn = utilstest.UtilsTest.getimage(img) fimg = fabio_open(fn) file_size = os.stat(fn).st_size / 1.0e6 # MB img_size = fimg.data.size / 1.0e6 # Mpix timer = timeit.Timer(stmt % fn, setup + (stmt % fn)) - tmin = min([i / (0.001 * number) for i in timer.repeat(repeat=repeat, number=number)]) - print("%13s %25s %.3f Mb \t %.3f Mpix \t %.3f ms \t %.3f ms/Mpix" % - (fimg.__class__.__name__, img, file_size, img_size, tmin, tmin / img_size)) + tmin = min( + [i / (0.001 * number) for i in timer.repeat(repeat=repeat, number=number)] + ) + print( + "%13s %25s %.3f Mb \t %.3f Mpix \t %.3f ms \t %.3f ms/Mpix" + % (fimg.__class__.__name__, img, file_size, img_size, tmin, tmin / img_size) + ) run = run_benchmark diff --git a/src/fabio/binaryimage.py b/src/fabio/binaryimage.py index a3319fb3..eb61c330 100644 --- a/src/fabio/binaryimage.py +++ b/src/fabio/binaryimage.py @@ -38,12 +38,13 @@ __contact__ = "gael.goret@esrf.fr" __license__ = "MIT" __copyright__ = "European Synchrotron Radiation Facility, Grenoble, France" -__date__ = "30/05/2024" +__date__ = "27/10/2025" import io from .fabioimage import FabioImage import numpy import logging + logger = logging.getLogger(__name__) @@ -70,9 +71,13 @@ def swap_needed(endian): """ Decide if we need to byteswap """ - if (endian == '<' and numpy.little_endian) or (endian == '>' and not numpy.little_endian): + if (endian == "<" and numpy.little_endian) or ( + endian == ">" and not numpy.little_endian + ): return False - if (endian == '>' and numpy.little_endian) or (endian == '<' and not numpy.little_endian): + if (endian == ">" and numpy.little_endian) or ( + endian == "<" and not numpy.little_endian + ): return True def read(self, fname, dim1, dim2, offset=0, bytecode="int32", endian="<"): @@ -87,7 +92,7 @@ def read(self, fname, dim1, dim2, offset=0, bytecode="int32", endian="<"): :param endian: among litte or big endian ("<" or ">") """ - assert endian in ('<', '>', '=') + assert endian in ("<", ">", "=") bytecode = numpy.dtype(bytecode) if not bytecode.str.startswith(endian): bytecode = numpy.dtype(endian + bytecode.str[1:]) @@ -105,10 +110,14 @@ def read(self, fname, dim1, dim2, offset=0, bytecode="int32", endian="<"): try: f.seek(-size + offset + 1, 2) # seek from EOF backwards except IOError: - logger.warning('expected datablock too large, please check bytecode settings: {}'.format(bytecode)) + logger.warning( + "expected datablock too large, please check bytecode settings: {}".format( + bytecode + ) + ) except Exception: logger.debug("Backtrace", exc_info=True) - logger.error('Uncommon error encountered when reading file') + logger.error("Uncommon error encountered when reading file") rawData = f.read(size) data = numpy.frombuffer(rawData, bytecode).copy().reshape(tuple(dims)) self.data = data @@ -121,9 +130,9 @@ def estimate_offset_value(self, fname, dim1, dim2, bytecode="int32"): bpp = len(numpy.array(0, bytecode).tobytes()) size = dim1 * dim2 * bpp totsize = len(f.read()) - logger.info('total size (bytes): %s', totsize) - logger.info('expected data size given parameters (bytes): %s', size) - logger.info('estimation of the offset value (bytes): %s', totsize - size) + logger.info("total size (bytes): %s", totsize) + logger.info("expected data size given parameters (bytes): %s", size) + logger.info("estimation of the offset value (bytes): %s", totsize - size) def write(self, fname): with self._open(fname, mode="wb") as outfile: diff --git a/src/fabio/bruker100image.py b/src/fabio/bruker100image.py index 2c7cab0a..89ccdbc9 100644 --- a/src/fabio/bruker100image.py +++ b/src/fabio/bruker100image.py @@ -29,20 +29,25 @@ # OTHER DEALINGS IN THE SOFTWARE. """Authors: Henning O. Sorensen & Erik Knudsen - Center for Fundamental Research: Metal Structures in Four Dimensions - Risoe National Laboratory - Frederiksborgvej 399 - DK-4000 Roskilde - email:erik.knudsen@risoe.dk +Center for Fundamental Research: Metal Structures in Four Dimensions +Risoe National Laboratory +Frederiksborgvej 399 +DK-4000 Roskilde +email:erik.knudsen@risoe.dk - Jérôme Kieffer, ESRF, Grenoble, France - Sigmund Neher, GWDG, Göttingen, Germany +Jérôme Kieffer, ESRF, Grenoble, France +Sigmund Neher, GWDG, Göttingen, Germany """ -__authors__ = ["Henning O. Sorensen", "Erik Knudsen", "Jon Wright", - "Jérôme Kieffer", "Sigmund Neher"] +__authors__ = [ + "Henning O. Sorensen", + "Erik Knudsen", + "Jon Wright", + "Jérôme Kieffer", + "Sigmund Neher", +] __status__ = "production" __copyright__ = "2007-2009 Risoe National Laboratory; 2015-2020 ESRF, 2016 GWDG" __licence__ = "MIT" @@ -53,16 +58,14 @@ import logging import time import numpy - -logger = logging.getLogger(__name__) - from .brukerimage import BrukerImage from .fabioutils import pad, StringTypes +logger = logging.getLogger(__name__) + def mround(value, multiple=16): - """Round a value up to the multiple of multiple - """ + """Round a value up to the multiple of multiple""" return int(multiple * ceil(value / multiple)) @@ -84,17 +87,19 @@ def _split_data(data, baseline=None): if (mode - mini) < 128: baseline = mini + 1 else: - baseline = mode + 128 # Ensures the mode is in the middle of the uint8 range + baseline = ( + mode + 128 + ) # Ensures the mode is in the middle of the uint8 range elif baseline is False: baseline = 0 use_underflow = False -# else: -# print("Forced baseline", baseline, data.min(), data.max()) + # else: + # print("Forced baseline", baseline, data.min(), data.max()) umask = flat <= baseline if use_underflow and numpy.any(umask): underflow = flat[umask] underflow_max = max(abs(underflow.min()), abs(underflow.max())) - underflow_dtype = numpy.dtype(f"int{mround(numpy.log2(underflow_max)+1,8)}") + underflow_dtype = numpy.dtype(f"int{mround(numpy.log2(underflow_max) + 1, 8)}") underflow = underflow.astype(underflow_dtype) flat -= baseline flat[umask] = 0 @@ -115,19 +120,20 @@ def _split_data(data, baseline=None): else: overflow1 = numpy.array([], dtype=numpy.uint16) data = flat.astype(numpy.uint8).reshape(data.shape) - res = {"data":data, - "baseline": baseline, - "underflow": underflow, - "overflow1": overflow1, - "overflow2": overflow2 - } + res = { + "data": data, + "baseline": baseline, + "underflow": underflow, + "overflow1": overflow1, + "overflow2": overflow2, + } return res def _merge_data(data, baseline=0, underflow=None, overflow1=None, overflow2=None): """ Build an array from the various components - + :param data: probably a uint8 array --> expanded to int32 :param baseline: value of the baseline :param underflow: value of the data below the baseline (any value with 0 are replaced with those values) @@ -145,7 +151,7 @@ def _merge_data(data, baseline=0, underflow=None, overflow1=None, overflow2=None # Use Overflow2 mask = numpy.where(data == 65535) data[mask] = overflow2 - if (underflow is None or underflow.size == 0): + if underflow is None or underflow.size == 0: data += baseline else: mask = data == 0 @@ -155,14 +161,11 @@ def _merge_data(data, baseline=0, underflow=None, overflow1=None, overflow2=None class Bruker100Image(BrukerImage): - DESCRIPTION = "SFRM File format used by Bruker detectors (version 100)" DEFAULT_EXTENSIONS = ["sfrm"] - bpp_to_numpy = {1: numpy.uint8, - 2: numpy.uint16, - 4: numpy.int32} + bpp_to_numpy = {1: numpy.uint8, 2: numpy.uint16, 4: numpy.int32} version = 100 def __init__(self, data=None, header=None): @@ -182,8 +185,8 @@ def _readheader(self, infile): self.__headerstring = infile.read(blocksize * nhdrblks).decode("ASCII") self.header = self.check_header() for i in range(0, nhdrblks * blocksize, line): - if self.__headerstring[i: i + line].find(":") > 0: - key, val = self.__headerstring[i: i + line].split(":", 1) + if self.__headerstring[i : i + line].find(":") > 0: + key, val = self.__headerstring[i : i + line].split(":", 1) key = key.strip() # remove the whitespace (why?) val = val.strip() if key in self.header: @@ -192,13 +195,15 @@ def _readheader(self, infile): else: self.header[key] = val # we must have read this in the first 5*512 bytes. - nhdrblks = int(self.header['HDRBLKS']) - self.header['HDRBLKS'] = nhdrblks + nhdrblks = int(self.header["HDRBLKS"]) + self.header["HDRBLKS"] = nhdrblks # Now read in the rest of the header blocks, appending self.__headerstring += infile.read(blocksize * (nhdrblks - 5)).decode("ASCII") for i in range(5 * blocksize, nhdrblks * blocksize, line): - if self.__headerstring[i: i + line].find(":") > 0: # as for first 512 bytes of header - key, val = self.__headerstring[i: i + line].split(":", 1) + if ( + self.__headerstring[i : i + line].find(":") > 0 + ): # as for first 512 bytes of header + key, val = self.__headerstring[i : i + line].split(":", 1) key = key.strip() val = val.strip() if key in self.header: @@ -206,9 +211,12 @@ def _readheader(self, infile): else: self.header[key] = val # set the image dimensions - shape = int(self.header['NROWS'].split()[0]), int(self.header['NCOLS'].split()[0]) + shape = ( + int(self.header["NROWS"].split()[0]), + int(self.header["NCOLS"].split()[0]), + ) self._shape = shape - self.version = int(self.header.get('FORMAT', "100")) + self.version = int(self.header.get("FORMAT", "100")) def read(self, fname, frame=None): """Read the data. @@ -225,7 +233,7 @@ def read(self, fname, frame=None): with self._open(fname, "rb") as infile: self._readheader(infile) rows, cols = self.shape - npixelb = int(self.header['NPIXELB'].split()[0]) + npixelb = int(self.header["NPIXELB"].split()[0]) # you had to read the Bruker docs to know this! # We are now at the start of the image - assuming bruker._readheader worked @@ -233,29 +241,33 @@ def read(self, fname, frame=None): # The total size is nbytes * nrows * ncolumns. data_size = rows * cols * npixelb -# data_size_padded = mround(data_size, 512) + # data_size_padded = mround(data_size, 512) data_size_padded = data_size raw_data = infile.read(data_size_padded) - data = numpy.frombuffer(raw_data[:data_size], dtype=self.bpp_to_numpy[npixelb]).reshape((rows, cols)) -# self.data = readbytestream(infile, infile.tell(), rows, cols, npixelb, -# datatype="int", signed='n', swap='n') + data = numpy.frombuffer( + raw_data[:data_size], dtype=self.bpp_to_numpy[npixelb] + ).reshape((rows, cols)) + # self.data = readbytestream(infile, infile.tell(), rows, cols, npixelb, + # datatype="int", signed='n', swap='n') if npixelb > 1 and not numpy.little_endian: self.data = data.byteswap() else: self.data = data # now process the overflows - noverfl_values = [int(f) for f in self.header['NOVERFL'].split()] - to_merge = {"data":data, - "underflow": None, - "overflow1": None, - "overflow2": None, - "baseline": None} + noverfl_values = [int(f) for f in self.header["NOVERFL"].split()] + to_merge = { + "data": data, + "underflow": None, + "overflow1": None, + "overflow2": None, + "baseline": None, + } for k, nov in enumerate(noverfl_values): if nov <= 0: continue if k == 0: - bpp = int(self.header['NPIXELB'].split()[1]) - datatype = numpy.dtype(f"int{bpp*8}") + bpp = int(self.header["NPIXELB"].split()[1]) + datatype = numpy.dtype(f"int{bpp * 8}") elif k > 2: break else: @@ -278,7 +290,9 @@ def read(self, fname, frame=None): to_merge["overflow2"] = ar else: break - logger.debug("%s bytes read + %d bytes padding" % (nov * bpp, nbytes - nov * bpp)) + logger.debug( + "%s bytes read + %d bytes padding" % (nov * bpp, nbytes - nov * bpp) + ) # Read baseline if noverfl_values[0] == -1: @@ -305,14 +319,30 @@ def gen_header(self): else: line = key.ljust(7) + ":" if type(value) in StringTypes: - if key == 'NOVERFL': - line += "".join(str(v).ljust(24, ' ') for k, v in enumerate(value.split()) if k < 3) + if key == "NOVERFL": + line += "".join( + str(v).ljust(24, " ") + for k, v in enumerate(value.split()) + if k < 3 + ) elif key == "NPIXELB": - line += "".join(str(v).ljust(36, ' ') for k, v in enumerate(value.split()) if k < 2) + line += "".join( + str(v).ljust(36, " ") + for k, v in enumerate(value.split()) + if k < 2 + ) elif key in ("NROWS", "NCOLS"): - line += "".join(str(v).ljust(36, ' ') for k, v in enumerate(value.split()) if k < 2) + line += "".join( + str(v).ljust(36, " ") + for k, v in enumerate(value.split()) + if k < 2 + ) elif key == "NEXP": - line += "".join(str(v).ljust(72 // 5, ' ') for k, v in enumerate(value.split()) if k < 5) + line += "".join( + str(v).ljust(72 // 5, " ") + for k, v in enumerate(value.split()) + if k < 5 + ) elif key == "DETTYPE": line += str(value) elif key == "CFR": @@ -327,9 +357,9 @@ def gen_header(self): line += str(value) else: for i in range(len(value) // 72): - headers.append((line + str(value[72 * i:72 * (i + 1)]))) + headers.append((line + str(value[72 * i : 72 * (i + 1)]))) line = key.ljust(7) + ":" - line += value[72 * (i + 1):] + line += value[72 * (i + 1) :] elif "__len__" in dir(value): f = "%%.%is" % (72 // len(value) - 1) line += " ".join([f % i for i in value]) @@ -344,7 +374,9 @@ def gen_header(self): if headers[i].startswith("HDRBLKS"): headers[i] = ("HDRBLKS:%s" % self.header["HDRBLKS"]).ljust(80, " ") - res = pad("".join(headers), self.SPACER + "." * 78, 512 * int(self.header["HDRBLKS"])) + res = pad( + "".join(headers), self.SPACER + "." * 78, 512 * int(self.header["HDRBLKS"]) + ) return res def write(self, fname): @@ -359,13 +391,16 @@ def write(self, fname): slope = float(slope) offset = float(offset) except Exception: - logger.warning("Error in converting to float data with linear parameter: %s" % self.header["LINEAR"]) + logger.warning( + "Error in converting to float data with linear parameter: %s" + % self.header["LINEAR"] + ) slope, offset = 1.0, 0.0 else: offset = self.data.min() max_data = self.data.max() - max_range = 2 ** 24 - 1 # similar to the mantissa of a float32 + max_range = 2**24 - 1 # similar to the mantissa of a float32 if max_data > offset: slope = (max_data - offset) / float(max_range) else: @@ -375,17 +410,17 @@ def write(self, fname): else: tmp_data = self.data - if (int(self.header.get("NOVERFL", "-1").split()[0]) == -1): + if int(self.header.get("NOVERFL", "-1").split()[0]) == -1: baseline = False - elif (len(self.header.get("NEXP", "").split()) > 2): + elif len(self.header.get("NEXP", "").split()) > 2: baseline = int(self.header.get("NEXP", "").split()[2]) else: baseline = None split = _split_data(tmp_data, baseline) data = split["data"] - underflow = split['underflow'] - overflow1 = split['overflow1'] - overflow2 = split['overflow2'] + underflow = split["underflow"] + overflow1 = split["overflow1"] + overflow2 = split["overflow2"] if baseline is False: self.header["NOVERFL"] = f"-1 {overflow1.size} {overflow2.size}" self.header["NPIXELB"] = f"{data.dtype.itemsize} 1" @@ -395,46 +430,48 @@ def write(self, fname): else: lst = ["1", "1", "32", "0", "0"] else: - self.header["NOVERFL"] = f"{underflow.size} {overflow1.size} {overflow2.size}" + self.header["NOVERFL"] = ( + f"{underflow.size} {overflow1.size} {overflow2.size}" + ) self.header["NPIXELB"] = f"{data.dtype.itemsize} {underflow.dtype.itemsize}" if "NEXP" in self.header: lst = self.header["NEXP"].split() - lst[2] = str(split['baseline']) + lst[2] = str(split["baseline"]) else: lst = ["1", "1", "0", "0", "0"] self.header["NEXP"] = " ".join(lst) self.header["HDRBLKS"] = "5" if "NROWS" in self.header: - self.header['NROWS'] = self.header['NROWS'].split() + self.header["NROWS"] = self.header["NROWS"].split() else: - self.header['NROWS'] = [None] + self.header["NROWS"] = [None] if "NCOLS" in self.header: - self.header['NCOLS'] = self.header['NCOLS'].split() + self.header["NCOLS"] = self.header["NCOLS"].split() else: - self.header['NCOLS'] = [None] - self.header['NROWS'][0] = str(tmp_data.shape[0]) - self.header['NCOLS'][0] = str(tmp_data.shape[1]) - self.header['NROWS'] = " ".join(self.header['NROWS']) - self.header['NCOLS'] = " ".join(self.header['NCOLS']) + self.header["NCOLS"] = [None] + self.header["NROWS"][0] = str(tmp_data.shape[0]) + self.header["NCOLS"][0] = str(tmp_data.shape[1]) + self.header["NROWS"] = " ".join(self.header["NROWS"]) + self.header["NCOLS"] = " ".join(self.header["NCOLS"]) self.header["FORMAT"] = str(self.version) if "VERSION" not in self.header: self.header["VERSION"] = "16" if "FILENAM" not in self.header: self.header["FILENAM"] = fname if "CREATED" not in self.header: - self.header["CREATED"] = time.strftime('%Y-%m-%d %H:%M:%S') + self.header["CREATED"] = time.strftime("%Y-%m-%d %H:%M:%S") if "TITLE" not in self.header: - self.header["TITLE"] = "\n"*8 + self.header["TITLE"] = "\n" * 8 if "DISTANC" not in self.header: self.header["DISTANC"] = 10 if "CENTER" not in self.header: - self.header["CENTER"] = f"{self.shape[1]/2} {self.shape[0]/2}" + self.header["CENTER"] = f"{self.shape[1] / 2} {self.shape[0] / 2}" if "WAVELEN" not in self.header: self.header["WAVELEN"] = "1.0 1.0 1.0" - if 'MAXXY' not in self.header: + if "MAXXY" not in self.header: argmax = self.data.argmax() width = data.shape[1] - self.header['MAXXY'] = f"{argmax%width} {argmax//width}" + self.header["MAXXY"] = f"{argmax % width} {argmax // width}" if "DETTYPE" not in self.header: self.header["DETTYPE"] = "UNKNOWN" @@ -447,9 +484,9 @@ def write(self, fname): data.tofile(bruker) else: bruker.write(data.tobytes()) -# # 512-Padding -# padded = mround(data.nbytes, 512) -# bruker.write(b"\x00"*(padded - data.nbytes)) + # # 512-Padding + # padded = mround(data.nbytes, 512) + # bruker.write(b"\x00"*(padded - data.nbytes)) for extra in (underflow, overflow1, overflow2): if extra.nbytes: @@ -458,7 +495,7 @@ def write(self, fname): else: bruker.write(extra.tobytes()) padded = mround(extra.nbytes, 16) - bruker.write(b"\x00"*(padded - extra.nbytes)) + bruker.write(b"\x00" * (padded - extra.nbytes)) bruker100image = Bruker100Image diff --git a/src/fabio/brukerimage.py b/src/fabio/brukerimage.py index a528b1e7..49d1c2eb 100644 --- a/src/fabio/brukerimage.py +++ b/src/fabio/brukerimage.py @@ -43,7 +43,7 @@ """ __authors__ = ["Henning O. Sorensen", "Erik Knudsen", "Jon Wright", "Jérôme Kieffer"] -__date__ = "05/01/2021" +__date__ = "27/10/2025" __status__ = "production" __copyright__ = "2007-2009 Risoe National Laboratory; 2010-2020 ESRF" __licence__ = "MIT" @@ -51,13 +51,15 @@ import logging import numpy from math import ceil -import os, io +import os +import io import getpass import time -logger = logging.getLogger(__name__) from .fabioimage import FabioImage from .fabioutils import pad, StringTypes +logger = logging.getLogger(__name__) + class BrukerImage(FabioImage): """ @@ -74,100 +76,99 @@ class BrukerImage(FabioImage): # There is no extension. It is used as frame counter DEFAULT_EXTENSIONS = [] - bpp_to_numpy = {1: numpy.uint8, - 2: numpy.uint16, - 4: numpy.uint32} + bpp_to_numpy = {1: numpy.uint8, 2: numpy.uint16, 4: numpy.uint32} # needed if you feel like writing - see ImageD11/scripts/edf2bruker.py SPACER = "\x1a\x04" # this is CTRL-Z CTRL-D - HEADERS_KEYS = ["FORMAT", # Frame format. Always “86” or "100" for Bruker-format frames. - "VERSION", # Header version #, such as: 1 to 17 (6 is obsolete). - "HDRBLKS", # Header size in 512-byte blocks, such as 10 or 15. Determines where the image block begins. - "TYPE", # String indicating kind of data in the frame. Used to determine if a spatial correction table was applied to the frame imag - "SITE", # Site name - "MODEL", # Diffractometer model - "USER", # Username - "SAMPLE", # Sample ID, - "SETNAME", # Basic data set name - "RUN", # Run number within the data set, usually starts at 0, but 1 for APEX2. - "SAMPNUM", # Specimen number within the data set - "TITLE", # User comments (8 lines) - "NCOUNTS", # Total frame counts - "NOVERFL", # Number of overflows when compression frame. - "MINIMUM", # Minimum counts in a pixel (uncompressed value) - "MAXIMUM", # Maximum counts in a pixel (uncompressed value) - "NONTIME", # Number of on-time events - "NLATE", # Number of late events. Always zero for many detectors. - "FILENAM", # (Original) frame filename - "CREATED", # Date and time of creation - "CUMULAT", # Accumulated frame exposure time in seconds - "ELAPSDR", # Requested time for last exposure in seconds - "ELAPSDA", # Actual time for last exposure in seconds. - "OSCILLA", # Nonzero if acquired by oscillation - "NSTEPS", # steps or oscillations in this frame - "RANGE", # Scan range in decimal degrees (unsigned) - "START", # Starting scan angle value, decimal degrees - "INCREME", # Scan angle increment between frames (signed) - "NUMBER", # Sequence number of this frame in series, usually starts at 0, but 1 for APEX2 - "NFRAMES", # Total number of frames in the series - "ANGLES", # Diffractometer angles in Eulerian space ( 2T, OM, PH, CH). - "NOVER64", # Number of pixels > 64K (actually LinearThreshold value) - "NPIXELB", # Number of bytes/pixel, such as 1, 2, or 4. - "NROWS", # Number of rasters in frame, such as 512, 1024, 2048, or 4096 - "NCOLS", # Number of pixels/raster, such as 512, 1024, 2048 or 4096 - "WORDORD", # Order of bytes in word (0=LSB first) - "LONGORD", # Order of words in a longword (0=LSW first) - "TARGET", # X-ray target material: Cu, Mo, Ag, Fe, Cr, Co, Ni, W, Mn, or other. - "SOURCEK", # X-ray source voltage in kV - "SOURCEM", # X-ray source current in mA - "FILTER", # Filter/monochromator setting: Such as: Parallel, graphite, Ni Filter, C Filter, Zr Filter,Cross coupled Goebel Mirrors ... - "CELL", # Unit cell A,B,C,ALPHA,BETA,GAMMA - "MATRIX", # 9R Orientation matrix (P3 conventions) - "LOWTEMP", # Low temp flag. - "TEMP", # set temperature - "HITEMP", # Acquired at high temperature - "ZOOM", # Zoom: Xc, Yc, Mag used for HI-STAR detectors: 0.5 0.5 1.0 - "CENTER", # X, Y of direct beam at 2-theta = 0. These are raw center for raw frames and unwarped center for unwarped frames. - "DISTANC", # Sample-detector distance, cm (see CmToGrid value) Adds: Sample-detector grid/phosphor distance, cm - "TRAILER", # Byte pointer to trailer info - "COMPRES", # Compression scheme ID, if any. Such as: NONE, LINEAR (Linear scale, offset for pixel values, typically 1.0, 0.0). - "LINEAR", # Linear scale (1.0 0.0 for no change; 0.1 0 for divided by 10...) - "PHD", # Discriminator: Pulse height settings. X100 and X1000 only. Stores CCD phosphor efficiency (first field). - "PREAMP", # Preamp gain setting. X100 and X1000 only. SMART: Stores Roper CCD gain table index value. - "CORRECT", # Flood table correction filename, UNKNOWN or LINEAR. - "WARPFIL", # Brass plate correction filename, UNKNOWN or LINEAR. Note: A filename here does NOT mean that spatial correction was performed. See TYPE and string “UNWARP” to determine that. - "WAVELEN", # Wavelengths (average, a1, a2) - "MAXXY", # X,Y pixel # of maximum counts (from lower corner of 0,0) - "AXIS", # Scan axis ib Eulerian space (1-4 for 2-theta, omega, phi, chi) (0 =none, 2 = default). - "ENDING", # Actual goniometer angles at end of frame in Eulerian space. - "DETPAR", # Detector position corrections (dX,dY,dDist,Pitch,Roll,Yaw) - "LUT", # Recommended display lookup table - "DISPLIM", # Recommended display limits - "PROGRAM", # Name and version of program writing frame, such as: - "ROTATE", # Non zero if acquired by rotation of phi during scan (or oscilate) - "BITMASK", # File name of active pixel mask associated with this frame or $NULL - "OCTMASK", # Octagon mask parameters to use if BITMASK=$null. Min X, Min X+Y, Min Y, Max X-Y, Max X, Max X+Y, Max Y, Max Y-X. - "ESDCELL", # Unit cell parameter standard deviations - "DETTYPE", # Detector or CCD chip type (as displayed on CEU). Default is MULTIWIRE but UNKNOWN is advised, can contain PIXPERCM: CMTOGRID: - "NEXP", # Number of exposures: 1=single, 2=correlated sum.32 for most ccds, and 64 for 2K ccds. - "CCDPARM", # CCD parameters: readnoise, e/ADU, e/photon, bias, full scale - "BIS", # Potential full linear scale if rescan and attenuator used. - "CHEM", # Chemical formula in CIFTAB string, such as “?” - "MORPH", # Crystal morphology in CIFTAB string, such as “?” - "CCOLOR", # Crystal color in CIFTAB string, such as “?” - "CSIZE", # Crystal dimensions (3 ea) in CIFTAB string, such as “?” - "DNSMET", # Density measurement method in CIFTAB string, such as “?” - "DARK", # Name of dark current correction or NONE. - "AUTORNG", # Auto-ranging: gain, high-speed time, scale, offset, full linear scale Note: If full linear scale is zero, then CCDPARM full scale is the full linear scale (BIS frames). - "ZEROADJ", # Goniometer zero corrections (refined in least squares) - "XTRANS", # Crystal XYZ translations (refined in least squares) - "HKL&XY", # HKL and pixel XY for reciprocal space scan. GADDS only. - "AXES2", # Diffractometer setting linear axes (4 ea). (X, Y, Z, Aux) - "ENDING2", # Actual goniometer linear axes @ end of frame. (X, Y, Z, Aux) - "FILTER2", # Monochromator 2-theta angle and monochromator roll angle. v15: Adds beam tilt angle and attenuator factor. - "LEPTOS", # String for LEPTOS. - "CFR", # Only in 21CFRPart11 mode, writes the checksum for header and image (2str).] - ] + HEADERS_KEYS = [ + "FORMAT", # Frame format. Always “86” or "100" for Bruker-format frames. + "VERSION", # Header version #, such as: 1 to 17 (6 is obsolete). + "HDRBLKS", # Header size in 512-byte blocks, such as 10 or 15. Determines where the image block begins. + "TYPE", # String indicating kind of data in the frame. Used to determine if a spatial correction table was applied to the frame imag + "SITE", # Site name + "MODEL", # Diffractometer model + "USER", # Username + "SAMPLE", # Sample ID, + "SETNAME", # Basic data set name + "RUN", # Run number within the data set, usually starts at 0, but 1 for APEX2. + "SAMPNUM", # Specimen number within the data set + "TITLE", # User comments (8 lines) + "NCOUNTS", # Total frame counts + "NOVERFL", # Number of overflows when compression frame. + "MINIMUM", # Minimum counts in a pixel (uncompressed value) + "MAXIMUM", # Maximum counts in a pixel (uncompressed value) + "NONTIME", # Number of on-time events + "NLATE", # Number of late events. Always zero for many detectors. + "FILENAM", # (Original) frame filename + "CREATED", # Date and time of creation + "CUMULAT", # Accumulated frame exposure time in seconds + "ELAPSDR", # Requested time for last exposure in seconds + "ELAPSDA", # Actual time for last exposure in seconds. + "OSCILLA", # Nonzero if acquired by oscillation + "NSTEPS", # steps or oscillations in this frame + "RANGE", # Scan range in decimal degrees (unsigned) + "START", # Starting scan angle value, decimal degrees + "INCREME", # Scan angle increment between frames (signed) + "NUMBER", # Sequence number of this frame in series, usually starts at 0, but 1 for APEX2 + "NFRAMES", # Total number of frames in the series + "ANGLES", # Diffractometer angles in Eulerian space ( 2T, OM, PH, CH). + "NOVER64", # Number of pixels > 64K (actually LinearThreshold value) + "NPIXELB", # Number of bytes/pixel, such as 1, 2, or 4. + "NROWS", # Number of rasters in frame, such as 512, 1024, 2048, or 4096 + "NCOLS", # Number of pixels/raster, such as 512, 1024, 2048 or 4096 + "WORDORD", # Order of bytes in word (0=LSB first) + "LONGORD", # Order of words in a longword (0=LSW first) + "TARGET", # X-ray target material: Cu, Mo, Ag, Fe, Cr, Co, Ni, W, Mn, or other. + "SOURCEK", # X-ray source voltage in kV + "SOURCEM", # X-ray source current in mA + "FILTER", # Filter/monochromator setting: Such as: Parallel, graphite, Ni Filter, C Filter, Zr Filter,Cross coupled Goebel Mirrors ... + "CELL", # Unit cell A,B,C,ALPHA,BETA,GAMMA + "MATRIX", # 9R Orientation matrix (P3 conventions) + "LOWTEMP", # Low temp flag. + "TEMP", # set temperature + "HITEMP", # Acquired at high temperature + "ZOOM", # Zoom: Xc, Yc, Mag used for HI-STAR detectors: 0.5 0.5 1.0 + "CENTER", # X, Y of direct beam at 2-theta = 0. These are raw center for raw frames and unwarped center for unwarped frames. + "DISTANC", # Sample-detector distance, cm (see CmToGrid value) Adds: Sample-detector grid/phosphor distance, cm + "TRAILER", # Byte pointer to trailer info + "COMPRES", # Compression scheme ID, if any. Such as: NONE, LINEAR (Linear scale, offset for pixel values, typically 1.0, 0.0). + "LINEAR", # Linear scale (1.0 0.0 for no change; 0.1 0 for divided by 10...) + "PHD", # Discriminator: Pulse height settings. X100 and X1000 only. Stores CCD phosphor efficiency (first field). + "PREAMP", # Preamp gain setting. X100 and X1000 only. SMART: Stores Roper CCD gain table index value. + "CORRECT", # Flood table correction filename, UNKNOWN or LINEAR. + "WARPFIL", # Brass plate correction filename, UNKNOWN or LINEAR. Note: A filename here does NOT mean that spatial correction was performed. See TYPE and string “UNWARP” to determine that. + "WAVELEN", # Wavelengths (average, a1, a2) + "MAXXY", # X,Y pixel # of maximum counts (from lower corner of 0,0) + "AXIS", # Scan axis ib Eulerian space (1-4 for 2-theta, omega, phi, chi) (0 =none, 2 = default). + "ENDING", # Actual goniometer angles at end of frame in Eulerian space. + "DETPAR", # Detector position corrections (dX,dY,dDist,Pitch,Roll,Yaw) + "LUT", # Recommended display lookup table + "DISPLIM", # Recommended display limits + "PROGRAM", # Name and version of program writing frame, such as: + "ROTATE", # Non zero if acquired by rotation of phi during scan (or oscilate) + "BITMASK", # File name of active pixel mask associated with this frame or $NULL + "OCTMASK", # Octagon mask parameters to use if BITMASK=$null. Min X, Min X+Y, Min Y, Max X-Y, Max X, Max X+Y, Max Y, Max Y-X. + "ESDCELL", # Unit cell parameter standard deviations + "DETTYPE", # Detector or CCD chip type (as displayed on CEU). Default is MULTIWIRE but UNKNOWN is advised, can contain PIXPERCM: CMTOGRID: + "NEXP", # Number of exposures: 1=single, 2=correlated sum.32 for most ccds, and 64 for 2K ccds. + "CCDPARM", # CCD parameters: readnoise, e/ADU, e/photon, bias, full scale + "BIS", # Potential full linear scale if rescan and attenuator used. + "CHEM", # Chemical formula in CIFTAB string, such as “?” + "MORPH", # Crystal morphology in CIFTAB string, such as “?” + "CCOLOR", # Crystal color in CIFTAB string, such as “?” + "CSIZE", # Crystal dimensions (3 ea) in CIFTAB string, such as “?” + "DNSMET", # Density measurement method in CIFTAB string, such as “?” + "DARK", # Name of dark current correction or NONE. + "AUTORNG", # Auto-ranging: gain, high-speed time, scale, offset, full linear scale Note: If full linear scale is zero, then CCDPARM full scale is the full linear scale (BIS frames). + "ZEROADJ", # Goniometer zero corrections (refined in least squares) + "XTRANS", # Crystal XYZ translations (refined in least squares) + "HKL&XY", # HKL and pixel XY for reciprocal space scan. GADDS only. + "AXES2", # Diffractometer setting linear axes (4 ea). (X, Y, Z, Aux) + "ENDING2", # Actual goniometer linear axes @ end of frame. (X, Y, Z, Aux) + "FILTER2", # Monochromator 2-theta angle and monochromator roll angle. v15: Adds beam tilt angle and attenuator factor. + "LEPTOS", # String for LEPTOS. + "CFR", # Only in 21CFRPart11 mode, writes the checksum for header and image (2str).] + ] version = 86 def __init__(self, data=None, header=None): @@ -189,8 +190,8 @@ def _readheader(self, infile): self.__headerstring__ = infile.read(blocksize * nhdrblks).decode("ASCII") self.header = self.check_header() for i in range(0, nhdrblks * blocksize, line): - if self.__headerstring__[i: i + line].find(":") > 0: - key, val = self.__headerstring__[i: i + line].split(":", 1) + if self.__headerstring__[i : i + line].find(":") > 0: + key, val = self.__headerstring__[i : i + line].split(":", 1) key = key.strip() # remove the whitespace (why?) val = val.strip() if key in self.header: @@ -199,13 +200,15 @@ def _readheader(self, infile): else: self.header[key] = val # we must have read this in the first 5*512 bytes. - nhdrblks = int(self.header.get('HDRBLKS', 5)) - self.header['HDRBLKS'] = nhdrblks + nhdrblks = int(self.header.get("HDRBLKS", 5)) + self.header["HDRBLKS"] = nhdrblks # Now read in the rest of the header blocks, appending self.__headerstring__ += infile.read(blocksize * (nhdrblks - 5)).decode("ASCII") for i in range(5 * blocksize, nhdrblks * blocksize, line): - if self.__headerstring__[i: i + line].find(":") > 0: # as for first 512 bytes of header - key, val = self.__headerstring__[i: i + line].split(":", 1) + if ( + self.__headerstring__[i : i + line].find(":") > 0 + ): # as for first 512 bytes of header + key, val = self.__headerstring__[i : i + line].split(":", 1) key = key.strip() val = val.strip() if key in self.header: @@ -213,12 +216,15 @@ def _readheader(self, infile): else: self.header[key] = val # make a (new) header item called "datastart" - self.header['datastart'] = blocksize * nhdrblks + self.header["datastart"] = blocksize * nhdrblks # set the image dimensions - shape = int(self.header['NROWS'].split()[0]), int(self.header['NCOLS'].split()[0]) + shape = ( + int(self.header["NROWS"].split()[0]), + int(self.header["NCOLS"].split()[0]), + ) self._shape = shape - self.version = int(self.header.get('FORMAT', "86")) + self.version = int(self.header.get("FORMAT", "86")) def read(self, fname, frame=None): """ @@ -234,20 +240,22 @@ def read(self, fname, frame=None): try: # you had to read the Bruker docs to know this! - npixelb = int(self.header['NPIXELB']) + npixelb = int(self.header["NPIXELB"]) except Exception: - errmsg = "length " + str(len(self.header['NPIXELB'])) + "\n" - for byt in self.header['NPIXELB']: + errmsg = "length " + str(len(self.header["NPIXELB"])) + "\n" + for byt in self.header["NPIXELB"]: errmsg += "char: " + str(byt) + " " + str(ord(byt)) + "\n" logger.warning(errmsg) raise RuntimeError(errmsg) - data = numpy.frombuffer(infile.read(rows * cols * npixelb), dtype=self.bpp_to_numpy[npixelb]).copy() + data = numpy.frombuffer( + infile.read(rows * cols * npixelb), dtype=self.bpp_to_numpy[npixelb] + ).copy() if not numpy.little_endian and data.dtype.itemsize > 1: data.byteswap(True) # handle overflows - nov = int(self.header['NOVERFL']) + nov = int(self.header["NOVERFL"]) if nov > 0: # Read in the overflows # need at least int32 sized data I guess - can reach 2^21 data = data.astype(numpy.uint32) @@ -256,8 +264,8 @@ def read(self, fname, frame=None): # 7 character position for _ in range(nov): ovfl = infile.read(16) - intensity = int(ovfl[0: 9]) - position = int(ovfl[9: 16]) + intensity = int(ovfl[0:9]) + position = int(ovfl[9:16]) data[position] = intensity # infile.close() @@ -268,12 +276,18 @@ def read(self, fname, frame=None): slope = float(slope) offset = float(offset) except Exception: - logger.warning("Error in converting to float data with linear parameter: %s" % self.header["LINEAR"]) + logger.warning( + "Error in converting to float data with linear parameter: %s" + % self.header["LINEAR"] + ) slope = 1 offset = 0 if (slope != 1) or (offset != 0): # TODO: check that the formula is OK, not reverted. - logger.warning("performing correction with slope=%s, offset=%s (LINEAR=%s)" % (slope, offset, self.header["LINEAR"])) + logger.warning( + "performing correction with slope=%s, offset=%s (LINEAR=%s)" + % (slope, offset, self.header["LINEAR"]) + ) data = (data * slope + offset).astype(numpy.float32) self.data = data.reshape(self._shape) @@ -292,13 +306,16 @@ def write(self, fname): slope = float(slope) offset = float(offset) except Exception: - logger.warning("Error in converting to float data with linear parameter: %s" % self.header["LINEAR"]) + logger.warning( + "Error in converting to float data with linear parameter: %s" + % self.header["LINEAR"] + ) slope, offset = 1.0, 0.0 else: offset = self.data.min() max_data = self.data.max() - max_range = 2 ** 24 - 1 # similar to the mantissa of a float32 + max_range = 2**24 - 1 # similar to the mantissa of a float32 if max_data > offset: slope = (max_data - offset) / float(max_range) else: @@ -334,7 +351,7 @@ def calc_bpp(self, data=None, max_entry=4096): data = self.data if self.__bpp_file is None: for i in [1, 2]: - overflown = (data >= (2 ** (8 * i) - 1)) + overflown = data >= (2 ** (8 * i) - 1) if overflown.sum() < max_entry: self.__bpp_file = i break @@ -362,9 +379,9 @@ def gen_header(self): line += str(value) else: for i in range(len(value) // 72): - headers.append((line + str(value[72 * i:72 * (i + 1)]))) + headers.append((line + str(value[72 * i : 72 * (i + 1)]))) line = key.ljust(7) + ":" - line += value[72 * (i + 1):] + line += value[72 * (i + 1) :] elif "__len__" in dir(value): f = "%%.%is" % (72 // len(value) - 1) line += " ".join([f % i for i in value]) @@ -378,8 +395,12 @@ def gen_header(self): self.header["HDRBLKS"] = int(ceil(tmp / 5.0) * 5.0) for i in range(len(headers)): if headers[i].startswith("HDRBLKS"): - headers[i] = headers.append(("HDRBLKS:%s" % self.header["HDRBLKS"]).ljust(80, " ")) - res = pad("".join(headers), self.SPACER + "." * 78, 512 * int(self.header["HDRBLKS"])) + headers[i] = headers.append( + ("HDRBLKS:%s" % self.header["HDRBLKS"]).ljust(80, " ") + ) + res = pad( + "".join(headers), self.SPACER + "." * 78, 512 * int(self.header["HDRBLKS"]) + ) return res def gen_overflow(self): @@ -390,7 +411,9 @@ def gen_overflow(self): flat = self.data.ravel() # flat memory view overflow_pos = numpy.where(flat >= limit)[0] # list of indexes overflow_val = flat[overflow_pos] - overflow = "".join(["%09i%07i" % (val, pos) for pos, val in zip(overflow_pos, overflow_val)]) + overflow = "".join( + ["%09i%07i" % (val, pos) for pos, val in zip(overflow_pos, overflow_val)] + ) return pad(overflow, ".", 512) def basic_translate(self, fname=None): @@ -411,7 +434,7 @@ def basic_translate(self, fname=None): self.header["CREATED"] = time.ctime() if "NOVERFL" not in self.header: self.header["NOVERFL"] = "0" -# if not "NPIXELB" in self.header: + # if not "NPIXELB" in self.header: self.header["NPIXELB"] = self.calc_bpp() # if not "NROWS" in self.header: self.header["NROWS"] = self.data.shape[0] diff --git a/src/fabio/cbfimage.py b/src/fabio/cbfimage.py index a3be498c..ef110bd0 100644 --- a/src/fabio/cbfimage.py +++ b/src/fabio/cbfimage.py @@ -51,25 +51,31 @@ from .compression import compByteOffset, decByteOffset, md5sum from .ext._cif import split_tokens from . import version, date + logger = logging.getLogger(__name__) -__version__ = ["##CBF: VERSION 1.5, FabIO version %s (%s) - %s" % (version, date, __copyright__)] - -DATA_TYPES = {"signed 8-bit integer": "int8", - "signed 16-bit integer": "int16", - "signed 32-bit integer": "int32", - "signed 64-bit integer": "int64", - "unsigned 8-bit integer": "uint8", - "unsigned 16-bit integer": "uint16", - "unsigned 32-bit integer": "uint32", - "unsigned 64-bit integer": "uint64" - } - -MINIMUM_KEYS = ["X-Binary-Size-Fastest-Dimension", - "X-Binary-Size-Second-Dimension", - "X-Binary-Size", - "X-Binary-Number-of-Elements", - 'X-Binary-Element-Type', - 'X-Binary-Number-of-Elements'] +__version__ = [ + "##CBF: VERSION 1.5, FabIO version %s (%s) - %s" % (version, date, __copyright__) +] + +DATA_TYPES = { + "signed 8-bit integer": "int8", + "signed 16-bit integer": "int16", + "signed 32-bit integer": "int32", + "signed 64-bit integer": "int64", + "unsigned 8-bit integer": "uint8", + "unsigned 16-bit integer": "uint16", + "unsigned 32-bit integer": "uint32", + "unsigned 64-bit integer": "uint64", +} + +MINIMUM_KEYS = [ + "X-Binary-Size-Fastest-Dimension", + "X-Binary-Size-Second-Dimension", + "X-Binary-Size", + "X-Binary-Number-of-Elements", + "X-Binary-Element-Type", + "X-Binary-Number-of-Elements", +] class CbfImage(FabioImage): @@ -156,10 +162,12 @@ def _read_cif_header(self, inStream): self.cbs = value else: if isinstance(value, str): - value = value.strip(" \"\n\r\t") + value = value.strip(' "\n\r\t') self.header[key] = value if self.header.get("_array_data.header_convention") == "PILATUS_1.2": - self.pilatus_headers = PilatusHeader(self.header.get("_array_data.header_contents", "")) + self.pilatus_headers = PilatusHeader( + self.header.get("_array_data.header_contents", "") + ) def _read_binary_section_header(self, inStream): """ @@ -169,17 +177,17 @@ def _read_binary_section_header(self, inStream): while self.start_binary < 0: self.cbs += inStream.read(self.PADDING) self.start_binary = self.cbs.find(self.STARTER) - bin_headers = self.cbs[:self.start_binary] + bin_headers = self.cbs[: self.start_binary] lines = bin_headers.split(b"\n") for line in lines[1:]: if len(line) < 10: break try: - key, val = line.split(b':', 1) + key, val = line.split(b":", 1) except ValueError: - key, val = line.split(b'=', 1) + key, val = line.split(b"=", 1) key = key.strip().decode("ASCII") - self.header[key] = val.strip(b" \"\n\r\t").decode("ASCII") + self.header[key] = val.strip(b' "\n\r\t').decode("ASCII") missing = [] for item in MINIMUM_KEYS: if item not in self.header: @@ -188,13 +196,13 @@ def _read_binary_section_header(self, inStream): logger.info("Mandatory keys missing in CBF file: " + ", ".join(missing)) # Compute image size try: - slow = int(self.header['X-Binary-Size-Fastest-Dimension']) - fast = int(self.header['X-Binary-Size-Second-Dimension']) + slow = int(self.header["X-Binary-Size-Fastest-Dimension"]) + fast = int(self.header["X-Binary-Size-Second-Dimension"]) self._shape = fast, slow except (KeyError, ValueError): raise IOError("CBF file %s is corrupt, no dimensions in it" % inStream.name) try: - bytecode = DATA_TYPES[self.header['X-Binary-Element-Type']] + bytecode = DATA_TYPES[self.header["X-Binary-Element-Type"]] except KeyError: bytecode = "int32" logger.warning("Defaulting type to int32") @@ -207,23 +215,37 @@ def read_raw_data(self, infile): :return: raw compressed stream """ if self.CIF_BINARY_BLOCK_KEY not in self.cif: - err = "Not key %s in CIF, no CBF image in %s" % (self.CIF_BINARY_BLOCK_KEY, self.filename) + err = "Not key %s in CIF, no CBF image in %s" % ( + self.CIF_BINARY_BLOCK_KEY, + self.filename, + ) logger.error(err) for kv in self.cif.items(): logger.debug("%s: %s", kv) raise RuntimeError(err) if self.cif[self.CIF_BINARY_BLOCK_KEY] == "CIF Binary Section": - size = len(self.STARTER) + int(self.header["X-Binary-Size"]) - len(self.cbs) + self.start_binary + size = ( + len(self.STARTER) + + int(self.header["X-Binary-Size"]) + - len(self.cbs) + + self.start_binary + ) if size > 0: self.cbs += infile.read(size) elif size < 0: self.cbs = self.cbs[:size] else: - if len(self.cif[self.CIF_BINARY_BLOCK_KEY]) > int(self.header["X-Binary-Size"]) + self.start_binary + len(self.STARTER): - self.cbs = self.cif[self.CIF_BINARY_BLOCK_KEY][:int(self.header["X-Binary-Size"]) + self.start_binary + len(self.STARTER)] + if len(self.cif[self.CIF_BINARY_BLOCK_KEY]) > int( + self.header["X-Binary-Size"] + ) + self.start_binary + len(self.STARTER): + self.cbs = self.cif[self.CIF_BINARY_BLOCK_KEY][ + : int(self.header["X-Binary-Size"]) + + self.start_binary + + len(self.STARTER) + ] else: self.cbs = self.cif[self.CIF_BINARY_BLOCK_KEY] - return self.cbs[self.start_binary + len(self.STARTER):] + return self.cbs[self.start_binary + len(self.STARTER) :] def read(self, fname, frame=None, check_MD5=True, only_raw=False): """Read in header into self.header and the data into self.data @@ -245,19 +267,29 @@ def read(self, fname, frame=None, check_MD5=True, only_raw=False): return binary_data if ("Content-MD5" in self.header) and check_MD5: - ref = numpy.bytes_(self.header["Content-MD5"]) - obt = md5sum(binary_data) - if ref != obt: - logger.error("Checksum of binary data mismatch: expected %s, got %s" % (ref, obt)) + ref = numpy.bytes_(self.header["Content-MD5"]) + obt = md5sum(binary_data) + if ref != obt: + logger.error( + "Checksum of binary data mismatch: expected %s, got %s" % (ref, obt) + ) if self.header["conversions"] == "x-CBF_BYTE_OFFSET": - data = numpy.ascontiguousarray(self._readbinary_byte_offset(binary_data,), self._dtype) + data = numpy.ascontiguousarray( + self._readbinary_byte_offset( + binary_data, + ), + self._dtype, + ) data.shape = self._shape self.data = data self._shape = None self._dtype = None else: - raise Exception(IOError, "Compression scheme not yet supported, please contact the author") + raise Exception( + IOError, + "Compression scheme not yet supported, please contact the author", + ) self.resetvals() return self @@ -290,28 +322,34 @@ def write(self, fname): for key, value in DATA_TYPES.items(): if value == self.data.dtype: dtype = key - binary_block = [b"--CIF-BINARY-FORMAT-SECTION--", - b"Content-Type: application/octet-stream;", - b' conversions="x-CBF_BYTE_OFFSET"', - b'Content-Transfer-Encoding: BINARY', - numpy.bytes_("X-Binary-Size: %d" % (len(binary_blob))), - b"X-Binary-ID: 1", - numpy.bytes_('X-Binary-Element-Type: "%s"' % (dtype)), - b"X-Binary-Element-Byte-Order: LITTLE_ENDIAN", - b"Content-MD5: " + md5sum(binary_blob), - numpy.bytes_("X-Binary-Number-of-Elements: %d" % (dim1 * dim2)), - numpy.bytes_("X-Binary-Size-Fastest-Dimension: %d" % dim1), - numpy.bytes_("X-Binary-Size-Second-Dimension: %d" % dim2), - b"X-Binary-Size-Padding: 1", - b"", - self.STARTER + binary_blob, - b"", - b"--CIF-BINARY-FORMAT-SECTION----"] + binary_block = [ + b"--CIF-BINARY-FORMAT-SECTION--", + b"Content-Type: application/octet-stream;", + b' conversions="x-CBF_BYTE_OFFSET"', + b"Content-Transfer-Encoding: BINARY", + numpy.bytes_("X-Binary-Size: %d" % (len(binary_blob))), + b"X-Binary-ID: 1", + numpy.bytes_('X-Binary-Element-Type: "%s"' % (dtype)), + b"X-Binary-Element-Byte-Order: LITTLE_ENDIAN", + b"Content-MD5: " + md5sum(binary_blob), + numpy.bytes_("X-Binary-Number-of-Elements: %d" % (dim1 * dim2)), + numpy.bytes_("X-Binary-Size-Fastest-Dimension: %d" % dim1), + numpy.bytes_("X-Binary-Size-Second-Dimension: %d" % dim2), + b"X-Binary-Size-Padding: 1", + b"", + self.STARTER + binary_blob, + b"", + b"--CIF-BINARY-FORMAT-SECTION----", + ] if "_array_data.header_contents" not in self.header: nonCifHeaders = [] else: - nonCifHeaders = [i.strip()[2:] for i in self.header["_array_data.header_contents"].split("\n") if i.find("# ") >= 0] + nonCifHeaders = [ + i.strip()[2:] + for i in self.header["_array_data.header_contents"].split("\n") + if i.find("# ") >= 0 + ] for key in self.header: if key.startswith("_"): @@ -333,10 +371,14 @@ def write(self, fname): self.cif["_array_data.header_convention"] = "PILATUS_1.2" if len(nonCifHeaders) > 0: - self.cif["_array_data.header_contents"] = "\r\n".join(["# %s" % i for i in nonCifHeaders]) + self.cif["_array_data.header_contents"] = "\r\n".join( + ["# %s" % i for i in nonCifHeaders] + ) self.cbf = b"\r\n".join(binary_block) - block = b"\r\n".join([b"", self.CIF_BINARY_BLOCK_KEY.encode("ASCII"), b";", self.cbf, b";"]) + block = b"\r\n".join( + [b"", self.CIF_BINARY_BLOCK_KEY.encode("ASCII"), b";", self.cbf, b";"] + ) self.cif.pop(self.CIF_BINARY_BLOCK_KEY, None) with open(fname, "wb") as out_file: out_file.write(self.cif.tostring(fname, "\r\n").encode("ASCII")) @@ -354,12 +396,13 @@ class CIF(dict): keys are always unicode (str in python3) values are bytes """ + EOL = [numpy.bytes_(i) for i in ("\r", "\n", "\r\n", "\n\r")] BLANK = [numpy.bytes_(i) for i in (" ", "\t")] + EOL SINGLE_QUOTE = numpy.bytes_("'") DOUBLE_QUOTE = numpy.bytes_('"') - SEMICOLUMN = numpy.bytes_(';') - DOT = numpy.bytes_('.') + SEMICOLUMN = numpy.bytes_(";") + DOT = numpy.bytes_(".") START_COMMENT = (SINGLE_QUOTE, DOUBLE_QUOTE) BINARY_MARKER = numpy.bytes_("--CIF-BINARY-FORMAT-SECTION--") HASH = numpy.bytes_("#") @@ -410,11 +453,15 @@ def loadCIF(self, _strFilename, _bKeepComment=False): infile = open(_strFilename, "rb") own_fd = True else: - raise RuntimeError("CIF.loadCIF: No such file to open: %s" % _strFilename) + raise RuntimeError( + "CIF.loadCIF: No such file to open: %s" % _strFilename + ) elif "read" in dir(_strFilename): infile = _strFilename else: - raise RuntimeError("CIF.loadCIF: what is %s type %s" % (_strFilename, type(_strFilename))) + raise RuntimeError( + "CIF.loadCIF: what is %s type %s" % (_strFilename, type(_strFilename)) + ) if _bKeepComment: self._parseCIF(numpy.bytes_(infile.read())) else: @@ -452,8 +499,11 @@ def _readCIF(cls, instream): :rtype: string """ if "read" not in dir(instream): - raise RuntimeError("CIF._readCIF(instream): I expected instream to be an opened file,\ - here I got %s type %s" % (instream, type(instream))) + raise RuntimeError( + "CIF._readCIF(instream): I expected instream to be an opened file,\ + here I got %s type %s" + % (instream, type(instream)) + ) out_bytes = numpy.bytes_("") for sLine in instream: nline = numpy.bytes_(sLine) @@ -462,11 +512,17 @@ def _readCIF(cls, instream): if cls.isAscii(nline): out_bytes += nline[:pos] + numpy.bytes_(os.linesep) if pos > 80: - logger.warning("This line is too long and could cause problems in PreQuest: %s", sLine) + logger.warning( + "This line is too long and could cause problems in PreQuest: %s", + sLine, + ) else: out_bytes += nline if len(sLine.strip()) > 80: - logger.warning("This line is too long and could cause problems in PreQuest: %s", sLine) + logger.warning( + "This line is too long and could cause problems in PreQuest: %s", + sLine, + ) return out_bytes def _parseCIF(self, bytes_text): @@ -486,7 +542,11 @@ def _parseCIF(self, bytes_text): loop = [] fields = split_tokens(bytes_text) - logger.debug("After split got %s fields of len: %s", len(fields), [len(i) for i in fields]) + logger.debug( + "After split got %s fields of len: %s", + len(fields), + [len(i) for i in fields], + ) for idx, field in enumerate(fields): if field.lower() == self.LOOP: @@ -498,7 +558,7 @@ def _parseCIF(self, bytes_text): looplen.append(length) for i in range(len(loopidx) - 1, -1, -1): - f1 = fields[:loopidx[i]] + fields[loopidx[i] + looplen[i]:] + f1 = fields[: loopidx[i]] + fields[loopidx[i] + looplen[i] :] fields = f1 self[self.LOOP.decode("ASCII")] = loop @@ -532,7 +592,7 @@ def _splitCIF(cls, bytes_text): idx = 0 finished = False while not finished: - idx += 1 + bytes_text[idx + 1:].find(cls.SINGLE_QUOTE) + idx += 1 + bytes_text[idx + 1 :].find(cls.SINGLE_QUOTE) if idx >= len(bytes_text) - 1: fields.append(bytes_text[1:-1].strip()) bytes_text = numpy.bytes_("") @@ -541,7 +601,7 @@ def _splitCIF(cls, bytes_text): if bytes_text[idx + 1] in cls.BLANK: fields.append(bytes_text[1:idx].strip()) - tmp_text = bytes_text[idx + 1:] + tmp_text = bytes_text[idx + 1 :] bytes_text = tmp_text.strip() finished = True @@ -549,7 +609,7 @@ def _splitCIF(cls, bytes_text): idx = 0 finished = False while not finished: - idx += 1 + bytes_text[idx + 1:].find(cls.DOUBLE_QUOTE) + idx += 1 + bytes_text[idx + 1 :].find(cls.DOUBLE_QUOTE) if idx >= len(bytes_text) - 1: fields.append(bytes_text[1:-1].strip()) bytes_text = numpy.bytes_("") @@ -558,7 +618,7 @@ def _splitCIF(cls, bytes_text): if bytes_text[idx + 1] in cls.BLANK: fields.append(bytes_text[1:idx].strip()) - tmp_text = bytes_text[idx + 1:] + tmp_text = bytes_text[idx + 1 :] bytes_text = tmp_text.strip() finished = True @@ -573,10 +633,10 @@ def _splitCIF(cls, bytes_text): idx = 0 finished = False while not finished: - idx += 1 + bytes_text[idx + 1:].find(cls.SEMICOLUMN) + idx += 1 + bytes_text[idx + 1 :].find(cls.SEMICOLUMN) if bytes_text[idx - 1] in cls.EOL: - fields.append(bytes_text[1:idx - 1].strip()) - tmp_text = bytes_text[idx + 1:] + fields.append(bytes_text[1 : idx - 1].strip()) + tmp_text = bytes_text[idx + 1 :] bytes_text = tmp_text.strip() finished = True else: @@ -589,7 +649,12 @@ def _splitCIF(cls, bytes_text): continue start_binary = bytes_text.find(cls.BINARY_MARKER) if start_binary > 0: - end_binary = bytes_text[start_binary + 1:].find(cls.BINARY_MARKER) + start_binary + 1 + len(cls.BINARY_MARKER) + end_binary = ( + bytes_text[start_binary + 1 :].find(cls.BINARY_MARKER) + + start_binary + + 1 + + len(cls.BINARY_MARKER) + ) fields.append(bytes_text[:end_binary]) bytes_text = bytes_text[end_binary:].strip() else: @@ -651,9 +716,9 @@ def _analyseOneLoop(cls, fields, start_idx): loop.append(element) return loop, 1 + len(keys) + len(data), keys -########################################## -# everything needed to write a CIF file # -########################################## + ########################################## + # everything needed to write a CIF file # + ########################################## def saveCIF(self, _strFilename="test.cif", linesep=os.linesep, binary=False): """Transforms the CIF object in string then write it into the given file :param _strFilename: the of the file to be written @@ -698,7 +763,9 @@ def tostring(self, _strFilename=None, linesep=os.linesep): continue if sKey not in self: self._ordered.remove(sKey) - logger.debug("Skipping key %s from ordered list as no more present in dict") + logger.debug( + "Skipping key %s from ordered list as no more present in dict" + ) continue sValue = str(self[sKey]) if sValue.find("\n") > -1: # should add value between ;; @@ -721,7 +788,10 @@ def tostring(self, _strFilename=None, linesep=os.linesep): lstStrCif.append("loop_ ") lKeys = loop[0] llData = loop[1] - lstStrCif += [f" {sKey.decode() if isinstance(sKey, bytes) else str(sKey)}" for sKey in lKeys] + lstStrCif += [ + f" {sKey.decode() if isinstance(sKey, bytes) else str(sKey)}" + for sKey in lKeys + ] for lData in llData: sLine = " " for key in lKeys: @@ -734,7 +804,9 @@ def tostring(self, _strFilename=None, linesep=os.linesep): lstStrCif += [sLine, ";", str(sRawValue), ";"] sLine = " " else: - if len(sRawValue.split()) > 1: # should add value between '' + if ( + len(sRawValue.split()) > 1 + ): # should add value between '' value = "'%s'" % (sRawValue) else: value = str(sRawValue) @@ -796,7 +868,7 @@ def loadCHIPLOT(self, _strFilename): logger.error(errStr) raise IOError(errStr) lInFile = open(_strFilename, "r").readlines() - self["_audit_creation_method"] = 'From 2-D detector using FIT2D and CIFfile' + self["_audit_creation_method"] = "From 2-D detector using FIT2D and CIFfile" self["_pd_meas_scan_method"] = "fixed" self["_pd_spec_description"] = lInFile[0].strip() try: @@ -817,7 +889,7 @@ def loadCHIPLOT(self, _strFilename): limitsOK = False f2ThetaMin = 180.0 f2ThetaMax = 0 -# print "limitsOK:", limitsOK + # print "limitsOK:", limitsOK for sLine in lInFile[4:]: sCleaned = sLine.split("#")[0].strip() data = sCleaned.split() @@ -831,8 +903,10 @@ def loadCHIPLOT(self, _strFilename): lOneLoop.append({"_pd_meas_intensity_total": data[1]}) if not iLenData: iLenData = len(lOneLoop) - assert (iLenData == len(lOneLoop)) - self["_pd_meas_2theta_range_inc"] = "%.4f" % ((f2ThetaMax - f2ThetaMin) / (iLenData - 1)) + assert iLenData == len(lOneLoop) + self["_pd_meas_2theta_range_inc"] = "%.4f" % ( + (f2ThetaMax - f2ThetaMin) / (iLenData - 1) + ) if self["_pd_meas_2theta_range_inc"] < 0: self["_pd_meas_2theta_range_inc"] = abs(self["_pd_meas_2theta_range_inc"]) tmp = f2ThetaMax @@ -845,7 +919,7 @@ def loadCHIPLOT(self, _strFilename): @staticmethod def LoopHasKey(loop, key): - "Returns True if the key (string) exist in the array called loop""" + "Returns True if the key (string) exist in the array called loop" try: loop.index(key) return True @@ -866,44 +940,100 @@ class PilatusKey(NamedTuple): class PilatusHeader(object): KEYWORDS = OrderedDict() - KEYWORDS["Detector"] = PilatusKey("Detector", 0, slice(1, None), str, "Detector: {}") - KEYWORDS["sensor"] = PilatusKey("sensor", 1, [0, 3], [str, float], "{} sensor, thickness {} m") - KEYWORDS["Pixel_size"] = PilatusKey("Pixel_size", 0, [1, 4], [float, float], "Pixel_size {} m x {} m") - KEYWORDS["Exposure_time"] = PilatusKey("Exposure_time", 0, [1], [float], "Exposure_time {} s") - KEYWORDS["Exposure_period"] = PilatusKey("Exposure_period", 0, [1], [float], "Exposure_period {} s") + KEYWORDS["Detector"] = PilatusKey( + "Detector", 0, slice(1, None), str, "Detector: {}" + ) + KEYWORDS["sensor"] = PilatusKey( + "sensor", 1, [0, 3], [str, float], "{} sensor, thickness {} m" + ) + KEYWORDS["Pixel_size"] = PilatusKey( + "Pixel_size", 0, [1, 4], [float, float], "Pixel_size {} m x {} m" + ) + KEYWORDS["Exposure_time"] = PilatusKey( + "Exposure_time", 0, [1], [float], "Exposure_time {} s" + ) + KEYWORDS["Exposure_period"] = PilatusKey( + "Exposure_period", 0, [1], [float], "Exposure_period {} s" + ) KEYWORDS["Tau"] = PilatusKey("Tau", 0, [1], [float], "Tau = {} s") - KEYWORDS["Count_cutoff"] = PilatusKey("Count_cutoff", 0, [1], [int], "Count_cutoff {} counts") - KEYWORDS["Threshold_setting"] = PilatusKey("Threshold_setting", 0, [1], [float], "Threshold_setting: {} eV") - KEYWORDS["Gain_setting"] = PilatusKey("Gain_setting", 0, [1, 2], [str, str], "Gain_setting: {} {} (vrf = -0.200)") - KEYWORDS["N_excluded_pixels"] = PilatusKey("N_excluded_pixels", 0, [1], [int], "N_excluded_pixels = {}") - KEYWORDS["Excluded_pixels"] = PilatusKey("Excluded_pixels", 0, [1], [str], "Excluded_pixels: {}") + KEYWORDS["Count_cutoff"] = PilatusKey( + "Count_cutoff", 0, [1], [int], "Count_cutoff {} counts" + ) + KEYWORDS["Threshold_setting"] = PilatusKey( + "Threshold_setting", 0, [1], [float], "Threshold_setting: {} eV" + ) + KEYWORDS["Gain_setting"] = PilatusKey( + "Gain_setting", 0, [1, 2], [str, str], "Gain_setting: {} {} (vrf = -0.200)" + ) + KEYWORDS["N_excluded_pixels"] = PilatusKey( + "N_excluded_pixels", 0, [1], [int], "N_excluded_pixels = {}" + ) + KEYWORDS["Excluded_pixels"] = PilatusKey( + "Excluded_pixels", 0, [1], [str], "Excluded_pixels: {}" + ) KEYWORDS["Flat_field"] = PilatusKey("Flat_field", 0, [1], [str], "Flat_field: {}") KEYWORDS["Trim_file"] = PilatusKey("Trim_file", 0, [1], [str], "Trim_file: {}") KEYWORDS["Image_path"] = PilatusKey("Image_path", 0, [1], [str], "Image_path: {}") - KEYWORDS["Wavelength"] = PilatusKey("Wavelength", 0, [1], [float], "Wavelength {} A") - KEYWORDS["Energy_range"] = PilatusKey("Energy_range", 0, [1, 2], [float, float], "Energy_range {} {} eV") - KEYWORDS["Detector_distance"] = PilatusKey("Detector_distance", 0, [1], [float], "Detector_distance {} m") - KEYWORDS["Detector_Voffset"] = PilatusKey("Detector_Voffset", 0, [1], [float], "Detector_Voffset {} m") - KEYWORDS["Beam_xy"] = PilatusKey("Beam_xy", 0, [1, 2], [float, float], "Beam_xy ({}, {}) pixels") + KEYWORDS["Wavelength"] = PilatusKey( + "Wavelength", 0, [1], [float], "Wavelength {} A" + ) + KEYWORDS["Energy_range"] = PilatusKey( + "Energy_range", 0, [1, 2], [float, float], "Energy_range {} {} eV" + ) + KEYWORDS["Detector_distance"] = PilatusKey( + "Detector_distance", 0, [1], [float], "Detector_distance {} m" + ) + KEYWORDS["Detector_Voffset"] = PilatusKey( + "Detector_Voffset", 0, [1], [float], "Detector_Voffset {} m" + ) + KEYWORDS["Beam_xy"] = PilatusKey( + "Beam_xy", 0, [1, 2], [float, float], "Beam_xy ({}, {}) pixels" + ) KEYWORDS["Flux"] = PilatusKey("Flux", 0, [1], [float], "Flux {}") - KEYWORDS["Filter_transmission"] = PilatusKey("Filter_transmission", 0, [1], [float], "Filter_transmission {}") - KEYWORDS["Start_angle"] = PilatusKey("Start_angle", 0, [1], [float], "Start_angle {} deg.") - KEYWORDS["Angle_increment"] = PilatusKey("Angle_increment", 0, [1], [float], "Angle_increment {} deg.") - KEYWORDS["Detector_2theta"] = PilatusKey("Detector_2theta", 0, [1], [float], "Detector_2theta {} deg.") - KEYWORDS["Polarization"] = PilatusKey("Polarization", 0, [1], [float], "Polarization {}") + KEYWORDS["Filter_transmission"] = PilatusKey( + "Filter_transmission", 0, [1], [float], "Filter_transmission {}" + ) + KEYWORDS["Start_angle"] = PilatusKey( + "Start_angle", 0, [1], [float], "Start_angle {} deg." + ) + KEYWORDS["Angle_increment"] = PilatusKey( + "Angle_increment", 0, [1], [float], "Angle_increment {} deg." + ) + KEYWORDS["Detector_2theta"] = PilatusKey( + "Detector_2theta", 0, [1], [float], "Detector_2theta {} deg." + ) + KEYWORDS["Polarization"] = PilatusKey( + "Polarization", 0, [1], [float], "Polarization {}" + ) KEYWORDS["Alpha"] = PilatusKey("Alpha", 0, [1], [float], "Alpha {} deg.") KEYWORDS["Kappa"] = PilatusKey("Kappa", 0, [1], [float], "Kappa {} deg.") KEYWORDS["Phi"] = PilatusKey("Phi", 0, [1], [float], "Phi {} deg.") - KEYWORDS["Phi_increment"] = PilatusKey("Phi_increment", 0, [1], [float], "Phi_increment {} deg.") + KEYWORDS["Phi_increment"] = PilatusKey( + "Phi_increment", 0, [1], [float], "Phi_increment {} deg." + ) KEYWORDS["Chi"] = PilatusKey("Chi", 0, [1], [float], "Chi {} deg.") - KEYWORDS["Chi_increment"] = PilatusKey("Chi_increment", 0, [1], [float], "Chi_increment {} deg.") + KEYWORDS["Chi_increment"] = PilatusKey( + "Chi_increment", 0, [1], [float], "Chi_increment {} deg." + ) KEYWORDS["Omega"] = PilatusKey("Omega", 0, [1], [float], "Omega {} deg.") - KEYWORDS["Omega_increment"] = PilatusKey("Omega_increment", 0, [1], [float], "Omega_increment {} deg.") - KEYWORDS["Oscillation_axis"] = PilatusKey("Oscillation_axis", 0, [1], [str], "Oscillation_axis {}") - KEYWORDS["N_oscillations"] = PilatusKey(" ('N_oscillations", 0, [1], [int], "N_oscillations {}") - KEYWORDS["Start_position"] = PilatusKey("Start_position", 0, [1], [float], "Start_position {}") - KEYWORDS["Position_increment"] = PilatusKey("Position_increment", 0, [1], [float], "Position_increment") - KEYWORDS["Shutter_time"] = PilatusKey("Shutter_time", 0, [1], [float], "Shutter_time {} s") + KEYWORDS["Omega_increment"] = PilatusKey( + "Omega_increment", 0, [1], [float], "Omega_increment {} deg." + ) + KEYWORDS["Oscillation_axis"] = PilatusKey( + "Oscillation_axis", 0, [1], [str], "Oscillation_axis {}" + ) + KEYWORDS["N_oscillations"] = PilatusKey( + " ('N_oscillations", 0, [1], [int], "N_oscillations {}" + ) + KEYWORDS["Start_position"] = PilatusKey( + "Start_position", 0, [1], [float], "Start_position {}" + ) + KEYWORDS["Position_increment"] = PilatusKey( + "Position_increment", 0, [1], [float], "Position_increment" + ) + KEYWORDS["Shutter_time"] = PilatusKey( + "Shutter_time", 0, [1], [float], "Shutter_time {} s" + ) SPACE_LIKE = "()#:=," @classmethod @@ -945,7 +1075,7 @@ def __repr__(self): def _parse(self, content): """Parse the header block - + :param str content: header block :return: dict with parsed headers """ @@ -961,10 +1091,14 @@ def _parse(self, content): if len(v.value_indices) == 1: dico[k] = v.types[0]((words[v.value_indices[0]])) else: - dico[k] = tuple(i(words[j]) for i, j in zip(v.types, v.value_indices)) + dico[k] = tuple( + i(words[j]) for i, j in zip(v.types, v.value_indices) + ) else: if isinstance(v.value_indices, slice): - dico[k] = " ".join([v.types(i) for i in words[v.value_indices]]) + dico[k] = " ".join( + [v.types(i) for i in words[v.value_indices]] + ) else: dico[k] = v.types(words[v.value_indices]) return dico @@ -976,6 +1110,6 @@ def __setitem__(self, key, value): def __getitem__(self, key): return self._dict[key] - + def __contains__(self, key): - return key in self._dict + return key in self._dict diff --git a/src/fabio/compression/__init__.py b/src/fabio/compression/__init__.py index fd742f82..a64ebb51 100644 --- a/src/fabio/compression/__init__.py +++ b/src/fabio/compression/__init__.py @@ -26,4 +26,4 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE -from .compression import * +from .compression import * # noqa diff --git a/src/fabio/compression/agi_bitfield.py b/src/fabio/compression/agi_bitfield.py index ff8c806e..91937ed6 100644 --- a/src/fabio/compression/agi_bitfield.py +++ b/src/fabio/compression/agi_bitfield.py @@ -36,29 +36,39 @@ Inspired by C++ code: https://git.3lp.cx/dyadkin/cryio/src/branch/master/src/esperantoframe.cpp Fortran code: https://svn.debroglie.net/debroglie/Oxford/trunk/diamond2crysalis/bitfield.F90 """ + __author__ = ["Florian Plaswig", "Jérôme Kieffer"] __contact__ = "jerome.kieffer@esrf.eu" __license__ = "MIT" -__date__ = "30/05/2024" +__date__ = "28/10/2025" __copyright__ = "European Synchrotron Radiation Facility, Grenoble, France" import logging from io import BytesIO from struct import pack, unpack as unpack_ +import numpy + try: - from ..ext._agi_bitfield import get_fieldsize as _get_fieldsize, compress_row as _compress_row, compress as _compress + from ..ext._agi_bitfield import ( + get_fieldsize as _get_fieldsize, + compress_row as _compress_row, + compress as _compress, + ) except ImportError: _get_fieldsize = None _compress_row = None _compress = None + logger = logging.getLogger(__name__) -import numpy -unpack = lambda fmt, buff: unpack_(fmt, buff)[0] MASK = [(1 << i) - 1 for i in range(9)] +def unpack(fmt, buff): + return unpack_(fmt, buff)[0] + + def compress(frame): """compress a frame using the agi_bitfield algorithm :param frame: numpy.ndarray @@ -135,7 +145,12 @@ def decompress(comp_frame, dimensions): # read data components (row indices are ignored) data_size = unpack("I", comp_frame[:4]) data_block = BytesIO(comp_frame[4:]) - logger.debug("Size of binary data block: %d with image size: %s, compression ratio: %.3fx", data_size, dimensions, 4 * row_count * col_count / data_size) + logger.debug( + "Size of binary data block: %d with image size: %s, compression ratio: %.3fx", + data_size, + dimensions, + 4 * row_count * col_count / data_size, + ) output = numpy.zeros(dimensions, dtype=numpy.int32) for row_index in range(row_count): @@ -177,33 +192,33 @@ def decompress_row(buffer, row_length): def fortran_fieldsize(nbvalue): "Direct translation of Fortran" - if(nbvalue < -63): + if nbvalue < -63: getfieldsize = 8 - elif(nbvalue < -31): + elif nbvalue < -31: getfieldsize = 7 - elif(nbvalue < -15): + elif nbvalue < -15: getfieldsize = 6 - elif(nbvalue < -7): + elif nbvalue < -7: getfieldsize = 5 - elif(nbvalue < -3): + elif nbvalue < -3: getfieldsize = 4 - elif(nbvalue < -1): + elif nbvalue < -1: getfieldsize = 3 - elif(nbvalue < 0): + elif nbvalue < 0: getfieldsize = 2 - elif(nbvalue < 2): + elif nbvalue < 2: getfieldsize = 1 - elif(nbvalue < 3): + elif nbvalue < 3: getfieldsize = 2 - elif(nbvalue < 5): + elif nbvalue < 5: getfieldsize = 3 - elif(nbvalue < 9): + elif nbvalue < 9: getfieldsize = 4 - elif(nbvalue < 17): + elif nbvalue < 17: getfieldsize = 5 - elif(nbvalue < 33): + elif nbvalue < 33: getfieldsize = 6 - elif(nbvalue < 65): + elif nbvalue < 65: getfieldsize = 7 else: getfieldsize = 8 @@ -253,7 +268,13 @@ def compress_field(ifield, fieldsize, overflow_table): try: res = pack("> (size * i)) & mask_ for i in range(8)] else: @@ -285,7 +306,7 @@ def read_len_byte(lb): :param lb: int/byte :returns tuple """ - return lb >> 4, lb & 0xf + return lb >> 4, lb & 0xF def write_escaped(value, buffer): @@ -296,9 +317,9 @@ def write_escaped(value, buffer): if -127 <= value < 127: buffer.write(pack("B", value + 127)) elif -32767 < value < 32767: - buffer.write(b'\xfe' + pack("u4') + BE_uint32 = numpy.dtype(">u4") self.infile.seek(0) file_format = self.readbytes(4, BE_uint32, swap=False)[0] # should be 3 - assert file_format == 3, 'Wrong file type' + assert file_format == 3, "Wrong file type" self.bytes_in_file = self.readbytes(4, BE_uint32, swap=False)[0] - self.byte_order = self.readbytes(4, BE_uint32, swap=False)[0] # 0 = big, 1= little - logger.debug('read dm3 file - file format %s' % file_format) - logger.debug('Bytes in file: %s' % self.bytes_in_file) - logger.debug('Byte order: %s - 0 = bigEndian , 1 = littleEndian' % self.byte_order) + self.byte_order = self.readbytes(4, BE_uint32, swap=False)[ + 0 + ] # 0 = big, 1= little + logger.debug("read dm3 file - file format %s" % file_format) + logger.debug("Bytes in file: %s" % self.bytes_in_file) + logger.debug( + "Byte order: %s - 0 = bigEndian , 1 = littleEndian" % self.byte_order + ) if self.byte_order == 0: self.swap = True @@ -127,17 +134,17 @@ def read(self, fname, frame=None): if self.infile.tell() > self.bytes_in_file: go_on = False - dim_raw = self.header['Active Size (pixels)'].split() + dim_raw = self.header["Active Size (pixels)"].split() dim1_raw = int(dim_raw[0]) dim2_raw = int(dim_raw[1]) - binning_raw = self.header['Binning'] + binning_raw = self.header["Binning"] try: dim1_binning, dim2_binning = map(int, binning_raw.split()) except AttributeError: dim1_binning, dim2_binning = map(lambda x: x * int(binning_raw) * x, (1, 1)) self._shape = dim2_raw // dim2_binning, dim1_raw // dim1_binning if "Data" in self.header: - self.data = self.header[u'Data'] + self.data = self.header["Data"] self.data.shape = self._shape self._shape = None return self @@ -153,20 +160,20 @@ def readbytes(self, bytes_to_read, format, swap=True): return data def read_tag_group(self): - self.grouptag_is_sorted = self.readbytes(1, numpy.uint8)[0] self.grouptag_is_open = self.readbytes(1, numpy.uint8)[0] self.grouptag_no_tags = self.readbytes(4, numpy.uint32)[0] - logger.debug('TagGroup is sorted? %s', self.grouptag_is_sorted) - logger.debug('TagGroup is open? %s', self.grouptag_is_open) - logger.debug('no of tags in TagGroup %s', self.grouptag_no_tags) + logger.debug("TagGroup is sorted? %s", self.grouptag_is_sorted) + logger.debug("TagGroup is open? %s", self.grouptag_is_open) + logger.debug("no of tags in TagGroup %s", self.grouptag_no_tags) def read_tag_entry(self): - self.tag_is_data = self.readbytes(1, numpy.uint8)[0] self.tag_label_length = self.readbytes(2, numpy.uint16)[0] - logger.debug('does Tag have data ? %s - 20 = Tag group , 21 = data ', self.tag_is_data) - logger.debug('length of tag_label %s', self.tag_label_length) + logger.debug( + "does Tag have data ? %s - 20 = Tag group , 21 = data ", self.tag_is_data + ) + logger.debug("length of tag_label %s", self.tag_label_length) if self.tag_label_length != 0: tag_label = self.infile.read(self.tag_label_length) else: @@ -184,32 +191,48 @@ def read_tag_entry(self): value = value.decode() logger.debug("%s: %s", key, value) if key in self.header: - logger.debug("Key '%s' already exists with value %s. Overwrited with %s.", key, self.header[key], value) + logger.debug( + "Key '%s' already exists with value %s. Overwrited with %s.", + key, + self.header[key], + value, + ) self.header[key] = value def read_tag_type(self): - if self.infile.read(4) != b'%%%%': + if self.infile.read(4) != b"%%%%": raise IOError self.tag_data_type = self.readbytes(4, numpy.uint32)[0] - logger.debug('data is of type: %s - 1 = simple, 2 = string, 3 = array, >3 structs.', self.tag_data_type) + logger.debug( + "data is of type: %s - 1 = simple, 2 = string, 3 = array, >3 structs.", + self.tag_data_type, + ) self.tag_encoded_type = self.readbytes(4, numpy.uint32)[0] - logger.debug('encode type: %s %s', self.tag_encoded_type, DATA_TYPES[self.tag_encoded_type]) + logger.debug( + "encode type: %s %s", + self.tag_encoded_type, + DATA_TYPES[self.tag_encoded_type], + ) if self.tag_data_type == 1: # simple type - return self.readbytes(DATA_BYTES[self.tag_encoded_type], - DATA_TYPES[self.tag_encoded_type], - swap=self.swap)[0] + return self.readbytes( + DATA_BYTES[self.tag_encoded_type], + DATA_TYPES[self.tag_encoded_type], + swap=self.swap, + )[0] # are the data stored in a simple array? if self.tag_encoded_type == 20 and self.tag_data_type == 3: self.data_type = self.readbytes(4, numpy.uint32)[0] self.no_data_elements = self.readbytes(4, numpy.uint32)[0] if self.data_type == 10: - logger.debug('skip bytes %s', self.no_data_elements) + logger.debug("skip bytes %s", self.no_data_elements) _dump = self.infile.read(self.no_data_elements) return None - logger.debug('Data are stored as a simple a array -') - logger.debug('%s data elements stored as %s', self.no_data_elements, self.data_type) + logger.debug("Data are stored as a simple a array -") + logger.debug( + "%s data elements stored as %s", self.no_data_elements, self.data_type + ) read_no_bytes = DATA_BYTES[self.data_type] * self.no_data_elements fmt = DATA_TYPES[self.data_type] return self.readbytes(read_no_bytes, fmt, swap=self.swap) @@ -220,7 +243,7 @@ def read_tag_type(self): # print self.tag_encoded_type , self.tag_data_type if self.tag_encoded_type == 20 and self.tag_data_type > 3: self.tag_encoded_type = self.readbytes(4, numpy.uint32)[0] - logger.debug('found array - new tag_encoded_type %s', self.tag_encoded_type) + logger.debug("found array - new tag_encoded_type %s", self.tag_encoded_type) if self.tag_encoded_type == 15: # struct type # ##type = self.readbytes(4,numpy.int32) _struct_name_length = self.readbytes(4, numpy.int32)[0] @@ -229,14 +252,19 @@ def read_tag_type(self): # print self.infile.read(_struct_name_length) field_info = [] for i in range(struct_number_fields): - field_info.append([self.readbytes(4, numpy.int32)[0], self.readbytes(4, numpy.int32)[0]]) + field_info.append( + [ + self.readbytes(4, numpy.int32)[0], + self.readbytes(4, numpy.int32)[0], + ] + ) # print field_info self.no_data_elements = self.readbytes(4, numpy.int32)[0] # print '%i data elemets stored as ' %self.no_data_elements bytes_in_struct = 0 for i in range(struct_number_fields): bytes_in_struct += DATA_BYTES[field_info[i][1]] - logger.debug('skip bytes %s', self.no_data_elements * bytes_in_struct) + logger.debug("skip bytes %s", self.no_data_elements * bytes_in_struct) _dump = self.infile.read(self.no_data_elements * bytes_in_struct) return None @@ -248,17 +276,28 @@ def read_tag_type(self): # print self.infile.read(struct_name_length) field_info = [] for i in range(struct_number_fields): - field_info.append([self.readbytes(4, numpy.int32)[0], self.readbytes(4, numpy.int32)[0]]) + field_info.append( + [ + self.readbytes(4, numpy.int32)[0], + self.readbytes(4, numpy.int32)[0], + ] + ) # print field_info - field_data = b'' + field_data = b"" for i in range(struct_number_fields): - field_data += self.readbytes(field_info[i][0], None, swap=False) + b' ' - data = self.readbytes(DATA_BYTES[field_info[i][1]], DATA_TYPES[field_info[i][1]], swap=self.swap) + field_data += self.readbytes(field_info[i][0], None, swap=False) + b" " + data = self.readbytes( + DATA_BYTES[field_info[i][1]], + DATA_TYPES[field_info[i][1]], + swap=self.swap, + ) field_data += str(data[0]).encode() + b" " return field_data def read_data(self): - self.encoded_datatype = numpy.frombuffer(self.infile.read(4), numpy.uint32).copy().byteswap() + self.encoded_datatype = ( + numpy.frombuffer(self.infile.read(4), numpy.uint32).copy().byteswap() + ) dm3image = Dm3Image diff --git a/src/fabio/dtrekimage.py b/src/fabio/dtrekimage.py index 178946e9..fea2ebfc 100644 --- a/src/fabio/dtrekimage.py +++ b/src/fabio/dtrekimage.py @@ -82,7 +82,7 @@ def __init__(self, *args, **kwargs): DtrekImage._keyvalue_pattern = re.compile(b"[^\n]+") def read(self, fname, frame=None): - """ read in the file """ + """read in the file""" with self._open(fname, "rb") as infile: try: self._readheader(infile) @@ -101,11 +101,15 @@ def read(self, fname, frame=None): if data_type is not None and data_type == "unsigned_short": pass else: - logger.warning("Data_type key is mandatory. Fallback to unsigner integer 16-bits.") + logger.warning( + "Data_type key is mandatory. Fallback to unsigner integer 16-bits." + ) numpy_type = numpy.uint16 else: if data_type not in _DATA_TYPES: - raise IOError("Data_type key contains an invalid/unsupported value: %s", data_type) + raise IOError( + "Data_type key contains an invalid/unsupported value: %s", data_type + ) numpy_type = _DATA_TYPES[data_type] if type is None: raise IOError("Data_type %s is not supported by fabio", data_type) @@ -122,7 +126,7 @@ def read(self, fname, frame=None): shape = [] for i in range(dim): - value = int(self.header['SIZE%d' % (i + 1)]) + value = int(self.header["SIZE%d" % (i + 1)]) shape.insert(0, value) self._shape = shape @@ -140,8 +144,10 @@ def read(self, fname, frame=None): try: data.shape = self._shape except ValueError: - raise IOError('Size spec in d*TREK header does not match ' + - 'size of image data field %s != %s' % (self._shape, data.size)) + raise IOError( + "Size spec in d*TREK header does not match " + + "size of image data field %s != %s" % (self._shape, data.size) + ) self.data = data self._shape = None self._dtype = None @@ -157,8 +163,8 @@ def _split_meta(self, line): if b"=" not in line: raise ValueError("No meta") line = line.decode("ascii") - key, value = line.split('=') - return key.strip(), value.strip(' ;\n\r') + key, value = line.split("=") + return key.strip(), value.strip(" ;\n\r") def _readheader(self, infile): """Read a d*TREK header. @@ -170,10 +176,10 @@ def _readheader(self, infile): of the header. """ header_line = infile.readline() - assert(header_line.startswith(b"{")) + assert header_line.startswith(b"{") header_bytes_line = infile.readline() key, header_bytes = self._split_meta(header_bytes_line) - assert(key == "HEADER_BYTES") + assert key == "HEADER_BYTES" self.header[key] = header_bytes header_bytes = int(header_bytes) @@ -184,7 +190,7 @@ def _readheader(self, infile): for line in DtrekImage._keyvalue_pattern.finditer(header_block): line = line.group(0) - if line.startswith(b'}'): + if line.startswith(b"}"): # Remining part is padding return try: @@ -203,7 +209,7 @@ def write(self, fname): # From specification HEADER_START = b"{\n" - HEADER_END = b"}\n\x0C\n" + HEADER_END = b"}\n\x0c\n" HEADER_BYTES_TEMPLATE = "HEADER_BYTES=% 5d;\n" # start + end + header_bytes_key + header_bytes_value + header_bytes_end MINIMAL_HEADER_SIZE = 2 + 4 + 13 + 5 + 2 @@ -217,33 +223,37 @@ def write(self, fname): break if dtrek_data_type is None: - if data.dtype.kind == 'f': + if data.dtype.kind == "f": dtrek_data_type = "float IEEE" - elif data.dtype.kind == 'u': + elif data.dtype.kind == "u": dtrek_data_type = "unsigned long int" - elif data.dtype.kind == 'i': + elif data.dtype.kind == "i": dtrek_data_type = "long int" else: raise TypeError("Unsupported data type %s", data.dtype) new_dtype = numpy.dtype(_DATA_TYPES[dtrek_data_type]) - logger.warning("Data type %s unsupported. Store it as %s.", data.dtype, new_dtype) + logger.warning( + "Data type %s unsupported. Store it as %s.", data.dtype, new_dtype + ) data = data.astype(new_dtype) - byte_order = self._get_dtrek_byte_order(default_little_endian=numpy.little_endian) + byte_order = self._get_dtrek_byte_order( + default_little_endian=numpy.little_endian + ) little_endian = byte_order == "little_endian" if little_endian != numpy.little_endian: data = data.byteswap() # Patch header to match the data self.header["Data_type"] = dtrek_data_type - self.header['DIM'] = str(len(data.shape)) + self.header["DIM"] = str(len(data.shape)) for i, size in enumerate(reversed(data.shape)): - self.header['SIZE%d' % (i + 1)] = str(size) + self.header["SIZE%d" % (i + 1)] = str(size) self.header["BYTE_ORDER"] = byte_order else: # No data self.header["Data_type"] = "long int" - self.header['DIM'] = "2" + self.header["DIM"] = "2" self.header["SIZE1"] = "0" self.header["SIZE2"] = "0" self.header["BYTE_ORDER"] = "little_endian" @@ -270,7 +280,13 @@ def write(self, fname): pad = hsize - minimal_hsize header_bytes = HEADER_BYTES_TEMPLATE % hsize - out = HEADER_START + header_bytes.encode("ascii") + out + HEADER_END + (b' ' * pad) + out = ( + HEADER_START + + header_bytes.encode("ascii") + + out + + HEADER_END + + (b" " * pad) + ) assert len(out) % 512 == 0, "Header is not multiple of 512" with open(fname, "wb") as outf: @@ -291,7 +307,10 @@ def _get_dtrek_byte_order(self, default_little_endian=None): little_endian = "little" in byte_order big_endian = "big" in byte_order if not little_endian and not big_endian: - logger.warning("Invalid BYTE_ORDER value. Found '%s', assuming little_endian", byte_order) + logger.warning( + "Invalid BYTE_ORDER value. Found '%s', assuming little_endian", + byte_order, + ) little_endian = True if little_endian: diff --git a/src/fabio/edfimage.py b/src/fabio/edfimage.py index 200138e7..185a8584 100644 --- a/src/fabio/edfimage.py +++ b/src/fabio/edfimage.py @@ -45,6 +45,7 @@ """ + __authors__ = ["Jérôme Kieffer", "Jon Wright", "Henning O. Sorensen", "Erik Knudsen"] __contact__ = "jerome.kieffer@esrf.fr" __license__ = "MIT" @@ -56,9 +57,7 @@ import string import logging import numpy - -logger = logging.getLogger(__name__) - +from collections import namedtuple from . import fabioimage from .fabioutils import isAscii, toAscii, nice_int, OrderedDict from .compression import decBzip2, decGzip, decZlib @@ -66,81 +65,97 @@ from . import fabioutils from .utils import deprecation -from collections import namedtuple +logger = logging.getLogger(__name__) BLOCKSIZE = 512 MAX_BLOCKS = 512 -DATA_TYPES = {"SignedByte": numpy.int8, - "Signed8": numpy.int8, - "UnsignedByte": numpy.uint8, - "Unsigned8": numpy.uint8, - "SignedShort": numpy.int16, - "Signed16": numpy.int16, - "UnsignedShort": numpy.uint16, - "Unsigned16": numpy.uint16, - "UnsignedShortInteger": numpy.uint16, - "SignedInteger": numpy.int32, - "Signed32": numpy.int32, - "UnsignedInteger": numpy.uint32, - "Unsigned32": numpy.uint32, - "SignedLong": numpy.int32, - "UnsignedLong": numpy.uint32, - "Signed64": numpy.int64, - "Unsigned64": numpy.uint64, - "FloatValue": numpy.float32, - "FLOATVALUE": numpy.float32, - "FLOAT": numpy.float32, # fit2d - "Float": numpy.float32, # fit2d - "FloatIEEE32": numpy.float32, - "Float32": numpy.float32, - "Double": numpy.float64, - "DoubleValue": numpy.float64, - "FloatIEEE64": numpy.float64, - "DoubleIEEE64": numpy.float64} +DATA_TYPES = { + "SignedByte": numpy.int8, + "Signed8": numpy.int8, + "UnsignedByte": numpy.uint8, + "Unsigned8": numpy.uint8, + "SignedShort": numpy.int16, + "Signed16": numpy.int16, + "UnsignedShort": numpy.uint16, + "Unsigned16": numpy.uint16, + "UnsignedShortInteger": numpy.uint16, + "SignedInteger": numpy.int32, + "Signed32": numpy.int32, + "UnsignedInteger": numpy.uint32, + "Unsigned32": numpy.uint32, + "SignedLong": numpy.int32, + "UnsignedLong": numpy.uint32, + "Signed64": numpy.int64, + "Unsigned64": numpy.uint64, + "FloatValue": numpy.float32, + "FLOATVALUE": numpy.float32, + "FLOAT": numpy.float32, # fit2d + "Float": numpy.float32, # fit2d + "FloatIEEE32": numpy.float32, + "Float32": numpy.float32, + "Double": numpy.float64, + "DoubleValue": numpy.float64, + "FloatIEEE64": numpy.float64, + "DoubleIEEE64": numpy.float64, +} try: - DATA_TYPES["FloatIEEE128"] = DATA_TYPES["DoubleIEEE128"] = DATA_TYPES["QuadrupleValue"] = numpy.float128 + DATA_TYPES["FloatIEEE128"] = DATA_TYPES["DoubleIEEE128"] = DATA_TYPES[ + "QuadrupleValue" + ] = numpy.float128 except AttributeError: # not in your numpy logger.debug("No support for float128 in your code") -NUMPY_EDF_DTYPE = {"int8": "SignedByte", - "int16": "SignedShort", - "int32": "SignedInteger", - "int64": "Signed64", - "uint8": "UnsignedByte", - "uint16": "UnsignedShort", - "uint32": "UnsignedInteger", - "uint64": "Unsigned64", - "float32": "FloatValue", - "float64": "DoubleValue", - "float128": "QuadrupleValue", - } - -MINIMUM_KEYS = set(['HEADERID', - 'IMAGE', # Image numbers are used for sorting and must be different - 'BYTEORDER', - 'DATATYPE', - 'DIM_1', - 'DIM_2', - 'SIZE']) # Size is thought to be essential for writing at least - -MINIMUM_KEYS2 = set([ 'EDF_DATABLOCKID', # Replaces HeaderID - 'EDF_BINARYSIZE', # Replaces Size - 'BYTEORDER', - 'DATATYPE', - 'DIM_1', - 'DIM_2']) +NUMPY_EDF_DTYPE = { + "int8": "SignedByte", + "int16": "SignedShort", + "int32": "SignedInteger", + "int64": "Signed64", + "uint8": "UnsignedByte", + "uint16": "UnsignedShort", + "uint32": "UnsignedInteger", + "uint64": "Unsigned64", + "float32": "FloatValue", + "float64": "DoubleValue", + "float128": "QuadrupleValue", +} + +MINIMUM_KEYS = set( + [ + "HEADERID", + "IMAGE", # Image numbers are used for sorting and must be different + "BYTEORDER", + "DATATYPE", + "DIM_1", + "DIM_2", + "SIZE", + ] +) # Size is thought to be essential for writing at least + +MINIMUM_KEYS2 = set( + [ + "EDF_DATABLOCKID", # Replaces HeaderID + "EDF_BINARYSIZE", # Replaces Size + "BYTEORDER", + "DATATYPE", + "DIM_1", + "DIM_2", + ] +) DEFAULT_VALUES = {} # I do not define default values as they will be calculated at write time # JK20110415 -HeaderBlockType = namedtuple("HeaderBlockType", "header, header_size, binary_size, data_format_version") +HeaderBlockType = namedtuple( + "HeaderBlockType", "header, header_size, binary_size, data_format_version" +) class MalformedHeaderError(IOError): """Raised when a header is malformed""" + pass @@ -175,11 +190,14 @@ def __init__(self, data=None, header=None, number=None): self.bfsize = None # Number of bytes to read from the file if number is not None: - deprecation.deprecated_warning(reason="Argument 'number' is not used anymore", deprecated_since="0.10.0beta") + deprecation.deprecated_warning( + reason="Argument 'number' is not used anymore", + deprecated_since="0.10.0beta", + ) @staticmethod def get_data_rank(header=None, capsHeader=None): - ''' + """ Get the rank of the data array by searching the header for the key DIM_i with the highest index i. The smallest index of DIM_i is 1 (1-based). The highest index is equal to the rank. @@ -187,7 +205,7 @@ def get_data_rank(header=None, capsHeader=None): :param: dict header :param: dict capsHeader (optional) :return: int rank - ''' + """ if header is None: header = {} if capsHeader is None: @@ -204,7 +222,9 @@ def get_data_rank(header=None, capsHeader=None): try: index = int(sidx) except ValueError: - logger.error("Unable converting index of {} to integer.".format(key)) + logger.error( + "Unable converting index of {} to integer.".format(key) + ) else: if index > rank: rank = index @@ -212,7 +232,7 @@ def get_data_rank(header=None, capsHeader=None): @staticmethod def get_data_shape(rank=0, header=None, capsHeader=None): - ''' + """ Returns a tuple with the number of dimensions up to the given rank. The dimensions DIM_i are read from the header dictionary. The values of missing DIM_i keys are set to 1, except DIM_1 which has @@ -234,7 +254,7 @@ def get_data_shape(rank=0, header=None, capsHeader=None): :param: dict header :param: dict capsHeader (optional) :return: tuple shape - ''' + """ if rank is None: rank = 0 if header is None: @@ -250,7 +270,11 @@ def get_data_shape(rank=0, header=None, capsHeader=None): try: dimi = nice_int(header[capsHeader[strDim]]) except ValueError: - logger.error("Unable converting value of {} to integer: {}".format(capsHeader[strDim], header[capsHeader[strDim]])) + logger.error( + "Unable converting value of {} to integer: {}".format( + capsHeader[strDim], header[capsHeader[strDim]] + ) + ) else: if irank == 1: dimi = 0 @@ -258,24 +282,25 @@ def get_data_shape(rank=0, header=None, capsHeader=None): dimi = 1 shape.insert(0, dimi) - return(tuple(shape)) + return tuple(shape) + # JON: this appears to be for nD images, but we don't treat those # PB38k20190607: if needed, it could be checked with get_data_rank(shape)<3 @staticmethod def get_data_counts(shape=None): - ''' + """ Counts items specified by shape (returns 1 for shape==None) :param: tuple shape :return: int counts - ''' + """ if shape is None: shape = () counts = 1 for ishape in range(0, len(shape)): counts *= shape[ishape] - return(counts) + return counts def _compute_capsheader(self): """ @@ -348,7 +373,10 @@ def _extract_header_metadata(self, capsHeader=None): try: self.blobsize = nice_int(self.header[capsHeader["SIZE"]]) except ValueError: - logger.warning("Unable to convert to integer : %s %s " % (capsHeader["SIZE"], self.header[capsHeader["SIZE"]])) + logger.warning( + "Unable to convert to integer : %s %s " + % (capsHeader["SIZE"], self.header[capsHeader["SIZE"]]) + ) rank = self.get_data_rank(self.header, capsHeader) shape = self.get_data_shape(rank, self.header, capsHeader) @@ -364,7 +392,7 @@ def _extract_header_metadata(self, capsHeader=None): if self._dtype is None: if "DATATYPE" in capsHeader: - bytecode = DATA_TYPES[self.header[capsHeader['DATATYPE']]] + bytecode = DATA_TYPES[self.header[capsHeader["DATATYPE"]]] else: bytecode = numpy.uint16 logger.warning("Defaulting type to uint16") @@ -397,7 +425,7 @@ def _extract_header_metadata(self, capsHeader=None): self.bfname = os.path.basename(self.header["EDF_BinaryFileName"]) # add dirname of opened edf-file if dirname != "": - self.bfname = dirname + '/' + self.bfname + self.bfname = dirname + "/" + self.bfname else: self.bfname = None if "EDF_BinaryFilePosition" in self.header: @@ -410,20 +438,28 @@ def _extract_header_metadata(self, capsHeader=None): self.bfsize = calcsize if self._data_compression is None: - '''The binary size can only be calculated for uncompressed data''' + """The binary size can only be calculated for uncompressed data""" if self.bfname is None: - if (self.blobsize < calcsize): - '''The edf binary block can store up to self.blobsize bytes. If + if self.blobsize < calcsize: + """The edf binary block can store up to self.blobsize bytes. If the required size is smaller, all data can be stored inside the blob, otherwise, if the actual blobsize is too small, the data must be truncated. - ''' - logger.warning("Malformed file: The physical size of the binary block {} is too small {}. This and the following frames could be broken.".format(self.blobsize, calcsize)) + """ + logger.warning( + "Malformed file: The physical size of the binary block {} is too small {}. This and the following frames could be broken.".format( + self.blobsize, calcsize + ) + ) else: - if (self.bfsize < calcsize): - '''The size of the binary file is smaller than expected. - ''' - logger.warning("The size available in the binary file {} is smaller than required {}.".format(self.bfsize, calcsize)) + if self.bfsize < calcsize: + """The size of the binary file is smaller than expected. + """ + logger.warning( + "The size available in the binary file {} is smaller than required {}.".format( + self.bfsize, calcsize + ) + ) if self.size is None: # preset with the calculated size, will be updated later @@ -431,7 +467,7 @@ def _extract_header_metadata(self, capsHeader=None): # after decompression self.size = calcsize - #+++++++++++++++++++++++++++++ + # +++++++++++++++++++++++++++++ # PB38k20190607: ATTENTION, weird!: # little_endian^=LowByteFirst, big_endian^=HighByteFirst # Why should _data_swap_needed depend on bpp? @@ -454,14 +490,16 @@ def _extract_header_metadata(self, capsHeader=None): # self._data_swap_needed = False # else: # self._data_swap_needed = True - #+++++++++++++++++++++++++++++ + # +++++++++++++++++++++++++++++ - byte_order = self.header[capsHeader['BYTEORDER']] - if ('Low' in byte_order and numpy.little_endian) or \ - ('High' in byte_order and not numpy.little_endian): + byte_order = self.header[capsHeader["BYTEORDER"]] + if ("Low" in byte_order and numpy.little_endian) or ( + "High" in byte_order and not numpy.little_endian + ): self._data_swap_needed = False - if ('High' in byte_order and numpy.little_endian) or \ - ('Low' in byte_order and not numpy.little_endian): + if ("High" in byte_order and numpy.little_endian) or ( + "Low" in byte_order and not numpy.little_endian + ): if bpp in [2, 4, 8]: self._data_swap_needed = True else: @@ -491,8 +529,10 @@ def _create_header(self, inputheader, defaultheader=None): for key in defaultheader: # exceptions # EDF_*, Size, Image, HeaderID - if (key[0:4] != "EDF_") and (key.upper() not in [ "SIZE", "IMAGE", "HEADERID" ]): - if (key not in self.header): + if (key[0:4] != "EDF_") and ( + key.upper() not in ["SIZE", "IMAGE", "HEADERID"] + ): + if key not in self.header: self.header[key] = defaultheader[key] for key in self.header: @@ -500,7 +540,7 @@ def _create_header(self, inputheader, defaultheader=None): return capsHeader - def _check_header_mandatory_keys(self, filename=''): + def _check_header_mandatory_keys(self, filename=""): """Check that frame header contains all mandatory keys :param str filename: Name of the EDF file @@ -544,14 +584,16 @@ def _unpack(self): data = self._data else: if self._dtype is None: - assert(False) + assert False shape = self.shape if self.bfname is None: - with self.file.lock: if self.file.closed: - logger.error("file: %s from %s is closed. Cannot read data." % (self.file, self.file.name)) + logger.error( + "file: %s from %s is closed. Cannot read data." + % (self.file, self.file.name) + ) return else: self.file.seek(self.start) @@ -559,7 +601,9 @@ def _unpack(self): fileData = self.file.read(self.blobsize) except Exception as e: if isinstance(self.file, fabioutils.GzipFile): - if compression_module.is_incomplete_gz_block_exception(e): + if compression_module.is_incomplete_gz_block_exception( + e + ): return numpy.zeros(shape) raise e @@ -571,12 +615,17 @@ def _unpack(self): fileData = f.read(self.bfsize) elif os.path.exists(self.bfname + ".gz"): import gzip + # Try self.bfname+".gz" if self.bfname does not exist with gzip.open(self.bfname + ".gz", "rb") as f: f.seek(self.bfstart) fileData = f.read(self.bfsize) else: - raise IOError("The binary file {} of {} does not exist".format(self.bfname, self.file.name)) + raise IOError( + "The binary file {} of {} does not exist".format( + self.bfname, self.file.name + ) + ) if self._data_compression is not None: compression = self._data_compression @@ -587,9 +636,14 @@ def _unpack(self): try: import byte_offset # IGNORE:F0401 except ImportError as error: - logger.error("Unimplemented compression scheme: %s (%s)" % (compression, error)) + logger.error( + "Unimplemented compression scheme: %s (%s)" + % (compression, error) + ) else: - myData = byte_offset.analyseCython(fileData, size=uncompressed_size) + myData = byte_offset.analyseCython( + fileData, size=uncompressed_size + ) rawData = myData.astype(self._dtype).tobytes() self.size = uncompressed_size elif compression == "NONE": @@ -612,10 +666,16 @@ def _unpack(self): expected = self.size obtained = len(rawData) if expected > obtained: - logger.error("Data stream is incomplete: %s < expected %s bytes" % (obtained, expected)) + logger.error( + "Data stream is incomplete: %s < expected %s bytes" + % (obtained, expected) + ) rawData += b"\x00" * (expected - obtained) elif expected < obtained: - logger.info("Data stream is padded : %s > required %s bytes" % (obtained, expected)) + logger.info( + "Data stream is padded : %s > required %s bytes" + % (obtained, expected) + ) rawData = rawData[:expected] # PB38k20190607: explicit way: count = get_data_counts(shape) count = self.size // self._dtype.itemsize @@ -640,7 +700,9 @@ def data(self, value): """Setter for data in edf frame""" self._data = value - @deprecation.deprecated(reason="Prefer using 'frame.data'", deprecated_since="0.10.0beta") + @deprecation.deprecated( + reason="Prefer using 'frame.data'", deprecated_since="0.10.0beta" + ) def getData(self): """ Returns the data after unpacking it if needed. @@ -649,7 +711,9 @@ def getData(self): """ return self.data - @deprecation.deprecated(reason="Prefer using 'frame.data ='", deprecated_since="0.10.0beta") + @deprecation.deprecated( + reason="Prefer using 'frame.data ='", deprecated_since="0.10.0beta" + ) def setData(self, npa=None): """Setter for data in edf frame""" self._data = npa @@ -684,7 +748,15 @@ def get_edf_block(self, force_type=None, fit2dMode=False): if "DIM_" in i: header.pop(capsHeader[i]) header_keys.remove(capsHeader[i]) - for KEY in ["SIZE", "EDF_BINARYSIZE", "EDF_HEADERSIZE", "BYTEORDER", "DATATYPE", "HEADERID", "IMAGE"]: + for KEY in [ + "SIZE", + "EDF_BINARYSIZE", + "EDF_HEADERSIZE", + "BYTEORDER", + "DATATYPE", + "HEADERID", + "IMAGE", + ]: if KEY in capsHeader: header.pop(capsHeader[KEY]) header_keys.remove(capsHeader[KEY]) @@ -743,14 +815,21 @@ def get_edf_block(self, force_type=None, fit2dMode=False): preciseSize += len(line) listHeader.append(line) if preciseSize > approxHeaderSize: - logger.error("I expected the header block only at %s in fact it is %s" % (approxHeaderSize, preciseSize)) + logger.error( + "I expected the header block only at %s in fact it is %s" + % (approxHeaderSize, preciseSize) + ) for idx, line in enumerate(listHeader[:]): if line.startswith("EDF_HeaderSize"): headerSize = BLOCKSIZE * (preciseSize // BLOCKSIZE + 1) newline = "EDF_HeaderSize = %5s ;\n" % headerSize delta = len(newline) - len(line) - if (preciseSize // BLOCKSIZE) != ((preciseSize + delta) // BLOCKSIZE): - headerSize = BLOCKSIZE * ((preciseSize + delta) // BLOCKSIZE + 1) + if (preciseSize // BLOCKSIZE) != ( + (preciseSize + delta) // BLOCKSIZE + ): + headerSize = BLOCKSIZE * ( + (preciseSize + delta) // BLOCKSIZE + 1 + ) newline = "EDF_HeaderSize = %5s ;\n" % headerSize preciseSize = preciseSize + delta listHeader[idx] = newline @@ -760,26 +839,38 @@ def get_edf_block(self, force_type=None, fit2dMode=False): listHeader.append(" " * (headerSize - preciseSize) + "}\n") return ("".join(listHeader)).encode("ASCII") + data.tobytes() - @deprecation.deprecated(reason="Prefer using 'getEdfBlock'", deprecated_since="0.10.0beta") + @deprecation.deprecated( + reason="Prefer using 'getEdfBlock'", deprecated_since="0.10.0beta" + ) def getEdfBlock(self, force_type=None, fit2dMode=False): return self.get_edf_block(force_type, fit2dMode) @property - @deprecation.deprecated(reason="Prefer using 'index'", deprecated_since="0.10.0beta") + @deprecation.deprecated( + reason="Prefer using 'index'", deprecated_since="0.10.0beta" + ) def iFrame(self): """Returns the frame index of this frame""" return self._index class EdfImage(fabioimage.FabioImage): - """ Read and try to write the ESRF edf data format """ + """Read and try to write the ESRF edf data format""" DESCRIPTION = "European Synchrotron Radiation Facility data format" DEFAULT_EXTENSIONS = ["edf", "cor"] - RESERVED_HEADER_KEYS = ['HEADERID', 'IMAGE', 'BYTEORDER', 'DATATYPE', - 'DIM_1', 'DIM_2', 'DIM_3', 'SIZE'] + RESERVED_HEADER_KEYS = [ + "HEADERID", + "IMAGE", + "BYTEORDER", + "DATATYPE", + "DIM_1", + "DIM_2", + "DIM_3", + "SIZE", + ] def __init__(self, data=None, header=None, frames=None, generalframe=None): self.currentframe = 0 @@ -793,20 +884,26 @@ def __init__(self, data=None, header=None, frames=None, generalframe=None): try: dim = len(data.shape) except Exception as error: # IGNORE:W0703 - logger.debug("Data don't look like a numpy array (%s), resetting all!!" % error) + logger.debug( + "Data don't look like a numpy array (%s), resetting all!!" % error + ) dim = 0 if dim == 0: raise Exception("Data with empty shape is unsupported") elif dim == 1: - logger.warning("Data in 1d dimension will be stored as a 2d dimension array") + logger.warning( + "Data in 1d dimension will be stored as a 2d dimension array" + ) # make sure we do not change the shape of the input data stored_data = numpy.array(data, copy=False) stored_data.shape = (1, len(data)) elif dim == 2: stored_data = data elif dim >= 3: - raise Exception("Data dimension too big. Only 1d or 2d arrays are supported.") + raise Exception( + "Data dimension too big. Only 1d or 2d arrays are supported." + ) fabioimage.FabioImage.__init__(self, stored_data, header) @@ -892,7 +989,9 @@ def _read_header_block(infile, frame_id): except Exception as e: if isinstance(infile, fabioutils.GzipFile): if compression_module.is_incomplete_gz_block_exception(e): - raise MalformedHeaderError("Incomplete GZ block for header frame %i", frame_id) + raise MalformedHeaderError( + "Incomplete GZ block for header frame %i", frame_id + ) raise e if len(block) == 0: @@ -905,7 +1004,9 @@ def _read_header_block(infile, frame_id): # Empty block looks to be a valid end of file return HeaderBlockType(None, None, None, None) logger.debug("Malformed header: %s", block) - raise MalformedHeaderError("Header frame %i does not contain '{'" % frame_id) + raise MalformedHeaderError( + "Header frame %i does not contain '{'" % frame_id + ) # PB38k20190730: removed unnecessary warning, also short headers can be correctly interpreted # if len(block) < BLOCKSIZE: @@ -914,7 +1015,9 @@ def _read_header_block(infile, frame_id): start = block[0:begin_block] if start.strip() != b"": logger.debug("Malformed header: %s", start) - raise MalformedHeaderError("Header frame %i contains non-whitespace before '{'" % frame_id) + raise MalformedHeaderError( + "Header frame %i contains non-whitespace before '{'" % frame_id + ) # skip the open block character begin_block = begin_block + 1 @@ -927,20 +1030,22 @@ def _read_header_block(infile, frame_id): start = block.find(b"EDF_HeaderSize", begin_block) if start >= 0: # Check that EDF_HeaderSize starts inside this header - end_pattern = re.compile(b'}(\r{0,1})\n') + end_pattern = re.compile(b"}(\r{0,1})\n") end = end_pattern.search(block) if end is not None: if start < end.start(): equal = block.index(b"=", start + len(b"EDF_HeaderSize")) end = block.index(b";", equal + 1) try: - chunk = block[equal + 1:end].strip() + chunk = block[equal + 1 : end].strip() max_header_size = int(chunk) except Exception: logger.warning("Unable to read header size, got: %s", chunk) else: if max_header_size > MAX_HEADER_SIZE: - logger.info("Redefining MAX_HEADER_SIZE to %s", max_header_size) + logger.info( + "Redefining MAX_HEADER_SIZE to %s", max_header_size + ) MAX_HEADER_SIZE = max_header_size # All other EDF_ keys should be read from the header dictionary. Then @@ -964,7 +1069,7 @@ def _read_header_block(infile, frame_id): # Additional checks are needed for locating header end # patterns that are distributed across two blocks. - end_pattern = re.compile(b'}(\r{0,1})\n') + end_pattern = re.compile(b"}(\r{0,1})\n") while True: end = end_pattern.search(block) @@ -982,19 +1087,19 @@ def _read_header_block(infile, frame_id): # at the start of the next block. nextblock = infile.read(BLOCKSIZE) - if block[-1:] == b'}': - if nextblock[:1] == b'\n': + if block[-1:] == b"}": + if nextblock[:1] == b"\n": end_block = block_size start_blob = block_size + 1 offset = start_blob - block_size - len(nextblock) break - elif nextblock[:2] == b'\r\n': + elif nextblock[:2] == b"\r\n": end_block = block_size start_blob = block_size + 2 offset = start_blob - block_size - len(nextblock) break - elif block[-2:] == b'}\r': - if nextblock[:1] == b'\n': + elif block[-2:] == b"}\r": + if nextblock[:1] == b"\n": end_block = block_size - 1 start_blob = block_size + 1 offset = start_blob - block_size - len(nextblock) @@ -1006,8 +1111,15 @@ def _read_header_block(infile, frame_id): blocks.append(block) if len(block) == 0 or block_size > MAX_HEADER_SIZE: block = b"".join(blocks) - logger.debug("Runaway header in EDF file MAX_HEADER_SIZE: %s\n%s", MAX_HEADER_SIZE, block) - raise MalformedHeaderError("Runaway header frame %i (max size: %i)" % (frame_id, MAX_HEADER_SIZE)) + logger.debug( + "Runaway header in EDF file MAX_HEADER_SIZE: %s\n%s", + MAX_HEADER_SIZE, + block, + ) + raise MalformedHeaderError( + "Runaway header frame %i (max size: %i)" + % (frame_id, MAX_HEADER_SIZE) + ) block = b"".join(blocks) @@ -1021,17 +1133,17 @@ def _read_header_block(infile, frame_id): header = OrderedDict() # Why would someone put null bytes in a header? - bytes_whitespace = (string.whitespace + "\x00").encode('ASCII') + bytes_whitespace = (string.whitespace + "\x00").encode("ASCII") # Start with the keys of the input header_block - for line in header_block.split(b';'): - if b'=' in line: - key, val = line.split(b'=', 1) + for line in header_block.split(b";"): + if b"=" in line: + key, val = line.split(b"=", 1) key = key.strip(bytes_whitespace) val = val.strip(bytes_whitespace) try: key, val = key.decode("ASCII"), val.decode("ASCII") - except: + except Exception: logger.warning("Non ASCII in key-value: Drop %s = %s", key, val) else: if key in header: @@ -1051,7 +1163,9 @@ def _read_header_block(infile, frame_id): try: binary_size = int(header["EDF_BinarySize"]) except Exception: - logger.warning("Unable to read binary size, got: %s", header["EDF_BinarySize"]) + logger.warning( + "Unable to read binary size, got: %s", header["EDF_BinarySize"] + ) binary_size = None # Set defaults @@ -1130,7 +1244,10 @@ def _readheader(self, infile): try: frame.blobsize = nice_int(frame.header[capsHeader["SIZE"]]) except ValueError: - logger.warning("Unable to convert to integer : %s %s " % (capsHeader["SIZE"], frame.header[capsHeader["SIZE"]])) + logger.warning( + "Unable to convert to integer : %s %s " + % (capsHeader["SIZE"], frame.header[capsHeader["SIZE"]]) + ) else: frame.blobsize = value.binary_size @@ -1143,7 +1260,11 @@ def _readheader(self, infile): if is_general_header: # This is a general block if self.generalframe is not None: - logger.warning("_readheader: Overwriting an existing general frame (file={},frame={}).".format(frame.file, frame._index)) + logger.warning( + "_readheader: Overwriting an existing general frame (file={},frame={}).".format( + frame.file, frame._index + ) + ) self.generalframe = frame else: # Add a standard frame @@ -1171,7 +1292,10 @@ def _readheader(self, infile): logger.warning("infile is %s" % infile) logger.warning("position is %s" % infile.tell()) logger.warning("blobsize is %s" % frame.blobsize) - logger.error("It seams this error occurs under windows when reading a (large-) file over network: %s ", error) + logger.error( + "It seams this error occurs under windows when reading a (large-) file over network: %s ", + error, + ) raise Exception(error) # PB38k20190607: _check_header_mandatory_keys is already @@ -1194,7 +1318,12 @@ def read(self, fname, frame=None): elif frame < self.nframes: self = self.getframe(frame) else: - logger.error("Reading file %s You requested frame %s but only %s frames are available", fname, frame, self.nframes) + logger.error( + "Reading file %s You requested frame %s but only %s frames are available", + fname, + frame, + self.nframes, + ) self.resetvals() except Exception as e: self._file.close() @@ -1218,26 +1347,32 @@ def unpack(self): return self._frames[self.currentframe].getData() def getframe(self, num): - """ returns the file numbered 'num' in the series as a FabioImage """ + """returns the file numbered 'num' in the series as a FabioImage""" new_image = None if self.nframes == 1: - logger.debug("Single frame EDF; having FabioImage default behavior: %s" % num) + logger.debug( + "Single frame EDF; having FabioImage default behavior: %s" % num + ) new_image = fabioimage.FabioImage.getframe(self, num) new_image._file = self._file elif num < self.nframes: - logger.debug("Multi frame EDF; having EdfImage specific behavior frame %s: 0<=frame<%s" % - (num, self.nframes)) + logger.debug( + "Multi frame EDF; having EdfImage specific behavior frame %s: 0<=frame<%s" + % (num, self.nframes) + ) new_image = self.__class__(frames=self._frames) new_image.currentframe = num new_image.filename = self.filename new_image._file = self._file else: - raise IOError("EdfImage.getframe: Cannot access frame %s: 0<=frame<%s" % - (num, self.nframes)) + raise IOError( + "EdfImage.getframe: Cannot access frame %s: 0<=frame<%s" + % (num, self.nframes) + ) return new_image def previous(self): - """ returns the previous file in the series as a FabioImage """ + """returns the previous file in the series as a FabioImage""" new_image = None if self.nframes == 1: new_image = fabioimage.FabioImage.previous(self) @@ -1274,7 +1409,9 @@ def write(self, fname, force_type=None, fit2dMode=False): with self._open(fname, mode="wb") as outfile: for i, frame in enumerate(self._frames): frame._set_container(self, i) - outfile.write(frame.get_edf_block(force_type=force_type, fit2dMode=fit2dMode)) + outfile.write( + frame.get_edf_block(force_type=force_type, fit2dMode=fit2dMode) + ) def append_frame(self, frame=None, data=None, header=None): """ @@ -1289,7 +1426,9 @@ def append_frame(self, frame=None, data=None, header=None): else: self._frames.append(EdfFrame(data, header)) - @deprecation.deprecated(reason="Prefer using 'append_frame'", deprecated_since="0.10.0beta") + @deprecation.deprecated( + reason="Prefer using 'append_frame'", deprecated_since="0.10.0beta" + ) def appendFrame(self, frame=None, data=None, header=None): self.append_frame(frame, data, header) @@ -1303,7 +1442,9 @@ def delete_frame(self, frameNb=None): else: self._frames.pop(frameNb) - @deprecation.deprecated(reason="Prefer using 'delete_frame'", deprecated_since="0.10.0beta") + @deprecation.deprecated( + reason="Prefer using 'delete_frame'", deprecated_since="0.10.0beta" + ) def deleteFrame(self, frameNb=None): self.delete_frame(frameNb) @@ -1315,10 +1456,13 @@ def fast_read_data(self, filename=None): :return: data from another file using positions from current EdfImage """ if (filename is None) or not os.path.isfile(filename): - raise RuntimeError("EdfImage.fast_read_data is only valid with another file: %s does not exist" % (filename)) + raise RuntimeError( + "EdfImage.fast_read_data is only valid with another file: %s does not exist" + % (filename) + ) data = None frame = self._frames[self.currentframe] - with open(filename, "rb")as f: + with open(filename, "rb") as f: f.seek(frame.start) raw = f.read(frame.blobsize) try: @@ -1330,7 +1474,9 @@ def fast_read_data(self, filename=None): data.byteswap(True) return data - @deprecation.deprecated(reason="Prefer using 'fastReadData'", deprecated_since="0.10.0beta") + @deprecation.deprecated( + reason="Prefer using 'fastReadData'", deprecated_since="0.10.0beta" + ) def fastReadData(self, filename): return self.fast_read_data(filename) @@ -1340,36 +1486,46 @@ def fast_read_roi(self, filename, coords=None): The aim is performances, ... but only supports uncompressed files. :param filename: name of another file with the same structure. - :paran coords: 2 tuple of slices (RECOMMANDED) or a 4 tuple (NOT RECOMMANDED, cryptic fabian convention) + :paran coords: 2 tuple of slices (RECOMMANDED) or a 4 tuple (NOT RECOMMANDED, cryptic fabian convention) :return: ROI-data from another file using positions from current EdfImage :rtype: numpy 2darray """ if (filename is None) or not os.path.isfile(filename): - raise RuntimeError("EdfImage.fast_read_roi is only valid with another file: %s does not exist" % (filename)) + raise RuntimeError( + "EdfImage.fast_read_roi is only valid with another file: %s does not exist" + % (filename) + ) data = None frame = self._frames[self.currentframe] if len(coords) == 4: slice1 = self.make_slice(coords) - elif (len(coords) == 2 and isinstance(coords[0], slice) and - isinstance(coords[1], slice)): + elif ( + len(coords) == 2 + and isinstance(coords[0], slice) + and isinstance(coords[1], slice) + ): slice1 = coords else: - logger.warning('readROI: Unable to understand Region Of Interest: got %s', coords) + logger.warning( + "readROI: Unable to understand Region Of Interest: got %s", coords + ) return d1 = self.data.shape[-1] start0 = slice1[0].start start1 = slice1[1].start - slice2 = (slice(0, slice1[0].stop - start0, slice1[0].step), - slice(0, slice1[1].stop - start1, slice1[1].step)) + slice2 = ( + slice(0, slice1[0].stop - start0, slice1[0].step), + slice(0, slice1[1].stop - start1, slice1[1].step), + ) start = frame.start + self.bpp * (d1 * start0 + start1) size = self.bpp * ((slice2[0].stop) * d1) - with open(filename, "rb")as f: + with open(filename, "rb") as f: f.seek(start) raw = f.read(size) - if len(raw) 1: - hds = data_grp.create_dataset(f"data_{i+1:06d}", data=data, chunks=chunks, **compression) + hds = data_grp.create_dataset( + f"data_{i + 1:06d}", data=data, chunks=chunks, **compression + ) elif len(self.dataset) == 1: - hds = data_grp.create_dataset(f"data", data=data, chunks=chunks, **compression) + hds = data_grp.create_dataset( + "data", data=data, chunks=chunks, **compression + ) hds.attrs["interpretation"] = "image" if "signal" not in data_grp.attrs: data_grp.attrs["signal"] = posixpath.split(hds.name)[-1] def getframe(self, num): - """ returns the frame numbered 'num' in the stack if applicable""" + """returns the frame numbered 'num' in the stack if applicable""" if self.nframes > 1: new_img = None if (num >= 0) and num < self.nframes: @@ -215,7 +226,7 @@ def getframe(self, num): nfr -= 1 elif ds.ndim == 3: # Stack of images - if (nfr < ds.shape[0]): + if nfr < ds.shape[0]: data = ds[nfr] break else: @@ -236,7 +247,7 @@ def getframe(self, num): return new_img def previous(self): - """ returns the previous file in the series as a FabioImage """ + """returns the previous file in the series as a FabioImage""" new_image = None if self.nframes == 1: new_image = FabioImage.previous(self) @@ -276,7 +287,10 @@ def get_data(self): data = None index = self.currentframe if isinstance(self.dataset, list): - frame_idx = [len(ds) if (ds is not None and ds.ndim == 3) else 1 for ds in self.dataset] + frame_idx = [ + len(ds) if (ds is not None and ds.ndim == 3) else 1 + for ds in self.dataset + ] end_ds = numpy.cumsum(frame_idx) for idx, end in enumerate(end_ds): start = 0 if idx == 0 else end_ds[idx - 1] @@ -303,7 +317,10 @@ def set_data(self, data, index=None): if index is None: index = self.currentframe if isinstance(self.dataset, list): - frame_idx = [len(ds) if (ds is not None and ds.ndim == 3) else 1 for ds in self.dataset] + frame_idx = [ + len(ds) if (ds is not None and ds.ndim == 3) else 1 + for ds in self.dataset + ] end_ds = numpy.cumsum(frame_idx) nframes = end_ds[-1] if index == nframes: diff --git a/src/fabio/esperantoimage.py b/src/fabio/esperantoimage.py index 6453aace..083110be 100644 --- a/src/fabio/esperantoimage.py +++ b/src/fabio/esperantoimage.py @@ -28,20 +28,21 @@ __authors__ = ["Florian Plaswig", "Jérôme Kieffer"] __license__ = "MIT" __copyright__ = "2019-2020 ESRF" -__date__ = "27/08/2024" +__date__ = "27/10/2025" import io from collections import OrderedDict import logging -logger = logging.getLogger(__name__) import numpy from .fabioimage import FabioImage from .compression import agi_bitfield +logger = logging.getLogger(__name__) + class EsperantoImage(FabioImage): - """FabIO image class for Esperanto image files - """ + """FabIO image class for Esperanto image files""" + DESCRIPTION = "CrysAlis Pro Esperanto file format" DEFAULT_EXTENSIONS = ["eseperanto", "esper"] HEADER_SEPARATOR = "\x0d\x0a" @@ -51,25 +52,53 @@ class EsperantoImage(FabioImage): VALID_FORMATS = ("AGI_BITFIELD", "4BYTE_LONG") DUMMY = 0 # Value to fill empty regions with when padding - HEADER_KEYS = OrderedDict([("IMAGE", "lnx lny lbx lby spixelformat"), - ("SPECIAL_CCD_1", "delectronsperadu ldarkcorrectionswitch lfloodfieldcorrectionswitch/mode dsystemdcdb2gain ddarksignal dreadnoiserms"), - ("SPECIAL_CCD_2", "ioverflowflag ioverflowafterremeasureflag inumofdarkcurrentimages inumofmultipleimages loverflowthreshold"), - ("SPECIAL_CCD_3", "ldetector_descriptor lisskipcorrelation lremeasureturbomode bfsoftbinningflag bflownoisemodeflag"), - ("SPECIAL_CCD_4", "lremeasureinturbo_done lisoverflowthresholdchanged loverflowthresholdfromimage lisdarksignalchanged lisreadnoisermschanged lisdarkdone lisremeasurewithskipcorrelation lcorrelationshift"), - ("SPECIAL_CCD_5", "dblessingrej ddarksignalfromimage dreadnoisermsfromimage dtrueimagegain"), - ("TIME", "dexposuretimeinsec doverflowtimeinsec doverflowfilter"), - ("MONITOR", "lmon1 lmon2 lmon3 lmon4"), - ("PIXELSIZE", "drealpixelsizex drealpixelsizey dsithicknessmmforpixeldetector"), - ("TIMESTAMP", "timestampstring"), - ("GRIDPATTERN", "filename"), - ("STARTANGLESINDEG", "dom_s dth_s dka_s dph_s"), - ("ENDANGLESINDEG", "dom_e dth_e dka_e dph_e"), - ("GONIOMODEL_1", "dbeam2indeg dbeam3indeg detectorrotindeg_x detectorrotindeg_y detectorrotindeg_z dxorigininpix dyorigininpix dalphaindeg dbetaindeg ddistanceinmm"), - ("GONIOMODEL_2", "dzerocorrectionsoftindeg_om dzerocorrectionsoftindeg_th dzerocorrectionsoftindeg_ka dzerocorrectionsoftindeg_ph"), - ("WAVELENGTH", "dalpha1 dalpha2 dalpha12 dbeta1"), - ("MONOCHROMATOR", "ddvalue-prepolfac orientation-type"), - ("ABSTORUN", "labstorunscale"), - ("HISTORY", "historystring")]) + HEADER_KEYS = OrderedDict( + [ + ("IMAGE", "lnx lny lbx lby spixelformat"), + ( + "SPECIAL_CCD_1", + "delectronsperadu ldarkcorrectionswitch lfloodfieldcorrectionswitch/mode dsystemdcdb2gain ddarksignal dreadnoiserms", + ), + ( + "SPECIAL_CCD_2", + "ioverflowflag ioverflowafterremeasureflag inumofdarkcurrentimages inumofmultipleimages loverflowthreshold", + ), + ( + "SPECIAL_CCD_3", + "ldetector_descriptor lisskipcorrelation lremeasureturbomode bfsoftbinningflag bflownoisemodeflag", + ), + ( + "SPECIAL_CCD_4", + "lremeasureinturbo_done lisoverflowthresholdchanged loverflowthresholdfromimage lisdarksignalchanged lisreadnoisermschanged lisdarkdone lisremeasurewithskipcorrelation lcorrelationshift", + ), + ( + "SPECIAL_CCD_5", + "dblessingrej ddarksignalfromimage dreadnoisermsfromimage dtrueimagegain", + ), + ("TIME", "dexposuretimeinsec doverflowtimeinsec doverflowfilter"), + ("MONITOR", "lmon1 lmon2 lmon3 lmon4"), + ( + "PIXELSIZE", + "drealpixelsizex drealpixelsizey dsithicknessmmforpixeldetector", + ), + ("TIMESTAMP", "timestampstring"), + ("GRIDPATTERN", "filename"), + ("STARTANGLESINDEG", "dom_s dth_s dka_s dph_s"), + ("ENDANGLESINDEG", "dom_e dth_e dka_e dph_e"), + ( + "GONIOMODEL_1", + "dbeam2indeg dbeam3indeg detectorrotindeg_x detectorrotindeg_y detectorrotindeg_z dxorigininpix dyorigininpix dalphaindeg dbetaindeg ddistanceinmm", + ), + ( + "GONIOMODEL_2", + "dzerocorrectionsoftindeg_om dzerocorrectionsoftindeg_th dzerocorrectionsoftindeg_ka dzerocorrectionsoftindeg_ph", + ), + ("WAVELENGTH", "dalpha1 dalpha2 dalpha12 dbeta1"), + ("MONOCHROMATOR", "ddvalue-prepolfac orientation-type"), + ("ABSTORUN", "labstorunscale"), + ("HISTORY", "historystring"), + ] + ) def __init__(self, *arg, **kwargs): """ @@ -86,7 +115,7 @@ def data(self): @data.setter def data(self, value): """Esperanto accepts only images square with size a multiple of 4 - and limit the size to 256-4096 + and limit the size to 256-4096 """ if value is None: self._data = value @@ -103,13 +132,17 @@ def data(self, value): value = value.astype(numpy.int32) if old_shape != new_shape: - logger.info("Padding image to be square, multiple of 4 and of size between 256 and 4096") + logger.info( + "Padding image to be square, multiple of 4 and of size between 256 and 4096" + ) self._data = numpy.zeros(new_shape, dtype=numpy.int32) + self.DUMMY # Nota: center horizontally, not vertically (!?) shift = max((new_shape[1] - old_shape[1]) // 2, 0) - self._data[:min(old_shape[0], new_shape[0]), - shift:min(shift + old_shape[1], new_shape[1])] = value + self._data[ + : min(old_shape[0], new_shape[0]), + shift : min(shift + old_shape[1], new_shape[1]), + ] = value else: self._data = value @@ -122,32 +155,40 @@ def _readheader(self, infile): self.header = self.check_header() # read the first line of the header - top_line = infile.read(self.HEADER_WIDTH).decode('ascii') + top_line = infile.read(self.HEADER_WIDTH).decode("ascii") if not top_line[-2:] == self.HEADER_SEPARATOR: - raise RuntimeError("Unable to read esperanto header: Invalid format of first line") + raise RuntimeError( + "Unable to read esperanto header: Invalid format of first line" + ) words = top_line.split() try: self.HEADER_LINES = int(words[5]) except Exception as err: raise RuntimeError("Unable to determine header size: %s" % err) assert self.HEADER_WIDTH == int(words[8]), "Line length match" - self.header["ESPERANTO FORMAT"] = ' '.join(words[2:]) + self.header["ESPERANTO FORMAT"] = " ".join(words[2:]) self.header["format"] = int(words[2]) # read the remaining lines for line_num in range(1, self.HEADER_LINES): - line = infile.read(self.HEADER_WIDTH).decode('ascii') + line = infile.read(self.HEADER_WIDTH).decode("ascii") if not line[-2] == self.HEADER_SEPARATOR[0]: - raise RuntimeError("Unable to read esperanto header: Invalid format of line %d." % (line_num + 1)) + raise RuntimeError( + "Unable to read esperanto header: Invalid format of line %d." + % (line_num + 1) + ) words = line.split() if not words: continue key = words[0] if len(key) == 1 and ord(key) < 32: continue - self.header[key] = ' '.join(words[1:]) + self.header[key] = " ".join(words[1:]) if key not in self.HEADER_KEYS: - logger.warning("Unable to read esperanto header: Invalid Key %s in line %d." % (key, line_num)) + logger.warning( + "Unable to read esperanto header: Invalid Key %s in line %d." + % (key, line_num) + ) else: # try to interpret if key in ("HISTORY", "TIMESTAMP"): self.header[self.HEADER_KEYS[key]] = " ".join(words[1:]).strip('"') @@ -158,12 +199,12 @@ def _readheader(self, infile): if k[0] in "lib": try: value = int(v) - except: + except Exception: value = v elif k[0] == "d": try: value = float(v) - except: + except Exception: value = v else: value = v.strip('"') @@ -171,8 +212,15 @@ def _readheader(self, infile): width = self.header["lnx"] height = self.header["lny"] - if 256 > width > 4096 or width % 4 != 0 or 256 > height > 4096 or height % 4 != 0: - logger.warning("The dimensions of the image is (%i, %i) but they should only be between 256 and 4096 and a multiple of 4. This might cause compatibility issues.") + if ( + 256 > width > 4096 + or width % 4 != 0 + or 256 > height > 4096 + or height % 4 != 0 + ): + logger.warning( + "The dimensions of the image is (%i, %i) but they should only be between 256 and 4096 and a multiple of 4. This might cause compatibility issues." + ) self.shape = (height, width) @@ -195,7 +243,9 @@ def read(self, fname, frame=None): try: pixelsize = 4 pixelcount = self.shape[0] * self.shape[1] - data = numpy.frombuffer(infile.read(pixelsize * pixelcount), dtype=self._dtype) + data = numpy.frombuffer( + infile.read(pixelsize * pixelcount), dtype=self._dtype + ) self.data = numpy.reshape(data, self.shape) except Exception as err: raise RuntimeError("Exception while reading pixel data %s." % err) @@ -204,40 +254,53 @@ def read(self, fname, frame=None): try: self.data = agi_bitfield.decompress(raw_data, self.shape) except Exception as err: - raise RuntimeError("Exception while decompressing pixel data %s." % err) + raise RuntimeError( + "Exception while decompressing pixel data %s." % err + ) else: - raise RuntimeError("Format not supported %s. Valid formats are %s" % (self.format, self.VALID_FORMATS)) + raise RuntimeError( + "Format not supported %s. Valid formats are %s" + % (self.format, self.VALID_FORMATS) + ) return self def _formatheaderline(self, line): "Return one line as ASCII bytes padded with end of line" assert len(line) <= self.HEADER_WIDTH - len(self.HEADER_SEPARATOR) - return (line.ljust(self.HEADER_WIDTH - len(self.HEADER_SEPARATOR), " ") + self.HEADER_SEPARATOR).encode("ASCII") + return ( + line.ljust(self.HEADER_WIDTH - len(self.HEADER_SEPARATOR), " ") + + self.HEADER_SEPARATOR + ).encode("ASCII") def _update_header(self): """ Upper-cases headers are directly written into the ASCII header of the file. This method updates them according to values found in lower-case header (if any) - - As a consequence, unforeseen headers are simply discarded. + + As a consequence, unforeseen headers are simply discarded. """ if "ESPERANTO FORMAT" not in self.header: # default format - self.header["ESPERANTO FORMAT"] = "1 CONSISTING OF 25 LINES OF 256 BYTES EACH" + self.header["ESPERANTO FORMAT"] = ( + "1 CONSISTING OF 25 LINES OF 256 BYTES EACH" + ) else: - self.header["ESPERANTO FORMAT"] = "%s CONSISTING OF %s LINES OF %s BYTES EACH" % (self.header["format"], self.HEADER_LINES, self.HEADER_WIDTH) + self.header["ESPERANTO FORMAT"] = ( + "%s CONSISTING OF %s LINES OF %s BYTES EACH" + % (self.header["format"], self.HEADER_LINES, self.HEADER_WIDTH) + ) self.header["lny"], self.header["lnx"] = self.data.shape self.header["spixelformat"] = self.format - if "lbx" not in self.header: + if "lbx" not in self.header: self.header["lbx"] = 1 - if "lby" not in self.header: + if "lby" not in self.header: self.header["lby"] = 1 for key, value in self.HEADER_KEYS.items(): updated = "" for lower_key in value.split(): if lower_key[0] in "ldib": - updated += '%s ' % self.header.get(lower_key, 0) + updated += "%s " % self.header.get(lower_key, 0) else: updated += '"%s" ' % self.header.get(lower_key, "") self.header[key] = updated.strip() @@ -251,12 +314,16 @@ def write(self, fname): # create header self._update_header() - bytes_header = self._formatheaderline("ESPERANTO FORMAT " + self.header["ESPERANTO FORMAT"]) + bytes_header = self._formatheaderline( + "ESPERANTO FORMAT " + self.header["ESPERANTO FORMAT"] + ) for key in self.HEADER_KEYS: - bytes_header += self._formatheaderline(key + ' ' + self.header[key]) + bytes_header += self._formatheaderline(key + " " + self.header[key]) if len(self.HEADER_KEYS) + 1 < self.HEADER_LINES: - bytes_header += self._formatheaderline("") * (self.HEADER_LINES - len(self.HEADER_KEYS) - 1) - bytes_header = bytes_header[:-len(self.HEADER_SEPARATOR)] + self.HEADER_END + bytes_header += self._formatheaderline("") * ( + self.HEADER_LINES - len(self.HEADER_KEYS) - 1 + ) + bytes_header = bytes_header[: -len(self.HEADER_SEPARATOR)] + self.HEADER_END with self._open(fname, "wb") as outfile: outfile.write(bytes_header) if self.format == "4BYTE_LONG": diff --git a/src/fabio/ext/_cif.pyx b/src/fabio/ext/_cif.pyx index 10fe1bf8..36efe6ba 100644 --- a/src/fabio/ext/_cif.pyx +++ b/src/fabio/ext/_cif.pyx @@ -27,11 +27,11 @@ """Cif parser helper functions""" -__author__ = "Jerome Kieffer" +__author__ = "Jérôme Kieffer" __contact__ = "jerome.kieffer@esrf.eu" __license__ = "MIT" __copyright__ = "2014, European Synchrotron Radiation Facility, Grenoble, France" -__date__ = "18/01/2019" +__date__ = "27/10/2025" import numpy import cython diff --git a/src/fabio/ext/byte_offset.pyx b/src/fabio/ext/byte_offset.pyx index 0c7f1d05..a778ebe8 100644 --- a/src/fabio/ext/byte_offset.pyx +++ b/src/fabio/ext/byte_offset.pyx @@ -31,11 +31,11 @@ They use a modified (simplified) byte-offset algorithm. This file contains the decompression function from a string to an int64 numpy array. """ -__author__ = "Jerome Kieffer" +__author__ = "Jérôme Kieffer" __contact__ = "jerome.kieffer@esrf.eu" __license__ = "MIT" __copyright__ = "2010-2016, European Synchrotron Radiation Facility, Grenoble, France" -__date__ = "14/12/2020" +__date__ = "27/10/2025" import numpy @@ -174,14 +174,14 @@ def dec_cbf(bytes stream not None, size=None): int lenStream = < int > len(stream) uint8_t[::1] cstream = bytearray(stream) int64_t[::1] data_out - + if size is None: csize = lenStream else: csize = < int > size - + data_out = numpy.empty(csize, dtype=numpy.int64) - + with nogil: while (i < lenStream) and (j < csize): if (cstream[i] == key8): diff --git a/src/fabio/ext/cf_io.pyx b/src/fabio/ext/cf_io.pyx index d5d46010..b5b3c074 100644 --- a/src/fabio/ext/cf_io.pyx +++ b/src/fabio/ext/cf_io.pyx @@ -27,11 +27,15 @@ """New Cython version of cf_iomodule.c for preparing the migration to Python3""" -__authors__ = ["Jerome Kieffer"] +__authors__ = ["Jérôme Kieffer"] __contact__ = "jerome.kieffer@esrf.eu" __license__ = "MIT" +__date__ = "27/10/2025" __copyright__ = "2013, European Synchrotron Radiation Facility, Grenoble, France" +from libc.string cimport memcpy +from libc.stdio cimport fopen, FILE + import cython import numpy import os @@ -39,8 +43,6 @@ import tempfile import logging logger = logging.getLogger(__name__) -from libc.string cimport memcpy -from libc.stdio cimport fopen, FILE CF_H = 1 diff --git a/src/fabio/ext/mar345_IO.pyx b/src/fabio/ext/mar345_IO.pyx index 5412b9f6..2a52bf02 100644 --- a/src/fabio/ext/mar345_IO.pyx +++ b/src/fabio/ext/mar345_IO.pyx @@ -44,13 +44,13 @@ original implementation which is buggy """ -__authors__ = ["Jerome Kieffer", "Gael Goret", "Thomas Vincent"] +__authors__ = ["Jérôme Kieffer", "Gael Goret", "Thomas Vincent"] __contact__ = "jerome.kieffer@esrf.eu" __license__ = "MIT" -__copyright__ = "2012-2020, European Synchrotron Radiation Facility, Grenoble, France" -__date__ = "25/10/2023" +__copyright__ = "2012-2025, European Synchrotron Radiation Facility, Grenoble, France" +__date__ = "25/10/2025" -from libc.stdint cimport int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t +from libc.stdint cimport int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t import cython diff --git a/src/fabio/fabioformats.py b/src/fabio/fabioformats.py index 8d1c1c7b..e59be481 100644 --- a/src/fabio/fabioformats.py +++ b/src/fabio/fabioformats.py @@ -34,18 +34,19 @@ __contact__ = "valentin.valls@esrf.eu" __license__ = "MIT" __copyright__ = "European Synchrotron Radiation Facility, Grenoble, France" -__date__ = "23/08/2024" +__date__ = "27/10/2025" __status__ = "stable" -__docformat__ = 'restructuredtext' +__docformat__ = "restructuredtext" import logging -_logger = logging.getLogger(__name__) - from . import fabioimage from .fabioutils import OrderedDict +_logger = logging.getLogger(__name__) + try: import importlib + importer = importlib.import_module except ImportError: @@ -58,6 +59,7 @@ def importer(module_name): module = getattr(module, name) return module + _default_codecs = [ ("edfimage", "EdfImage"), ("dtrekimage", "DtrekImage"), @@ -111,7 +113,9 @@ def register(codec_class): """Register a class format to the core fabio library""" global _extension_cache if not issubclass(codec_class, fabioimage.FabioImage): - raise AssertionError("Expected subclass of FabioImage class but found %s" % type(codec_class)) + raise AssertionError( + "Expected subclass of FabioImage class but found %s" % type(codec_class) + ) _registry[codec_class.codec_name()] = codec_class # clean u[p the cache _extension_cache = None @@ -129,7 +133,9 @@ def register_default_formats(): module = importer("fabio." + module_name) codec_class = getattr(module, class_name) if codec_class is None: - raise RuntimeError("Class name '%s' from mudule '%s' not found" % (class_name, module_name)) + raise RuntimeError( + "Class name '%s' from mudule '%s' not found" % (class_name, module_name) + ) register(codec_class) @@ -240,8 +246,11 @@ def factory(name): if name in _registry: obj = _registry[name]() else: - msg = ("FileType %s is unknown !, " - "please check if the filename exists or select one from %s" % (name, _registry.keys())) + msg = ( + "FileType %s is unknown !, " + "please check if the filename exists or select one from %s" + % (name, _registry.keys()) + ) _logger.debug(msg) raise RuntimeError(msg) return obj diff --git a/src/fabio/fabioimage.py b/src/fabio/fabioimage.py index 49f27e2b..689628da 100644 --- a/src/fabio/fabioimage.py +++ b/src/fabio/fabioimage.py @@ -42,70 +42,98 @@ __contact__ = "jerome.kieffer@esrf.fr" __license__ = "MIT" __copyright__ = "ESRF" -__date__ = "20/08/2024" +__date__ = "27/10/2025" import os import logging import sys import tempfile import weakref -logger = logging.getLogger(__name__) import numpy from . import fabioutils, converters from .fabioutils import OrderedDict +from .compression import COMPRESSORS from .utils import pilutils from .utils import deprecation +logger = logging.getLogger(__name__) + class _FabioArray(object): - """"Abstract class providing array API used by :class:`FabioImage` and + """ "Abstract class providing array API used by :class:`FabioImage` and :class:`FabioFrame`.""" @property - @deprecation.deprecated(reason="Prefer using 'shape[-1]' instead of 'dim1'", deprecated_since="0.10.0beta") + @deprecation.deprecated( + reason="Prefer using 'shape[-1]' instead of 'dim1'", + deprecated_since="0.10.0beta", + ) def dim1(self): return self.shape[-1] @property - @deprecation.deprecated(reason="Prefer using 'shape[-2]' instead of 'dim2'", deprecated_since="0.10.0beta") + @deprecation.deprecated( + reason="Prefer using 'shape[-2]' instead of 'dim2'", + deprecated_since="0.10.0beta", + ) def dim2(self): return self.shape[-2] @dim1.setter - @deprecation.deprecated(reason="dim1 should not be updated", deprecated_since="0.10.0beta") + @deprecation.deprecated( + reason="dim1 should not be updated", deprecated_since="0.10.0beta" + ) def dim1(self, value): self.__set_dim1(value) @dim2.setter - @deprecation.deprecated(reason="dim2 should not be updated", deprecated_since="0.10.0beta") + @deprecation.deprecated( + reason="dim2 should not be updated", deprecated_since="0.10.0beta" + ) def dim2(self, value): self.__set_dim2(value) @property - @deprecation.deprecated(reason="Prefer using 'shape[-3]' instead of 'dim3'", deprecated_since="0.10.0beta") + @deprecation.deprecated( + reason="Prefer using 'shape[-3]' instead of 'dim3'", + deprecated_since="0.10.0beta", + ) def dim3(self): if len(self.shape) < 3: raise AttributeError("No attribye dim3") return self.shape[-3] @property - @deprecation.deprecated(reason="Prefer using 'shape' instead of 'dims' (the content in reverse order)", deprecated_since="0.10.0beta") + @deprecation.deprecated( + reason="Prefer using 'shape' instead of 'dims' (the content in reverse order)", + deprecated_since="0.10.0beta", + ) def dims(self): return list(reversed(self.shape)) - @deprecation.deprecated(reason="Prefer using 'shape[-1]' instead of 'get_dim1'", deprecated_since="0.10.0beta") + @deprecation.deprecated( + reason="Prefer using 'shape[-1]' instead of 'get_dim1'", + deprecated_since="0.10.0beta", + ) def get_dim1(self): return self.shape[-1] - @deprecation.deprecated(reason="Prefer using 'shape[-2]' instead of 'get_dim2'", deprecated_since="0.10.0beta") + @deprecation.deprecated( + reason="Prefer using 'shape[-2]' instead of 'get_dim2'", + deprecated_since="0.10.0beta", + ) def get_dim2(self): return self.shape[-2] - @deprecation.deprecated(reason="dim1 should not be updated", deprecated_since="0.10.0beta") + @deprecation.deprecated( + reason="dim1 should not be updated", deprecated_since="0.10.0beta" + ) def set_dim1(self, value): self.__set_dim1(value) - @deprecation.deprecated(reason="dim2 should not be updated", deprecated_since="0.10.0beta") + @deprecation.deprecated( + reason="dim2 should not be updated", deprecated_since="0.10.0beta" + ) def set_dim2(self, value): self.__set_dim2(value) @@ -116,7 +144,7 @@ def __set_dim2(self, value): self.data.shape = self.data.shape[:-2] + (value, -1) def resetvals(self): - """ Reset cache - call on changing data """ + """Reset cache - call on changing data""" self.mean = None self.stddev = None self.maxval = None @@ -125,7 +153,11 @@ def resetvals(self): self.slice = None self.area_sum = None - @deprecation.deprecated(reason="Not maintained", replacement="fabio.utils.pilutils.create_pil_16", since_version="0.9") + @deprecation.deprecated( + reason="Not maintained", + replacement="fabio.utils.pilutils.create_pil_16", + since_version="0.9", + ) def toPIL16(self, filename=None): """ Convert the image to Python Imaging Library 16-bits greyscale image. @@ -135,7 +167,11 @@ def toPIL16(self, filename=None): return pilutils.create_pil_16(self.data) @property - @deprecation.deprecated(reason="Not maintained", replacement="fabio.utils.pilutils.create_pil_16", since_version="0.9") + @deprecation.deprecated( + reason="Not maintained", + replacement="fabio.utils.pilutils.create_pil_16", + since_version="0.9", + ) def pilimage(self): """ Convert the image to Python Imaging Library 16-bits greyscale image. @@ -143,20 +179,23 @@ def pilimage(self): return self.toPIL16() @pilimage.setter - @deprecation.deprecated(reason="Setting pilimage not supported. This attrbute is not cached anymore", deprecated_since="0.10.0beta") + @deprecation.deprecated( + reason="Setting pilimage not supported. This attrbute is not cached anymore", + deprecated_since="0.10.0beta", + ) def pilimage(self, value): if value is not None: raise ValueError("Setting pilimage attribute is not supported") def getmax(self): - """ Find max value in self.data, caching for the future """ + """Find max value in self.data, caching for the future""" if self.maxval is None: if self.data is not None: self.maxval = self.data.max() return self.maxval def getmin(self): - """ Find min value in self.data, caching for the future """ + """Find min value in self.data, caching for the future""" if self.minval is None: if self.data is not None: self.minval = self.data.min() @@ -182,12 +221,11 @@ def make_slice(self, coords): # down wrt to the matrix hence these tranformations dim2, dim1 = self.data.shape # FIXME: This code is just not working dim2 is used in place of dim1 - fixme = (dim2 - coords[3] - 1, - coords[0], - dim2 - coords[1] - 1, - coords[2]) - return (slice(int(fixme[0]), int(fixme[2]) + 1), - slice(int(fixme[1]), int(fixme[3]) + 1)) + fixme = (dim2 - coords[3] - 1, coords[0], dim2 - coords[1] - 1, coords[2]) + return ( + slice(int(fixme[0]), int(fixme[2]) + 1), + slice(int(fixme[1]), int(fixme[3]) + 1), + ) def integrate_area(self, coords): """ @@ -201,8 +239,11 @@ def integrate_area(self, coords): return 0 if len(coords) == 4: sli = self.make_slice(coords) - elif len(coords) == 2 and isinstance(coords[0], slice) and \ - isinstance(coords[1], slice): + elif ( + len(coords) == 2 + and isinstance(coords[0], slice) + and isinstance(coords[1], slice) + ): sli = coords if sli == self.slice and self.area_sum is not None: @@ -216,13 +257,13 @@ def integrate_area(self, coords): return self.area_sum def getmean(self): - """ return the mean """ + """return the mean""" if self.mean is None: self.mean = self.data.mean(dtype=numpy.float64) return self.mean def getstddev(self): - """ return the standard deviation """ + """return the standard deviation""" if self.stddev is None: self.stddev = self.data.std(dtype=numpy.float64) return self.stddev @@ -254,7 +295,10 @@ def bytecode(self): def get_bytecode(self): return self.bytecode - @deprecation.deprecated(reason="Prefer using 'bytecode' instead of 'getByteCode'", deprecated_since="0.10.0beta") + @deprecation.deprecated( + reason="Prefer using 'bytecode' instead of 'getByteCode'", + deprecated_since="0.10.0beta", + ) def getByteCode(self): return self.bytecode @@ -412,6 +456,7 @@ def factory(cls, name): :rtype: fabio.fabioimage.FabioImage """ from . import fabioformats + return fabioformats.factory(name) @classmethod @@ -473,7 +518,10 @@ def _get_frame(self, num): frame = FabioFrame(self.data, self.header) else: if not (0 <= num < self.nframes): - raise IndexError("Frame number out of range (requested %d, but found %d)" % (num, self.nframes)) + raise IndexError( + "Frame number out of range (requested %d, but found %d)" + % (num, self.nframes) + ) # Try to use the old getframe API to avoid to implement many # things on mostly unused formats. @@ -578,22 +626,29 @@ def getclassname(self): :return: the name of the class """ if self._classname is None: - self._classname = str(self.__class__).replace("", "").split(".")[-1] + self._classname = ( + str(self.__class__) + .replace("", "") + .split(".")[-1] + ) return self._classname classname = property(getclassname) def getframe(self, num): - """ returns the file numbered 'num' in the series as a fabioimage """ + """returns the file numbered 'num' in the series as a fabioimage""" if self.nframes == 1: # single image per file from .openimage import openimage + return openimage(fabioutils.jump_filename(self.filename, num)) raise Exception("getframe out of range") def previous(self): - """ returns the previous file in the series as a fabioimage """ + """returns the previous file in the series as a fabioimage""" from .openimage import openimage + if self.filename is None: raise IOError() return openimage(fabioutils.previous_filename(self.filename)) @@ -604,13 +659,13 @@ def next(self): :raise IOError: When there is no next file in the series. """ from .openimage import openimage + if self.filename is None: raise IOError() - return openimage( - fabioutils.next_filename(self.filename)) + return openimage(fabioutils.next_filename(self.filename)) def getheader(self): - """ returns self.header """ + """returns self.header""" return self.header def add(self, other): @@ -621,10 +676,13 @@ def add(self, other): :param FabioImage other: Another image to accumulate. """ - if not hasattr(other, 'data'): - logger.warning('edfimage.add() called with something that ' - 'does not have a data field') - assert self.data.shape == other.data.shape, 'incompatible images - Do they have the same size?' + if not hasattr(other, "data"): + logger.warning( + "edfimage.add() called with something that does not have a data field" + ) + assert self.data.shape == other.data.shape, ( + "incompatible images - Do they have the same size?" + ) self.data = self.data + other.data self.resetvals() @@ -637,12 +695,13 @@ def rebin(self, x_rebin_fact, y_rebin_fact, keep_I=True): :param bool keep_I: shall the signal increase ? """ if self.data is None: - raise Exception('Please read in the file you wish to rebin first') + raise Exception("Please read in the file you wish to rebin first") dim2, dim1 = self.data.shape if (dim1 % x_rebin_fact != 0) or (dim2 % y_rebin_fact != 0): - raise RuntimeError('image size is not divisible by rebin factor - ' - 'skipping rebin') + raise RuntimeError( + "image size is not divisible by rebin factor - skipping rebin" + ) else: dataIn = self.data.astype("float64") shapeIn = self.data.shape @@ -676,10 +735,12 @@ def write(self, fname): if not isinstance(fname, fabioutils.StringTypes): fname = str(fname) module = sys.modules[self.__class__.__module__] - raise NotImplementedError("Writing %s format is not implemented" % module.__name__) + raise NotImplementedError( + "Writing %s format is not implemented" % module.__name__ + ) def save(self, fname): - 'wrapper for write' + "wrapper for write" self.write(fname) def readheader(self, filename): @@ -715,7 +776,8 @@ def read(self, filename, frame=None): To be overridden - fill in self.header and self.data """ raise Exception("Class has not implemented read method yet") -# return self + + # return self def load(self, *arg, **kwarg): "Wrapper for read" @@ -732,11 +794,16 @@ def readROI(self, filename, frame=None, coords=None): self.read(filename, frame) if len(coords) == 4: self.slice = self.make_slice(coords) - elif len(coords) == 2 and isinstance(coords[0], slice) and \ - isinstance(coords[1], slice): + elif ( + len(coords) == 2 + and isinstance(coords[0], slice) + and isinstance(coords[1], slice) + ): self.slice = coords else: - logger.warning('readROI: Unable to understand Region Of Interest: got %s', coords) + logger.warning( + "readROI: Unable to understand Region Of Interest: got %s", coords + ) self.roi = self.data[self.slice] return self.roi @@ -757,7 +824,10 @@ def _open(self, fname, mode="rb"): setattr(fname, "name", self.filename) except AttributeError: # cStringIO - logger.warning("Unable to set filename attribute to stream (cStringIO?) of type %s" % type(fname)) + logger.warning( + "Unable to set filename attribute to stream (cStringIO?) of type %s" + % type(fname) + ) return fname if isinstance(fname, fabioutils.PathTypes): @@ -772,15 +842,13 @@ def _open(self, fname, mode="rb"): comp_type = os.path.splitext(fname)[-1] if comp_type == ".gz": - fileObject = self._compressed_stream(fname, - fabioutils.COMPRESSORS['.gz'], - fabioutils.GzipFile, - mode) - elif comp_type == '.bz2': - fileObject = self._compressed_stream(fname, - fabioutils.COMPRESSORS['.bz2'], - fabioutils.BZ2File, - mode) + fileObject = self._compressed_stream( + fname, COMPRESSORS[".gz"], fabioutils.GzipFile, mode + ) + elif comp_type == ".bz2": + fileObject = self._compressed_stream( + fname, COMPRESSORS[".bz2"], fabioutils.BZ2File, mode + ) # # Here we return the file even though it may be bzipped or gzipped # but named incorrectly... @@ -794,11 +862,9 @@ def _open(self, fname, mode="rb"): return fileObject - def _compressed_stream(self, - fname, - system_uncompress, - python_uncompress, - mode='rb'): + def _compressed_stream( + self, fname, system_uncompress, python_uncompress, mode="rb" + ): """ Try to transparently handle gzip / bzip2 without always getting python performance @@ -836,6 +902,7 @@ def convert(self, dest): dest = dest[:-5] codec_name = dest + "image" from . import fabioformats + codec_class = fabioformats.get_class_by_name(codec_name) if codec_class is not None: other = fabioformats.factory(codec_name) @@ -855,7 +922,9 @@ def convert(self, dest): logger.error("Unrecognized destination format: %s " % dest) return self other.data = converters.convert_data(self.classname, other.classname, self.data) - other.header = converters.convert_header(self.classname, other.classname, self.header) + other.header = converters.convert_header( + self.classname, other.classname, self.header + ) return other def __iter__(self): diff --git a/src/fabio/fabioutils.py b/src/fabio/fabioutils.py index 4bf920bd..b7f68ff9 100644 --- a/src/fabio/fabioutils.py +++ b/src/fabio/fabioutils.py @@ -29,27 +29,26 @@ # OTHER DEALINGS IN THE SOFTWARE. # -"""General purpose utilities functions for fabio - -""" +"""General purpose utilities functions for fabio""" __author__ = "Jérôme Kieffer" __contact__ = "Jerome.Kieffer@ESRF.eu" __license__ = "MIT" __copyright__ = "European Synchrotron Radiation Facility, Grenoble, France" -__date__ = "03/11/2020" +__date__ = "27/10/2025" __status__ = "stable" -__docformat__ = 'restructuredtext' +__docformat__ = "restructuredtext" import re import os import logging import sys import json - -logger = logging.getLogger(__name__) - from collections import OrderedDict as _OrderedDict +import traceback +from math import ceil +import threading +from .compression import bz2, gzip try: import pathlib @@ -58,9 +57,10 @@ import pathlib2 as pathlib except ImportError: pathlib = None +from io import FileIO, BytesIO as _BytesIO +logger = logging.getLogger(__name__) StringTypes = (str, bytes) -from io import FileIO, BytesIO as _BytesIO def to_str(s): @@ -71,11 +71,6 @@ def to_str(s): if pathlib is not None: PathTypes += (pathlib.PurePath,) -from .compression import bz2, gzip, COMPRESSORS -import traceback -from math import ceil - -import threading dictAscii = {None: [chr(i) for i in range(32, 127)]} @@ -94,7 +89,9 @@ def pad(mystr, pattern=" ", size=80): if len(pattern) == 1: return mystr.ljust(padded_size, pattern) else: - return (mystr + pattern * int(ceil(float(padded_size - len(mystr)) / len(pattern))))[:padded_size] + return ( + mystr + pattern * int(ceil(float(padded_size - len(mystr)) / len(pattern))) + )[:padded_size] def getnum(name): @@ -118,14 +115,17 @@ class FilenameObject(object): The 'meaning' of a filename ... """ - def __init__(self, stem=None, - num=None, - directory=None, - format_=None, - extension=None, - postnum=None, - digits=4, - filename=None): + def __init__( + self, + stem=None, + num=None, + directory=None, + format_=None, + extension=None, + postnum=None, + digits=4, + filename=None, + ): """ This class can either be instanciated by a set of parameters like directory, prefix, num, extension, ... @@ -154,16 +154,19 @@ def __init__(self, stem=None, self.deconstruct_filename(filename) def str(self): - """ Return a string representation """ - fmt = "stem %s, num %s format %s extension %s " + \ - "postnum = %s digits %s dir %s" - attrs = [self.stem, - self.num, - self.format, - self.extension, - self.postnum, - self.digits, - self.directory] + """Return a string representation""" + fmt = ( + "stem %s, num %s format %s extension %s " + "postnum = %s digits %s dir %s" + ) + attrs = [ + self.stem, + self.num, + self.format, + self.extension, + self.postnum, + self.digits, + self.directory, + ] return fmt % tuple([str(x) for x in attrs]) __repr__ = str @@ -189,6 +192,7 @@ def deconstruct_filename(self, filename): Break up a filename to get image type and number """ from . import fabioformats + direc, name = os.path.split(filename) direc = direc or None parts = name.split(".") @@ -229,13 +233,13 @@ def deconstruct_filename(self, filename): if parts2[-1].isdigit(): num = int(parts2[-1]) ndigit = len(parts2[-1]) - typ = ['GE'] + typ = ["GE"] stem = "_".join(parts2[:-1]) + "_" else: try: num = int(parts[-1]) ndigit = len(parts[-1]) - typ = ['bruker'] + typ = ["bruker"] stem = ".".join(parts[:-1]) + "." except Exception as err: logger.debug("l262: %s" % err) @@ -264,7 +268,7 @@ def deconstruct_filename(self, filename): def numstem(name): - """ cant see how to do without reversing strings + """cant see how to do without reversing strings Match 1 or more digits going backwards from the end of the string """ reg = re.compile(r"^(.*?)(-?[0-9]{0,9})(\D*)$") @@ -274,7 +278,7 @@ def numstem(name): # res = reg.match(name[::-1]).groups() # return [ r[::-1] for r in res[::-1]] if len(res[0]) == len(res[1]) == 0: # Hack for file without number - return [res[2], '', ''] + return [res[2], "", ""] return [r for r in res] except AttributeError: # no digits found return [name, "", ""] @@ -298,7 +302,7 @@ def construct_filename(filename, frame=None): def next_filename(name, padding=True): - """ increment number """ + """increment number""" fobj = FilenameObject(filename=name) fobj.num += 1 if not padding: @@ -307,7 +311,7 @@ def next_filename(name, padding=True): def previous_filename(name, padding=True): - """ decrement number """ + """decrement number""" fobj = FilenameObject(filename=name) fobj.num -= 1 if not padding: @@ -316,7 +320,7 @@ def previous_filename(name, padding=True): def jump_filename(name, num, padding=True): - """ jump to number """ + """jump to number""" fobj = FilenameObject(filename=name) fobj.num = num if not padding: @@ -325,7 +329,7 @@ def jump_filename(name, num, padding=True): def extract_filenumber(name): - """ extract file number """ + """extract file number""" fobj = FilenameObject(filename=name) return fobj.num @@ -343,7 +347,7 @@ def isAscii(name, listExcluded=None): isascii = False else: if listExcluded: - isascii = not(any(bad in name for bad in listExcluded)) + isascii = not (any(bad in name for bad in listExcluded)) else: isascii = True return isascii @@ -448,8 +452,7 @@ def __init__(self, name, mode="rb", temporary=False): self.__temporary = temporary def __del__(self): - """Explicit close at deletion - """ + """Explicit close at deletion""" if hasattr(self, "closed") and not self.closed: self.close() @@ -461,7 +464,7 @@ def close(self): os.unlink(name) except Exception as err: logger.error("Unable to remove %s: %s" % (name, err)) - raise(err) + raise (err) def getSize(self): if self.__size is None: @@ -499,7 +502,7 @@ def write(self, readablebuffer): break return nwritten - def read(self, size = -1): + def read(self, size=-1): buffer = super().read(size) size -= len(buffer) while size > 0: @@ -520,12 +523,13 @@ class UnknownCompressedFile(File): """ def __init__(self, name, mode="rb", buffering=0): - logger.warning("No decompressor found for this type of file (are gzip anf bz2 installed ???") + logger.warning( + "No decompressor found for this type of file (are gzip anf bz2 installed ???" + ) File.__init__(self, name, mode, buffering) def __del__(self): - """Explicit close at deletion - """ + """Explicit close at deletion""" if hasattr(self, "closed") and not self.closed: self.close() @@ -572,8 +576,7 @@ def __init__(self, filename=None, mode=None, compresslevel=9, fileobj=None): self.__size = None def __del__(self): - """Explicit close at deletion - """ + """Explicit close at deletion""" if hasattr(self, "closed") and not self.closed: self.close() @@ -592,7 +595,10 @@ def measure_size(self): pos = self.tell() end_pos = len(gzip.GzipFile.read(self)) + pos self.seek(pos) - logger.debug("Measuring size of %s: %s @ %s == %s" % (self.name, end_pos, pos, pos)) + logger.debug( + "Measuring size of %s: %s @ %s == %s" + % (self.name, end_pos, pos, pos) + ) self.__size = end_pos return self.__size @@ -605,6 +611,7 @@ def __exit__(self, *args): """ gzip.GzipFile.close(self) + if bz2 is None: BZ2File = UnknownCompressedFile else: @@ -612,7 +619,7 @@ def __exit__(self, *args): class BZ2File(bz2.BZ2File): "Wrapper with lock" - def __init__(self, name, mode='r', compresslevel=9): + def __init__(self, name, mode="r", compresslevel=9): """ BZ2File(name [, mode='r', compresslevel=9]) -> file object @@ -634,8 +641,7 @@ def __init__(self, name, mode='r', compresslevel=9): self.__size = None def __del__(self): - """Explicit close at deletion - """ + """Explicit close at deletion""" if hasattr(self, "closed") and not self.closed: self.close() @@ -665,8 +671,8 @@ def __exit__(self, *args): class NotGoodReader(RuntimeError): - """The reader used is probably not the good one - """ + """The reader used is probably not the good one""" + pass @@ -674,6 +680,7 @@ class DebugSemaphore(threading.Semaphore): """ threading.Semaphore like class with helper for fighting dead-locks """ + write_lock = threading.Semaphore() blocked = [] @@ -684,8 +691,13 @@ def acquire(self, *arg, **kwarg): if self._Semaphore__value == 0: with self.write_lock: self.blocked.append(id(self)) - sys.stderr.write(os.linesep.join(["Blocking sem %s" % id(self)] + - traceback.format_stack()[:-1] + [""])) + sys.stderr.write( + os.linesep.join( + ["Blocking sem %s" % id(self)] + + traceback.format_stack()[:-1] + + [""] + ) + ) return threading.Semaphore.acquire(self, *arg, **kwarg) diff --git a/src/fabio/file_series.py b/src/fabio/file_series.py index 983ae56e..fbdb5cc3 100644 --- a/src/fabio/file_series.py +++ b/src/fabio/file_series.py @@ -46,15 +46,14 @@ import sys import os.path import collections - -logger = logging.getLogger(__name__) - import fabio from .fabioutils import FilenameObject, next_filename from .openimage import openimage from .fabioimage import FabioImage from .utils import deprecation +logger = logging.getLogger(__name__) + def new_file_series0(first_object, first=None, last=None, step=1): """ @@ -137,7 +136,7 @@ def new_file_series(first_object, nimages=0, step=1, traceback=False): retVal = sys.exc_info() # Skip bad images logger.warning("Got a problem here: next() failed %s", ex) - if(traceback): + if traceback: logger.error("Backtrace", exc_info=True) else: logger.debug("Backtrace", exc_info=True) @@ -145,7 +144,9 @@ def new_file_series(first_object, nimages=0, step=1, traceback=False): try: im.filename = next_filename(im.filename) except Exception as ex: - logger.warning("Got another problem here: next_filename(im.filename) %s", ex) + logger.warning( + "Got another problem here: next_filename(im.filename) %s", ex + ) if nprocessed % step == 0: yield retVal # Avoid cyclic references with exc_info ? @@ -207,9 +208,7 @@ def previous(self): return self[self._current] def current(self): - """Current position in a sequence - - """ + """Current position in a sequence""" return self[self._current] def next(self): @@ -355,8 +354,7 @@ class numbered_file_series(file_series): mydata0003.edf = "mydata" + 0003 + ".edf" """ - def __init__(self, stem, first, last, extension, - digits=4, padding='Y', step=1): + def __init__(self, stem, first, last, extension, digits=4, padding="Y", step=1): """ Constructor @@ -365,7 +363,7 @@ def __init__(self, stem, first, last, extension, :param padding: possibility for specifying that numbers are not padded with zeroes up to digits """ - if padding == 'Y': + if padding == "Y": fmt = "%s%0" + str(digits) + "d%s" else: fmt = "%s%i%s" @@ -382,74 +380,76 @@ class filename_series(object): :param Union[str,FilenameObject] filename: The first filename of the iteration. """ + """ Much like the others, but created from a string filename """ def __init__(self, filename): - """ create from a filename (String)""" + """create from a filename (String)""" if isinstance(filename, FilenameObject): self.obj = filename else: self.obj = FilenameObject(filename=filename) def next(self): - """ increment number """ + """increment number""" self.obj.num += 1 return self.obj.tostring() def previous(self): - """ decrement number """ + """decrement number""" self.obj.num -= 1 return self.obj.tostring() def current(self): - """ return current filename string""" + """return current filename string""" return self.obj.tostring() def jump(self, num): - """ jump to a specific number """ + """jump to a specific number""" self.obj.num = num return self.obj.tostring() # image methods def next_image(self): - """ returns the next image as a fabioimage """ + """returns the next image as a fabioimage""" return openimage(self.next()) def prev_image(self): - """ returns the previos image as a fabioimage """ + """returns the previos image as a fabioimage""" return openimage(self.previous()) def current_image(self): - """ returns the current image as a fabioimage""" + """returns the current image as a fabioimage""" return openimage(self.current()) def jump_image(self, num): - """ returns the image number as a fabioimage""" + """returns the image number as a fabioimage""" return openimage(self.jump(num)) # object methods def next_object(self): - """ returns the next filename as a fabio.FilenameObject""" + """returns the next filename as a fabio.FilenameObject""" self.obj.num += 1 return self.obj def previous_object(self): - """ returns the previous filename as a fabio.FilenameObject""" + """returns the previous filename as a fabio.FilenameObject""" self.obj.num -= 1 return self.obj def current_object(self): - """ returns the current filename as a fabio.FilenameObject""" + """returns the current filename as a fabio.FilenameObject""" return self.obj def jump_object(self, num): - """ returns the filename num as a fabio.FilenameObject""" + """returns the filename num as a fabio.FilenameObject""" self.obj.num = num return self.obj -_FileDescription = collections.namedtuple('_FileDescription', - ['filename', 'file_number', 'first_frame_number', 'nframes']) +_FileDescription = collections.namedtuple( + "_FileDescription", ["filename", "file_number", "first_frame_number", "nframes"] +) """Object storing description of a file from a file serie""" @@ -459,7 +459,7 @@ def _filename_series_adapter(series): Without the adaptater `filename_series` will not list the first element, and will loop to the infinite. """ - assert(isinstance(series, filename_series)) + assert isinstance(series, filename_series) filename = series.current() if not os.path.exists(filename): return @@ -535,9 +535,12 @@ class FileSeries(FabioImage): # Each files contains 100 frames (the last one could contain less) serie = FileSeries(filenames=filenames, fixed_frame_number=100) """ + DEFAULT_EXTENSIONS = [] - def __init__(self, filenames, single_frame=None, fixed_frames=None, fixed_frame_number=None): + def __init__( + self, filenames, single_frame=None, fixed_frames=None, fixed_frame_number=None + ): """ Constructor @@ -609,9 +612,9 @@ def frames(self): """Returns an iterator throug all frames of all filenames of this file series.""" import fabio.edfimage + nframe = 0 for filename in self.__iter_filenames(): - if self.use_edf_shortcut: info = FilenameObject(filename=filename) # It is the supported formats @@ -700,7 +703,7 @@ def __iter_file_descriptions(self): Use a cached structure which grows according to the requestes. """ - assert(self.__file_descriptions is not None) + assert self.__file_descriptions is not None for description in self.__file_descriptions: yield description @@ -733,7 +736,7 @@ def __find_file_description(self, frame_number): :param int frame_number: A frame number :rtype: _FileDescription """ - assert(self.__file_descriptions is not None) + assert self.__file_descriptions is not None # Check it on the last cached description if self.__current_file_description is not None: @@ -797,7 +800,9 @@ def _get_frame(self, num): frame._set_container(self, num) return frame - @deprecation.deprecated(reason="Replaced by get_frame.", deprecated_since="0.10.0beta") + @deprecation.deprecated( + reason="Replaced by get_frame.", deprecated_since="0.10.0beta" + ) def getframe(self, num): return self.get_frame(num) @@ -838,26 +843,36 @@ def nframes(self): return self.__nframes file_number = len(self.__filenames) - 1 fabiofile = self.__get_file(file_number) - nframes = self.__fixed_frame_number * (len(self.__filenames) - 1) + fabiofile.nframes + nframes = ( + self.__fixed_frame_number * (len(self.__filenames) - 1) + fabiofile.nframes + ) self.__nframes = nframes return nframes @property def data(self): # This could provide access to the first or the current frame - raise NotImplementedError("Not implemented. Use serie.frames() or serie.get_frame(int)") + raise NotImplementedError( + "Not implemented. Use serie.frames() or serie.get_frame(int)" + ) @property def header(self): # This could provide access to the first or the current frame - raise NotImplementedError("Not implemented. Use serie.frames() or serie.get_frame(int)") + raise NotImplementedError( + "Not implemented. Use serie.frames() or serie.get_frame(int)" + ) @property def shape(self): # This could provide access to the first or the current frame - raise NotImplementedError("Not implemented. Use serie.frames() or serie.get_frame(int)") + raise NotImplementedError( + "Not implemented. Use serie.frames() or serie.get_frame(int)" + ) @property def dtype(self): # This could provide access to the first or the current frame - raise NotImplementedError("Not implemented. Use serie.frames() or serie.get_frame(int)") + raise NotImplementedError( + "Not implemented. Use serie.frames() or serie.get_frame(int)" + ) diff --git a/src/fabio/fit2dimage.py b/src/fabio/fit2dimage.py index 448c934d..68236a49 100644 --- a/src/fabio/fit2dimage.py +++ b/src/fabio/fit2dimage.py @@ -33,13 +33,14 @@ __contact__ = "jerome.kiefer@esrf.fr" __license__ = "MIT" __copyright__ = "2016-2016 European Synchrotron Radiation Facility" -__date__ = "09/02/2023" +__date__ = "27/10/2025" import logging -logger = logging.getLogger(__name__) import numpy from .fabioimage import FabioImage, OrderedDict +logger = logging.getLogger(__name__) + def hex_to(stg, type_="int"): """convert a 8-byte-long string (bytes) into an int or a float @@ -96,7 +97,7 @@ def _readheader(self, infile): infile.seek(0) break else: - err = "issue while reading header, expected '\', got %s" % line[0] + err = "issue while reading header, expected '', got %s" % line[0] logger.error(err) raise RuntimeError(err) key, line = line.split(b":", 1) @@ -105,7 +106,7 @@ def _readheader(self, infile): key = key[1:].decode(self.ENC) if metadatatype == "s": len_value = hex_to(line[9:17]) - header[key] = line[17:17 + len_value].decode(self.ENC) + header[key] = line[17 : 17 + len_value].decode(self.ENC) elif metadatatype == "r": header[key] = hex_to(line[9:17], "float") elif metadatatype == "i": @@ -134,7 +135,7 @@ def _readheader(self, infile): # Remove the sign bit which is the first in big-endian # all pixels are in reverse order in the group of 31 r31 = r32[:, -1:0:-1] - mask = r31.ravel()[:dim1 * dim2].reshape((dim2, dim1)) + mask = r31.ravel()[: dim1 * dim2].reshape((dim2, dim1)) header[key] = mask continue else: @@ -142,10 +143,14 @@ def _readheader(self, infile): logger.error(err) raise RuntimeError(err) raw = infile.read(self.num_block * self.BUFFER_SIZE) - decoded = numpy.frombuffer(raw, bytecode).copy().reshape((-1, self.BUFFER_SIZE // bpp)) + decoded = ( + numpy.frombuffer(raw, bytecode) + .copy() + .reshape((-1, self.BUFFER_SIZE // bpp)) + ) # There is a bug in this format: throw away 3/4 of the read data: - decoded = decoded[:, :self.PIXELS_PER_CHUNK].ravel() - header[key] = decoded[:dim1 * dim2].reshape(dim2, dim1) + decoded = decoded[:, : self.PIXELS_PER_CHUNK].ravel() + header[key] = decoded[: dim1 * dim2].reshape(dim2, dim1) self.header = header def read(self, fname, frame=None): diff --git a/src/fabio/fit2dmaskimage.py b/src/fabio/fit2dmaskimage.py index 491f6c85..844d56f0 100644 --- a/src/fabio/fit2dmaskimage.py +++ b/src/fabio/fit2dmaskimage.py @@ -37,12 +37,11 @@ __contact__ = "Jerome.Kieffer@esrf.fr" __license__ = "MIT" __copyright__ = "European Synchrotron Radiation Facility, Grenoble, France" -__version__ = "06/01/2015" +__date__ = "27/10/2025" import io import numpy import struct - from .fabioimage import FabioImage @@ -68,7 +67,7 @@ def _readheader(self, infile): dim2 = fit2dhdr[5] self._shape = dim2, dim1 - def read(self, fname:str, frame=None): + def read(self, fname: str, frame=None): """ Read in header into self.header and the data into self.data @@ -116,7 +115,7 @@ def read(self, fname:str, frame=None): self._shape = None return self - def write(self, fname:str): + def write(self, fname: str): """ Try to write a file """ diff --git a/src/fabio/fit2dspreadsheetimage.py b/src/fabio/fit2dspreadsheetimage.py index 603633de..9cda3a27 100644 --- a/src/fabio/fit2dspreadsheetimage.py +++ b/src/fabio/fit2dspreadsheetimage.py @@ -34,11 +34,10 @@ import numpy import logging +from .fabioimage import FabioImage _logger = logging.getLogger(__name__) -from .fabioimage import FabioImage - class Fit2dSpreadsheetImage(FabioImage): """ @@ -59,9 +58,9 @@ def _readheader(self, infile): items = line.split() xdim = int(items[0]) ydim = int(items[1]) - self.header['title'] = line - self.header['Dim_1'] = xdim - self.header['Dim_2'] = ydim + self.header["title"] = line + self.header["Dim_1"] = xdim + self.header["Dim_2"] = ydim def read(self, fname, frame=None): """ @@ -74,8 +73,8 @@ def read(self, fname, frame=None): self._readheader(infile) # Compute image size try: - dim1 = int(self.header['Dim_1']) - dim2 = int(self.header['Dim_2']) + dim1 = int(self.header["Dim_1"]) + dim2 = int(self.header["Dim_2"]) self._shape = dim2, dim1 except (ValueError, KeyError): raise IOError("file %s is corrupt, cannot read it" % str(fname)) diff --git a/src/fabio/hdf5image.py b/src/fabio/hdf5image.py index 2dc1b378..c9331684 100644 --- a/src/fabio/hdf5image.py +++ b/src/fabio/hdf5image.py @@ -41,20 +41,20 @@ __contact__ = "Jerome.Kieffer@terre-adelie.org" __license__ = "MIT" __copyright__ = "Jérôme Kieffer" -__date__ = "03/04/2020" +__date__ = "27/10/2025" import logging import os import posixpath from . import fabioimage - -logger = logging.getLogger(__name__) +from .fabioutils import previous_filename, next_filename try: import h5py except ImportError: h5py = None -from .fabioutils import previous_filename, next_filename + +logger = logging.getLogger(__name__) class Hdf5Frame(fabioimage.FabioFrame): @@ -89,7 +89,9 @@ def __init__(self, *arg, **kwargs): Generic constructor """ if not h5py: - raise RuntimeError("fabio.Hdf5Image cannot be used without h5py. Please install h5py and restart") + raise RuntimeError( + "fabio.Hdf5Image cannot be used without h5py. Please install h5py and restart" + ) fabioimage.FabioImage.__init__(self, *arg, **kwargs) self.hdf5 = None @@ -103,7 +105,10 @@ def read(self, fname, frame=None): self.resetvals() if "::" not in fname: - err = "the '::' separator in mandatory for HDF5 container, absent in %s" % fname + err = ( + "the '::' separator in mandatory for HDF5 container, absent in %s" + % fname + ) logger.error(err) raise RuntimeError(err) filename, datapath = fname.split("::", 1) @@ -137,7 +142,10 @@ def read(self, fname, frame=None): elif ndim == 2: self.data = self.dataset[:, :] else: - err = "Only 2D and 3D datasets are supported by FabIO, here %sD" % self.dataset.ndim + err = ( + "Only 2D and 3D datasets are supported by FabIO, here %sD" + % self.dataset.ndim + ) logger.error(err) raise RuntimeError(err) return self @@ -151,7 +159,10 @@ def getframe(self, num): :param num: frame number """ if num < 0 or num >= self.nframes: - raise IndexError("Requested frame number %i is out of range [0, %i[ " % (num, self.nframes)) + raise IndexError( + "Requested frame number %i is out of range [0, %i[ " + % (num, self.nframes) + ) # Do a deep copy of the header to make a new one frame = Hdf5Frame(self, num) frame._set_container(self, num) diff --git a/src/fabio/jpeg2kimage.py b/src/fabio/jpeg2kimage.py index fbfde5ee..44e5e16a 100644 --- a/src/fabio/jpeg2kimage.py +++ b/src/fabio/jpeg2kimage.py @@ -29,29 +29,28 @@ """ __authors__ = ["Valentin Valls"] -__date__ = "05/09/2025" +__date__ = "27/10/2025" __license__ = "MIT" __copyright__ = "European Synchrotron Radiation Facility, Grenoble, France" __status__ = "stable" import logging -logger = logging.getLogger(__name__) try: import PIL import PIL.Image except ImportError: PIL = None - try: import glymur except ImportError: glymur = None - from .fabioimage import FabioImage from .fabioutils import OrderedDict from .utils import pilutils +logger = logging.getLogger(__name__) + class Jpeg2KImage(FabioImage): """ @@ -59,6 +58,7 @@ class Jpeg2KImage(FabioImage): It uses PIL or glymur libraries. """ + DESCRIPTION = "JPEG 2000 format" DEFAULT_EXTENSIONS = ["jp2", "jpx", "j2k", "jpf", "jpg2"] @@ -66,7 +66,7 @@ class Jpeg2KImage(FabioImage): _need_a_seek_to_read = True def __init__(self, *args, **kwds): - """ Tifimage constructor adds an nbits member attribute """ + """Tifimage constructor adds an nbits member attribute""" self.nbits = None FabioImage.__init__(self, *args, **kwds) self.lib = "" @@ -97,7 +97,7 @@ def _loadGlymurImage(self, filename, infile): version = tuple(int(i) for i in glymur.__version__.split(".")[:2]) if version == (0, 7): image = glymur.Jp2k(filename=filename) - elif version >= (0,8): + elif version >= (0, 8): # inject a shape to avoid calling the read function image = glymur.Jp2k(filename=filename, shape=(1, 1)) else: @@ -133,7 +133,7 @@ def _readWithGlymur(self, filename, infile): self.data = image._read() else: self.data = image.read() - + def read(self, filename, frame=None): infile = self._open(filename, "rb") self.data = None @@ -148,7 +148,9 @@ def read(self, filename, frame=None): except IOError as e: self.data = None self.header = OrderedDict() - logger.debug("Error while using %s library: %s" % (name, e), exc_info=True) + logger.debug( + "Error while using %s library: %s" % (name, e), exc_info=True + ) if self.data is None: infile.seek(0) diff --git a/src/fabio/jpegimage.py b/src/fabio/jpegimage.py index 6b26c907..e4769f2c 100644 --- a/src/fabio/jpegimage.py +++ b/src/fabio/jpegimage.py @@ -29,22 +29,22 @@ """ __authors__ = ["Valentin Valls"] -__date__ = "03/04/2020" +__date__ = "27/10/2025" __license__ = "MIT" __copyright__ = "European Synchrotron Radiation Facility, Grenoble, France" __status__ = "stable" import logging -logger = logging.getLogger(__name__) try: from PIL import Image except ImportError: Image = None - from .fabioimage import FabioImage from .utils import pilutils +logger = logging.getLogger(__name__) + # List of reserved keys reached from # http://pillow.readthedocs.io/en/3.4.x/handbook/image-file-formats.html#jpeg JPEG_RESERVED_HEADER_KEYS = [ @@ -64,7 +64,7 @@ "dpi", "exif", "subsampling", - "qtables" + "qtables", ] @@ -72,6 +72,7 @@ class JpegImage(FabioImage): """ Images in JPEG format using PIL """ + DESCRIPTION = "JPEG format" DEFAULT_EXTENSIONS = ["jpg", "jpeg"] @@ -81,7 +82,7 @@ class JpegImage(FabioImage): _need_a_seek_to_read = True def __init__(self, *args, **kwds): - """ Tifimage constructor adds an nbits member attribute """ + """Tifimage constructor adds an nbits member attribute""" self.nbits = None FabioImage.__init__(self, *args, **kwds) diff --git a/src/fabio/kcdimage.py b/src/fabio/kcdimage.py index 8a914f73..3b52e9a3 100644 --- a/src/fabio/kcdimage.py +++ b/src/fabio/kcdimage.py @@ -37,12 +37,13 @@ import numpy import logging import os - import string from .fabioimage import FabioImage +import io + + logger = logging.getLogger(__name__) -import io if not hasattr(io, "SEEK_END"): SEEK_END = 2 else: @@ -52,10 +53,11 @@ MINIMUM_KEYS = [ # 'ByteOrder', Assume little by default - 'Data type', - 'X dimension', - 'Y dimension', - 'Number of readouts'] + "Data type", + "X dimension", + "Y dimension", + "Number of readouts", +] DEFAULT_VALUES = {"Data type": "u16"} @@ -64,7 +66,7 @@ class KcdImage(FabioImage): """ - Read the Nonius kcd data format """ + Read the Nonius kcd data format""" DESCRIPTION = "KCD file format from Nonius's KappaCCD diffractometer" @@ -83,7 +85,9 @@ def _readheader(self, infile): if asciiHeader is False: # This does not look like an KappaCCD file - logger.warning("First line of %s does not seam to be ascii text!" % infile.name) + logger.warning( + "First line of %s does not seam to be ascii text!" % infile.name + ) end_of_headers = False while not end_of_headers: one_line = infile.readline() @@ -98,7 +102,7 @@ def _readheader(self, infile): if one_line.strip() == "Binned mode": one_line = "Mode = Binned" if "=" in one_line: - key, val = one_line.split('=', 1) + key, val = one_line.split("=", 1) key = key.strip() self.header[key] = val.strip() else: @@ -122,19 +126,19 @@ def read(self, fname, frame=None): self._readheader(infile) # Compute image size try: - dim1 = int(self.header['X dimension']) - dim2 = int(self.header['Y dimension']) + dim1 = int(self.header["X dimension"]) + dim2 = int(self.header["Y dimension"]) self._shape = dim2, dim1 except (KeyError, ValueError): raise IOError("KCD file %s is corrupt, cannot read it" % fname) try: - bytecode = DATA_TYPES[self.header['Data type']] + bytecode = DATA_TYPES[self.header["Data type"]] except KeyError: bytecode = numpy.uint16 logger.warning("Defaulting type to uint16") self._dtype = numpy.dtype(bytecode) try: - nbReadOut = int(self.header['Number of readouts']) + nbReadOut = int(self.header["Number of readouts"]) except KeyError: logger.warning("Defaulting number of ReadOut to 1") nbReadOut = 1 @@ -144,7 +148,9 @@ def read(self, fname, frame=None): infile.seek(-expected_size, SEEK_END) except Exception: logger.debug("Backtrace", exc_info=True) - logger.warning("seeking from end is not implemeneted for file %s", fname) + logger.warning( + "seeking from end is not implemeneted for file %s", fname + ) if hasattr(infile, "measure_size"): fileSize = infile.measure_size() elif hasattr(infile, "size"): @@ -164,7 +170,7 @@ def read(self, fname, frame=None): for i in range(nbReadOut): start = stop stop = (i + 1) * expected_size // nbReadOut - data = numpy.frombuffer(block[start: stop], self._dtype).copy() + data = numpy.frombuffer(block[start:stop], self._dtype).copy() data.shape = dim2, dim1 if not numpy.little_endian: data.byteswap(True) diff --git a/src/fabio/lambdaimage.py b/src/fabio/lambdaimage.py index 76402ac3..18a9d25e 100644 --- a/src/fabio/lambdaimage.py +++ b/src/fabio/lambdaimage.py @@ -26,23 +26,23 @@ # OTHER DEALINGS IN THE SOFTWARE. """ -Basic read support for NeXus/HDF5 files saved by Lambda-detectors. +Basic read support for NeXus/HDF5 files saved by Lambda-detectors. """ __authors__ = ["Jérôme Kieffer"] __contact__ = "jerome.kieffer@esrf.fr" __license__ = "MIT" __copyright__ = "ESRF" -__date__ = "02/05/2024" +__date__ = "27/10/2025" import logging -logger = logging.getLogger(__name__) import posixpath import os import numpy from .fabioimage import FabioImage from .fabioutils import NotGoodReader from . import nexus + try: import h5py except ImportError: @@ -51,12 +51,13 @@ import hdf5plugin except ImportError: hdf5plugin = None +logger = logging.getLogger(__name__) class LambdaImage(FabioImage): """FabIO image class for Images for Lambda detector - Lambda detector are medipix based detectors sold by X-Spectrum: + Lambda detector are medipix based detectors sold by X-Spectrum: https://x-spectrum.de/products/lambda/ """ @@ -70,7 +71,9 @@ def __init__(self, data=None, header=None): Set up initial values """ if not h5py: - raise RuntimeError("fabio.LambdaImage cannot be used without h5py. Please install h5py and restart") + raise RuntimeError( + "fabio.LambdaImage cannot be used without h5py. Please install h5py and restart" + ) self.dataset = [data] self._data = None @@ -103,7 +106,7 @@ def set_data(self, data, index=None): if index == len(self.dataset): self.dataset.append(data) elif index > len(self.dataset): - # pad dataset with None ? + # pad dataset with None ? self.dataset += [None] * (1 + index - len(self.dataset)) self.dataset[index] = data else: @@ -117,7 +120,10 @@ def __repr__(self): if self.h5 is None: return "%s object at %s" % (self.__class__.__name__, hex(id(self))) else: - return "Lambda/nexus dataset with %i frames from %s" % (self.nframes, self.h5.filename) + return "Lambda/nexus dataset with %i frames from %s" % ( + self.nframes, + self.h5.filename, + ) def _readheader(self, infile): """ @@ -132,16 +138,20 @@ def _readheader(self, infile): name_path = posixpath.join(self.DETECTOR_GRP, "local_name") with h5py.File(infile, mode="r") as h5: if not (data_path in h5 and description_path in h5): - raise NotGoodReader("HDF5's does not look like a Lambda-detector NeXus file.") + raise NotGoodReader( + "HDF5's does not look like a Lambda-detector NeXus file." + ) description = h5[description_path][()] if isinstance(description, bytes): description = description.decode() else: description = str(description) if description != "Lambda": - raise NotGoodReader("Nexus file does not look like it has been written by a Lambda-detector.") + raise NotGoodReader( + "Nexus file does not look like it has been written by a Lambda-detector." + ) if name_path in h5: - self.header["detector"] = str(h5[name_path][()]) + self.header["detector"] = str(h5[name_path][()]) else: self.header["detector"] = "detector" @@ -178,7 +188,7 @@ def read(self, fname, frame=None): return self def getframe(self, num): - """ returns the frame numbered 'num' in the stack if applicable""" + """returns the frame numbered 'num' in the stack if applicable""" if self.nframes > 1: new_img = None if (num >= 0) and num < self.nframes: @@ -195,7 +205,7 @@ def getframe(self, num): return new_img def previous(self): - """ returns the previous file in the series as a FabioImage """ + """returns the previous file in the series as a FabioImage""" new_image = None if self.nframes == 1: new_image = FabioImage.previous(self) @@ -228,32 +238,49 @@ def write(self, filename): abs_name = os.path.abspath(filename) mode = "w" if hdf5plugin is None: - logger.warning("hdf5plugin is needed for bitshuffle-LZ4 compression, falling back on gzip (slower)") - compression = {"compression":"gzip", - "compression_opts":1} + logger.warning( + "hdf5plugin is needed for bitshuffle-LZ4 compression, falling back on gzip (slower)" + ) + compression = {"compression": "gzip", "compression_opts": 1} else: compression = hdf5plugin.Bitshuffle() with nexus.Nexus(abs_name, mode=mode) as nxs: - entry = nxs.new_entry(entry="entry", - program_name=None, - force_time=start_time, - force_name=True) - instrument_grp = nxs.new_class(entry, "instrument", class_type="NXinstrument") - detector_grp = nxs.new_class(instrument_grp, "detector", class_type="NXdetector") + entry = nxs.new_entry( + entry="entry", program_name=None, force_time=start_time, force_name=True + ) + instrument_grp = nxs.new_class( + entry, "instrument", class_type="NXinstrument" + ) + detector_grp = nxs.new_class( + instrument_grp, "detector", class_type="NXdetector" + ) detector_grp["description"] = b"Lambda" - detector_grp["local_name"] = self.header.get("detector", "detector").encode() - detector_grp["layout"] = "X".join(str(i) for i in self.shape[-1::-1]).encode() - header_grp = nxs.new_class(detector_grp, "collection", class_type="NXcollection") - acq_grp = nxs.new_class(detector_grp, "acquisition", class_type="NXcollection") + detector_grp["local_name"] = self.header.get( + "detector", "detector" + ).encode() + detector_grp["layout"] = "X".join( + str(i) for i in self.shape[-1::-1] + ).encode() + nxs.new_class(detector_grp, "collection", class_type="NXcollection") + acq_grp = nxs.new_class( + detector_grp, "acquisition", class_type="NXcollection" + ) acq_grp["frame_numbers"] = numpy.int32(self.nframes) shape = (self.nframes,) + self.shape - dataset = detector_grp.create_dataset("data", shape=shape, chunks=(1,) + self.shape, dtype=self.dtype, **compression) + dataset = detector_grp.create_dataset( + "data", + shape=shape, + chunks=(1,) + self.shape, + dtype=self.dtype, + **compression, + ) dataset.attrs["interpretation"] = "image" for i, frame in enumerate(self.dataset): dataset[i] = frame + # This is for compatibility with old code: lambdaimage = LambdaImage diff --git a/src/fabio/limaimage.py b/src/fabio/limaimage.py index 1d3ddb43..c05b7329 100644 --- a/src/fabio/limaimage.py +++ b/src/fabio/limaimage.py @@ -26,22 +26,22 @@ # OTHER DEALINGS IN THE SOFTWARE. """ -Basic read support for HDF5 files saved by LImA. +Basic read support for HDF5 files saved by LImA. """ __authors__ = ["Jérôme Kieffer"] __contact__ = "jerome.kieffer@esrf.fr" __license__ = "MIT" __copyright__ = "ESRF" -__date__ = "09/02/2023" +__date__ = "27/10/2025" import logging -logger = logging.getLogger(__name__) import os import numpy from .fabioimage import FabioImage from .fabioutils import NotGoodReader from . import nexus + try: import h5py except ImportError: @@ -50,6 +50,7 @@ import hdf5plugin except ImportError: hdf5plugin = None +logger = logging.getLogger(__name__) class LimaImage(FabioImage): @@ -69,7 +70,9 @@ def __init__(self, data=None, header=None): Set up initial values """ if not h5py: - raise RuntimeError("fabio.LimaImage cannot be used without h5py. Please install h5py and restart") + raise RuntimeError( + "fabio.LimaImage cannot be used without h5py. Please install h5py and restart" + ) self.dataset = [data] self._data = None @@ -102,7 +105,7 @@ def set_data(self, data, index=None): if index == len(self.dataset): self.dataset.append(data) elif index > len(self.dataset): - # pad dataset with None ? + # pad dataset with None ? self.dataset += [None] * (1 + index - len(self.dataset)) self.dataset[index] = data else: @@ -114,7 +117,10 @@ def set_data(self, data, index=None): def __repr__(self): if self.h5 is not None: - return "LImA-HDF5 dataset with %i frames from %s" % (self.nframes, self.h5.filename) + return "LImA-HDF5 dataset with %i frames from %s" % ( + self.nframes, + self.h5.filename, + ) else: return "%s object at %s" % (self.__class__.__name__, hex(id(self))) @@ -183,7 +189,7 @@ def read(self, fname, frame=None): return self def getframe(self, num): - """ returns the frame numbered 'num' in the stack if applicable""" + """returns the frame numbered 'num' in the stack if applicable""" if self.nframes > 1: new_img = None if (num >= 0) and num < self.nframes: @@ -200,7 +206,7 @@ def getframe(self, num): return new_img def previous(self): - """ returns the previous file in the series as a FabioImage """ + """returns the previous file in the series as a FabioImage""" new_image = None if self.nframes == 1: new_image = FabioImage.previous(self) @@ -236,35 +242,56 @@ def write(self, filename): else: mode = "w" if hdf5plugin is None: - logger.warning("hdf5plugin is needed for bitshuffle-LZ4 compression, falling back on gzip (slower)") - compression = {"compression":"gzip", - "compression_opts":1} + logger.warning( + "hdf5plugin is needed for bitshuffle-LZ4 compression, falling back on gzip (slower)" + ) + compression = {"compression": "gzip", "compression_opts": 1} else: compression = hdf5plugin.Bitshuffle() with nexus.Nexus(abs_name, mode=mode, creator="LIMA-1.9.7") as nxs: - entry = nxs.new_entry(entry="entry", - program_name=None, - title="Lima 2D detector acquisition", - force_time=start_time, - force_name=False) - measurement_grp = nxs.new_class(entry, "measurement", class_type="NXcollection") - instrument_grp = nxs.new_class(entry, "instrument", class_type="NXinstrument") - detector_grp = nxs.new_class(instrument_grp, self.header.get("detector", "detector"), class_type="NXdetector") - acq_grp = nxs.new_class(detector_grp, "acquisition", class_type="NXcollection") - info_grp = nxs.new_class(detector_grp, "detector_information", class_type="NXcollection") - info_grp["image_lima_type"] = f"Bpp{8*numpy.dtype(self.dtype).itemsize}" - max_grp = nxs.new_class(info_grp, "max_image_size", class_type="NXcollection") + entry = nxs.new_entry( + entry="entry", + program_name=None, + title="Lima 2D detector acquisition", + force_time=start_time, + force_name=False, + ) + measurement_grp = nxs.new_class( + entry, "measurement", class_type="NXcollection" + ) + instrument_grp = nxs.new_class( + entry, "instrument", class_type="NXinstrument" + ) + detector_grp = nxs.new_class( + instrument_grp, + self.header.get("detector", "detector"), + class_type="NXdetector", + ) + acq_grp = nxs.new_class( + detector_grp, "acquisition", class_type="NXcollection" + ) + info_grp = nxs.new_class( + detector_grp, "detector_information", class_type="NXcollection" + ) + info_grp["image_lima_type"] = f"Bpp{8 * numpy.dtype(self.dtype).itemsize}" + max_grp = nxs.new_class( + info_grp, "max_image_size", class_type="NXcollection" + ) max_grp["xsize"] = numpy.int32(self.shape[-1]) max_grp["ysize"] = numpy.int32(self.shape[-2]) - header_grp = nxs.new_class(detector_grp, "header", class_type="NXcollection") + header_grp = nxs.new_class( + detector_grp, "header", class_type="NXcollection" + ) header_grp["acq_nb_frames"] = str(self.nframes) header_grp["image_bin"] = "<1x1>" header_grp["image_flip"] = "" header_grp["image_roi"] = f"<0,0>-<{self.shape[-2]}x{self.shape[-1]}>" header_grp["image_rotation"] = "Rotation_0" - op_grp = nxs.new_class(detector_grp, "image_operation", class_type="NXcollection") + op_grp = nxs.new_class( + detector_grp, "image_operation", class_type="NXcollection" + ) op_grp["rotation"] = "Rotation_0" bin_grp = nxs.new_class(op_grp, "binning", class_type="NXcollection") bin_grp["x"] = numpy.int32(1) @@ -275,7 +302,9 @@ def write(self, filename): flp_grp = nxs.new_class(op_grp, "flipping", class_type="NXcollection") flp_grp["x"] = numpy.uint8(0) flp_grp["y"] = numpy.uint8(0) - roi_grp = nxs.new_class(op_grp, "region_of_interest", class_type="NXcollection") + roi_grp = nxs.new_class( + op_grp, "region_of_interest", class_type="NXcollection" + ) roi_grp["xsize"] = numpy.int32(self.shape[-1]) roi_grp["ysize"] = numpy.int32(self.shape[-2]) roi_grp["xstart"] = numpy.int32(0) @@ -286,7 +315,13 @@ def write(self, filename): acq_grp["nb_frames"] = numpy.int32(self.nframes) shape = (self.nframes,) + self.shape - dataset = detector_grp.create_dataset("data", shape=shape, chunks=(1,) + self.shape, dtype=self.dtype, **compression) + dataset = detector_grp.create_dataset( + "data", + shape=shape, + chunks=(1,) + self.shape, + dtype=self.dtype, + **compression, + ) dataset.attrs["interpretation"] = "image" plot_grp["data"] = dataset plot_grp.attrs["signal"] = "data" diff --git a/src/fabio/mar345image.py b/src/fabio/mar345image.py index 62c6af28..7216d420 100644 --- a/src/fabio/mar345image.py +++ b/src/fabio/mar345image.py @@ -47,7 +47,7 @@ """ __authors__ = ["Henning O. Sorensen", "Erik Knudsen", "Jon Wright", "Jérôme Kieffer"] -__date__ = "03/04/2020" +__date__ = "27/10/2025" __status__ = "production" __copyright__ = "2007-2009 Risoe National Laboratory; 2010-2020 ESRF" __licence__ = "MIT" @@ -56,12 +56,11 @@ import time import logging import numpy - import fabio from .fabioimage import FabioImage +from .compression import compPCK, decPCK logger = logging.getLogger(__name__) -from .compression import compPCK, decPCK class Mar345Image(FabioImage): @@ -69,8 +68,16 @@ class Mar345Image(FabioImage): DESCRIPTION = "File format from Mar345 imaging plate and Mar555 flat panel" - DEFAULT_EXTENSIONS = ["mar2300", "mar1200", "mar1600", "mar2000", # 150µm pixel size - "mar3450", "mar3000", "mar2400", "mar1800"] # 100µm pixel size + DEFAULT_EXTENSIONS = [ + "mar2300", + "mar1200", + "mar1600", + "mar2000", # 150µm pixel size + "mar3450", + "mar3000", + "mar2400", + "mar1800", + ] # 100µm pixel size def __init__(self, *args, **kwargs): FabioImage.__init__(self, *args, **kwargs) @@ -79,13 +86,15 @@ def __init__(self, *args, **kwargs): self.swap_needed = None def read(self, fname, frame=None): - """ Read a mar345 image""" + """Read a mar345 image""" self.filename = fname f = self._open(self.filename, "rb") self._readheader(f) - if 'compressed' in self.header['Format']: + if "compressed" in self.header["Format"]: dim2, dim1 = self._shape - self.data = decPCK(f, dim1, dim2, self.numhigh, swap_needed=self.swap_needed) + self.data = decPCK( + f, dim1, dim2, self.numhigh, swap_needed=self.swap_needed + ) self._shape = None else: logger.error("Cannot handle these formats yet due to lack of documentation") @@ -95,7 +104,7 @@ def read(self, fname, frame=None): return self def _readheader(self, infile=None): - """ Read a mar345 image header """ + """Read a mar345 image header""" # clip was not used anywhere - commented out # clip = '\x00' # using a couple of local variables inside this function @@ -111,62 +120,67 @@ def _readheader(self, infile=None): # example image it seems to 128 bytes?) # first 4-byte integer is a marker to check endianness if struct.unpack(" self.nframes or img_num < 0): + if img_num > self.nframes or img_num < 0: raise RuntimeError("Requested frame number is out of range") infile.seek(self._calc_offset(img_num), 0) data_buffer = infile.read(self.imagesize) @@ -168,7 +195,14 @@ def getframe(self, num): raise RuntimeError("Requested frame number is out of range") # Do a deep copy of the header to make a new one frame = MrcImage(header=self.header.copy()) - for key in ("dim1", "dim2", "nframes", "bytecode", "imagesize", "sequencefilename"): + for key in ( + "dim1", + "dim2", + "nframes", + "bytecode", + "imagesize", + "sequencefilename", + ): frame.__setattr__(key, self.__getattribute__(key)) with frame._open(self.sequencefilename, "rb") as infile: frame._readframe(infile, num) @@ -193,8 +227,7 @@ def previous(self): return self.getframe(self.currentframe - 1) else: newobj = MrcImage() - newobj.read(previous_filename( - self.sequencefilename)) + newobj.read(previous_filename(self.sequencefilename)) return newobj diff --git a/src/fabio/nexus.py b/src/fabio/nexus.py index 51d418c2..a2820ef0 100644 --- a/src/fabio/nexus.py +++ b/src/fabio/nexus.py @@ -33,23 +33,21 @@ __contact__ = "Jerome.Kieffer@ESRF.eu" __license__ = "MIT" __copyright__ = "European Synchrotron Radiation Facility, Grenoble, France" -__date__ = "15/03/2024" +__date__ = "28/10/2025" __status__ = "production" -__docformat__ = 'restructuredtext' +__docformat__ = "restructuredtext" import logging - import sys import os import time -import numpy from .fabioutils import exists from .version import version logger = logging.getLogger(__name__) try: import h5py -except ImportError as error: +except ImportError: h5py = None logger.error("h5py module missing") else: @@ -85,7 +83,12 @@ def from_isotime(text, use_tz=False): text = text[0] if isinstance(text, bytes): text = text.decode() - if len(text) > 3 and text.startswith("b") and text[1] == text[-1] and text[1] in ('"', "'"): + if ( + len(text) > 3 + and text.startswith("b") + and text[1] == text[-1] + and text[1] in ('"', "'") + ): text = text[2:-1] if len(text) < 19: logger.warning("Not a iso-time string: %s", text) @@ -205,10 +208,12 @@ def get_entry(self, name): for grp_name in self.h5: if grp_name == name: grp = self.h5[grp_name] - if isinstance(grp, h5py.Group) and \ - ("start_time" in grp) and \ - self.get_attr(grp, "NX_class") == "NXentry": - return grp + if ( + isinstance(grp, h5py.Group) + and ("start_time" in grp) + and self.get_attr(grp, "NX_class") == "NXentry" + ): + return grp def get_entries(self): """ @@ -216,12 +221,16 @@ def get_entries(self): :return: list of HDF5 groups """ - entries = [(grp, from_isotime(self.h5[grp + "/start_time"][()])) - for grp in self.h5 - if isinstance(self.h5[grp], h5py.Group) and - ("start_time" in self.h5[grp]) and - self.get_attr(self.h5[grp], "NX_class") == "NXentry"] - entries.sort(key=lambda a: a[1], reverse=True) # sort entries in decreasing time + entries = [ + (grp, from_isotime(self.h5[grp + "/start_time"][()])) + for grp in self.h5 + if isinstance(self.h5[grp], h5py.Group) + and ("start_time" in self.h5[grp]) + and self.get_attr(self.h5[grp], "NX_class") == "NXentry" + ] + entries.sort( + key=lambda a: a[1], reverse=True + ) # sort entries in decreasing time return [self.h5[i[0]] for i in entries] def find_detector(self, all=False): @@ -232,7 +241,9 @@ def find_detector(self, all=False): """ result = [] for entry in self.get_entries(): - for instrument in self.get_class(entry, "NXsubentry") + self.get_class(entry, "NXinstrument"): + for instrument in self.get_class(entry, "NXsubentry") + self.get_class( + entry, "NXinstrument" + ): for detector in self.get_class(instrument, "NXdetector"): if all: result.append(detector) @@ -286,8 +297,14 @@ def find_data(self, all=False): return result - def new_entry(self, entry="entry", program_name="pyFAI", - title=None, force_time=None, force_name=False): + def new_entry( + self, + entry="entry", + program_name="pyFAI", + title=None, + force_time=None, + force_name=False, + ): """ Create a new entry @@ -316,15 +333,20 @@ def new_entry(self, entry="entry", program_name="pyFAI", self.to_close.append(entry_grp) return entry_grp - def new_instrument(self, entry="entry", instrument_name="id00",): + def new_instrument( + self, + entry="entry", + instrument_name="id00", + ): """ Create an instrument in an entry or create both the entry and the instrument if """ if not isinstance(entry, h5py.Group): entry = self.new_entry(entry) return self.new_class(entry, instrument_name, "NXinstrument") -# howto external link - # myfile['ext link'] = h5py.ExternalLink("otherfile.hdf5", "/path/to/resource") + + # howto external link + # myfile['ext link'] = h5py.ExternalLink("otherfile.hdf5", "/path/to/resource") def new_class(self, grp, name, class_type="NXcollection"): """ @@ -367,9 +389,12 @@ def get_class(self, grp, class_type="NXcollection"): :param grp: HDF5 group :param class_type: name of the NeXus class """ - coll = [grp[name] for name in grp - if isinstance(grp[name], h5py.Group) and - self.get_attr(grp[name], "NX_class") == class_type] + coll = [ + grp[name] + for name in grp + if isinstance(grp[name], h5py.Group) + and self.get_attr(grp[name], "NX_class") == class_type + ] return coll def get_data(self, grp, attr=None, value=None): @@ -379,14 +404,17 @@ def get_data(self, grp, attr=None, value=None): :param attr: name of an attribute :param value: requested value """ - coll = [grp[name] for name in grp - if isinstance(grp[name], h5py.Dataset) and - self.get_attr(grp[name], attr) == value] + coll = [ + grp[name] + for name in grp + if isinstance(grp[name], h5py.Dataset) + and self.get_attr(grp[name], attr) == value + ] return coll def get_default_NXdata(self): """Return the default plot configured in the nexus structure. - + :return: the group with the default plot or None if not found """ entry_name = self.h5.attrs.get("default") @@ -399,7 +427,9 @@ def get_default_NXdata(self): else: return entry_grp.get(nxdata_name) - def deep_copy(self, name, obj, where="/", toplevel=None, excluded=None, overwrite=False): + def deep_copy( + self, name, obj, where="/", toplevel=None, excluded=None, overwrite=False + ): """ perform a deep copy: create a "name" entry in self containing a copy of the object @@ -417,14 +447,18 @@ def deep_copy(self, name, obj, where="/", toplevel=None, excluded=None, overwrit if name not in toplevel: grp = toplevel.require_group(name) for k, v in obj.attrs.items(): - grp.attrs[k] = v + grp.attrs[k] = v elif isinstance(obj, h5py.Dataset): if name in toplevel: if overwrite: del toplevel[name] - logger.warning("Overwriting %s in %s", toplevel[name].name, self.filename) + logger.warning( + "Overwriting %s in %s", toplevel[name].name, self.filename + ) else: - logger.warning("Not overwriting %s in %s", toplevel[name].name, self.filename) + logger.warning( + "Not overwriting %s in %s", toplevel[name].name, self.filename + ) return toplevel[name] = obj[()] for k, v in obj.attrs.items(): diff --git a/src/fabio/numpyimage.py b/src/fabio/numpyimage.py index 8ed8f809..ec2c268d 100644 --- a/src/fabio/numpyimage.py +++ b/src/fabio/numpyimage.py @@ -30,13 +30,14 @@ __contact__ = "jerome.kieffer@esrf.fr" __license__ = "MIT" __copyright__ = "ESRF" -__date__ = "03/04/2020" +__date__ = "27/10/2025" import logging -logger = logging.getLogger(__name__) import numpy from . import fabioimage +logger = logging.getLogger(__name__) + class NumpyImage(fabioimage.FabioImage): """ @@ -180,13 +181,15 @@ def _get_frame(self, num): frame._set_container(self, num) frame._set_file_container(self, num) else: - raise IndexError("getframe %s out of range [%s %s[" % (num, 0, self.nframes)) + raise IndexError( + "getframe %s out of range [%s %s[" % (num, 0, self.nframes) + ) else: frame = fabioimage.FabioImage._get_frame(self, num) return frame def getframe(self, num): - """ returns the frame numbered 'num' in the stack if applicable""" + """returns the frame numbered 'num' in the stack if applicable""" if self.nframes > 1: frame = None if (num >= 0) and num < self.nframes: @@ -196,17 +199,19 @@ def getframe(self, num): frame._nframes = self.nframes frame.currentframe = num else: - raise IndexError("getframe %s out of range [%s %s[" % (num, 0, self.nframes)) + raise IndexError( + "getframe %s out of range [%s %s[" % (num, 0, self.nframes) + ) else: frame = fabioimage.FabioImage.getframe(self, num) return frame def previous(self): - """ returns the previous frame in the series as a fabioimage """ + """returns the previous frame in the series as a fabioimage""" return self.getframe(self.currentframe - 1) def next(self): - """ returns the next frame in the series as a fabioimage """ + """returns the next frame in the series as a fabioimage""" return self.getframe(self.currentframe + 1) diff --git a/src/fabio/openimage.py b/src/fabio/openimage.py index f03f9e7d..0d2fd686 100644 --- a/src/fabio/openimage.py +++ b/src/fabio/openimage.py @@ -56,65 +56,74 @@ MAGIC_NUMBERS = [ # "\42\5a" : 'bzipped' # "\1f\8b" : 'gzipped' - (b"FORMAT :100", 'bruker100'), - (b"FORMAT : 86", 'bruker'), - (b"\x4d\x4d\x00\x2a", 'tif'), - (b"\x4d\x4d\x2b\x00", 'tif'), # bigtiff, big endian + (b"FORMAT :100", "bruker100"), + (b"FORMAT : 86", "bruker"), + (b"\x4d\x4d\x00\x2a", "tif"), + (b"\x4d\x4d\x2b\x00", "tif"), # bigtiff, big endian # The marCCD and Pilatus formats are both standard tif with a header # hopefully these byte patterns are unique for the formats # If not the image will be read, but the is missing - (b"\x49\x49\x2a\x00\x08\x00", 'marccd/tif'), - (b"\x49\x49\x2a\x00\x82\x00", 'pilatus'), - (b"\x49\x49\x2a\x00", 'tif'), - (b"\x49\x49\x2b\x00", 'tif'), # bigtiff, little endian + (b"\x49\x49\x2a\x00\x08\x00", "marccd/tif"), + (b"\x49\x49\x2a\x00\x82\x00", "pilatus"), + (b"\x49\x49\x2a\x00", "tif"), + (b"\x49\x49\x2b\x00", "tif"), # bigtiff, little endian # d*TREK must come before edf - (b"{\nHEA", 'dtrek'), + (b"{\nHEA", "dtrek"), # EDF_ types - (b"\r\n{\r\nEDF", 'edf'), # EDF3 (can be interpreted like EDF1 but refused by fit2d) - (b"\n{\r\nEDF", 'edf'), # EDF2 (can be interpreted like EDF1 but refused by fit2d) - (b"{\r\nEDF", 'edf'), # EDF1 (EDF >=V2.4 starting with EDF_, fit2d friendly, without starting newline) - (b"{\n", 'edf'), # EDF0 (EDF V1.xx "standard", without additional EDF_ structure information) - (b"\n{\n", 'edf'), # EDFU (EDF unknown source, V1.xx) + ( + b"\r\n{\r\nEDF", + "edf", + ), # EDF3 (can be interpreted like EDF1 but refused by fit2d) + (b"\n{\r\nEDF", "edf"), # EDF2 (can be interpreted like EDF1 but refused by fit2d) + ( + b"{\r\nEDF", + "edf", + ), # EDF1 (EDF >=V2.4 starting with EDF_, fit2d friendly, without starting newline) + ( + b"{\n", + "edf", + ), # EDF0 (EDF V1.xx "standard", without additional EDF_ structure information) + (b"\n{\n", "edf"), # EDFU (EDF unknown source, V1.xx) # conventional - (b"{", 'edf'), - (b"\r{", 'edf'), - (b"\n{", 'edf'), + (b"{", "edf"), + (b"\r{", "edf"), + (b"\n{", "edf"), # had to add a special case for GE here because they blanked out # the default header for the GE's at APS with the firmware # update as of 2018 - (b"ADEPT", 'GE'), - (b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 'GE'), - (b"OD", 'OXD'), - (b"IM", 'HiPiC'), - (b'\x2d\x04', 'mar345'), - (b'\xd2\x04', 'mar345'), - (b'\x04\x2d', 'mar345'), # some machines may need byteswapping - (b'\x04\xd2', 'mar345'), + (b"ADEPT", "GE"), + (b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", "GE"), + (b"OD", "OXD"), + (b"IM", "HiPiC"), + (b"\x2d\x04", "mar345"), + (b"\xd2\x04", "mar345"), + (b"\x04\x2d", "mar345"), # some machines may need byteswapping + (b"\x04\xd2", "mar345"), # hint : MASK in 32 bit - (b'M\x00\x00\x00A\x00\x00\x00S\x00\x00\x00K\x00\x00\x00', 'fit2dmask'), - (b'\x00\x00\x00\x03', 'dm3'), + (b"M\x00\x00\x00A\x00\x00\x00S\x00\x00\x00K\x00\x00\x00", "fit2dmask"), + (b"\x00\x00\x00\x03", "dm3"), (b"No", "kcd"), (b"<", "xsd"), - (b"\n\xb8\x03\x00", 'pixi'), + (b"\n\xb8\x03\x00", "pixi"), (b"\x89\x48\x44\x46\x0d\x0a\x1a\x0a", "eiger/lima/sparse/hdf5/lambda"), - (b"R-AXIS", 'raxis'), - (b"\x93NUMPY", 'numpy'), - (b"\\$FFF_START", 'fit2d'), + (b"R-AXIS", "raxis"), + (b"\x93NUMPY", "numpy"), + (b"\\$FFF_START", "fit2d"), # Raw JPEG - (b"\xFF\xD8\xFF\xDB", "jpeg"), + (b"\xff\xd8\xff\xdb", "jpeg"), # JFIF format - (b"\xFF\xD8\xFF\xE0", "jpeg"), + (b"\xff\xd8\xff\xe0", "jpeg"), # Exif format - (b"\xFF\xD8\xFF\xE1", "jpeg"), + (b"\xff\xd8\xff\xe1", "jpeg"), # JPEG 2000 (from RFC 3745) - (b"\x00\x00\x00\x0C\x6A\x50\x20\x20\x0D\x0A\x87\x0A", "jpeg2k"), + (b"\x00\x00\x00\x0c\x6a\x50\x20\x20\x0d\x0a\x87\x0a", "jpeg2k"), (b"ESPERANTO FORMAT", "esperanto"), - (b'###CBF: VERSION', "cbf") + (b"###CBF: VERSION", "cbf"), ] def do_magic(byts, filename): - """ Try to interpret the bytes starting the file as a magic number """ + """Try to interpret the bytes starting the file as a magic number""" for magic, format_type in MAGIC_NUMBERS: if byts.startswith(magic): if "/" in format_type: @@ -125,6 +134,7 @@ def do_magic(byts, filename): # check if the creator is LIMA or other lambda_path = "/entry/instrument/detector/description" import h5py + with h5py.File(filename, "r") as h: creator = h.attrs.get("creator") if str(creator).startswith("LIMA"): @@ -145,7 +155,10 @@ def do_magic(byts, filename): return "lima" elif str(creator).startswith("pyFAI"): return "sparse" - elif lambda_path in h and h[lambda_path][()].decode() == "Lambda": + elif ( + lambda_path in h + and h[lambda_path][()].decode() == "Lambda" + ): return "lambda" else: return "eiger" @@ -159,11 +172,11 @@ def do_magic(byts, filename): # Might be GE with an EDF header. Check the extension. extension = filename.split(".")[-1] # If it is a compression extension, check the next one up. - if f'.{extension}' in ExternalCompressors.COMMANDS: + if f".{extension}" in ExternalCompressors.COMMANDS: extension = filename.split(".")[-2] # If the extension is `ge` plus a number, assume it is GE - if re.search(r'^ge\d*$', extension): + if re.search(r"^ge\d*$", extension): return "GE" return format_type @@ -196,7 +209,12 @@ def openimage(filename, frame=None): actual_filename = filename.tostring() logger.debug("Attempting to open %s", actual_filename) obj = _openimage(actual_filename) - logger.debug("Attempting to read frame %s from %s with reader %s", frame, actual_filename, obj.classname) + logger.debug( + "Attempting to read frame %s from %s with reader %s", + frame, + actual_filename, + obj.classname, + ) obj = obj.read(actual_filename, frame) except Exception as ex: # multiframe file @@ -209,13 +227,16 @@ def openimage(filename, frame=None): else: logger.debug("Attempting to open %s" % (filename)) obj = _openimage(filename) - logger.debug("Attempting to read frame %s from %s with reader %s" % (frame, filename, obj.classname)) + logger.debug( + "Attempting to read frame %s from %s with reader %s" + % (frame, filename, obj.classname) + ) obj = obj.read(obj.filename, frame) return obj def openheader(filename): - """ return only the header""" + """return only the header""" if isinstance(filename, fabioutils.PathTypes): if not isinstance(filename, fabioutils.StringTypes): filename = str(filename) @@ -270,9 +291,11 @@ def _openimage(filename): file_obj = FilenameObject(filename=filename) if file_obj is None: raise Exception("Unable to deconstruct filename") - if (file_obj.format is not None) and\ - len(file_obj.format) != 1 and \ - isinstance(file_obj.format, list): + if ( + (file_obj.format is not None) + and len(file_obj.format) != 1 + and isinstance(file_obj.format, list) + ): # one of OXD/ADSC - should have got in previous raise Exception("openimage failed on magic bytes & name guess") filetype = file_obj.format @@ -284,7 +307,7 @@ def _openimage(filename): if filetype is None: raise IOError("Fabio could not identify " + filename) - klass_name = "".join(filetype) + 'image' + klass_name = "".join(filetype) + "image" try: obj = fabioformats.factory(klass_name) @@ -297,8 +320,13 @@ def _openimage(filename): return obj -def open_series(filenames=None, first_filename=None, - single_frame=None, fixed_frames=None, fixed_frame_number=None): +def open_series( + filenames=None, + first_filename=None, + single_frame=None, + fixed_frames=None, + fixed_frame_number=None, +): """ Create an object to iterate frames through a file series. @@ -332,7 +360,9 @@ def open_series(filenames=None, first_filename=None, if first_filename is not None: filenames = file_series.filename_series(filename=first_filename) - return file_series.FileSeries(filenames=filenames, - single_frame=single_frame, - fixed_frames=fixed_frames, - fixed_frame_number=fixed_frame_number) + return file_series.FileSeries( + filenames=filenames, + single_frame=single_frame, + fixed_frames=fixed_frames, + fixed_frame_number=fixed_frame_number, + ) diff --git a/src/fabio/pilatusimage.py b/src/fabio/pilatusimage.py index 00894785..88ee8411 100644 --- a/src/fabio/pilatusimage.py +++ b/src/fabio/pilatusimage.py @@ -48,8 +48,8 @@ def __init__(self, data, tiff_header, pilatus_header): class PilatusImage(tifimage.TifImage): - """ Read in Pilatus format, also - pilatus images, including header info """ + """Read in Pilatus format, also + pilatus images, including header info""" DESCRIPTION = "Pilatus file format based on Tiff" @@ -86,7 +86,7 @@ def _create_pilatus_header(self, tiff_header): description = tiff_header["imageDescription"] for line in description.split("\n"): - index = line.find('# ') + index = line.find("# ") if index == -1: if line.strip(" \x00") != "": # If it is not an empty line diff --git a/src/fabio/pixiimage.py b/src/fabio/pixiimage.py index 7de65632..7f67fb33 100644 --- a/src/fabio/pixiimage.py +++ b/src/fabio/pixiimage.py @@ -37,20 +37,18 @@ __contact__ = "wright@esrf.fr" __license__ = "MIT" __copyright__ = "European Synchrotron Radiation Facility, Grenoble, France" -__date__ = "30/05/2024" +__date__ = "27/10/2025" import numpy import os import logging - -logger = logging.getLogger(__name__) - from . import fabioimage from .fabioutils import previous_filename, next_filename +logger = logging.getLogger(__name__) -class PixiImage(fabioimage.FabioImage): +class PixiImage(fabioimage.FabioImage): DESCRIPTION = "Pixi file format" DEFAULT_EXTENSIONS = [] @@ -73,10 +71,10 @@ def _readheader(self, infile): byt = infile.read(4) framesize = numpy.frombuffer(byt, numpy.dtype("= self.nframes or img_num < 0): + if img_num >= self.nframes or img_num < 0: raise Exception("Bad image number") - imgstart = self.header['offset'] + img_num * self._FRAME_SIZE + imgstart = self.header["offset"] + img_num * self._FRAME_SIZE filepointer.seek(imgstart, 0) - data = numpy.frombuffer(filepointer.read(self._IMAGE_SIZE), - numpy.dtype(" section, then automatically load all the custom widget classes. """ - import sys import importlib from xml.etree.ElementTree import ElementTree @@ -211,7 +208,6 @@ def _get_custom_widgets(ui_file): return custom_widget_classes - def loadUi(uifile, baseinstance=None, package=None, resource_suffix=None): """ Dynamically load a user interface from the given ``uifile``. @@ -237,7 +233,9 @@ def loadUi(uifile, baseinstance=None, package=None, resource_suffix=None): if package is not None: _logger.warning("loadUi package parameter not implemented with PySide") if resource_suffix is not None: - _logger.warning("loadUi resource_suffix parameter not implemented with PySide") + _logger.warning( + "loadUi resource_suffix parameter not implemented with PySide" + ) # We parse the UI file and import any required custom widgets customWidgets = _get_custom_widgets(uifile) diff --git a/src/fabio/qt/_qt.py b/src/fabio/qt/_qt.py index f756a278..e902ca48 100644 --- a/src/fabio/qt/_qt.py +++ b/src/fabio/qt/_qt.py @@ -25,7 +25,7 @@ __authors__ = ["V.A. Sole"] __license__ = "MIT" -__date__ = "12/01/2022" +__date__ = "27/10/2025" import importlib @@ -144,18 +144,18 @@ def _select_binding() -> str: from PyQt5.uic import loadUi # noqa - Signal = pyqtSignal + Signal = pyqtSignal # noqa - Property = pyqtProperty + Property = pyqtProperty # noqa - Slot = pyqtSlot + Slot = pyqtSlot # noqa # Disable PyQt5's cooperative multi-inheritance since other bindings do not provide it. # See https://www.riverbankcomputing.com/static/Docs/PyQt5/multiinheritance.html?highlight=inheritance class _Foo(object): pass - class QObject(QObject, _Foo): + class QObject(QObject, _Foo): # noqa pass elif BINDING == "PySide6": @@ -239,11 +239,11 @@ class QObject(QObject, _Foo): from PyQt6.uic import loadUi # noqa - Signal = pyqtSignal + Signal = pyqtSignal # noqa - Property = pyqtProperty + Property = pyqtProperty # noqa - Slot = pyqtSlot + Slot = pyqtSlot # noqa # Disable PyQt6 cooperative multi-inheritance since other bindings do not provide it. # See https://www.riverbankcomputing.com/static/Docs/PyQt6/multiinheritance.html?highlight=inheritance @@ -272,9 +272,9 @@ def exceptionHandler(type_, value, trace): """ _logger.error("%s %s %s", type_, value, "".join(traceback.format_tb(trace))) - msg = QMessageBox() + msg = QMessageBox() # noqa msg.setWindowTitle("Unhandled exception") - msg.setIcon(QMessageBox.Critical) + msg.setIcon(QMessageBox.Critical) # noqa msg.setInformativeText("%s %s\nPlease report details" % (type_, value)) msg.setDetailedText(("%s " % value) + "".join(traceback.format_tb(trace))) msg.raise_() diff --git a/src/fabio/qt/_utils.py b/src/fabio/qt/_utils.py index 1015c293..9c56ce9b 100644 --- a/src/fabio/qt/_utils.py +++ b/src/fabio/qt/_utils.py @@ -21,8 +21,7 @@ # THE SOFTWARE. # # ###########################################################################*/ -"""This module provides convenient functions related to Qt. -""" +"""This module provides convenient functions related to Qt.""" __authors__ = ["V. Valls"] __license__ = "MIT" diff --git a/src/fabio/qt/matplotlib.py b/src/fabio/qt/matplotlib.py index 0b7379af..699b7705 100644 --- a/src/fabio/qt/matplotlib.py +++ b/src/fabio/qt/matplotlib.py @@ -29,6 +29,7 @@ It provides the matplotlib :class:`FigureCanvasQTAgg` class corresponding to the used backend. """ + from __future__ import annotations diff --git a/src/fabio/raxisimage.py b/src/fabio/raxisimage.py index 4071dcdf..c1a4fd29 100644 --- a/src/fabio/raxisimage.py +++ b/src/fabio/raxisimage.py @@ -47,123 +47,129 @@ import numpy from .fabioimage import FabioImage from .fabioutils import OrderedDict + logger = logging.getLogger(__name__) -RIGAKU_KEYS = OrderedDict([ - ('InstrumentType', 10), - ('Version', 10), - ('Crystal Name', 20), - ('Crystal System', 12), - ('A', 'float'), - ('B', 'float'), - ('C', 'float'), - ('Alpha', 'float'), - ('Beta', 'float'), - ('Gamma', 'float'), - ('Space Group', 12), - ('Mosaicity', 'float'), - ('Memo', 80), - ('Date', 12), - ('Reserved Space 1', 84), - ('User', 20), - ('Xray Target', 4), - ('Wavelength', 'float'), - ('Monochromator', 20), - ('Monochromator 2theta', 'float'), - ('Collimator', 20), - ('Filter', 4), - ('Crystal-to-detector Distance', 'float'), - ('Generator Voltage', 'float'), - ('Generator Current', 'float'), - ('Focus', 12), - ('Xray Memo', 80), - ('IP shape', 'long'), # 1= cylindrical, 0=flat. A "long" is overkill. - ('Oscillation Type', 'float'), # 1=weissenberg. else regular. "float"? really? - ('Reserved Space 2', 56), - ('Crystal Mount (spindle axis)', 4), - ('Crystal Mount (beam axis)', 4), - ('Phi Datum', 'float'), # degrees - ('Phi Oscillation Start', 'float'), # deg - ('Phi Oscillation Stop', 'float'), # deg - ('Frame Number', 'long'), - ('Exposure Time', 'float'), # minutes - ('Direct beam X position', 'float'), # special, x,y - ('Direct beam Y position', 'float'), # special, x,y - ('Omega Angle', 'float'), # omega angle - ('Chi Angle', 'float'), # omega angle - ('2Theta Angle', 'float'), # omega angle - ('Mu Angle', 'float'), # omega angle - ('Image Template', 204), # used for storing scan template.. - ('X Pixels', 'long'), - ('Y Pixels', 'long'), - ('X Pixel Length', 'float'), # mm - ('Y Pixel Length', 'float'), # mm - ('Record Length', 'long'), - ('Total', 'long'), - ('Starting Line', 'long'), - ('IP Number', 'long'), - ('Photomultiplier Ratio', 'float'), - ('Fade Time (to start of read)', 'float'), - ('Fade Time (to end of read)', 'float'), # good that they thought of this, but is it applied? - ('Host Type/Endian', 10), - ('IP Type', 10), - ('Horizontal Scan', 'long'), # 0=left->Right, 1=Rigth->Left - ('Vertical Scan', 'long'), # 0=down->up, 1=up->down - ('Front/Back Scan', 'long'), # 0=front, 1=back - ('Pixel Shift (RAXIS V)', 'float'), - ('Even/Odd Intensity Ratio (RAXIS V)', 'float'), - ('Magic number', 'long'), # 'RAPID'-specific - ('Number of Axes', 'long'), - ('Goniometer Vector ax.1.1', 'float'), - ('Goniometer Vector ax.1.2', 'float'), - ('Goniometer Vector ax.1.3', 'float'), - ('Goniometer Vector ax.2.1', 'float'), - ('Goniometer Vector ax.2.2', 'float'), - ('Goniometer Vector ax.2.3', 'float'), - ('Goniometer Vector ax.3.1', 'float'), - ('Goniometer Vector ax.3.2', 'float'), - ('Goniometer Vector ax.3.3', 'float'), - ('Goniometer Vector ax.4.1', 'float'), - ('Goniometer Vector ax.4.2', 'float'), - ('Goniometer Vector ax.4.3', 'float'), - ('Goniometer Vector ax.5.1', 'float'), - ('Goniometer Vector ax.5.2', 'float'), - ('Goniometer Vector ax.5.3', 'float'), - ('Goniometer Start ax.1', 'float'), - ('Goniometer Start ax.2', 'float'), - ('Goniometer Start ax.3', 'float'), - ('Goniometer Start ax.4', 'float'), - ('Goniometer Start ax.5', 'float'), - ('Goniometer End ax.1', 'float'), - ('Goniometer End ax.2', 'float'), - ('Goniometer End ax.3', 'float'), - ('Goniometer End ax.4', 'float'), - ('Goniometer End ax.5', 'float'), - ('Goniometer Offset ax.1', 'float'), - ('Goniometer Offset ax.2', 'float'), - ('Goniometer Offset ax.3', 'float'), - ('Goniometer Offset ax.4', 'float'), - ('Goniometer Offset ax.5', 'float'), - ('Goniometer Scan Axis', 'long'), - ('Axes Names', 40), - ('file', 16), - ('cmnt', 20), - ('smpl', 20), - ('iext', 'long'), - ('reso', 'long'), - ('save', 'long'), - ('dint', 'long'), - ('byte', 'long'), - ('init', 'long'), - ('ipus', 'long'), - ('dexp', 'long'), - ('expn', 'long'), - ('posx', 20), - ('posy', 20), - ('xray', 'long'), - # more values can be added here - ('Header Leftovers', -1) -]) +RIGAKU_KEYS = OrderedDict( + [ + ("InstrumentType", 10), + ("Version", 10), + ("Crystal Name", 20), + ("Crystal System", 12), + ("A", "float"), + ("B", "float"), + ("C", "float"), + ("Alpha", "float"), + ("Beta", "float"), + ("Gamma", "float"), + ("Space Group", 12), + ("Mosaicity", "float"), + ("Memo", 80), + ("Date", 12), + ("Reserved Space 1", 84), + ("User", 20), + ("Xray Target", 4), + ("Wavelength", "float"), + ("Monochromator", 20), + ("Monochromator 2theta", "float"), + ("Collimator", 20), + ("Filter", 4), + ("Crystal-to-detector Distance", "float"), + ("Generator Voltage", "float"), + ("Generator Current", "float"), + ("Focus", 12), + ("Xray Memo", 80), + ("IP shape", "long"), # 1= cylindrical, 0=flat. A "long" is overkill. + ("Oscillation Type", "float"), # 1=weissenberg. else regular. "float"? really? + ("Reserved Space 2", 56), + ("Crystal Mount (spindle axis)", 4), + ("Crystal Mount (beam axis)", 4), + ("Phi Datum", "float"), # degrees + ("Phi Oscillation Start", "float"), # deg + ("Phi Oscillation Stop", "float"), # deg + ("Frame Number", "long"), + ("Exposure Time", "float"), # minutes + ("Direct beam X position", "float"), # special, x,y + ("Direct beam Y position", "float"), # special, x,y + ("Omega Angle", "float"), # omega angle + ("Chi Angle", "float"), # omega angle + ("2Theta Angle", "float"), # omega angle + ("Mu Angle", "float"), # omega angle + ("Image Template", 204), # used for storing scan template.. + ("X Pixels", "long"), + ("Y Pixels", "long"), + ("X Pixel Length", "float"), # mm + ("Y Pixel Length", "float"), # mm + ("Record Length", "long"), + ("Total", "long"), + ("Starting Line", "long"), + ("IP Number", "long"), + ("Photomultiplier Ratio", "float"), + ("Fade Time (to start of read)", "float"), + ( + "Fade Time (to end of read)", + "float", + ), # good that they thought of this, but is it applied? + ("Host Type/Endian", 10), + ("IP Type", 10), + ("Horizontal Scan", "long"), # 0=left->Right, 1=Rigth->Left + ("Vertical Scan", "long"), # 0=down->up, 1=up->down + ("Front/Back Scan", "long"), # 0=front, 1=back + ("Pixel Shift (RAXIS V)", "float"), + ("Even/Odd Intensity Ratio (RAXIS V)", "float"), + ("Magic number", "long"), # 'RAPID'-specific + ("Number of Axes", "long"), + ("Goniometer Vector ax.1.1", "float"), + ("Goniometer Vector ax.1.2", "float"), + ("Goniometer Vector ax.1.3", "float"), + ("Goniometer Vector ax.2.1", "float"), + ("Goniometer Vector ax.2.2", "float"), + ("Goniometer Vector ax.2.3", "float"), + ("Goniometer Vector ax.3.1", "float"), + ("Goniometer Vector ax.3.2", "float"), + ("Goniometer Vector ax.3.3", "float"), + ("Goniometer Vector ax.4.1", "float"), + ("Goniometer Vector ax.4.2", "float"), + ("Goniometer Vector ax.4.3", "float"), + ("Goniometer Vector ax.5.1", "float"), + ("Goniometer Vector ax.5.2", "float"), + ("Goniometer Vector ax.5.3", "float"), + ("Goniometer Start ax.1", "float"), + ("Goniometer Start ax.2", "float"), + ("Goniometer Start ax.3", "float"), + ("Goniometer Start ax.4", "float"), + ("Goniometer Start ax.5", "float"), + ("Goniometer End ax.1", "float"), + ("Goniometer End ax.2", "float"), + ("Goniometer End ax.3", "float"), + ("Goniometer End ax.4", "float"), + ("Goniometer End ax.5", "float"), + ("Goniometer Offset ax.1", "float"), + ("Goniometer Offset ax.2", "float"), + ("Goniometer Offset ax.3", "float"), + ("Goniometer Offset ax.4", "float"), + ("Goniometer Offset ax.5", "float"), + ("Goniometer Scan Axis", "long"), + ("Axes Names", 40), + ("file", 16), + ("cmnt", 20), + ("smpl", 20), + ("iext", "long"), + ("reso", "long"), + ("save", "long"), + ("dint", "long"), + ("byte", "long"), + ("init", "long"), + ("ipus", "long"), + ("dexp", "long"), + ("expn", "long"), + ("posx", 20), + ("posy", 20), + ("xray", "long"), + # more values can be added here + ("Header Leftovers", -1), + ] +) class RaxisImage(FabioImage): @@ -186,16 +192,20 @@ def __init__(self, *arg, **kwargs): Generic constructor """ FabioImage.__init__(self, *arg, **kwargs) - self._dtype = numpy.dtype('uint16') # same for all RAXIS images AFAICT - self.endianness = '>' # this may be tested for. + self._dtype = numpy.dtype("uint16") # same for all RAXIS images AFAICT + self.endianness = ">" # this may be tested for. def swap_needed(self): """not sure if this function is needed""" endian = self.endianness # Decide if we need to byteswap - if (endian == '<' and numpy.little_endian) or (endian == '>' and not numpy.little_endian): + if (endian == "<" and numpy.little_endian) or ( + endian == ">" and not numpy.little_endian + ): return False - if (endian == '>' and numpy.little_endian) or (endian == '<' and not numpy.little_endian): + if (endian == ">" and numpy.little_endian) or ( + endian == "<" and not numpy.little_endian + ): return True def _readheader(self, infile): @@ -233,29 +243,33 @@ def _readheader(self, infile): # if -1, read remainder of header if kind == -1: rByte = len(rawHead) - curByte - self.header[key] = struct.unpack(fs + str(rByte) + 's', - rawHead[curByte: curByte + rByte])[0] + self.header[key] = struct.unpack( + fs + str(rByte) + "s", rawHead[curByte : curByte + rByte] + )[0] curByte += rByte break rByte = kind - self.header[key] = struct.unpack(fs + str(rByte) + 's', - rawHead[curByte: curByte + rByte])[0] + self.header[key] = struct.unpack( + fs + str(rByte) + "s", rawHead[curByte : curByte + rByte] + )[0] curByte += rByte - elif kind == 'float': + elif kind == "float": # read a float, 4 bytes rByte = 4 - self.header[key] = struct.unpack(fs + 'f', - rawHead[curByte: curByte + rByte])[0] + self.header[key] = struct.unpack( + fs + "f", rawHead[curByte : curByte + rByte] + )[0] curByte += rByte - elif kind == 'long': + elif kind == "long": # read a long, 4 bytes rByte = 4 - self.header[key] = struct.unpack(fs + 'l', - rawHead[curByte: curByte + rByte])[0] + self.header[key] = struct.unpack( + fs + "l", rawHead[curByte : curByte + rByte] + )[0] curByte += rByte else: - logger.warning('special header data type %s not understood', kind) + logger.warning("special header data type %s not understood", kind) if len(rawHead) == curByte: # "end reached" break @@ -267,7 +281,7 @@ def read(self, fname, frame=None): :param frame: """ self.resetvals() - infile = self._open(fname, 'rb') + infile = self._open(fname, "rb") offset = -1 # read from EOF backward self._readheader(infile) @@ -275,8 +289,8 @@ def read(self, fname, frame=None): # lifted from binaryimage # read the image data - dim1 = self.header['X Pixels'] - dim2 = self.header['Y Pixels'] + dim1 = self.header["X Pixels"] + dim2 = self.header["Y Pixels"] self._shape = dim2, dim1 self._dtype = numpy.dtype(numpy.uint16) @@ -294,11 +308,16 @@ def read(self, fname, frame=None): if "len" in attrs: infile.seek(infile.len - size) # seek from EOF backwards else: - infile.seek(-size + offset + 1, os.SEEK_END) # seek from EOF backwards + infile.seek( + -size + offset + 1, os.SEEK_END + ) # seek from EOF backwards except IOError as error: - logger.warning('expected datablock too large, please check bytecode settings: %s, IOError: %s' % (self._dtype.type, error)) + logger.warning( + "expected datablock too large, please check bytecode settings: %s, IOError: %s" + % (self._dtype.type, error) + ) except Exception as error: - logger.error('Uncommon error encountered when reading file: %s' % error) + logger.error("Uncommon error encountered when reading file: %s" % error) rawData = infile.read(size) data = numpy.frombuffer(rawData, self._dtype).copy().reshape(shape) if self.swap_needed(): @@ -313,7 +332,7 @@ def read(self, fname, frame=None): self._dtype = numpy.dtype(numpy.uint32) data = data.astype(self._dtype) # Now we do some fixing for Rigaku's refusal to adhere to standards: - sf = self.header['Photomultiplier Ratio'] + sf = self.header["Photomultiplier Ratio"] # multiply by the ratio defined in the header # data[di] *= sf data[di] = (sf * data[di]).astype(self._dtype) diff --git a/src/fabio/readbytestream.py b/src/fabio/readbytestream.py index 86f1a3ae..b3ee64a5 100644 --- a/src/fabio/readbytestream.py +++ b/src/fabio/readbytestream.py @@ -34,30 +34,33 @@ import logging import numpy + logger = logging.getLogger(__name__) DATATYPES = { # type sign bytes - ("int", 'n', 1): numpy.uint8, - ("int", 'n', 2): numpy.uint16, - ("int", 'n', 4): numpy.uint32, - ("int", 'y', 1): numpy.int8, - ("int", 'y', 2): numpy.int16, - ("int", 'y', 4): numpy.int32, - ('float', 'y', 4): numpy.float32, # does this occur in bruker? - ('double', 'y', 4): numpy.float64 + ("int", "n", 1): numpy.uint8, + ("int", "n", 2): numpy.uint16, + ("int", "n", 4): numpy.uint32, + ("int", "y", 1): numpy.int8, + ("int", "y", 2): numpy.int16, + ("int", "y", 4): numpy.int32, + ("float", "y", 4): numpy.float32, # does this occur in bruker? + ("double", "y", 4): numpy.float64, } -def readbytestream(fil, - offset, - x, - y, - nbytespp, - datatype='int', - signed='n', - swap='n', - typeout=numpy.uint16): +def readbytestream( + fil, + offset, + x, + y, + nbytespp, + datatype="int", + signed="n", + swap="n", + typeout=numpy.uint16, +): """ Reads in a bytestream from a file (which may be a string indicating a filename, or an already opened file (should be "rb")) @@ -79,8 +82,8 @@ def readbytestream(fil, """ tin = "dunno" length = nbytespp * x * y # bytes per pixel times number of pixels - if datatype in ['float', 'double']: - signed = 'y' + if datatype in ["float", "double"]: + signed = "y" key = (datatype, signed, nbytespp) try: @@ -94,7 +97,7 @@ def readbytestream(fil, infile = fil opened = False else: - infile = open(fil, 'rb') + infile = open(fil, "rb") opened = True infile.seek(offset) @@ -102,7 +105,7 @@ def readbytestream(fil, data = numpy.frombuffer(infile.read(length), tin) arr = numpy.array(numpy.reshape(data, (x, y)), typeout) - if swap == 'y': + if swap == "y": arr.byteswap(True) if opened: diff --git a/src/fabio/sparseimage.py b/src/fabio/sparseimage.py index 9ad3040e..0ce0fc62 100644 --- a/src/fabio/sparseimage.py +++ b/src/fabio/sparseimage.py @@ -37,39 +37,38 @@ __contact__ = "jerome.kieffer@esrf.fr" __license__ = "MIT" __copyright__ = "2020-2025 ESRF" -__date__ = "05/05/2025" +__date__ = "27/10/2025" import logging -logger = logging.getLogger(__name__) import json import numpy + try: import h5py except ImportError: h5py = None -else: - try: - import hdf5plugin - except: - pass from .fabioutils import NotGoodReader -from .fabioimage import FabioImage, OrderedDict +from .fabioimage import FabioImage + try: from .ext import dense as cython_densify except ImportError: cython_densify = None +logger = logging.getLogger(__name__) -def densify(mask, - radius, - index, - intensity, - dummy, - background, - background_std=None, - normalization=None, - cutoff=None, - seed=None): +def densify( + mask, + radius, + index, + intensity, + dummy, + background, + background_std=None, + normalization=None, + cutoff=None, + seed=None, +): """Generate a dense image of its sparse representation :param mask: 2D array with NaNs for mask and pixel radius for the valid pixels @@ -108,8 +107,8 @@ def densify(mask, if numpy.issubdtype(dtype, numpy.integer): # Foolded by banker's rounding !!!! - #numpy.round(dense, out=dense) - numpy.fix(dense+0.5*numpy.sign(dense), out=dense) + # numpy.round(dense, out=dense) + numpy.fix(dense + 0.5 * numpy.sign(dense), out=dense) dense = numpy.ascontiguousarray(dense, dtype=dtype) dense[numpy.logical_not(numpy.isfinite(mask))] = dummy @@ -137,11 +136,15 @@ def __init__(self, *arg, **kwargs): Generic constructor """ if not h5py: - raise RuntimeError("fabio.SparseImage cannot be used without h5py. Please install h5py and restart") + raise RuntimeError( + "fabio.SparseImage cannot be used without h5py. Please install h5py and restart" + ) FabioImage.__init__(self, *arg, **kwargs) self.mask = None - self.normalization = None # Correspond to the flat/polarization/solid-angle correction + self.normalization = ( + None # Correspond to the flat/polarization/solid-angle correction + ) self.radius = None self.background_avg = None self.background_std = None @@ -183,7 +186,9 @@ def read(self, fname, frame=None): if default_entry is None or default_entry not in self.h5: raise NotGoodReader("HDF5 file does not contain any default entry.") entry = self.h5[default_entry] - default_data = entry.attrs.get("pyFAI_sparse_frames") or entry.attrs.get("default") + default_data = entry.attrs.get("pyFAI_sparse_frames") or entry.attrs.get( + "default" + ) if default_data is None or default_data not in entry: raise NotGoodReader("HDF5 file does not contain any default NXdata.") nx_data = entry[default_data] @@ -201,21 +206,27 @@ def read(self, fname, frame=None): try: self.dummy = self.intensity.dtype.type(nx_data["dummy"][()]) except KeyError: - if self.intensity.dtype.char in numpy.typecodes['AllFloat']: + if self.intensity.dtype.char in numpy.typecodes["AllFloat"]: self.dummy = numpy.nan else: self.dummy = 0 self._nframes = self.frame_ptr.shape[0] - 1 if "normalization" in nx_data: - self.normalization = numpy.ascontiguousarray(nx_data["normalization"][()], dtype=numpy.float32) + self.normalization = numpy.ascontiguousarray( + nx_data["normalization"][()], dtype=numpy.float32 + ) # Read cutoff_pick config = {} try: config = json.loads(entry["sparsify/configuration/data"][()]) except Exception as err: - logger.warning("Unable to read configuration of sparsification:\n%s: %s",type(err), err) + logger.warning( + "Unable to read configuration of sparsification:\n%s: %s", + type(err), + err, + ) self.cutoff = config.get("sparsify", {}).get("cutoff_pick") # Read the peak position in the file @@ -235,54 +246,61 @@ def _generate_data(self, index=0): if self.h5 is None: logger.warning("Not data have been read from disk") return - start, stop = self.frame_ptr[index:index + 2] + start, stop = self.frame_ptr[index : index + 2] if self.radius is None: if cython_densify is None: # Numpy implementation - dense = densify(mask=self.mask, - radius=None, - index=self.index[start:stop], - intensity=self.intensity[start:stop], - dummy = self.dummy, - background=None, - background_std=None, - normalization=self.normalization, - cutoff=self.cutoff - ) + dense = densify( + mask=self.mask, + radius=None, + index=self.index[start:stop], + intensity=self.intensity[start:stop], + dummy=self.dummy, + background=None, + background_std=None, + normalization=self.normalization, + cutoff=self.cutoff, + ) else: # Cython - dense = cython_densify.densify(mask=self.mask, - radius=None, - index=self.index[start:stop], - intensity=self.intensity[start:stop], - dummy=self.dummy, - dtype=self.intensity.dtype, - background=None, - background_std=None, - normalization=self.normalization, - cutoff=self.cutoff) + dense = cython_densify.densify( + mask=self.mask, + radius=None, + index=self.index[start:stop], + intensity=self.intensity[start:stop], + dummy=self.dummy, + dtype=self.intensity.dtype, + background=None, + background_std=None, + normalization=self.normalization, + cutoff=self.cutoff, + ) else: if cython_densify is None: # Numpy - dense = densify(self.mask, - self.radius, - self.index[start:stop], - self.intensity[start:stop], - self.dummy, - self.background_avg[index], - self.background_std[index] * self.noisy if self.noisy else None, - self.normalization) + dense = densify( + self.mask, + self.radius, + self.index[start:stop], + self.intensity[start:stop], + self.dummy, + self.background_avg[index], + self.background_std[index] * self.noisy if self.noisy else None, + self.normalization, + ) else: - dense = cython_densify.densify(self.mask, - self.radius, - self.index[start:stop], - self.intensity[start:stop], - self.dummy, - self.intensity.dtype, - self.background_avg[index], - self.background_std[index] * self.noisy if self.noisy else None, - self.normalization) + dense = cython_densify.densify( + self.mask, + self.radius, + self.index[start:stop], + self.intensity[start:stop], + self.dummy, + self.intensity.dtype, + self.background_avg[index], + self.background_std[index] * self.noisy if self.noisy else None, + self.normalization, + ) return dense def getframe(self, num): - """ returns the frame numbered 'num' in the stack if applicable""" + """returns the frame numbered 'num' in the stack if applicable""" if self.nframes > 1: new_img = None if (num >= 0) and num < self.nframes: @@ -302,17 +320,19 @@ def getframe(self, num): new_img.currentframe = num new_img.normalization = self.normalization else: - raise IOError("getframe %s out of range [%s %s[" % (num, 0, self.nframes)) + raise IOError( + "getframe %s out of range [%s %s[" % (num, 0, self.nframes) + ) else: new_img = FabioImage.getframe(self, num) return new_img def previous(self): - """ returns the previous frame in the series as a fabioimage """ + """returns the previous frame in the series as a fabioimage""" return self.getframe(self.currentframe - 1) def next(self): - """ returns the next frame in the series as a fabioimage """ + """returns the next frame in the series as a fabioimage""" return self.getframe(self.currentframe + 1) diff --git a/src/fabio/speimage.py b/src/fabio/speimage.py index 67913622..9ff782f2 100644 --- a/src/fabio/speimage.py +++ b/src/fabio/speimage.py @@ -27,39 +27,31 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. -"""Princeton instrument SPE image reader for FabIO - - -""" +"""Princeton instrument SPE image reader for FabIO""" __authors__ = ["Clemens Prescher"] __contact__ = "c.prescher@uni-koeln.de" __license__ = "MIT" __copyright__ = "Clemens Prescher" -__date__ = "09/02/2023" +__date__ = "27/10/2025" import logging - -logger = logging.getLogger(__name__) - import datetime from xml.dom.minidom import parseString - import numpy as np from numpy.polynomial.polynomial import polyval - from .fabioimage import FabioImage +logger = logging.getLogger(__name__) + class SpeImage(FabioImage): """FabIO image class for Images for Princeton/SPE detector Put some documentation here """ - DATA_TYPES = {0: np.float32, - 1: np.int32, - 2: np.int16, - 3: np.uint16} + + DATA_TYPES = {0: np.float32, 1: np.int32, 2: np.int16, 3: np.uint16} DESCRIPTION = "Princeton instrument SPE file format" @@ -72,33 +64,37 @@ def _readheader(self, infile): :param infile: Opened python file (can be stringIO or bipped file) """ - self.header['version'] = self._get_version(infile) - - self.header['data_type'] = self._read_at(infile, 108, 1, np.uint16)[0] - self.header['x_dim'] = int(self._read_at(infile, 42, 1, np.int16)[0]) - self.header['y_dim'] = int(self._read_at(infile, 656, 1, np.int16)[0]) - self.header['num_frames'] = self._read_at(infile, 1446, 1, np.int32)[0] - - if self.header['version'] == 2: - self.header['time'] = self._read_date_time_from_header(infile) - self.header['x_calibration'] = self._read_calibration_from_header(infile) - self.header['exposure_time'] = self._read_at(infile, 10, 1, np.float32)[0] - self.header['detector'] = 'unspecified' - self.header['grating'] = str(self._read_at(infile, 650, 1, np.float32)[0]) - self.header['center_wavelength'] = float(self._read_at(infile, 72, 1, np.float32)[0]) + self.header["version"] = self._get_version(infile) + + self.header["data_type"] = self._read_at(infile, 108, 1, np.uint16)[0] + self.header["x_dim"] = int(self._read_at(infile, 42, 1, np.int16)[0]) + self.header["y_dim"] = int(self._read_at(infile, 656, 1, np.int16)[0]) + self.header["num_frames"] = self._read_at(infile, 1446, 1, np.int32)[0] + + if self.header["version"] == 2: + self.header["time"] = self._read_date_time_from_header(infile) + self.header["x_calibration"] = self._read_calibration_from_header(infile) + self.header["exposure_time"] = self._read_at(infile, 10, 1, np.float32)[0] + self.header["detector"] = "unspecified" + self.header["grating"] = str(self._read_at(infile, 650, 1, np.float32)[0]) + self.header["center_wavelength"] = float( + self._read_at(infile, 72, 1, np.float32)[0] + ) # # self._read_roi_from_header() # self._read_num_frames_from_header() # self._read_num_combined_frames_from_header() - elif self.header['version'] == 3: + elif self.header["version"] == 3: xml_string = self._get_xml_string(infile) dom = self._create_dom_from_xml(xml_string) - self.header['time'] = self._read_date_time_from_dom(dom) - self.header['roi'] = self._read_roi_from_dom(dom) - self.header['x_calibration'] = self._read_calibration_from_dom(dom) - self.header['exposure_time'] = self._read_exposure_from_dom(dom) - self.header['detector'] = self._read_detector_from_dom(dom) - self.header['grating'] = self._read_grating_from_dom(dom, infile) - self.header['center_wavelength'] = self._read_center_wavelength_from_dom(dom, infile) + self.header["time"] = self._read_date_time_from_dom(dom) + self.header["roi"] = self._read_roi_from_dom(dom) + self.header["x_calibration"] = self._read_calibration_from_dom(dom) + self.header["exposure_time"] = self._read_exposure_from_dom(dom) + self.header["detector"] = self._read_detector_from_dom(dom) + self.header["grating"] = self._read_grating_from_dom(dom, infile) + self.header["center_wavelength"] = self._read_center_wavelength_from_dom( + dom, infile + ) self.header = self.check_header(self.header) @@ -111,7 +107,7 @@ def read(self, fname, frame=None): self.resetvals() - with self._open(fname, 'rb') as infile: + with self._open(fname, "rb") as infile: self._readheader(infile) # read the image data and declare self.data = self._read_data(infile, frame) @@ -129,37 +125,43 @@ def _read_date_time_from_header(self, infile): """Reads the collection time from the header into the date_time field""" raw_date = self._read_at(infile, 20, 9, np.int8) raw_time = self._read_at(infile, 172, 6, np.int8) - str_date = ''.join([chr(i) for i in raw_date]) - str_date += ''.join([chr(i) for i in raw_time]) + str_date = "".join([chr(i) for i in raw_date]) + str_date += "".join([chr(i) for i in raw_time]) date_time = datetime.datetime.strptime(str_date, "%d%b%Y%H%M%S") return date_time.strftime("%m/%d/%Y %H:%M:%S") def _read_date_time_from_dom(self, dom): """Reads the time of collection and saves it date_time field""" - date_time_str = dom.getElementsByTagName('Origin')[0].getAttribute('created') + date_time_str = dom.getElementsByTagName("Origin")[0].getAttribute("created") try: - date_time = datetime.datetime.strptime(date_time_str[:-7], "%Y-%m-%dT%H:%M:%S.%f") + date_time = datetime.datetime.strptime( + date_time_str[:-7], "%Y-%m-%dT%H:%M:%S.%f" + ) return date_time.strftime("%m/%d/%Y %H:%M:%S.%f") except ValueError: - date_time = datetime.datetime.strptime(date_time_str[:-6], "%Y-%m-%dT%H:%M:%S") + date_time = datetime.datetime.strptime( + date_time_str[:-6], "%Y-%m-%dT%H:%M:%S" + ) return date_time.strftime("%m/%d/%Y %H:%M:%S") def _read_calibration_from_header(self, infile): """Reads the calibration from the header into the x_calibration field""" x_polynocoeff = self._read_at(infile, 3263, 6, np.double) - x_val = np.arange(self.header['x_dim']) + 1 + x_val = np.arange(self.header["x_dim"]) + 1 return np.array(polyval(x_val, x_polynocoeff)) def _read_calibration_from_dom(self, dom): """Reads the x calibration of the image from the xml footer and saves it in the x_calibration field""" spe_format = dom.childNodes[0] - calibrations = spe_format.getElementsByTagName('Calibrations')[0] - wavelengthmapping = calibrations.getElementsByTagName('WavelengthMapping')[0] - wavelengths = wavelengthmapping.getElementsByTagName('Wavelength')[0] + calibrations = spe_format.getElementsByTagName("Calibrations")[0] + wavelengthmapping = calibrations.getElementsByTagName("WavelengthMapping")[0] + wavelengths = wavelengthmapping.getElementsByTagName("Wavelength")[0] wavelength_values = wavelengths.childNodes[0] - x_calibration = np.array([float(i) for i in wavelength_values.toxml().split(',')]) - return x_calibration[self.header['roi'][0]:self.header['roi'][1]] + x_calibration = np.array( + [float(i) for i in wavelength_values.toxml().split(",")] + ) + return x_calibration[self.header["roi"][0] : self.header["roi"][1]] def _read_num_frames_from_header(self, infile): self.num_frames = self._read_at(infile, 1446, 1, np.int32)[0] @@ -174,7 +176,7 @@ def _get_xml_string(self, infile): raise RuntimeError("Unable to guess the actual size of the file") xml_size = size - self.xml_offset xml = self._read_at(infile, self.xml_offset, xml_size, np.byte) - return ''.join([chr(i) for i in xml]) + return "".join([chr(i) for i in xml]) # if self.debug: # fid = open(self.filename + '.xml', 'w') # for line in self.xml_string: @@ -188,38 +190,60 @@ def _create_dom_from_xml(self, xml_string): def _read_exposure_from_dom(self, dom): """Reads th exposure time of the experiment into the exposure_time field""" - if len(dom.getElementsByTagName('Experiment')) != 1: # check if it is a real v3.0 file - if len(dom.getElementsByTagName('ShutterTiming')) == 1: # check if it is a pixis detector - exposure_time = dom.getElementsByTagName('ExposureTime')[0].childNodes[0] + if ( + len(dom.getElementsByTagName("Experiment")) != 1 + ): # check if it is a real v3.0 file + if ( + len(dom.getElementsByTagName("ShutterTiming")) == 1 + ): # check if it is a pixis detector + exposure_time = dom.getElementsByTagName("ExposureTime")[0].childNodes[ + 0 + ] return np.float64(exposure_time.toxml()) / 1000.0 else: - exposure_time = dom.getElementsByTagName('ReadoutControl')[0]. \ - getElementsByTagName('Time')[0].childNodes[0].nodeValue - self.header['accumulations'] = dom.getElementsByTagName('Accumulations')[0].childNodes[0].nodeValue - return np.float64(exposure_time) * np.float64(self.header['accumulations']) + exposure_time = ( + dom.getElementsByTagName("ReadoutControl")[0] + .getElementsByTagName("Time")[0] + .childNodes[0] + .nodeValue + ) + self.header["accumulations"] = ( + dom.getElementsByTagName("Accumulations")[0].childNodes[0].nodeValue + ) + return np.float64(exposure_time) * np.float64( + self.header["accumulations"] + ) else: # this is searching for legacy experiment: - self._exposure_time = dom.getElementsByTagName('LegacyExperiment')[0]. \ - getElementsByTagName('Experiment')[0]. \ - getElementsByTagName('CollectionParameters')[0]. \ - getElementsByTagName('Exposure')[0].attributes["value"].value + self._exposure_time = ( + dom.getElementsByTagName("LegacyExperiment")[0] + .getElementsByTagName("Experiment")[0] + .getElementsByTagName("CollectionParameters")[0] + .getElementsByTagName("Exposure")[0] + .attributes["value"] + .value + ) return np.float64(self._exposure_time.split()[0]) def _read_detector_from_dom(self, dom): """Reads the detector information from the dom object""" - self._camera = dom.getElementsByTagName('Camera') + self._camera = dom.getElementsByTagName("Camera") if len(self._camera) >= 1: - return self._camera[0].getAttribute('model') + return self._camera[0].getAttribute("model") else: - return 'unspecified' + return "unspecified" def _read_grating_from_dom(self, dom, infile): """Reads the type of grating from the dom Model""" try: - grating = dom.getElementsByTagName('Devices')[0]. \ - getElementsByTagName('Spectrometer')[0]. \ - getElementsByTagName('Grating')[0]. \ - getElementsByTagName('Selected')[0].childNodes[0].toxml() - return grating.split('[')[1].split(']')[0].replace(',', ' ') + grating = ( + dom.getElementsByTagName("Devices")[0] + .getElementsByTagName("Spectrometer")[0] + .getElementsByTagName("Grating")[0] + .getElementsByTagName("Selected")[0] + .childNodes[0] + .toxml() + ) + return grating.split("[")[1].split("]")[0].replace(",", " ") except IndexError: # try from header: return str(self._read_at(infile, 650, 1, np.float32)[0]) @@ -227,11 +251,14 @@ def _read_grating_from_dom(self, dom, infile): def _read_center_wavelength_from_dom(self, dom, infile): """Reads the center wavelength from the dom Model and saves it center_wavelength field""" try: - center_wavelength = dom.getElementsByTagName('Devices')[0]. \ - getElementsByTagName('Spectrometer')[0]. \ - getElementsByTagName('Grating')[0]. \ - getElementsByTagName('CenterWavelength')[0]. \ - childNodes[0].toxml() + center_wavelength = ( + dom.getElementsByTagName("Devices")[0] + .getElementsByTagName("Spectrometer")[0] + .getElementsByTagName("Grating")[0] + .getElementsByTagName("CenterWavelength")[0] + .childNodes[0] + .toxml() + ) return float(center_wavelength) except IndexError: # try from header @@ -245,30 +272,35 @@ def _read_roi_from_dom(self, dom): For FullSensor roi_x,roi_y, roi_width, roi_height""" try: - roi_modus = str(dom.getElementsByTagName('ReadoutControl')[0]. - getElementsByTagName('RegionsOfInterest')[0]. - getElementsByTagName('Selection')[0]. - childNodes[0].toxml()) - if roi_modus == 'CustomRegions': - roi_dom = dom.getElementsByTagName('ReadoutControl')[0]. \ - getElementsByTagName('RegionsOfInterest')[0]. \ - getElementsByTagName('CustomRegions')[0]. \ - getElementsByTagName('RegionOfInterest')[0] - roi_x = int(roi_dom.attributes['x'].value) - roi_y = int(roi_dom.attributes['y'].value) - roi_width = int(roi_dom.attributes['width'].value) - roi_height = int(roi_dom.attributes['height'].value) + roi_modus = str( + dom.getElementsByTagName("ReadoutControl")[0] + .getElementsByTagName("RegionsOfInterest")[0] + .getElementsByTagName("Selection")[0] + .childNodes[0] + .toxml() + ) + if roi_modus == "CustomRegions": + roi_dom = ( + dom.getElementsByTagName("ReadoutControl")[0] + .getElementsByTagName("RegionsOfInterest")[0] + .getElementsByTagName("CustomRegions")[0] + .getElementsByTagName("RegionOfInterest")[0] + ) + roi_x = int(roi_dom.attributes["x"].value) + roi_y = int(roi_dom.attributes["y"].value) + roi_width = int(roi_dom.attributes["width"].value) + roi_height = int(roi_dom.attributes["height"].value) else: roi_x = 0 roi_y = 0 - roi_width = self.header['x_dim'] - roi_height = self.header['y_dim'] + roi_width = self.header["x_dim"] + roi_height = self.header["y_dim"] except IndexError: roi_x = 0 roi_y = 0 - roi_width = self.header['x_dim'] - roi_height = self.header['y_dim'] + roi_width = self.header["x_dim"] + roi_height = self.header["y_dim"] return roi_x, roi_x + roi_width, roi_y, roi_y + roi_height @@ -282,11 +314,11 @@ def _read_at(self, infile, pos, size, ntype): def _read_data(self, infile, frame=None): if frame is None: frame = 0 - dtype = self.DATA_TYPES.get(self.header['data_type']) + dtype = self.DATA_TYPES.get(self.header["data_type"]) if dtype is None: - raise RuntimeError("Unsuported data type: %s" % self.header['data_type']) + raise RuntimeError("Unsuported data type: %s" % self.header["data_type"]) number_size = np.dtype(dtype).itemsize - frame_size = self.header['x_dim'] * self.header['y_dim'] * number_size + frame_size = self.header["x_dim"] * self.header["y_dim"] * number_size return self._read_frame(infile, 4100 + frame * frame_size) def _read_frame(self, infile, pos=None): @@ -297,13 +329,15 @@ def _read_frame(self, infile, pos=None): """ if pos is None: pos = infile.tell() - dtype = self.DATA_TYPES.get(self.header['data_type']) + dtype = self.DATA_TYPES.get(self.header["data_type"]) if dtype is None: return None - data = self._read_at(infile, pos, self.header['x_dim'] * self.header['y_dim'], dtype) - return data.reshape((self.header['y_dim'], self.header['x_dim'])) + data = self._read_at( + infile, pos, self.header["x_dim"] * self.header["y_dim"], dtype + ) + return data.reshape((self.header["y_dim"], self.header["x_dim"])) # this is for compatibility with old code: diff --git a/src/fabio/templateimage.py b/src/fabio/templateimage.py index d50887dd..e1d158cd 100644 --- a/src/fabio/templateimage.py +++ b/src/fabio/templateimage.py @@ -76,9 +76,10 @@ __date__ = "09/02/2023" import logging -logger = logging.getLogger(__name__) import numpy -from .fabioimage import FabioImage, OrderedDict +from .fabioimage import FabioImage + +logger = logging.getLogger(__name__) class TemplateImage(FabioImage): diff --git a/src/fabio/test/__init__.py b/src/fabio/test/__init__.py index c1cfb035..3cf4c993 100755 --- a/src/fabio/test/__init__.py +++ b/src/fabio/test/__init__.py @@ -30,11 +30,9 @@ __contact__ = "jerome.kieffer@esrf.eu" __license__ = "MIT" __copyright__ = "European Synchrotron Radiation Facility, Grenoble, France" -__data__ = "30/10/2015" +__date__ = "27/10/2025" -import sys import unittest -from . import utilstest from . import test_all diff --git a/src/fabio/test/codecs/__init__.py b/src/fabio/test/codecs/__init__.py index b698f49c..be16d659 100755 --- a/src/fabio/test/codecs/__init__.py +++ b/src/fabio/test/codecs/__init__.py @@ -28,13 +28,12 @@ __authors__ = ["Jérôme Kieffer"] __contact__ = "jerome.kieffer@esrf.eu" -__license__ = "GPLv3+" +__license__ = "MIT+" __copyright__ = "European Synchrotron Radiation Facility, Grenoble, France" -__date__ = "02/05/2024" +__date__ = "27/10/2025" + -import sys import unittest -from .. import utilstest def suite(): diff --git a/src/fabio/test/codecs/test_binaryimage.py b/src/fabio/test/codecs/test_binaryimage.py index 6fbdd25b..2a4ce123 100644 --- a/src/fabio/test/codecs/test_binaryimage.py +++ b/src/fabio/test/codecs/test_binaryimage.py @@ -26,19 +26,18 @@ # THE SOFTWARE. # -"""Test binary images -""" +"""Test binary images""" import unittest import os import logging - -logger = logging.getLogger(__name__) import numpy from fabio.openimage import openimage from fabio.binaryimage import BinaryImage from ..utilstest import UtilsTest +logger = logging.getLogger(__name__) + def make_file(name, shape, dtype): data = numpy.random.random(shape).astype(dtype) @@ -57,9 +56,16 @@ def setUpClass(cls): cls.data = make_file(cls.fn3, cls.shape, cls.dtype) def test_read(self): - """ check we can read images from Eiger""" + """check we can read images from Eiger""" e = BinaryImage() - e.read(self.fn3, self.shape[1], self.shape[0], offset=-1, bytecode=self.dtype[1], endian=self.dtype[0]) + e.read( + self.fn3, + self.shape[1], + self.shape[0], + offset=-1, + bytecode=self.dtype[1], + endian=self.dtype[0], + ) f = openimage(self.fn3) self.assertEqual(e.shape, f.shape) self.assertEqual(e.bpp, f.bpp, "bpp OK") @@ -73,8 +79,17 @@ def test_write(self): e.save(fn) self.assertTrue(os.path.exists(fn), "file exists") f = BinaryImage() - f.read(fn, self.shape[1], self.shape[0], offset=0, bytecode=ary.dtype, endian=ary.dtype.str[0]) - self.assertEqual(str(f.__class__.__name__), "BinaryImage", "Used the write reader") + f.read( + fn, + self.shape[1], + self.shape[0], + offset=0, + bytecode=ary.dtype, + endian=ary.dtype.str[0], + ) + self.assertEqual( + str(f.__class__.__name__), "BinaryImage", "Used the write reader" + ) self.assertEqual(self.shape, f.shape, "shape matches") self.assertEqual(abs(f.data - ary).max(), 0, "first frame matches") @@ -86,6 +101,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/codecs/test_bruker100image.py b/src/fabio/test/codecs/test_bruker100image.py index 307b86e3..f13919ed 100644 --- a/src/fabio/test/codecs/test_bruker100image.py +++ b/src/fabio/test/codecs/test_bruker100image.py @@ -35,14 +35,13 @@ import unittest import os import logging - -logger = logging.getLogger(__name__) - import numpy from fabio.bruker100image import Bruker100Image, _split_data, _merge_data from fabio.openimage import openimage from ..utilstest import UtilsTest +logger = logging.getLogger(__name__) + # filename dim1 dim2 min max mean stddev TESTIMAGES = """NaCl_10_01_0009.sfrm 512 512 -30 5912 34.4626 26.189 NaCl_10_01_0009.sfrm.gz 512 512 -30 5912 34.4626 26.189 @@ -51,17 +50,19 @@ class TestBruker100(unittest.TestCase): - """ check some read data from bruker version100 detector""" + """check some read data from bruker version100 detector""" def setUp(self): """ download images """ UtilsTest.getimage(REFIMAGE) - self.im_dir = os.path.dirname(UtilsTest.getimage(TESTIMAGES.split()[0] + ".bz2")) + self.im_dir = os.path.dirname( + UtilsTest.getimage(TESTIMAGES.split()[0] + ".bz2") + ) def test_read(self): - """ check we can read bruker100 images""" + """check we can read bruker100 images""" for line in TESTIMAGES.split("\n"): vals = line.split() name = vals[0] @@ -77,7 +78,7 @@ def test_read(self): self.assertEqual(shape, obj.shape) def test_same(self): - """ check we can read bruker100 images""" + """check we can read bruker100 images""" ref = openimage(os.path.join(self.im_dir, REFIMAGE)) for line in TESTIMAGES.split("\n"): obt = openimage(os.path.join(self.im_dir, line.split()[0])) @@ -94,7 +95,11 @@ def test_write(self): self.assertEqual(abs(obt.data - other.data).max(), 0, "data are the same") for key in obt.header: self.assertTrue(key in other.header, "Key %s is in header" % key) - self.assertEqual(obt.header[key], other.header[key], "value are the same for key %s" % key) + self.assertEqual( + obt.header[key], + other.header[key], + "value are the same for key %s" % key, + ) os.unlink(os.path.join(UtilsTest.tempdir, name)) def test_split_merge(self): @@ -102,15 +107,23 @@ def test_split_merge(self): shape = 256, 256 outliers = 100 a = numpy.random.normal(100, 50, size=shape) - a.ravel()[numpy.random.randint(0, numpy.prod(shape), size=outliers)] = numpy.random.randint(10000, 1000000, size=outliers) + a.ravel()[numpy.random.randint(0, numpy.prod(shape), size=outliers)] = ( + numpy.random.randint(10000, 1000000, size=outliers) + ) ref = a.astype("int32") for baseline in (None, 0, False): split = _split_data(ref, baseline=baseline) - logger.info("size of underflow: %s overflow1 %s overflow2: %s", - split["underflow"].shape, split["overflow1"].shape, split["overflow2"].shape) + logger.info( + "size of underflow: %s overflow1 %s overflow2: %s", + split["underflow"].shape, + split["overflow1"].shape, + split["overflow2"].shape, + ) obt = _merge_data(**split) - self.assertTrue(numpy.allclose(obt, ref), f"data are the same, baseline={baseline}") + self.assertTrue( + numpy.allclose(obt, ref), f"data are the same, baseline={baseline}" + ) def test_conversion(self): fname = UtilsTest.getimage("testcbf.cbf.bz2")[:-4] @@ -130,6 +143,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/codecs/test_brukerimage.py b/src/fabio/test/codecs/test_brukerimage.py index b7eface8..85924f46 100644 --- a/src/fabio/test/codecs/test_brukerimage.py +++ b/src/fabio/test/codecs/test_brukerimage.py @@ -37,24 +37,25 @@ import os import numpy import logging - -logger = logging.getLogger(__name__) - from ...brukerimage import brukerimage from ... import fabioutils from ..utilstest import UtilsTest +logger = logging.getLogger(__name__) + # this is actually a violation of the bruker format since the order of # the header items is specified # in the standard, whereas the order of a python dictionary is not -MYHEADER = {"FORMAT": '86', - 'NPIXELB': '2', - 'VERSION': '9', - 'HDRBLKS': '5', - 'NOVERFL': '4', - 'NCOLS': '256', - 'NROWS': '256', - 'WORDORD': '0'} +MYHEADER = { + "FORMAT": "86", + "NPIXELB": "2", + "VERSION": "9", + "HDRBLKS": "5", + "NOVERFL": "4", + "NCOLS": "256", + "NROWS": "256", + "WORDORD": "0", +} MYIMAGE = numpy.ones((256, 256), numpy.uint16) * 16 MYIMAGE[0, 0] = 0 @@ -67,7 +68,7 @@ ["%09d" % 4194304, ("%07d" % (127 * 256 + 127))], ["%09d" % 4194304, ("%07d" % (127 * 256 + 128))], ["%09d" % 4194304, ("%07d" % (128 * 256 + 127))], - ["%09d" % 4194304, ("%07d" % (128 * 256 + 128))] + ["%09d" % 4194304, ("%07d" % (128 * 256 + 128))], ] @@ -75,25 +76,25 @@ class TestBruker(unittest.TestCase): """basic test""" def setUp(self): - """ Generate a test bruker image """ + """Generate a test bruker image""" self.filename = os.path.join(UtilsTest.tempdir, "image.0000") - with open(self.filename, 'wb') as fout: + with open(self.filename, "wb") as fout: wrb = 0 for key, val in MYHEADER.items(): - fout.write((("%-7s" % key) + ':' + ("%-72s" % val)).encode("ASCII")) + fout.write((("%-7s" % key) + ":" + ("%-72s" % val)).encode("ASCII")) wrb = wrb + 80 - hdrblks = int(MYHEADER['HDRBLKS']) - while (wrb < hdrblks * 512): + hdrblks = int(MYHEADER["HDRBLKS"]) + while wrb < hdrblks * 512: fout.write(b"\x1a\x04") - fout.write(b'.' * 78) + fout.write(b"." * 78) wrb = wrb + 80 fout.write(MYIMAGE.tobytes()) - noverfl = int(MYHEADER['NOVERFL']) + noverfl = int(MYHEADER["NOVERFL"]) for ovf in OVERFLOWS: fout.write((ovf[0] + ovf[1]).encode("ASCII")) - fout.write(b'.' * (512 - (16 * noverfl) % 512)) + fout.write(b"." * (512 - (16 * noverfl) % 512)) def tearDown(self): unittest.TestCase.tearDown(self) @@ -101,7 +102,7 @@ def tearDown(self): os.unlink(self.filename) def test_read(self): - """ see if we can read the test image """ + """see if we can read the test image""" obj = brukerimage() obj.read(self.filename) self.assertAlmostEqual(obj.getmean(), 272.0, 2) @@ -110,10 +111,10 @@ def test_read(self): class TestBzipBruker(TestBruker): - """ test for a bzipped image """ + """test for a bzipped image""" def setUp(self): - """ create the image """ + """create the image""" TestBruker.setUp(self) if os.path.isfile(self.filename + ".bz2"): os.unlink(self.filename + ".bz2") @@ -124,10 +125,10 @@ def setUp(self): class TestGzipBruker(TestBruker): - """ test for a gzipped image """ + """test for a gzipped image""" def setUp(self): - """ Create the image """ + """Create the image""" TestBruker.setUp(self) if os.path.isfile(self.filename + ".gz"): os.unlink(self.filename + ".gz") @@ -146,13 +147,15 @@ def setUp(self): self.data = numpy.random.random((500, 550)).astype("float32") def test_linear(self): - """ test for self consistency of random data read/write """ + """test for self consistency of random data read/write""" obj = brukerimage(data=self.data) obj.write(self.filename) new = brukerimage() new.read(self.filename) error = abs(new.data - self.data).max() - self.assertTrue(error < numpy.finfo(numpy.float32).eps, "Error is %s>1e-7" % error) + self.assertTrue( + error < numpy.finfo(numpy.float32).eps, "Error is %s>1e-7" % error + ) def tearDown(self): unittest.TestCase.tearDown(self) @@ -168,7 +171,7 @@ def tearDown(self): class TestRealImg(unittest.TestCase): - """ check some read data from bruker detector""" + """check some read data from bruker detector""" def setUp(self): """ @@ -181,7 +184,7 @@ def tearDown(self): self.im_dir = None def test_read(self): - """ check we can read bruker images""" + """check we can read bruker images""" for line in TESTIMAGES.split("\n"): vals = line.split() name = vals[0] @@ -214,9 +217,17 @@ def test_write(self): if key in ("filename",): continue if key not in other.header: - logger.warning("Key %s is missing in new header, was %s" % (key, ref.header[key])) + logger.warning( + "Key %s is missing in new header, was %s" + % (key, ref.header[key]) + ) else: - self.assertEqual(ref.header[key], other.header[key], "value are the same for key %s: was %s now %s" % (key, ref.header[key], other.header[key])) + self.assertEqual( + ref.header[key], + other.header[key], + "value are the same for key %s: was %s now %s" + % (key, ref.header[key], other.header[key]), + ) def suite(): @@ -230,6 +241,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/codecs/test_cbfimage.py b/src/fabio/test/codecs/test_cbfimage.py index db9841df..0c6e1381 100755 --- a/src/fabio/test/codecs/test_cbfimage.py +++ b/src/fabio/test/codecs/test_cbfimage.py @@ -40,8 +40,6 @@ import os import time import logging - -logger = logging.getLogger(__name__) import numpy import fabio from fabio.cbfimage import CbfImage, CIF @@ -49,9 +47,11 @@ from ..utilstest import UtilsTest from ..testutils import LoggingValidator +logger = logging.getLogger(__name__) + class TestCbfReader(unittest.TestCase): - """ test cbf image reader """ + """test cbf image reader""" def setUp(self): """Download images""" @@ -61,7 +61,7 @@ def setUp(self): self.cbf_filename = UtilsTest.getimage(self.cbf_filename)[:-4] def test_read(self): - """ check whole reader""" + """check whole reader""" times = [] times.append(time.time()) cbf = fabio.open(self.cbf_filename) @@ -70,7 +70,10 @@ def test_read(self): times.append(time.time()) self.assertAlmostEqual(0, abs(cbf.data - edf.data).max()) - logger.info("Reading CBF took %.3fs whereas the same EDF took %.3fs" % (times[1] - times[0], times[2] - times[1])) + logger.info( + "Reading CBF took %.3fs whereas the same EDF took %.3fs" + % (times[1] - times[0], times[2] - times[1]) + ) def test_write(self): "Rest writing with self consistency at the fabio level" @@ -82,10 +85,14 @@ def test_write(self): other.read(os.path.join(UtilsTest.tempdir, name)) self.assertEqual(abs(obj.data - other.data).max(), 0, "data are the same") for key in obj.header: - if key in["filename", "X-Binary-Size-Padding"]: + if key in ["filename", "X-Binary-Size-Padding"]: continue self.assertTrue(key in other.header, "Key %s is in header" % key) - self.assertEqual(obj.header[key], other.header[key], "value are the same for key %s" % key) + self.assertEqual( + obj.header[key], + other.header[key], + "value are the same for key %s" % key, + ) # By destroying the object, one actually closes the file, which is needed under windows. del obj del other @@ -93,12 +100,12 @@ def test_write(self): os.unlink(os.path.join(UtilsTest.tempdir, name)) def test_byte_offset(self): - """ check byte offset algorithm""" + """check byte offset algorithm""" cbf = fabio.open(self.cbf_filename) starter = b"\x0c\x1a\x04\xd5" cbs = cbf.cbs startPos = cbs.find(starter) + 4 - data = cbs[startPos: startPos + int(cbf.header["X-Binary-Size"])] + data = cbs[startPos : startPos + int(cbf.header["X-Binary-Size"])] startTime = time.time() size = cbf.shape[0] * cbf.shape[1] numpyRes = decByteOffset_numpy(data, size=size) @@ -110,7 +117,9 @@ def test_byte_offset(self): tCython = time.time() - startTime delta = abs(numpyRes - cythonRes).max() self.assertAlmostEqual(0, delta) - logger.info("Timing for Cython method : %.3fs, max delta= %s" % (tCython, delta)) + logger.info( + "Timing for Cython method : %.3fs, max delta= %s" % (tCython, delta) + ) def test_consitency_manual(self): """ @@ -123,10 +132,15 @@ def test_consitency_manual(self): other = fabio.open(os.path.join(UtilsTest.tempdir, name)) self.assertEqual(abs(obj.data - other.data).max(), 0, "data are the same") for key in obj.header: - if key in["filename", "X-Binary-Size-Padding"]: + if key in ["filename", "X-Binary-Size-Padding"]: continue self.assertTrue(key in other.header, "Key %s is in header" % key) - self.assertEqual(obj.header[key], other.header[key], "value are the same for key %s [%s|%s]" % (key, obj.header[key], other.header[key])) + self.assertEqual( + obj.header[key], + other.header[key], + "value are the same for key %s [%s|%s]" + % (key, obj.header[key], other.header[key]), + ) def test_consitency_convert(self): """ @@ -139,25 +153,35 @@ def test_consitency_convert(self): other = fabio.open(os.path.join(UtilsTest.tempdir, name)) self.assertEqual(abs(obj.data - other.data).max(), 0, "data are the same") for key in obj.header: - if key in["filename", "X-Binary-Size-Padding"]: + if key in ["filename", "X-Binary-Size-Padding"]: continue self.assertTrue(key in other.header, "Key %s is in header" % key) - self.assertEqual(obj.header[key], other.header[key], "value are the same for key %s [%s|%s]" % (key, obj.header[key], other.header[key])) + self.assertEqual( + obj.header[key], + other.header[key], + "value are the same for key %s [%s|%s]" + % (key, obj.header[key], other.header[key]), + ) def test_unicode(self): """ Test if an image can be read and saved to an unicode named """ - name = u"%s" % os.path.basename(self.cbf_filename) + name = "%s" % os.path.basename(self.cbf_filename) obj = fabio.open(self.cbf_filename) obj.write(os.path.join(UtilsTest.tempdir, name)) other = fabio.open(os.path.join(UtilsTest.tempdir, name)) self.assertEqual(abs(obj.data - other.data).max(), 0, "data are the same") for key in obj.header: - if key in["filename", "X-Binary-Size-Padding"]: + if key in ["filename", "X-Binary-Size-Padding"]: continue self.assertTrue(key in other.header, "Key %s is in header" % key) - self.assertEqual(obj.header[key], other.header[key], "value are the same for key %s [%s|%s]" % (key, obj.header[key], other.header[key])) + self.assertEqual( + obj.header[key], + other.header[key], + "value are the same for key %s [%s|%s]" + % (key, obj.header[key], other.header[key]), + ) def test_bug_388(self): data = numpy.random.randint(0, 100, size=(27, 31)).astype(numpy.int32) @@ -165,7 +189,9 @@ def test_bug_388(self): filename = os.path.join(UtilsTest.tempdir, "bad_name.edf") im.write(filename) res = fabio.open(filename) - self.assertEqual(abs(data - res.data).max(), 0, "Data are the same despite the wrong name") + self.assertEqual( + abs(data - res.data).max(), 0, "Data are the same despite the wrong name" + ) def test_bug_408(self): "Bug when reading files generated by XDS" @@ -202,7 +228,7 @@ def test_cif(self): 'Paderno Y' 'Khlyustova S' _journal_name_full 'Kristallografiya' -_journal_volume 31 +_journal_volume 31 _journal_year 1986 _journal_page_first 803 _journal_page_last 805 @@ -301,6 +327,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/codecs/test_dm3image.py b/src/fabio/test/codecs/test_dm3image.py index eb1a2a12..02a67379 100644 --- a/src/fabio/test/codecs/test_dm3image.py +++ b/src/fabio/test/codecs/test_dm3image.py @@ -36,36 +36,50 @@ """ -__date__ = "03/04/2020" +__date__ = "27/10/2025" __author__ = "jerome Kieffer" import unittest import os import logging - -logger = logging.getLogger(__name__) - import fabio from fabio.dm3image import Dm3Image from ..utilstest import UtilsTest +logger = logging.getLogger(__name__) + # statistics come from fit2d I think # filename dim1 dim2 min max mean stddev -TESTIMAGES = [("ref_d20x_310mm.dm3", (2048, 2048), -31842.354, 23461.672, 569.38782, 1348.4183), - ("ref_d20x_310mm.dm3.gz", (2048, 2048), -31842.354, 23461.672, 569.38782, 1348.4183), - ("ref_d20x_310mm.dm3.bz2", (2048, 2048), -31842.354, 23461.672, 569.38782, 1348.4183)] +TESTIMAGES = [ + ("ref_d20x_310mm.dm3", (2048, 2048), -31842.354, 23461.672, 569.38782, 1348.4183), + ( + "ref_d20x_310mm.dm3.gz", + (2048, 2048), + -31842.354, + 23461.672, + 569.38782, + 1348.4183, + ), + ( + "ref_d20x_310mm.dm3.bz2", + (2048, 2048), + -31842.354, + 23461.672, + 569.38782, + 1348.4183, + ), +] class TestDm3Image(unittest.TestCase): - """ - """ + """ """ def setUp(self): - """ Download images """ + """Download images""" self.im_dir = os.path.dirname(UtilsTest.getimage("ref_d20x_310mm.dm3.bz2")) def test_read(self): - """ check we can read dm3 images""" + """check we can read dm3 images""" for info in TESTIMAGES: name, shape, mini, maxi, mean, stddev = info fname = os.path.join(self.im_dir, name) @@ -76,7 +90,9 @@ def test_read(self): self.assertAlmostEqual(mini, obj.getmin(), 2, "getmin") self.assertAlmostEqual(maxi, obj.getmax(), 2, "getmax") got_mean = obj.getmean() - self.assertAlmostEqual(mean, got_mean, 2, "getmean exp %s != got %s" % (mean, got_mean)) + self.assertAlmostEqual( + mean, got_mean, 2, "getmean exp %s != got %s" % (mean, got_mean) + ) self.assertAlmostEqual(stddev, obj.getstddev(), 2, "getstddev") self.assertEqual(shape, obj.shape) @@ -88,6 +104,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/codecs/test_dtrekimage.py b/src/fabio/test/codecs/test_dtrekimage.py index 45a7b19a..8024004b 100644 --- a/src/fabio/test/codecs/test_dtrekimage.py +++ b/src/fabio/test/codecs/test_dtrekimage.py @@ -36,27 +36,54 @@ import logging import numpy import shutil - from ..utilstest import UtilsTest - -logger = logging.getLogger(__name__) - import fabio from fabio.dtrekimage import DtrekImage from fabio.edfimage import EdfImage from fabio.utils import testutils +logger = logging.getLogger(__name__) + # statistics come from fit2d I think # filename dim1 dim2 min max mean stddev -TESTIMAGES = [("mb_LP_1_001.img", (3072, 3072), 0.0000, 65535., 120.33, 147.38, - {"BEAM_CENTER_Y": "157.500000"}), - ("mb_LP_1_001.img.gz", (3072, 3072), 0.0000, 65535., 120.33, 147.38, - {"BEAM_CENTER_Y": "157.500000"}), - ("mb_LP_1_001.img.bz2", (3072, 3072), 0.0000, 65535., 120.33, 147.38, - {"BEAM_CENTER_Y": "157.500000"}), - ("HSA_1_5mg_C1_0004.img", (385, 775), -2, 2127, 69.25, 59.52, - {"WAVELENGTH": "1.0 1.541870"}), - ] +TESTIMAGES = [ + ( + "mb_LP_1_001.img", + (3072, 3072), + 0.0000, + 65535.0, + 120.33, + 147.38, + {"BEAM_CENTER_Y": "157.500000"}, + ), + ( + "mb_LP_1_001.img.gz", + (3072, 3072), + 0.0000, + 65535.0, + 120.33, + 147.38, + {"BEAM_CENTER_Y": "157.500000"}, + ), + ( + "mb_LP_1_001.img.bz2", + (3072, 3072), + 0.0000, + 65535.0, + 120.33, + 147.38, + {"BEAM_CENTER_Y": "157.500000"}, + ), + ( + "HSA_1_5mg_C1_0004.img", + (385, 775), + -2, + 2127, + 69.25, + 59.52, + {"WAVELENGTH": "1.0 1.541870"}, + ), +] class TestMatch(unittest.TestCase): @@ -65,7 +92,7 @@ class TestMatch(unittest.TestCase): """ def setUp(self): - """ Download images """ + """Download images""" self.fn_adsc = UtilsTest.getimage("mb_LP_1_001.img.bz2")[:-4] self.fn_edf = UtilsTest.getimage("mb_LP_1_001.edf.bz2")[:-4] @@ -75,15 +102,22 @@ def testsame(self): im1.read(self.fn_edf) im2 = DtrekImage() im2.read(self.fn_adsc) - diff = (im1.data.astype("float32") - im2.data.astype("float32")) - logger.debug("type: %s %s shape %s %s " % (im1.data.dtype, im2.data.dtype, im1.data.shape, im2.data.shape)) - logger.debug("im1 min %s %s max %s %s " % (im1.data.min(), im2.data.min(), im1.data.max(), im2.data.max())) - logger.debug("delta min %s max %s mean %s" % (diff.min(), diff.max(), diff.mean())) + diff = im1.data.astype("float32") - im2.data.astype("float32") + logger.debug( + "type: %s %s shape %s %s " + % (im1.data.dtype, im2.data.dtype, im1.data.shape, im2.data.shape) + ) + logger.debug( + "im1 min %s %s max %s %s " + % (im1.data.min(), im2.data.min(), im1.data.max(), im2.data.max()) + ) + logger.debug( + "delta min %s max %s mean %s" % (diff.min(), diff.max(), diff.mean()) + ) self.assertEqual(abs(diff).max(), 0.0, "asdc data == edf data") class TestDtrekImplementation(testutils.ParametricTestCase): - @classmethod def setUpClass(cls): cls.tmp_directory = os.path.join(UtilsTest.tempdir, cls.__name__) @@ -141,7 +175,9 @@ def test_write_and_read(self): data = numpy.arange(5 * 10).reshape(5, 10) data = data.astype(input_type) obj = DtrekImage(data=data, header=header) - filename = os.path.join(self.tmp_directory, "saved_%s.img" % hash(config)) + filename = os.path.join( + self.tmp_directory, "saved_%s.img" % hash(config) + ) obj.save(filename) self.assertEqual(obj.data.dtype.type, input_type) obj2 = fabio.open(filename) @@ -174,7 +210,7 @@ def setUpClass(cls): cls.im_dir = UtilsTest.resources.data_home def test_read(self): - """ check we can read flat ADSC images""" + """check we can read flat ADSC images""" for datainfo in TESTIMAGES: with self.subTest(datainfo=datainfo): name, shape, mini, maxi, mean, stddev, keys = datainfo @@ -183,7 +219,9 @@ def test_read(self): self.assertAlmostEqual(mini, obj.getmin(), 2, "getmin") self.assertAlmostEqual(maxi, obj.getmax(), 2, "getmax") got_mean = obj.getmean() - self.assertAlmostEqual(mean, got_mean, 2, "getmean exp %s != got %s" % (mean, got_mean)) + self.assertAlmostEqual( + mean, got_mean, 2, "getmean exp %s != got %s" % (mean, got_mean) + ) self.assertAlmostEqual(stddev, obj.getstddev(), 2, "getstddev") for key, value in keys.items(): self.assertIn(key, obj.header) @@ -200,6 +238,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/codecs/test_edfimage.py b/src/fabio/test/codecs/test_edfimage.py index 8420e19c..d15195f6 100755 --- a/src/fabio/test/codecs/test_edfimage.py +++ b/src/fabio/test/codecs/test_edfimage.py @@ -39,38 +39,44 @@ import shutil import io import logging - -logger = logging.getLogger(__name__) - import fabio from ...edfimage import edfimage from ...fabioutils import GzipFile, BZ2File from ..utilstest import UtilsTest -from ..testutils import LoggingValidator +from ..testutils import LoggingValidator + +logger = logging.getLogger(__name__) class TestFlatEdfs(unittest.TestCase): - """ test some flat images """ + """test some flat images""" def common_setup(self): self.BYTE_ORDER = "LowByteFirst" if numpy.little_endian else "HighByteFirst" - self.MYHEADER = ("{\n%-1020s}\n" % ( - """Omega = 0.0 ; + self.MYHEADER = ( + "{\n%-1020s}\n" + % ( + """Omega = 0.0 ; Dim_1 = 256 ; Dim_2 = 256 ; DataType = FloatValue ; ByteOrder = %s ; Image = 1; History-1 = something=something else; - \n\n""" % self.BYTE_ORDER)).encode("latin-1") + \n\n""" + % self.BYTE_ORDER + ) + ).encode("latin-1") self.MYIMAGE = numpy.ones((256, 256), numpy.float32) * 10 self.MYIMAGE[0, 0] = 0 self.MYIMAGE[1, 1] = 20 - assert len(self.MYIMAGE[0:1, 0:1].tobytes()) == 4, self.MYIMAGE[0:1, 0:1].tobytes() + assert len(self.MYIMAGE[0:1, 0:1].tobytes()) == 4, self.MYIMAGE[ + 0:1, 0:1 + ].tobytes() def setUp(self): - """ initialize""" + """initialize""" self.common_setup() self.filename = os.path.join(UtilsTest.tempdir, "im0000.edf") if not os.path.isfile(self.filename): @@ -91,21 +97,39 @@ def tearDown(self): self.BYTE_ORDER = self.MYHEADER = self.MYIMAGE = None def test_read(self): - """ check readable""" - self.assertEqual(self.obj.shape, (256, 256), msg="File %s has wrong shape " % self.filename) + """check readable""" + self.assertEqual( + self.obj.shape, (256, 256), msg="File %s has wrong shape " % self.filename + ) self.assertEqual(self.obj.bpp, 4, msg="bpp!=4 for file: %s" % self.filename) - self.assertEqual(self.obj.bytecode, numpy.float32, msg="bytecode!=flot32 for file: %s" % self.filename) - self.assertEqual(self.obj.data.shape, (256, 256), msg="shape!=(256,256) for file: %s" % self.filename) + self.assertEqual( + self.obj.bytecode, + numpy.float32, + msg="bytecode!=flot32 for file: %s" % self.filename, + ) + self.assertEqual( + self.obj.data.shape, + (256, 256), + msg="shape!=(256,256) for file: %s" % self.filename, + ) def test_getstats(self): - """ test statistics""" + """test statistics""" self.assertEqual(self.obj.getmean(), 10) self.assertEqual(self.obj.getmin(), 0) self.assertEqual(self.obj.getmax(), 20) def test_headers(self): self.assertEqual(len(self.obj.header), 7) - expected_keys = ["Omega", "Dim_1", "Dim_2", "DataType", "ByteOrder", "Image", "History-1"] + expected_keys = [ + "Omega", + "Dim_1", + "Dim_2", + "DataType", + "ByteOrder", + "Image", + "History-1", + ] self.assertEqual(expected_keys, list(self.obj.header.keys())) expected_values = { @@ -114,14 +138,14 @@ def test_headers(self): "Dim_2": "256", "DataType": "FloatValue", "Image": "1", - "History-1": "something=something else" + "History-1": "something=something else", } for k, expected_value in expected_values.items(): self.assertEqual(self.obj.header[k], expected_value) class TestBzipEdf(TestFlatEdfs): - """ same for bzipped versions """ + """same for bzipped versions""" def setUp(self): """set it up""" @@ -134,10 +158,10 @@ def setUp(self): class TestGzipEdf(TestFlatEdfs): - """ same for gzipped versions """ + """same for gzipped versions""" def setUp(self): - """ set it up """ + """set it up""" TestFlatEdfs.setUp(self) if not os.path.isfile(self.filename + ".gz"): with GzipFile(self.filename + ".gz", "wb") as f: @@ -164,7 +188,7 @@ def setUp(self): UtilsTest.getimage("id13_badPadding.edf.bz2") def test_read(self): - """ check we can read these images""" + """check we can read these images""" for line in TESTIMAGES.split("\n"): vals = line.split() name = vals[0] @@ -177,12 +201,18 @@ def test_read(self): except Exception: logger.error("Cannot read image %s", name) raise - self.assertAlmostEqual(mini, obj.getmin(), 2, "testedfs: %s getmin()" % name) + self.assertAlmostEqual( + mini, obj.getmin(), 2, "testedfs: %s getmin()" % name + ) self.assertAlmostEqual(maxi, obj.getmax(), 2, "testedfs: %s getmax" % name) logger.info("%s Mean: exp=%s, obt=%s" % (name, mean, obj.getmean())) - self.assertAlmostEqual(mean, obj.getmean(), 2, "testedfs: %s getmean" % name) + self.assertAlmostEqual( + mean, obj.getmean(), 2, "testedfs: %s getmean" % name + ) logger.info("%s StdDev: exp=%s, obt=%s" % (name, stddev, obj.getstddev())) - self.assertAlmostEqual(stddev, obj.getstddev(), 2, "testedfs: %s getstddev" % name) + self.assertAlmostEqual( + stddev, obj.getstddev(), 2, "testedfs: %s getstddev" % name + ) self.assertEqual(obj.shape, shape, "testedfs: %s shape" % name) obj = None @@ -191,7 +221,11 @@ def test_rebin(self): f = edfimage() f.read(os.path.join(self.im_dir, "F2K_Seb_Lyso0675.edf")) f.rebin(1024, 1024) - self.assertEqual(abs(numpy.array([[1547, 1439], [1536, 1494]]) - f.data).max(), 0, "data are the same after rebin") + self.assertEqual( + abs(numpy.array([[1547, 1439], [1536, 1494]]) - f.data).max(), + 0, + "data are the same after rebin", + ) def tearDown(self): unittest.TestCase.tearDown(self) @@ -210,7 +244,7 @@ def setUp(self): UtilsTest.getimage("edfUncompressed_U16.edf.bz2") def test_read(self): - """ check we can read these images""" + """check we can read these images""" ref = edfimage() gzipped = edfimage() compressed = edfimage() @@ -222,8 +256,14 @@ def test_read(self): gzipped.read(os.path.join(self.im_dir, gzippedFile)) compressed.read(os.path.join(self.im_dir, compressedFile)) - self.assertEqual((ref.data - gzipped.data).max(), 0, "Gzipped data block is correct") - self.assertEqual((ref.data - compressed.data).max(), 0, "Zlib compressed data block is correct") + self.assertEqual( + (ref.data - gzipped.data).max(), 0, "Gzipped data block is correct" + ) + self.assertEqual( + (ref.data - compressed.data).max(), + 0, + "Zlib compressed data block is correct", + ) class TestEdfMultiFrame(unittest.TestCase): @@ -246,47 +286,107 @@ def setUp(self): def tearDown(self): unittest.TestCase.tearDown(self) - self.multiFrameFilename = self.Frame0Filename = self.Frame1Filename = self.ref = self.frame0 = self.frame1 = None + self.multiFrameFilename = self.Frame0Filename = self.Frame1Filename = ( + self.ref + ) = self.frame0 = self.frame1 = None def test_getFrame_multi(self): - self.assertEqual((self.ref.data - self.frame0.data).max(), 0, "getFrame_multi: Same data for frame 0") + self.assertEqual( + (self.ref.data - self.frame0.data).max(), + 0, + "getFrame_multi: Same data for frame 0", + ) f1_multi = self.ref.getframe(1) # logger.warning("f1_multi.header=%s\nf1_multi.data= %s" % (f1_multi.header, f1_multi.data)) - self.assertEqual((f1_multi.data - self.frame1.data).max(), 0, "getFrame_multi: Same data for frame 1") + self.assertEqual( + (f1_multi.data - self.frame1.data).max(), + 0, + "getFrame_multi: Same data for frame 1", + ) def test_getFrame_mono(self): - self.assertEqual((self.ref.data - self.frame0.data).max(), 0, "getFrame_mono: Same data for frame 0") + self.assertEqual( + (self.ref.data - self.frame0.data).max(), + 0, + "getFrame_mono: Same data for frame 0", + ) f1_mono = self.frame0.getframe(1) - self.assertEqual((f1_mono.data - self.frame1.data).max(), 0, "getFrame_mono: Same data for frame 1") + self.assertEqual( + (f1_mono.data - self.frame1.data).max(), + 0, + "getFrame_mono: Same data for frame 1", + ) def test_next_multi(self): - self.assertEqual((self.ref.data - self.frame0.data).max(), 0, "next_multi: Same data for frame 0") + self.assertEqual( + (self.ref.data - self.frame0.data).max(), + 0, + "next_multi: Same data for frame 0", + ) next_ = self.ref.next() - self.assertEqual((next_.data - self.frame1.data).max(), 0, "next_multi: Same data for frame 1") + self.assertEqual( + (next_.data - self.frame1.data).max(), + 0, + "next_multi: Same data for frame 1", + ) def text_next_mono(self): - self.assertEqual((self.ref.data - self.frame0.data).max(), 0, "next_mono: Same data for frame 0") + self.assertEqual( + (self.ref.data - self.frame0.data).max(), + 0, + "next_mono: Same data for frame 0", + ) next_ = self.frame0.next() - self.assertEqual((next_.data - self.frame1.data).max(), 0, "next_mono: Same data for frame 1") + self.assertEqual( + (next_.data - self.frame1.data).max(), 0, "next_mono: Same data for frame 1" + ) def test_previous_multi(self): f1 = self.ref.getframe(1) - self.assertEqual((f1.data - self.frame1.data).max(), 0, "previous_multi: Same data for frame 1") + self.assertEqual( + (f1.data - self.frame1.data).max(), + 0, + "previous_multi: Same data for frame 1", + ) f0 = f1.previous() - self.assertEqual((f0.data - self.frame1.data).max(), 0, "previous_multi: Same data for frame 0") + self.assertEqual( + (f0.data - self.frame1.data).max(), + 0, + "previous_multi: Same data for frame 0", + ) def test_previous_mono(self): f1 = self.ref.getframe(1) - self.assertEqual((f1.data - self.frame1.data).max(), 0, "previous_mono: Same data for frame 1") + self.assertEqual( + (f1.data - self.frame1.data).max(), + 0, + "previous_mono: Same data for frame 1", + ) prev = self.frame1.previous() - self.assertEqual((prev.data - self.frame0.data).max(), 0, "previous_mono: Same data for frame 0") + self.assertEqual( + (prev.data - self.frame0.data).max(), + 0, + "previous_mono: Same data for frame 0", + ) def test_openimage_multiframes(self): "test if openimage can directly read first or second frame of a multi-frame" - self.assertEqual((fabio.open(self.multiFrameFilename).data - self.frame0.data).max(), 0, "openimage_multiframes: Same data for default ") + self.assertEqual( + (fabio.open(self.multiFrameFilename).data - self.frame0.data).max(), + 0, + "openimage_multiframes: Same data for default ", + ) # print(fabio.open(self.multiFrameFilename, 0).data) - self.assertEqual((fabio.open(self.multiFrameFilename, 0).data - self.frame0.data).max(), 0, "openimage_multiframes: Same data for frame 0") - self.assertEqual((fabio.open(self.multiFrameFilename, 1).data - self.frame1.data).max(), 0, "openimage_multiframes: Same data for frame 1") + self.assertEqual( + (fabio.open(self.multiFrameFilename, 0).data - self.frame0.data).max(), + 0, + "openimage_multiframes: Same data for frame 0", + ) + self.assertEqual( + (fabio.open(self.multiFrameFilename, 1).data - self.frame1.data).max(), + 0, + "openimage_multiframes: Same data for frame 1", + ) class TestEdfFastRead(unittest.TestCase): @@ -314,25 +414,31 @@ def test_578(self): data=numpy.arange(0, 99).reshape(9, 11), ) edf_writer.write(file_name) - + edf_reader = fabio.open(file_name) - + # if I want to read it line by line line_index_to_read = 4 line_data = edf_reader.fast_read_roi( - file_name,coords=( + file_name, + coords=( slice(3, 9), - slice(line_index_to_read, line_index_to_read+1), + slice(line_index_to_read, line_index_to_read + 1), ), ) - self.assertTrue(numpy.allclose(edf_writer.data[3:9,line_index_to_read:line_index_to_read+1], - line_data)) + self.assertTrue( + numpy.allclose( + edf_writer.data[3:9, line_index_to_read : line_index_to_read + 1], + line_data, + ) + ) class TestEdfWrite(unittest.TestCase): """ Write dummy edf files with various compression schemes """ + tmpdir = UtilsTest.tempdir def setUp(self): @@ -346,7 +452,9 @@ def testFlat(self): r = fabio.open(self.filename) self.assertTrue(r.header["toto"] == self.header["toto"], "header are OK") self.assertTrue(abs(r.data - self.data).max() == 0, "data are OK") - self.assertEqual(int(r.header["EDF_HeaderSize"]), 512, "header size is one 512 block") + self.assertEqual( + int(r.header["EDF_HeaderSize"]), 512, "header size is one 512 block" + ) def testGzip(self): self.filename = os.path.join(self.tmpdir, "merged.azim.gz") @@ -355,7 +463,9 @@ def testGzip(self): r = fabio.open(self.filename) self.assertTrue(r.header["toto"] == self.header["toto"], "header are OK") self.assertTrue(abs(r.data - self.data).max() == 0, "data are OK") - self.assertEqual(int(r.header["EDF_HeaderSize"]), 512, "header size is one 512 block") + self.assertEqual( + int(r.header["EDF_HeaderSize"]), 512, "header size is one 512 block" + ) def testBzip2(self): self.filename = os.path.join(self.tmpdir, "merged.azim.gz") @@ -364,7 +474,9 @@ def testBzip2(self): r = fabio.open(self.filename) self.assertTrue(r.header["toto"] == self.header["toto"], "header are OK") self.assertTrue(abs(r.data - self.data).max() == 0, "data are OK") - self.assertEqual(int(r.header["EDF_HeaderSize"]), 512, "header size is one 512 block") + self.assertEqual( + int(r.header["EDF_HeaderSize"]), 512, "header size is one 512 block" + ) def tearDown(self): os.unlink(self.filename) @@ -384,7 +496,11 @@ def test_bug_27(self): """ # create dummy image: shape = (32, 32) - data = numpy.random.randint(0, 6500, size=shape[0] * shape[1]).astype("uint16").reshape(shape) + data = ( + numpy.random.randint(0, 6500, size=shape[0] * shape[1]) + .astype("uint16") + .reshape(shape) + ) fname = os.path.join(UtilsTest.tempdir, "bug27.edf") e = edfimage(data=data, header={"key1": "value1"}) e.write(fname) @@ -398,7 +514,9 @@ def test_bug_27(self): def test_remove_metadata_header(self): filename = UtilsTest.getimage("face.edf.bz2")[0:-4] - output_filename = os.path.join(UtilsTest.tempdir, "test_remove_metadata_header.edf") + output_filename = os.path.join( + UtilsTest.tempdir, "test_remove_metadata_header.edf" + ) image = fabio.open(filename) del image.header["Dim_1"] @@ -407,57 +525,58 @@ def test_remove_metadata_header(self): self.assertEqual(image.shape, image2.shape) def test_bug_459(self): - h = {'HeaderID': 'EH:000001:000000:000000', - 'Image': '1', - 'ByteOrder': 'LowByteFirst', - 'DataType': 'Float', - 'Dim_1': '3216', - 'Dim_2': '3000', - 'Size': '38592000', - 'Date': '30-Oct-2021', - 'Lcurve': '0', - 'Mh': '24.996', - 'Mv': '24.996', - 'angles_fname': 'angles_file.txt', - 'angles_sign': '-1', - 'ans': ';', - 'approach': '8', - 'argn': '[0,', - 'avg_plane_zero': '6.2031', - 'axisfilesduringscan': '1', - 'axisposition': 'global', - 'betash': '0.0179803', - 'betasv': '0.0179803', - 'calc_residu': '0', - 'centralpart': '2048', - 'centralpart_shift_h': '0', - 'centralpart_shift_v': '0', - 'centralpart_struct': ';', - 'check_spectrum': '0', - 'check_z1v': '0', - 'constrain': '0', - 'correct_detector': '3', - 'correct_distortion_par': ';', - 'correct_shrink': '0', - 'correct_shrink_positive': '0', - 'correct_whitefield_par': '17', - 'cutn': '0.0511098', - 'cylinder': '0', - 'debug': '0', - 'delta_beta': '2119.4', - 'delta_beta_normalised': '0', - 'delta_beta_range': '2119.4', - 'delta_beta_test': '0', - 'dim_h': '2048', - 'dim_v': '2048', - 'dir': '/data/visitor/ls3023/id16a/PR3/', - 'direc': ''} + h = { + "HeaderID": "EH:000001:000000:000000", + "Image": "1", + "ByteOrder": "LowByteFirst", + "DataType": "Float", + "Dim_1": "3216", + "Dim_2": "3000", + "Size": "38592000", + "Date": "30-Oct-2021", + "Lcurve": "0", + "Mh": "24.996", + "Mv": "24.996", + "angles_fname": "angles_file.txt", + "angles_sign": "-1", + "ans": ";", + "approach": "8", + "argn": "[0,", + "avg_plane_zero": "6.2031", + "axisfilesduringscan": "1", + "axisposition": "global", + "betash": "0.0179803", + "betasv": "0.0179803", + "calc_residu": "0", + "centralpart": "2048", + "centralpart_shift_h": "0", + "centralpart_shift_v": "0", + "centralpart_struct": ";", + "check_spectrum": "0", + "check_z1v": "0", + "constrain": "0", + "correct_detector": "3", + "correct_distortion_par": ";", + "correct_shrink": "0", + "correct_shrink_positive": "0", + "correct_whitefield_par": "17", + "cutn": "0.0511098", + "cylinder": "0", + "debug": "0", + "delta_beta": "2119.4", + "delta_beta_normalised": "0", + "delta_beta_range": "2119.4", + "delta_beta_test": "0", + "dim_h": "2048", + "dim_v": "2048", + "dir": "/data/visitor/ls3023/id16a/PR3/", + "direc": "", + } with LoggingValidator(fabio.edfimage.logger, error=0, warning=0): fabio.edfimage.EdfFrame.get_data_rank(h) - - -class TestBadFiles(unittest.TestCase): + +class TestBadFiles(unittest.TestCase): filename_template = "%s.edf" @classmethod @@ -519,7 +638,9 @@ def open(cls, filename): return image def test_base(self): - filename = os.path.join(self.tmp_directory, self.filename_template % str(self.id())) + filename = os.path.join( + self.tmp_directory, self.filename_template % str(self.id()) + ) size = self.data2 self.copy_base(filename, size) @@ -534,14 +655,18 @@ def test_base(self): self.assertEqual(frame.data[-1].sum(), 2560) def test_empty(self): - filename = os.path.join(self.tmp_directory, self.filename_template % str(self.id())) + filename = os.path.join( + self.tmp_directory, self.filename_template % str(self.id()) + ) f = io.open(filename, "wb") f.close() self.assertRaises(IOError, self.open, filename) def test_wrong_magic(self): - filename = os.path.join(self.tmp_directory, self.filename_template % str(self.id())) + filename = os.path.join( + self.tmp_directory, self.filename_template % str(self.id()) + ) f = io.open(filename, "wb") f.write(b"\x10\x20\x30") f.close() @@ -549,14 +674,18 @@ def test_wrong_magic(self): self.assertRaises(IOError, self.open, filename) def test_half_header(self): - filename = os.path.join(self.tmp_directory, self.filename_template % str(self.id())) + filename = os.path.join( + self.tmp_directory, self.filename_template % str(self.id()) + ) size = self.header1 // 2 self.copy_base(filename, size) self.assertRaises(IOError, self.open, filename) def test_header_with_no_data(self): - filename = os.path.join(self.tmp_directory, self.filename_template % str(self.id())) + filename = os.path.join( + self.tmp_directory, self.filename_template % str(self.id()) + ) size = self.header1 self.copy_base(filename, size) @@ -565,7 +694,9 @@ def test_header_with_no_data(self): self.assertTrue(image.incomplete_file) def test_header_with_half_data(self): - filename = os.path.join(self.tmp_directory, self.filename_template % str(self.id())) + filename = os.path.join( + self.tmp_directory, self.filename_template % str(self.id()) + ) size = (self.header1 + self.data1) // 2 self.copy_base(filename, size) @@ -579,7 +710,9 @@ def test_header_with_half_data(self): self.assertTrue(frame.incomplete_data) def test_full_frame_plus_half_header(self): - filename = os.path.join(self.tmp_directory, self.filename_template % str(self.id())) + filename = os.path.join( + self.tmp_directory, self.filename_template % str(self.id()) + ) size = (self.data1 + self.header2) // 2 self.copy_base(filename, size) @@ -593,7 +726,9 @@ def test_full_frame_plus_half_header(self): self.assertFalse(frame.incomplete_data) def test_full_frame_plus_header_with_no_data(self): - filename = os.path.join(self.tmp_directory, self.filename_template % str(self.id())) + filename = os.path.join( + self.tmp_directory, self.filename_template % str(self.id()) + ) size = self.header2 self.copy_base(filename, size) @@ -607,7 +742,9 @@ def test_full_frame_plus_header_with_no_data(self): self.assertFalse(frame.incomplete_data) def test_full_frame_plus_header_with_half_data(self): - filename = os.path.join(self.tmp_directory, self.filename_template % str(self.id())) + filename = os.path.join( + self.tmp_directory, self.filename_template % str(self.id()) + ) size = (self.header2 + self.data2) // 2 self.copy_base(filename, size) @@ -627,7 +764,6 @@ def test_full_frame_plus_header_with_half_data(self): class TestBadGzFiles(TestBadFiles): - filename_template = "%s.edf.gz" @classmethod @@ -649,10 +785,30 @@ def setUp(self): self.samples = UtilsTest.resources.getdir("sphere2saxs_output.tar.bz2") SAMPLES = { - "multi.edf": (5, (200, 100), numpy.float32, (6.292408e-05, 0.5594252, 3.2911296, 0.82902604)), - "multi.edf.gz": (5, (200, 100), numpy.float32, (6.292408e-05, 0.5594252, 3.2911296, 0.82902604)), - "sphere.edf": (1, (200, 100), numpy.float32, (6.292408e-05, 0.5594252, 3.2911296, 0.82902604)), - "sphere.edf.gz": (1, (200, 100), numpy.float32, (6.292408e-05, 0.5594252, 3.2911296, 0.82902604)), + "multi.edf": ( + 5, + (200, 100), + numpy.float32, + (6.292408e-05, 0.5594252, 3.2911296, 0.82902604), + ), + "multi.edf.gz": ( + 5, + (200, 100), + numpy.float32, + (6.292408e-05, 0.5594252, 3.2911296, 0.82902604), + ), + "sphere.edf": ( + 1, + (200, 100), + numpy.float32, + (6.292408e-05, 0.5594252, 3.2911296, 0.82902604), + ), + "sphere.edf.gz": ( + 1, + (200, 100), + numpy.float32, + (6.292408e-05, 0.5594252, 3.2911296, 0.82902604), + ), } def test_all_images(self): @@ -675,8 +831,7 @@ def test_all_images(self): class TestEdfIterator(unittest.TestCase): - """Read different EDF files with lazy iterator - """ + """Read different EDF files with lazy iterator""" def test_multi_frame(self): """Test iterator on a multi-frame EDF""" @@ -688,8 +843,14 @@ def test_multi_frame(self): for index in range(ref.nframes): frame = next(iterator) ref_frame = ref.getframe(index) - self.assertEqual(numpy.abs(ref_frame.data - frame.data).max(), 0, 'Test frame %d data' % index) - self.assertEqual(ref_frame.header, frame.header, 'Test frame %d header' % index) + self.assertEqual( + numpy.abs(ref_frame.data - frame.data).max(), + 0, + "Test frame %d data" % index, + ) + self.assertEqual( + ref_frame.header, frame.header, "Test frame %d header" % index + ) with self.assertRaises(StopIteration): next(iterator) @@ -708,7 +869,6 @@ def test_single_frame(self): next(iterator) - class TestEdfBadHeader(unittest.TestCase): """Test reader behavior with corrupted header file""" @@ -730,21 +890,21 @@ def setUp(self): while hdr.find(b"}") < 0: hdr += fh.read(512) data = fh.read() - with open( self.fbad, "wb") as fb: + with open(self.fbad, "wb") as fb: start = hdr.rfind(b";") + 1 end = hdr.find(b"}") - 1 - hdr[start:end] = [ord('\n')] + [0xcd] * (end - start - 1) + hdr[start:end] = [ord("\n")] + [0xCD] * (end - start - 1) fb.write(hdr) fb.write(data) - with open( self.fzero, "wb") as fb: + with open(self.fzero, "wb") as fb: # insert some 0x00 to be stripped key = b"myvalue" z = hdr.find(key) hdr[z + len(key)] = 0 fb.write(hdr) fb.write(data) - with open( self.fnonascii, "wb") as fb: - hdr[z:z + 1]= 0xc3, 0xa9 # e-acute in utf-8 ?? + with open(self.fnonascii, "wb") as fb: + hdr[z : z + 1] = 0xC3, 0xA9 # e-acute in utf-8 ?? with open(self.fnonascii, "wb") as fb: fb.write(hdr) fb.write(data) @@ -781,6 +941,7 @@ def testNonAsciiHeader(self): expected.pop("mykey") self.assertEqual(im.header, expected) + def suite(): loadTests = unittest.defaultTestLoader.loadTestsFromTestCase testsuite = unittest.TestSuite() @@ -801,6 +962,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/codecs/test_edfimage_expg.py b/src/fabio/test/codecs/test_edfimage_expg.py index 8099674b..0d56e9a2 100644 --- a/src/fabio/test/codecs/test_edfimage_expg.py +++ b/src/fabio/test/codecs/test_edfimage_expg.py @@ -34,12 +34,11 @@ import os import numpy import logging - -logger = logging.getLogger(__name__) - import fabio from ..utilstest import UtilsTest +logger = logging.getLogger(__name__) + # logging.basicConfig(level=logging.DEBUG) # logging.basicConfig(level=logging.WARNING) # logging.basicConfig(level=logging.INFO) @@ -66,11 +65,19 @@ def open_frame(filename, frameno): nerrorframes = 0 if frameno >= npsdframes: - logging.warning("Psd frame {} out of range: 0 <= {} < {}".format(frameno, frameno, npsdframes)) + logging.warning( + "Psd frame {} out of range: 0 <= {} < {}".format( + frameno, frameno, npsdframes + ) + ) if frameno < 0: if -frameno > nerrorframes: - logging.warning("Error frame {} out of range: {} <= {} < 0 ".format(frameno, -nerrorframes, frameno)) + logging.warning( + "Error frame {} out of range: {} <= {} < 0 ".format( + frameno, -nerrorframes, frameno + ) + ) frameno += nframes frame = None @@ -85,23 +92,26 @@ def open_frame(filename, frameno): header = image.header frame = fabio.fabioimage.FabioFrame(data=data, header=header) else: - raise IOError("fopen: Cannot access frame: {} (0<=frame<{})".format(frameno, nframes)) + raise IOError( + "fopen: Cannot access frame: {} (0<=frame<{})".format(frameno, nframes) + ) return frame def get_data_counts(shape=None): - ''' + """ Counts all items specified by shape - ''' + """ if shape is None: shape = () counts = 1 for ishape in range(0, len(shape)): counts *= shape[ishape] - return(counts) + return counts + -#============================================ +# ============================================ # Hint: Must be defined outside the test case class, otherwise used as test @@ -134,14 +144,23 @@ def test_00(cls, filename, avglist=None, keylist=None): counts = get_data_counts(frame.shape) data_counts = get_data_counts(frame.data.shape) - cls.assertEqual(counts, data_counts, "A:filename={},frameno={}: inconsistent data shapes: header.{},data.{}". - format(filename, frameno, frame.shape, frame.data.shape)) + cls.assertEqual( + counts, + data_counts, + "A:filename={},frameno={}: inconsistent data shapes: header.{},data.{}".format( + filename, frameno, frame.shape, frame.data.shape + ), + ) # calculate mean value fsum = numpy.sum(frame.data) fmean = fsum / counts - logging.debug("filename={},frameno={},sum={},counts={},fmean={}".format(filename, frameno, fsum, counts, fmean)) + logging.debug( + "filename={},frameno={},sum={},counts={},fmean={}".format( + filename, frameno, fsum, counts, fmean + ) + ) # read known mean value from avglist if avglist is not None: @@ -149,8 +168,13 @@ def test_00(cls, filename, avglist=None, keylist=None): avg = avglist[frameno] else: avg = avglist[-1] - cls.assertLessEqual(abs(fmean - avg), abs(fmean + avg) * 5e-6, "B:filename={},frameno={}: unexpected average value: calculated {}, expected {}". - format(filename, frameno, fmean, avg)) + cls.assertLessEqual( + abs(fmean - avg), + abs(fmean + avg) * 5e-6, + "B:filename={},frameno={}: unexpected average value: calculated {}, expected {}".format( + filename, frameno, fmean, avg + ), + ) # read a key to read from keylist if keylist is not None: @@ -160,11 +184,25 @@ def test_00(cls, filename, avglist=None, keylist=None): key = keylist[-1] if key in frame.header: - logging.debug("filename={}, frameno={}: '{}' = {}".format(filename, frameno, key, frame.header[key])) + logging.debug( + "filename={}, frameno={}: '{}' = {}".format( + filename, frameno, key, frame.header[key] + ) + ) else: - logging.debug("filename={}, frameno={}: '{}' = None".format(filename, frameno, key)) - - cls.assertIn(key, frame.header, "C:filename={},frameno={}: Missing expected header key '{}'".format(filename, frameno, key)) + logging.debug( + "filename={}, frameno={}: '{}' = None".format( + filename, frameno, key + ) + ) + + cls.assertIn( + key, + frame.header, + "C:filename={},frameno={}: Missing expected header key '{}'".format( + filename, frameno, key + ), + ) # error data for frameno in range(0, nerrorframes): @@ -174,14 +212,23 @@ def test_00(cls, filename, avglist=None, keylist=None): counts = get_data_counts(frame.shape) data_counts = get_data_counts(frame.data.shape) - cls.assertEqual(counts, data_counts, "D:filename={},frameno={}: inconsistent data shapes: header.{},data.{}". - format(filename, frameno, frame.shape, frame.data.shape)) + cls.assertEqual( + counts, + data_counts, + "D:filename={},frameno={}: inconsistent data shapes: header.{},data.{}".format( + filename, frameno, frame.shape, frame.data.shape + ), + ) # calculate mean value fsum = numpy.sum(frame.data) fmean = sum / counts - logging.debug("filename={},frameno={},sum={},counts={},fmean={}".format(filename, frameno, fsum, counts, fmean)) + logging.debug( + "filename={},frameno={},sum={},counts={},fmean={}".format( + filename, frameno, fsum, counts, fmean + ) + ) # read known mean value from avglist if avglist is not None: @@ -190,9 +237,13 @@ def test_00(cls, filename, avglist=None, keylist=None): avg = avglist[nframes - frameno - 1] else: avg = avglist[-1] - cls.assertLessEqual(abs(fmean - avg), abs(fmean + avg) * 5e-6, - "E:filename={},frameno={}: unexpected average value: calculated {}, expected {}". - format(filename, frameno, fmean, avg)) + cls.assertLessEqual( + abs(fmean - avg), + abs(fmean + avg) * 5e-6, + "E:filename={},frameno={}: unexpected average value: calculated {}, expected {}".format( + filename, frameno, fmean, avg + ), + ) # read a key to read from keylist if keylist is not None: @@ -202,11 +253,28 @@ def test_00(cls, filename, avglist=None, keylist=None): key = keylist[-1] if key in frame.header: - logging.debug("filename={},frameno={}: key='{}'".format(filename, frameno, key, frame.header[key])) + logging.debug( + "filename={},frameno={}: key='{}'".format( + filename, + frameno, + key, + ) + ) else: - logging.debug("filename={},frameno={}: key=None".format(filename, frameno, key)) - - cls.assertIn(key, frame.header, "F:filename={},frameno={}: Missing expected header key '{}'".format(filename, frameno, key)) + logging.debug( + "filename={},frameno={}: key=None".format( + filename, + frameno, + ) + ) + + cls.assertIn( + key, + frame.header, + "F:filename={},frameno={}: Missing expected header key '{}'".format( + filename, frameno, key + ), + ) class EdfBlockBoundaryCases(unittest.TestCase): @@ -231,7 +299,7 @@ def test_edfblocktypes(self): multi5+gblk_gzip.edf : like multi5+gblk.edf.gz, but with internal compression """ - avglist = [0., 1., 2., 3., 4.] + avglist = [0.0, 1.0, 2.0, 3.0, 4.0] filename = os.path.join(self.root, "00_edfblocktypes/multi5.edf.gz") test_00(self, filename, avglist) keylist = ["DefaultKey"] # the defaultkey is coming from the general block @@ -251,7 +319,9 @@ def test_edfsingle_raw_bf_gblk(self): """ avglist = [25743.2] - filename = os.path.join(self.root, "01_single_raw_bf_gblk/pj19_frelon_00028_raw.ehf") + filename = os.path.join( + self.root, "01_single_raw_bf_gblk/pj19_frelon_00028_raw.ehf" + ) keylist = ["ExperimentInfo"] test_00(self, filename, avglist, keylist) @@ -267,11 +337,31 @@ def test_edfmulti_raw_bf_gblk(self): frame 18: Test reading with EDF_BinaryFileSize bigger than required => currently an unnecessary info is given """ - avglist = [9584.23, 9592.64, 9591.69, 9599.7, 9602.51, 9604.29, - 9610.97, 9609.86, 9614.14, 9610.52, 9603.12, 9603.27, - 9600.22, 9606.86, 9605.26, 9601.37, 9606.09, 9604.51, - 9604.45, 9617.5] - filename = os.path.join(self.root, "02_multi_raw_bf_gblk/rh28a_saxs_00022_raw_binned.ehf") + avglist = [ + 9584.23, + 9592.64, + 9591.69, + 9599.7, + 9602.51, + 9604.29, + 9610.97, + 9609.86, + 9614.14, + 9610.52, + 9603.12, + 9603.27, + 9600.22, + 9606.86, + 9605.26, + 9601.37, + 9606.09, + 9604.51, + 9604.45, + 9617.5, + ] + filename = os.path.join( + self.root, "02_multi_raw_bf_gblk/rh28a_saxs_00022_raw_binned.ehf" + ) test_00(self, filename, avglist) def test_edfmulti_raw_dark_raw_bf_gblk(self): @@ -288,10 +378,14 @@ def test_edfmulti_raw_dark_raw_bf_gblk(self): => unnecessary, feature, e.g. for saving byte arrays of odd length """ avglist = [2487.36, 2488.28, 2488.11] - filename = os.path.join(self.root, "03_multi_raw_dark_bf_gblk/rh28a_saxs_00003_dark_binned.ehf") + filename = os.path.join( + self.root, "03_multi_raw_dark_bf_gblk/rh28a_saxs_00003_dark_binned.ehf" + ) test_00(self, filename, avglist) avglist = [9651.25] - filename = os.path.join(self.root, "03_multi_raw_dark_bf_gblk/rh28a_saxs_00003_raw_binned.ehf") + filename = os.path.join( + self.root, "03_multi_raw_dark_bf_gblk/rh28a_saxs_00003_raw_binned.ehf" + ) test_00(self, filename, avglist) def test_edfsingle_raw_bf_gblk_gz(self): @@ -302,7 +396,9 @@ def test_edfsingle_raw_bf_gblk_gz(self): header. Both files are gzipped. """ avglist = [25743.2] - filename = os.path.join(self.root, "04_single_raw_bf_gblk_gz/pj19_frelon_00028_raw.ehf.gz") + filename = os.path.join( + self.root, "04_single_raw_bf_gblk_gz/pj19_frelon_00028_raw.ehf.gz" + ) test_00(self, filename, avglist) def test_edf6_single_raw_bf_gblk_gz(self): @@ -313,7 +409,9 @@ def test_edf6_single_raw_bf_gblk_gz(self): header. Only the binary data file is gzipped. """ avglist = [25743.2] - filename = os.path.join(self.root, "06_single_raw_bf_gblk_gz/pj19_frelon_00028_raw.ehf") + filename = os.path.join( + self.root, "06_single_raw_bf_gblk_gz/pj19_frelon_00028_raw.ehf" + ) test_00(self, filename, avglist) def test_edfmulti_raw_bf_gblk_gz(self): @@ -332,11 +430,31 @@ def test_edfmulti_raw_bf_gblk_gz(self): the real file size. => data must only be read to the end of the binary data file. """ - avglist = [9584.23, 9592.64, 9591.69, 9599.7, 9602.51, 9604.29, - 9610.97, 9609.86, 9614.14, 9610.52, 9603.12, 9603.27, - 9600.22, 9606.86, 9605.26, 9601.37, 9606.09, 9604.51, - 9604.45, 9617.5] - filename = os.path.join(self.root, "07_multi_raw_bf_gblk_gz/rh28a_saxs_00022_raw_binned.ehf.gz") + avglist = [ + 9584.23, + 9592.64, + 9591.69, + 9599.7, + 9602.51, + 9604.29, + 9610.97, + 9609.86, + 9614.14, + 9610.52, + 9603.12, + 9603.27, + 9600.22, + 9606.86, + 9605.26, + 9601.37, + 9606.09, + 9604.51, + 9604.45, + 9617.5, + ] + filename = os.path.join( + self.root, "07_multi_raw_bf_gblk_gz/rh28a_saxs_00022_raw_binned.ehf.gz" + ) test_00(self, filename, avglist) def test_pitfalls(self): @@ -361,7 +479,9 @@ def test_pitfalls(self): # edf1 file with header end pattern in Title value and binary blob # => must always work - filename = os.path.join(self.root, "08_pitfalls/multi5+headerblob+headerendinheader_edf1.edf.gz") + filename = os.path.join( + self.root, "08_pitfalls/multi5+headerblob+headerendinheader_edf1.edf.gz" + ) test_00(self, filename, avglist) # edf0 file => must always work @@ -413,13 +533,19 @@ def test_special(self): # patterns are not recognized. avglist = [0.178897] # header end character '}' at BLOCKSIZE-1 followed by '\n' at BLOCKSIZE - filename = os.path.join(self.root, "09_special/face_headerendatboundary1.edf.gz") + filename = os.path.join( + self.root, "09_special/face_headerendatboundary1.edf.gz" + ) test_00(self, filename, avglist) # first part of header end pattern '}\r' at BLOCKSIZE-2 followed by '\n' at BLOCKSIZE - filename = os.path.join(self.root, "09_special/face_headerendatboundary2.edf.gz") + filename = os.path.join( + self.root, "09_special/face_headerendatboundary2.edf.gz" + ) test_00(self, filename, avglist) # header end character '}' at BLOCKSIZE-1 followed by '\n' at BLOCKSIZE - filename = os.path.join(self.root, "09_special/face_headerendatboundary3.edf.gz") + filename = os.path.join( + self.root, "09_special/face_headerendatboundary3.edf.gz" + ) test_00(self, filename, avglist) # Here, the binary blob is too short @@ -433,6 +559,7 @@ def test_special(self): # test files 10 and 11 for testing with to be copied and added later + def suite(): loadTests = unittest.defaultTestLoader.loadTestsFromTestCase testsuite = unittest.TestSuite() @@ -440,7 +567,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) - diff --git a/src/fabio/test/codecs/test_eigerimage.py b/src/fabio/test/codecs/test_eigerimage.py index f4a63f57..0f7096d7 100644 --- a/src/fabio/test/codecs/test_eigerimage.py +++ b/src/fabio/test/codecs/test_eigerimage.py @@ -26,20 +26,19 @@ # THE SOFTWARE. # -"""Test Eiger images -""" +"""Test Eiger images""" import unittest import os import logging - -logger = logging.getLogger(__name__) import numpy from fabio.openimage import openimage from fabio.eigerimage import EigerImage, h5py from ..utilstest import UtilsTest from ..test_frames import _CommonTestFrames +logger = logging.getLogger(__name__) + def make_hdf5(name, shape=(50, 99, 101)): if h5py is None: @@ -48,9 +47,18 @@ def make_hdf5(name, shape=(50, 99, 101)): with h5py.File(name, mode="w") as h: e = h.require_group("entry/data") if len(shape) == 2: - e.require_dataset("data", shape, compression="gzip", compression_opts=9, dtype="float32") + e.require_dataset( + "data", shape, compression="gzip", compression_opts=9, dtype="float32" + ) elif len(shape) == 3: - e.require_dataset("data", shape, chunks=(1,) + shape[1:], compression="gzip", compression_opts=9, dtype="float32") + e.require_dataset( + "data", + shape, + chunks=(1,) + shape[1:], + compression="gzip", + compression_opts=9, + dtype="float32", + ) class TestEiger(_CommonTestFrames): @@ -82,7 +90,7 @@ def tearDownClass(cls): os.unlink(cls.fn3) def test_read(self): - """ check we can read images from Eiger""" + """check we can read images from Eiger""" e = EigerImage() e.read(self.fn3) self.assertEqual(e.shape, (99, 101)) @@ -90,7 +98,7 @@ def test_read(self): self.assertEqual(e.bpp, 4, "bpp OK") def test_open(self): - """ check we can read images from Eiger""" + """check we can read images from Eiger""" e = openimage(self.fn3) self.assertEqual(e.shape, (99, 101)) self.assertEqual(e.nframes, 50, "nframe: got %s!=50" % e.nframes) @@ -108,7 +116,9 @@ def test_write(self): e.save(fn) self.assertTrue(os.path.exists(fn), "file exists") f = openimage(fn) - self.assertEqual(str(f.__class__.__name__), "EigerImage", "Used the write reader") + self.assertEqual( + str(f.__class__.__name__), "EigerImage", "Used the write reader" + ) self.assertEqual(shape[0], f.nframes, "shape matches") self.assertEqual(shape[1:], f.shape, "shape matches") self.assertEqual(abs(f.data - ary[0]).max(), 0, "first frame matches") @@ -133,6 +143,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/codecs/test_esperantoimage.py b/src/fabio/test/codecs/test_esperantoimage.py index b86c9492..3e0bb268 100644 --- a/src/fabio/test/codecs/test_esperantoimage.py +++ b/src/fabio/test/codecs/test_esperantoimage.py @@ -26,24 +26,39 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE.# -"""Test Esperanto images -""" +"""Test Esperanto images""" import os import fabio.esperantoimage from ..utilstest import UtilsTest - import unittest import logging import numpy + logger = logging.getLogger(__name__) class TestEsperanto(unittest.TestCase): # filename dim1 dim2 min max mean stddev TESTIMAGES = [ - ("sucrose_1s__1_1.esperanto.bz2", 2048, 2048, -173, 66043, 16.31592893600464, 266.4471326013064), # To validate - ("reference.esperanto.bz2", 256, 256, -1, 10963, 1.767120361328125, 50.87154169213312) + ( + "sucrose_1s__1_1.esperanto.bz2", + 2048, + 2048, + -173, + 66043, + 16.31592893600464, + 266.4471326013064, + ), # To validate + ( + "reference.esperanto.bz2", + 256, + 256, + -1, + 10963, + 1.767120361328125, + 50.87154169213312, + ), ] def test_read(self): @@ -59,10 +74,21 @@ def test_read(self): obj = fabio.esperantoimage.EsperantoImage() obj.read(UtilsTest.getimage(name)) - self.assertAlmostEqual(mini, obj.getmin(), 2, "getmin [%s,%s]" % (mini, obj.getmin())) - self.assertAlmostEqual(maxi, obj.getmax(), 2, "getmax [%s,%s]" % (maxi, obj.getmax())) - self.assertAlmostEqual(mean, obj.getmean(), 2, "getmean [%s,%s]" % (mean, obj.getmean())) - self.assertAlmostEqual(stddev, obj.getstddev(), 2, "getstddev [%s,%s]" % (stddev, obj.getstddev())) + self.assertAlmostEqual( + mini, obj.getmin(), 2, "getmin [%s,%s]" % (mini, obj.getmin()) + ) + self.assertAlmostEqual( + maxi, obj.getmax(), 2, "getmax [%s,%s]" % (maxi, obj.getmax()) + ) + self.assertAlmostEqual( + mean, obj.getmean(), 2, "getmean [%s,%s]" % (mean, obj.getmean()) + ) + self.assertAlmostEqual( + stddev, + obj.getstddev(), + 2, + "getstddev [%s,%s]" % (stddev, obj.getstddev()), + ) self.assertEqual(shape, obj.shape, "dim1") @@ -73,27 +99,30 @@ def test_header(self): obj = fabio.esperantoimage.EsperantoImage() obj.read(UtilsTest.getimage(name)) - expected_keys = set([ - 'IMAGE', - 'SPECIAL_CCD_1', - 'SPECIAL_CCD_2', - 'SPECIAL_CCD_3', - 'SPECIAL_CCD_4', - 'SPECIAL_CCD_5', - 'TIME', - 'MONITOR', - 'PIXELSIZE', - 'TIMESTAMP', - 'GRIDPATTERN', - 'STARTANGLESINDEG', - 'ENDANGLESINDEG', - 'GONIOMODEL_1', - 'GONIOMODEL_2', - 'WAVELENGTH', - 'MONOCHROMATOR', - 'ABSTORUN', - 'HISTORY', - 'ESPERANTO FORMAT']) + expected_keys = set( + [ + "IMAGE", + "SPECIAL_CCD_1", + "SPECIAL_CCD_2", + "SPECIAL_CCD_3", + "SPECIAL_CCD_4", + "SPECIAL_CCD_5", + "TIME", + "MONITOR", + "PIXELSIZE", + "TIMESTAMP", + "GRIDPATTERN", + "STARTANGLESINDEG", + "ENDANGLESINDEG", + "GONIOMODEL_1", + "GONIOMODEL_2", + "WAVELENGTH", + "MONOCHROMATOR", + "ABSTORUN", + "HISTORY", + "ESPERANTO FORMAT", + ] + ) upper_keys = set(i for i in obj.header.keys() if i.isupper()) self.assertEqual(upper_keys, expected_keys) @@ -107,9 +136,12 @@ def test_header(self): new = fabio.open(dst) self.assertTrue(numpy.allclose(obj.data, new.data), msg="data are the same") for k, v in obj.header.items(): - if k not in ("ESPERANTO FORMAT", - ): - self.assertEqual(v, new.header.get(k), "header differ on %s: %s vs %s" % (k, v, new.header.get(k))) + if k not in ("ESPERANTO FORMAT",): + self.assertEqual( + v, + new.header.get(k), + "header differ on %s: %s vs %s" % (k, v, new.header.get(k)), + ) # Test write compressed: obj.format = "AGI_BITFIELD" @@ -120,9 +152,12 @@ def test_header(self): new = fabio.open(dst) self.assertTrue(numpy.allclose(obj.data, new.data), msg="data are the same") for k, v in obj.header.items(): - if k not in ("ESPERANTO FORMAT", - ): - self.assertEqual(v, new.header.get(k), "header differ on %s: %s vs %s" % (k, v, new.header.get(k))) + if k not in ("ESPERANTO FORMAT",): + self.assertEqual( + v, + new.header.get(k), + "header differ on %s: %s vs %s" % (k, v, new.header.get(k)), + ) def test_data(self): a = (numpy.random.random((257, 421)) * 100).round() @@ -144,6 +179,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/codecs/test_fit2dimage.py b/src/fabio/test/codecs/test_fit2dimage.py index 2de0197f..9ec56cc3 100644 --- a/src/fabio/test/codecs/test_fit2dimage.py +++ b/src/fabio/test/codecs/test_fit2dimage.py @@ -24,28 +24,26 @@ # THE SOFTWARE. # -"""Test for FabIO reader for Fit2D binary images -""" +"""Test for FabIO reader for Fit2D binary images""" __authors__ = ["Jérôme Kieffer"] __contact__ = "jerome.kiefer@esrf.fr" __license__ = "MIT" __copyright__ = "2016-2020 European Synchrotron Radiation Facility" -__date__ = "03/04/2020" +__date__ = "27/10/2025" import unittest import numpy import logging - -logger = logging.getLogger(__name__) - import fabio from fabio.fit2dimage import fit2dimage from ..utilstest import UtilsTest +logger = logging.getLogger(__name__) + class TestFit2DImage(unittest.TestCase): - """ A few random clicks to make a test mask """ + """A few random clicks to make a test mask""" def setUp(self): """ @@ -55,7 +53,7 @@ def setUp(self): self.tiffilename = UtilsTest.getimage("fit2d.tif.bz2")[:-4] def test_read(self): - """ Check it reads a mask OK """ + """Check it reads a mask OK""" i = fit2dimage() i.read(self.filename) self.assertEqual(i.shape, (28, 25)) @@ -64,7 +62,7 @@ def test_read(self): self.assertEqual(i.data.shape, (28, 25)) def test_match(self): - """ test edf and msk are the same """ + """test edf and msk are the same""" i = fabio.open(self.filename) j = fabio.open(self.tiffilename) i.read(self.filename) @@ -90,6 +88,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/codecs/test_fit2dmaskimage.py b/src/fabio/test/codecs/test_fit2dmaskimage.py index 3ded7ac0..d9e7693a 100644 --- a/src/fabio/test/codecs/test_fit2dmaskimage.py +++ b/src/fabio/test/codecs/test_fit2dmaskimage.py @@ -26,7 +26,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # -""" Test the fit2d mask reader +"""Test the fit2d mask reader Updated by Jerome Kieffer (jerome.kieffer@esrf.eu), 2011 28/11/2014 @@ -36,16 +36,15 @@ import os import numpy import logging - -logger = logging.getLogger(__name__) - import fabio from fabio.fit2dmaskimage import fit2dmaskimage from ..utilstest import UtilsTest +logger = logging.getLogger(__name__) + class TestFaceMask(unittest.TestCase): - """ test the picture of a face """ + """test the picture of a face""" def setUp(self): """ @@ -55,7 +54,7 @@ def setUp(self): self.edffilename = UtilsTest.getimage("face.edf.bz2")[:-4] def test_getmatch(self): - """ test edf and msk are the same """ + """test edf and msk are the same""" i = fit2dmaskimage() i.read(self.filename) j = fabio.open(self.edffilename) @@ -67,7 +66,7 @@ def test_getmatch(self): class TestClickedMask(unittest.TestCase): - """ A few random clicks to make a test mask """ + """A few random clicks to make a test mask""" def setUp(self): """ @@ -77,7 +76,7 @@ def setUp(self): self.edffilename = UtilsTest.getimage("fit2d_click.edf.bz2")[:-4] def test_read(self): - """ Check it reads a mask OK """ + """Check it reads a mask OK""" i = fit2dmaskimage() i.read(self.filename) self.assertEqual(i.shape, (1024, 1024)) @@ -86,7 +85,7 @@ def test_read(self): self.assertEqual(i.data.shape, (1024, 1024)) def test_getmatch(self): - """ test edf and msk are the same """ + """test edf and msk are the same""" i = fit2dmaskimage() j = fabio.open(self.edffilename) i.read(self.filename) @@ -106,7 +105,7 @@ class TestMskWrite(unittest.TestCase): def setUp(self): shape = (199, 211) # those are prime numbers - self.data = (numpy.random.random(shape) > 0.6) + self.data = numpy.random.random(shape) > 0.6 self.header = fit2dmaskimage.check_header() def atest(self): @@ -152,6 +151,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/codecs/test_fit2dspreadsheetimage.py b/src/fabio/test/codecs/test_fit2dspreadsheetimage.py index 3f51df61..579537e9 100644 --- a/src/fabio/test/codecs/test_fit2dspreadsheetimage.py +++ b/src/fabio/test/codecs/test_fit2dspreadsheetimage.py @@ -34,20 +34,19 @@ import unittest import os import logging - from ..utilstest import UtilsTest - -logger = logging.getLogger(__name__) - from fabio.fit2dspreadsheetimage import Fit2dSpreadsheetImage from fabio.utils import testutils +logger = logging.getLogger(__name__) + # statistics come from fit2d I think # filename dim1 dim2 min max mean stddev -TESTIMAGES = [("example.spr", (512, 512), 86.0, 61204.0, 511.63, 667.148), - ("example.spr.gz", (512, 512), 86.0, 61204.0, 511.63, 667.148), - ("example.spr.bz2", (512, 512), 86.0, 61204.0, 511.63, 667.148), - ] +TESTIMAGES = [ + ("example.spr", (512, 512), 86.0, 61204.0, 511.63, 667.148), + ("example.spr.gz", (512, 512), 86.0, 61204.0, 511.63, 667.148), + ("example.spr.bz2", (512, 512), 86.0, 61204.0, 511.63, 667.148), +] class TestRealSamples(testutils.ParametricTestCase): @@ -73,7 +72,7 @@ def setUpClass(cls): cls.im_dir = UtilsTest.resources.data_home def test_read(self): - """ check we can read flat ADSC images""" + """check we can read flat ADSC images""" for datainfo in TESTIMAGES: with self.subTest(datainfo=datainfo): name, shape, mini, maxi, mean, stddev = datainfo @@ -94,6 +93,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/codecs/test_geimage.py b/src/fabio/test/codecs/test_geimage.py index 3622240a..f5652be4 100644 --- a/src/fabio/test/codecs/test_geimage.py +++ b/src/fabio/test/codecs/test_geimage.py @@ -36,24 +36,41 @@ import unittest import os import logging - from ..utilstest import UtilsTest +from fabio.GEimage import GEimage logger = logging.getLogger(__name__) -from fabio.GEimage import GEimage - class TestGE(unittest.TestCase): - # filename dim1 dim2 min max mean stddev TESTIMAGES = [ - ("GE_aSI_detector_image_1529.bz2", (2048, 2048), (1515, 16353, 1833.0311, 56.9124)), - ("GE_aSI_detector_image_1529.gz", (2048, 2048), (1515, 16353, 1833.0311, 56.9124)), + ( + "GE_aSI_detector_image_1529.bz2", + (2048, 2048), + (1515, 16353, 1833.0311, 56.9124), + ), + ( + "GE_aSI_detector_image_1529.gz", + (2048, 2048), + (1515, 16353, 1833.0311, 56.9124), + ), ("GE_aSI_detector_image_1529", (2048, 2048), (1515, 16353, 1833.0311, 56.9124)), - ("GE_image_1frame_intact_header.ge", (2048, 2048), (1515, 16353, 2209.1113, 437.6377)), - ("GE_image_1frame_blanked_header.ge", (2048, 2048), (1300, 16349, 1886.41111, 117.0603)), - ("dark_before_000428.edf.ge5", (2048, 2048), (1300, 16349, 1833.87892, 84.5265)), + ( + "GE_image_1frame_intact_header.ge", + (2048, 2048), + (1515, 16353, 2209.1113, 437.6377), + ), + ( + "GE_image_1frame_blanked_header.ge", + (2048, 2048), + (1300, 16349, 1886.41111, 117.0603), + ), + ( + "dark_before_000428.edf.ge5", + (2048, 2048), + (1300, 16349, 1833.87892, 84.5265), + ), ] def setUp(self): @@ -87,6 +104,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/codecs/test_hdf5image.py b/src/fabio/test/codecs/test_hdf5image.py index 0ee31a74..20ce02d7 100644 --- a/src/fabio/test/codecs/test_hdf5image.py +++ b/src/fabio/test/codecs/test_hdf5image.py @@ -26,20 +26,18 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE.# -"""Test Eiger images -""" +"""Test Eiger images""" import unittest import os import logging - -logger = logging.getLogger(__name__) - from fabio.fabioutils import exists from fabio.openimage import openimage from fabio.hdf5image import Hdf5Image, h5py from ..utilstest import UtilsTest +logger = logging.getLogger(__name__) + def make_hdf5(name, shape=(50, 99, 101)): if h5py is None: @@ -48,9 +46,18 @@ def make_hdf5(name, shape=(50, 99, 101)): with h5py.File(name, mode="w") as h: e = h.require_group("entry") if len(shape) == 2: - e.require_dataset("data", shape, compression="gzip", compression_opts=9, dtype="float32") + e.require_dataset( + "data", shape, compression="gzip", compression_opts=9, dtype="float32" + ) elif len(shape) == 3: - e.require_dataset("data", shape, chunks=(1,) + shape[1:], compression="gzip", compression_opts=9, dtype="float32") + e.require_dataset( + "data", + shape, + chunks=(1,) + shape[1:], + compression="gzip", + compression_opts=9, + dtype="float32", + ) return name + "::entry/data" @@ -74,7 +81,7 @@ def tearDownClass(cls): os.unlink(cls.fn2.split("::")[0]) def test_read(self): - """ check we can read images from Eiger""" + """check we can read images from Eiger""" e = Hdf5Image() e.read(self.fn2) self.assertEqual(e.shape, (99, 101)) @@ -88,7 +95,7 @@ def test_read(self): self.assertEqual(e.bpp, 4, "nframes OK") def test_open(self): - """ check we can read images from Eiger""" + """check we can read images from Eiger""" e = openimage(self.fn2) self.assertEqual(e.shape, (99, 101)) self.assertEqual(e.nframes, 1, "nframes OK") @@ -100,7 +107,7 @@ def test_open(self): self.assertEqual(e.bpp, 4, "nframes OK") def test_next_frames(self): - """ check the legacy next API""" + """check the legacy next API""" h5 = openimage(self.fn3) frame_nb = 1 frame_id = 0 @@ -128,6 +135,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/codecs/test_hipicimage.py b/src/fabio/test/codecs/test_hipicimage.py index aa1d81a0..f85bf13b 100644 --- a/src/fabio/test/codecs/test_hipicimage.py +++ b/src/fabio/test/codecs/test_hipicimage.py @@ -31,35 +31,58 @@ Image provided by Henning O. Sørensen """ -__date__ = "09/12/2022" +__date__ = "27/10/2025" __author__ = "Jérôme Kieffer" import unittest import os import logging - -logger = logging.getLogger(__name__) - import fabio from fabio.HiPiCimage import HipicImage from ..utilstest import UtilsTest +logger = logging.getLogger(__name__) + # filename dim1 dim2 min max mean stddev -TESTIMAGES = [("Radiograph_SP8_BL47XU_HiPicimage.img", (2048, 2048), 2029, 46937, 22125.5196352005, 9089.959313208194), - ("Radiograph_SP8_BL47XU_HiPicimage.img.gz", (2048, 2048), 2029, 46937, 22125.5196352005, 9089.959313208194), - ("Radiograph_SP8_BL47XU_HiPicimage.img.bz2", (2048, 2048), 2029, 46937, 22125.5196352005, 9089.959313208194)] +TESTIMAGES = [ + ( + "Radiograph_SP8_BL47XU_HiPicimage.img", + (2048, 2048), + 2029, + 46937, + 22125.5196352005, + 9089.959313208194, + ), + ( + "Radiograph_SP8_BL47XU_HiPicimage.img.gz", + (2048, 2048), + 2029, + 46937, + 22125.5196352005, + 9089.959313208194, + ), + ( + "Radiograph_SP8_BL47XU_HiPicimage.img.bz2", + (2048, 2048), + 2029, + 46937, + 22125.5196352005, + 9089.959313208194, + ), +] class TestHiPiCImage(unittest.TestCase): - """ - """ + """ """ def setUp(self): - """ Download images """ - self.im_dir = os.path.dirname(UtilsTest.getimage("Radiograph_SP8_BL47XU_HiPicimage.img.bz2")) + """Download images""" + self.im_dir = os.path.dirname( + UtilsTest.getimage("Radiograph_SP8_BL47XU_HiPicimage.img.bz2") + ) def test_read(self): - """ check we can read dm3 images""" + """check we can read dm3 images""" for info in TESTIMAGES: name, shape, mini, maxi, mean, stddev = info fname = os.path.join(self.im_dir, name) @@ -70,7 +93,9 @@ def test_read(self): self.assertAlmostEqual(mini, obj.getmin(), 2, "getmin") self.assertAlmostEqual(maxi, obj.getmax(), 2, "getmax") got_mean = obj.getmean() - self.assertAlmostEqual(mean, got_mean, 2, "getmean exp %s != got %s" % (mean, got_mean)) + self.assertAlmostEqual( + mean, got_mean, 2, "getmean exp %s != got %s" % (mean, got_mean) + ) self.assertAlmostEqual(stddev, obj.getstddev(), 2, "getstddev") self.assertEqual(shape, obj.shape) @@ -82,6 +107,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/codecs/test_jpeg2kimage.py b/src/fabio/test/codecs/test_jpeg2kimage.py index 89553ad0..572d7f79 100644 --- a/src/fabio/test/codecs/test_jpeg2kimage.py +++ b/src/fabio/test/codecs/test_jpeg2kimage.py @@ -33,17 +33,17 @@ import unittest import numpy import logging + try: from PIL import Image except ImportError: Image = None - -logger = logging.getLogger(__name__) - import fabio from ... import jpeg2kimage from ..utilstest import UtilsTest +logger = logging.getLogger(__name__) + def isPilUsable(): print("jpeg2kimage.PIL:", jpeg2kimage.PIL) @@ -54,7 +54,7 @@ def isPilUsable(): frombytes = jpeg2kimage.PIL.Image.frombytes else: frombytes = jpeg2kimage.PIL.Image.frombuffer - frombytes("1", (2, 2), b"", decoder_name='jpeg2k') + frombytes("1", (2, 2), b"", decoder_name="jpeg2k") except Exception as e: if e.args[0] == "decoder jpeg2k not available": return False @@ -66,6 +66,7 @@ def isGlymurUsable(): if jpeg2kimage.glymur is None: return None import glymur + if tuple(glymur.version.openjpeg_version_tuple) < (1, 5, 0): return False return True @@ -178,6 +179,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/codecs/test_jpegimage.py b/src/fabio/test/codecs/test_jpegimage.py index 1c4bd8cd..b9fa6180 100644 --- a/src/fabio/test/codecs/test_jpegimage.py +++ b/src/fabio/test/codecs/test_jpegimage.py @@ -34,13 +34,12 @@ import os import shutil import logging - -logger = logging.getLogger(__name__) - import fabio from ... import jpegimage from ..utilstest import UtilsTest +logger = logging.getLogger(__name__) + TEST_DIRECTORY = None # Temporary directory where storing test data @@ -110,7 +109,6 @@ def test_read_missing_file(self): class TestPilNotAvailable(unittest.TestCase): - def setUp(self): filename = UtilsTest.getimage("rand_uint8.jpg.bz2")[:-4] self.filename = filename @@ -168,6 +166,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/codecs/test_kcdimage.py b/src/fabio/test/codecs/test_kcdimage.py index 07c7c686..92f29b73 100755 --- a/src/fabio/test/codecs/test_kcdimage.py +++ b/src/fabio/test/codecs/test_kcdimage.py @@ -35,19 +35,19 @@ import unittest import os import logging - -logger = logging.getLogger(__name__) - import fabio from ...kcdimage import kcdimage from ...openimage import openimage from ..utilstest import UtilsTest +logger = logging.getLogger(__name__) + class TestKcd(unittest.TestCase): """basic test""" - kcdfilename = 'i01f0001.kcd' - edffilename = 'i01f0001.edf' + + kcdfilename = "i01f0001.kcd" + edffilename = "i01f0001.edf" results = """i01f0001.kcd 625 576 96 66814.0 195.3862972 243.58150990245315""" def setUp(self): @@ -59,7 +59,7 @@ def setUp(self): assert os.path.exists(self.fn[i]) def test_read(self): - """ check we can read kcd images""" + """check we can read kcd images""" vals = self.results.split() dim1, dim2 = [int(x) for x in vals[1:3]] shape = dim2, dim1 @@ -77,11 +77,11 @@ def test_read(self): self.assertEqual(shape, obj.shape, "shape" + ext) def test_same(self): - """ see if we can read kcd images and if they are the same as the EDF """ + """see if we can read kcd images and if they are the same as the EDF""" kcd = kcdimage() kcd.read(self.fn[self.kcdfilename]) edf = fabio.open(self.fn[self.edffilename]) - diff = (kcd.data.astype("int32") - edf.data.astype("int32")) + diff = kcd.data.astype("int32") - edf.data.astype("int32") self.assertAlmostEqual(abs(diff).sum(dtype=int), 0, 4) @@ -92,6 +92,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/codecs/test_lambdaimage.py b/src/fabio/test/codecs/test_lambdaimage.py index c1a28906..73b403f4 100644 --- a/src/fabio/test/codecs/test_lambdaimage.py +++ b/src/fabio/test/codecs/test_lambdaimage.py @@ -26,26 +26,25 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE.# -"""Test lambda images -""" +"""Test lambda images""" import os import numpy import fabio.lambdaimage -from fabio.openimage import openimage +from fabio.openimage import openimage from ..utilstest import UtilsTest - import unittest import logging + logger = logging.getLogger(__name__) class TestLambda(unittest.TestCase): # filename dim1 dim2 min max mean stddev TESTIMAGES = [ - ("l1_test02_00002_m01.nxs", 1554, 516, 0, 548, 0.00, 0.81024), # WIP - ("l1_test02_00002_m02.nxs", 1554, 516, 0, 0, 0.0, 0.0), # WIP - ("l1_test02_00002_m03.nxs", 1554, 516, 0, 45, 0.00 ,0.0534), # WIP + ("l1_test02_00002_m01.nxs", 1554, 516, 0, 548, 0.00, 0.81024), # WIP + ("l1_test02_00002_m02.nxs", 1554, 516, 0, 0, 0.0, 0.0), # WIP + ("l1_test02_00002_m03.nxs", 1554, 516, 0, 45, 0.00, 0.0534), # WIP ("l1_test02_00002_master.nxs", 1555, 1813, 0, 548, 0.00, 0.433), # WIP ] @@ -60,29 +59,43 @@ def test_read(self): shape = dim2, dim1 mini, maxi, mean, stddev = params[3:] obj = fabio.lambdaimage.LambdaImage() - filename =UtilsTest.getimage(name) + filename = UtilsTest.getimage(name) obj.read(filename) - self.assertAlmostEqual(mini, obj.getmin(), 2, "getmin [%s,%s]" % (mini, obj.getmin())) - self.assertAlmostEqual(maxi, obj.getmax(), 2, "getmax [%s,%s]" % (maxi, obj.getmax())) - self.assertAlmostEqual(mean, obj.getmean(), 2, "getmean [%s,%s]" % (mean, obj.getmean())) - self.assertAlmostEqual(stddev, obj.getstddev(), 2, "getstddev [%s,%s]" % (stddev, obj.getstddev())) + self.assertAlmostEqual( + mini, obj.getmin(), 2, "getmin [%s,%s]" % (mini, obj.getmin()) + ) + self.assertAlmostEqual( + maxi, obj.getmax(), 2, "getmax [%s,%s]" % (maxi, obj.getmax()) + ) + self.assertAlmostEqual( + mean, obj.getmean(), 2, "getmean [%s,%s]" % (mean, obj.getmean()) + ) + self.assertAlmostEqual( + stddev, + obj.getstddev(), + 2, + "getstddev [%s,%s]" % (stddev, obj.getstddev()), + ) self.assertEqual(shape, obj.shape, "dim1") - + def test_write(self): """read file using fabio.open and save and reopen ... check consistency""" for params in self.TESTIMAGES: fname = UtilsTest.getimage(params[0]) obj = openimage(fname) self.assertEqual(obj.shape, params[2:0:-1]) - + dst = os.path.join(UtilsTest.tempdir, os.path.basename(fname)) obj.write(dst) for idx, read_back in enumerate(openimage(dst)): - self.assertTrue(numpy.all(read_back.data == obj.getframe(idx).data), f"data are the same {fname} #{idx}") + self.assertTrue( + numpy.all(read_back.data == obj.getframe(idx).data), + f"data are the same {fname} #{idx}", + ) + - def suite(): loadTests = unittest.defaultTestLoader.loadTestsFromTestCase testsuite = unittest.TestSuite() @@ -90,6 +103,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/codecs/test_limaimage.py b/src/fabio/test/codecs/test_limaimage.py index d88ddd33..72fd3ccf 100644 --- a/src/fabio/test/codecs/test_limaimage.py +++ b/src/fabio/test/codecs/test_limaimage.py @@ -26,20 +26,19 @@ # THE SOFTWARE. # -"""Test Eiger images -""" +"""Test Eiger images""" import unittest import os import logging - -logger = logging.getLogger(__name__) import numpy from fabio.openimage import openimage from fabio.limaimage import LimaImage, h5py from ..utilstest import UtilsTest from ..test_frames import _CommonTestFrames +logger = logging.getLogger(__name__) + def make_hdf5(name, shape=(50, 99, 101)): if h5py is None: @@ -50,9 +49,18 @@ def make_hdf5(name, shape=(50, 99, 101)): h.attrs["default"] = "/entry" e = h.require_group("/entry/measurement") if len(shape) == 2: - e.require_dataset("data", shape, compression="gzip", compression_opts=9, dtype="uint16") + e.require_dataset( + "data", shape, compression="gzip", compression_opts=9, dtype="uint16" + ) elif len(shape) == 3: - e.require_dataset("data", shape, chunks=(1,) + shape[1:], compression="gzip", compression_opts=9, dtype="uint16") + e.require_dataset( + "data", + shape, + chunks=(1,) + shape[1:], + compression="gzip", + compression_opts=9, + dtype="uint16", + ) class TestLima(_CommonTestFrames): @@ -61,7 +69,7 @@ class TestLima(_CommonTestFrames): @classmethod def setUpClass(cls): cls.fn3 = os.path.join(UtilsTest.tempdir, "lima3d.h5") - print(cls.fn3 ) + print(cls.fn3) make_hdf5(cls.fn3, (17, 99, 101)) super(TestLima, cls).setUpClass() @@ -101,7 +109,7 @@ def test_open(self): def test_write(self): fn = os.path.join(UtilsTest.tempdir, "lima_write.h5") - shape=(10, 11, 13) + shape = (10, 11, 13) ary = numpy.random.randint(0, 100, size=shape) e = LimaImage() for i, d in enumerate(ary): @@ -111,18 +119,21 @@ def test_write(self): e.save(fn) self.assertTrue(os.path.exists(fn), "file exists") f = openimage(fn) - self.assertEqual(str(f.__class__.__name__), "LimaImage", "Used the write reader") + self.assertEqual( + str(f.__class__.__name__), "LimaImage", "Used the write reader" + ) self.assertEqual(shape[0], f.nframes, "shape matches") self.assertEqual(shape[1:], f.shape, "shape matches") - self.assertEqual(abs(f.data-ary[0]).max(), 0, "first frame matches") - for i,g in enumerate(f): - self.assertEqual(abs(g.data-ary[i]).max(), 0, f"frame {i} matches") + self.assertEqual(abs(f.data - ary[0]).max(), 0, "first frame matches") + for i, g in enumerate(f): + self.assertEqual(abs(g.data - ary[i]).max(), 0, f"frame {i} matches") def test_identify(self): fn = UtilsTest.getimage("output_sparse_0_00000.h5") res = openimage(fn) self.assertTrue(res.__class__.__name__.startswith("Sparse")) + def suite(): loadTests = unittest.defaultTestLoader.loadTestsFromTestCase testsuite = unittest.TestSuite() @@ -130,6 +141,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/codecs/test_mar345image.py b/src/fabio/test/codecs/test_mar345image.py index e9cd6883..b73e23e5 100644 --- a/src/fabio/test/codecs/test_mar345image.py +++ b/src/fabio/test/codecs/test_mar345image.py @@ -32,13 +32,12 @@ import os import numpy import logging - -logger = logging.getLogger(__name__) - import fabio from fabio.mar345image import mar345image from ..utilstest import UtilsTest +logger = logging.getLogger(__name__) + # filename dim1 dim2 min max mean stddev TESTIMAGES = """example.mar2300 2300 2300 0 999999 180.15 4122.67 example.mar2300.bz2 2300 2300 0 999999 180.15 4122.67 @@ -50,7 +49,6 @@ class TestMar345(unittest.TestCase): - def setUp(self): """ download images @@ -66,7 +64,7 @@ def test_read(self): """ Test the reading of Mar345 images """ - for line in TESTIMAGES.split('\n'): + for line in TESTIMAGES.split("\n"): vals = line.strip().split() name = vals[0] dim1, dim2 = [int(x) for x in vals[1:3]] @@ -75,10 +73,21 @@ def test_read(self): obj = mar345image() obj.read(UtilsTest.getimage(name)) - self.assertAlmostEqual(mini, obj.getmin(), 2, "getmin [%s,%s]" % (mini, obj.getmin())) - self.assertAlmostEqual(maxi, obj.getmax(), 2, "getmax [%s,%s]" % (maxi, obj.getmax())) - self.assertAlmostEqual(mean, obj.getmean(), 2, "getmean [%s,%s]" % (mean, obj.getmean())) - self.assertAlmostEqual(stddev, obj.getstddev(), 2, "getstddev [%s,%s]" % (stddev, obj.getstddev())) + self.assertAlmostEqual( + mini, obj.getmin(), 2, "getmin [%s,%s]" % (mini, obj.getmin()) + ) + self.assertAlmostEqual( + maxi, obj.getmax(), 2, "getmax [%s,%s]" % (maxi, obj.getmax()) + ) + self.assertAlmostEqual( + mean, obj.getmean(), 2, "getmean [%s,%s]" % (mean, obj.getmean()) + ) + self.assertAlmostEqual( + stddev, + obj.getstddev(), + 2, + "getstddev [%s,%s]" % (stddev, obj.getstddev()), + ) self.assertEqual(shape, obj.shape, "shape") def test_write(self): @@ -93,13 +102,22 @@ def test_write(self): other = mar345image() other.read(os.path.join(UtilsTest.tempdir, name)) if abs(obj.data - other.data).max(): - logger.error("data for %s are not the same %s", line, numpy.where(obj.data - other.data)) + logger.error( + "data for %s are not the same %s", + line, + numpy.where(obj.data - other.data), + ) self.assertEqual(abs(obj.data - other.data).max(), 0, "data are the same") for key in obj.header: if key == "filename": continue self.assertTrue(key in other.header, "Key %s is in header" % key) - self.assertEqual(obj.header[key], other.header[key], "value are the same for key %s: [%s|%s]" % (key, obj.header[key], other.header[key])) + self.assertEqual( + obj.header[key], + other.header[key], + "value are the same for key %s: [%s|%s]" + % (key, obj.header[key], other.header[key]), + ) os.unlink(os.path.join(UtilsTest.tempdir, name)) @@ -120,7 +138,12 @@ def test_byteswap_write(self): if key == "filename": continue self.assertTrue(key in other.header, "Key %s is in header" % key) - self.assertEqual(obj.header[key], other.header[key], "value are the same for key %s: [%s|%s]" % (key, obj.header[key], other.header[key])) + self.assertEqual( + obj.header[key], + other.header[key], + "value are the same for key %s: [%s|%s]" + % (key, obj.header[key], other.header[key]), + ) os.unlink(os.path.join(UtilsTest.tempdir, name)) @@ -137,15 +160,17 @@ def test_memoryleak(self): print("reading #%s/%s" % (i, N)) def test_aux(self): - """test auxillary functions - """ + """test auxillary functions""" from fabio.ext import mar345_IO + shape = 120, 130 size = shape[0] * shape[1] img = numpy.random.randint(0, 32000, size).astype("int16") b = mar345_IO.precomp(img, shape[-1]) c = mar345_IO.postdec(b, shape[-1]) - self.assertEqual(abs(c - img).max(), 0, "pre-compression and post-decompression works") + self.assertEqual( + abs(c - img).max(), 0, "pre-compression and post-decompression works" + ) a = mar345_IO.calc_nb_bits(numpy.arange(8).astype("int32"), 0, 8) self.assertEqual(a, 32, "8*4") @@ -199,6 +224,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite) diff --git a/src/fabio/test/codecs/test_mccdimage.py b/src/fabio/test/codecs/test_mccdimage.py index 77ee1840..a88550aa 100644 --- a/src/fabio/test/codecs/test_mccdimage.py +++ b/src/fabio/test/codecs/test_mccdimage.py @@ -37,13 +37,13 @@ import os import numpy import logging - -logger = logging.getLogger(__name__) - from ...tifimage import tifimage from ...marccdimage import marccdimage from ..utilstest import UtilsTest +logger = logging.getLogger(__name__) + + # statistics come from fit2d I think # filename dim1 dim2 min max mean stddev TESTIMAGES = """corkcont2_H_0089.mccd 2048 2048 0 354 7.2611 14.639 @@ -71,13 +71,15 @@ def setUp(self): def test_read_openimage(self): from fabio.openimage import openimage + obj = openimage(self.image) if obj.data.astype(int).tobytes() != self.imdata.astype(int).tobytes(): logger.info("%s %s" % (type(self.imdata), self.imdata.dtype)) logger.info("%s %s" % (type(obj.data), obj.data.dtype)) logger.info("%s %s" % (obj.data - self.imdata)) - self.assertEqual(obj.data.astype(int).tobytes(), - self.imdata.astype(int).tobytes()) + self.assertEqual( + obj.data.astype(int).tobytes(), self.imdata.astype(int).tobytes() + ) def tearDown(self): unittest.TestCase.tearDown(self) @@ -85,7 +87,6 @@ def tearDown(self): class TestFlatMccds(unittest.TestCase): - def setUp(self): self.fn = {} for i in ["corkcont2_H_0089.mccd", "somedata_0001.mccd"]: @@ -96,7 +97,7 @@ def setUp(self): assert os.path.exists(self.fn[i]) def test_read(self): - """ check we can read MarCCD images""" + """check we can read MarCCD images""" for line in TESTIMAGES.split("\n"): vals = line.split() name = vals[0] @@ -120,6 +121,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/codecs/test_mpaimage.py b/src/fabio/test/codecs/test_mpaimage.py index e2b277c1..085a0c96 100644 --- a/src/fabio/test/codecs/test_mpaimage.py +++ b/src/fabio/test/codecs/test_mpaimage.py @@ -30,17 +30,17 @@ import unittest import logging - -logger = logging.getLogger(__name__) - import fabio from ..utilstest import UtilsTest +logger = logging.getLogger(__name__) + class TestMpa(unittest.TestCase): """ Test classe for multiwire (mpa) images """ + TESTIMAGES = [ # filename dim1 dim2 min max mean stddev ("mpa_test.mpa", 1024, 1024, 0, 1295, 0.8590, 18.9393), @@ -59,10 +59,21 @@ def test_read(self): obj = fabio.mpaimage.MpaImage() obj.read(path) - self.assertAlmostEqual(mini, obj.getmin(), 2, "getmin [%s,%s]" % (mini, obj.getmin())) - self.assertAlmostEqual(maxi, obj.getmax(), 2, "getmax [%s,%s]" % (maxi, obj.getmax())) - self.assertAlmostEqual(mean, obj.getmean(), 2, "getmean [%s,%s]" % (mean, obj.getmean())) - self.assertAlmostEqual(stddev, obj.getstddev(), 2, "getstddev [%s,%s]" % (stddev, obj.getstddev())) + self.assertAlmostEqual( + mini, obj.getmin(), 2, "getmin [%s,%s]" % (mini, obj.getmin()) + ) + self.assertAlmostEqual( + maxi, obj.getmax(), 2, "getmax [%s,%s]" % (maxi, obj.getmax()) + ) + self.assertAlmostEqual( + mean, obj.getmean(), 2, "getmean [%s,%s]" % (mean, obj.getmean()) + ) + self.assertAlmostEqual( + stddev, + obj.getstddev(), + 2, + "getstddev [%s,%s]" % (stddev, obj.getstddev()), + ) self.assertEqual(shape, obj.shape) @@ -73,6 +84,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/codecs/test_mrcimage.py b/src/fabio/test/codecs/test_mrcimage.py index 56430456..c9726088 100755 --- a/src/fabio/test/codecs/test_mrcimage.py +++ b/src/fabio/test/codecs/test_mrcimage.py @@ -35,19 +35,19 @@ import unittest import os import logging - -logger = logging.getLogger(__name__) - import fabio from ...mrcimage import MrcImage from ...openimage import openimage from ..utilstest import UtilsTest +logger = logging.getLogger(__name__) + class TestMrc(unittest.TestCase): """basic test""" - mrcfilename = ('EMD-3001.map', "0p67_5s_0000.mrc") - npyfilename = ('EMD-3001.npy', "0p67_5s_0000.npy") + + mrcfilename = ("EMD-3001.map", "0p67_5s_0000.mrc") + npyfilename = ("EMD-3001.npy", "0p67_5s_0000.npy") results = """EMD-3001.map 73 43 -0.36814222 0.678705 0.0062340125 0.16349247 0p67_5s_0000.mrc 2048 2048 -344.0 33553.0 82.653259 243.213061""" @@ -60,7 +60,7 @@ def setUp(self): assert os.path.exists(self.fn[i]) def test_read(self): - """ check we can read mrc images""" + """check we can read mrc images""" for results in self.results.split("\n"): vals = results.split() dim1, dim2 = [int(x) for x in vals[1:3]] @@ -76,17 +76,19 @@ def test_read(self): self.assertAlmostEqual(mini, obj.getmin(), 4, f"{filename} getmin") self.assertAlmostEqual(maxi, obj.getmax(), 4, f"{filename} getmax") self.assertAlmostEqual(mean, obj.getmean(), 4, f"{filename} getmean") - self.assertAlmostEqual(stddev, obj.getstddev(), 4, f"{filename} getstddev") + self.assertAlmostEqual( + stddev, obj.getstddev(), 4, f"{filename} getstddev" + ) self.assertEqual(shape, obj.shape, f"{filename} shape") def test_same(self): - """ see if we can read mrc images and if they are the same as the numpy dump""" + """see if we can read mrc images and if they are the same as the numpy dump""" for mrcfilename, npyfilename in zip(self.mrcfilename, self.npyfilename): logger.info("Comparing files %s and %s", mrcfilename, npyfilename) mrc = MrcImage() mrc.read(self.fn[mrcfilename]) npy = fabio.open(self.fn[npyfilename]) - diff = (mrc.data - npy.data) + diff = mrc.data - npy.data self.assertAlmostEqual(abs(diff).sum(), 0, 4, msg=mrcfilename) @@ -97,6 +99,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/codecs/test_numpyimage.py b/src/fabio/test/codecs/test_numpyimage.py index afe065d5..963951e1 100755 --- a/src/fabio/test/codecs/test_numpyimage.py +++ b/src/fabio/test/codecs/test_numpyimage.py @@ -29,20 +29,20 @@ """ Test for numpy images. """ + __author__ = "Jérôme Kieffer" -__date__ = "03/04/2020" +__date__ = "27/10/2025" import os import unittest import numpy import logging - -logger = logging.getLogger(__name__) - from fabio.numpyimage import NumpyImage from fabio.openimage import openimage from ..utilstest import UtilsTest +logger = logging.getLogger(__name__) + class TestNumpy(unittest.TestCase): """basic test""" @@ -50,7 +50,9 @@ class TestNumpy(unittest.TestCase): def setUp(self): """Generate files""" - self.ary = numpy.random.randint(0, 6500, size=99).reshape(11, 9).astype("uint16") + self.ary = ( + numpy.random.randint(0, 6500, size=99).reshape(11, 9).astype("uint16") + ) self.fn = os.path.join(UtilsTest.tempdir, "numpy.npy") self.fn2 = os.path.join(UtilsTest.tempdir, "numpy2.npy") numpy.save(self.fn, self.ary) @@ -63,7 +65,7 @@ def tearDown(self): self.ary = self.fn = self.fn2 = None def test_read(self): - """ check we can read pnm images""" + """check we can read pnm images""" obj = openimage(self.fn) self.assertEqual(obj.bytecode, numpy.uint16, msg="bytecode is OK") @@ -71,7 +73,7 @@ def test_read(self): self.assertTrue(numpy.allclose(obj.data, self.ary), "data") def test_write(self): - """ check we can write numpy images""" + """check we can write numpy images""" ref = NumpyImage(data=self.ary) ref.save(self.fn2) with openimage(self.fn2) as obj: @@ -105,6 +107,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/codecs/test_oxdimage.py b/src/fabio/test/codecs/test_oxdimage.py index a61d0397..606dc631 100644 --- a/src/fabio/test/codecs/test_oxdimage.py +++ b/src/fabio/test/codecs/test_oxdimage.py @@ -31,31 +31,30 @@ # Unit tests for OXD image (Oxford diffraction now Rigaku) """ -__author__ = "Jerome Kieffer" +__author__ = "Jérôme Kieffer" __license__ = "MIT" -__date__ = "03/04/2020" +__date__ = "27/10/2025" __contact__ = "jerome.kieffer@esrf.fr" import unittest import os import logging - -logger = logging.getLogger(__name__) - import fabio from fabio.OXDimage import OXDimage from ..utilstest import UtilsTest +logger = logging.getLogger(__name__) + # filename dim1 dim2 min max mean stddev values are from OD Sapphire 3.0 TESTIMAGES = [ ("b191_1_9_1.img", 512, 512, -500, 11975, 25.70, 90.2526, "Sapphire2"), ("b191_1_9_1_uncompressed.img", 512, 512, -500, 11975, 25.70, 90.2526, "Sapphire2"), ("100nmfilmonglass_1_1.img", 1024, 1024, -172, 460, 44.20, 63.0245, "Sapphire3"), - ("pilatus300k.rod_img", 487, 619, -2, 173075, 27.315, 538.938, "Pilatus")] + ("pilatus300k.rod_img", 487, 619, -2, 173075, 27.315, 538.938, "Pilatus"), +] class TestOxd(unittest.TestCase): - def setUp(self): self.fn = {} for vals in TESTIMAGES: @@ -86,7 +85,9 @@ def test_read(self): self.assertAlmostEqual(mean, obj.getmean(), 2, "getmean on " + name) self.assertAlmostEqual(stddev, obj.getstddev(), 2, "getstddev on " + name) - self.assertIn(detector_type, obj.header["Detector type"], "detector type on " + name) + self.assertIn( + detector_type, obj.header["Detector type"], "detector type on " + name + ) def test_write(self): "Test writing with self consistency at the fabio level" @@ -100,18 +101,25 @@ def test_write(self): obj.write(os.path.join(UtilsTest.tempdir, name)) other = OXDimage() other.read(os.path.join(UtilsTest.tempdir, name)) - self.assertEqual(abs(obj.data - other.data).max(), 0, "data are not the same for %s" % name) + self.assertEqual( + abs(obj.data - other.data).max(), + 0, + "data are not the same for %s" % name, + ) for key in obj.header: if key == "filename": continue self.assertTrue(key in other.header, "Key %s is in header" % key) - self.assertEqual(obj.header[key], other.header[key], "metadata '%s' are not the same for %s" % (key, name)) + self.assertEqual( + obj.header[key], + other.header[key], + "metadata '%s' are not the same for %s" % (key, name), + ) os.unlink(os.path.join(UtilsTest.tempdir, name)) class TestOxdSame(unittest.TestCase): - def setUp(self): self.fn = {} for i in ["b191_1_9_1.img", "b191_1_9_1_uncompressed.img"]: @@ -153,7 +161,6 @@ def test_same(self): class TestConvert(unittest.TestCase): - def setUp(self): self.fn = {} for i in ["face.msk"]: @@ -183,6 +190,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/codecs/test_pilatusimage.py b/src/fabio/test/codecs/test_pilatusimage.py index 2d89f6b4..de1f6582 100644 --- a/src/fabio/test/codecs/test_pilatusimage.py +++ b/src/fabio/test/codecs/test_pilatusimage.py @@ -30,12 +30,11 @@ import unittest import logging - -logger = logging.getLogger(__name__) - import fabio from ..utilstest import UtilsTest +logger = logging.getLogger(__name__) + class TestPilatus(unittest.TestCase): # filename dim1 dim2 min max mean stddev @@ -58,10 +57,21 @@ def test_read(self): obj = fabio.pilatusimage.PilatusImage() obj.read(UtilsTest.getimage(name)) - self.assertAlmostEqual(mini, obj.getmin(), 2, "getmin [%s,%s]" % (mini, obj.getmin())) - self.assertAlmostEqual(maxi, obj.getmax(), 2, "getmax [%s,%s]" % (maxi, obj.getmax())) - self.assertAlmostEqual(mean, obj.getmean(), 2, "getmean [%s,%s]" % (mean, obj.getmean())) - self.assertAlmostEqual(stddev, obj.getstddev(), 2, "getstddev [%s,%s]" % (stddev, obj.getstddev())) + self.assertAlmostEqual( + mini, obj.getmin(), 2, "getmin [%s,%s]" % (mini, obj.getmin()) + ) + self.assertAlmostEqual( + maxi, obj.getmax(), 2, "getmax [%s,%s]" % (maxi, obj.getmax()) + ) + self.assertAlmostEqual( + mean, obj.getmean(), 2, "getmean [%s,%s]" % (mean, obj.getmean()) + ) + self.assertAlmostEqual( + stddev, + obj.getstddev(), + 2, + "getstddev [%s,%s]" % (stddev, obj.getstddev()), + ) self.assertEqual(shape, obj.shape) def test_header(self): @@ -82,7 +92,8 @@ def test_header(self): "N_excluded_pixels", "Excluded_pixels", "Flat_field", - "Trim_directory"] + "Trim_directory", + ] self.assertEqual(list(obj.header.keys()), expected_keys) expected_values = { @@ -97,7 +108,8 @@ def test_header(self): "N_excluded_pixels": "0", "Excluded_pixels": "(nil)", "Flat_field": "(nil)", - "Trim_directory": "(nil)"} + "Trim_directory": "(nil)", + } self.assertEqual(dict(obj.header), expected_values) @@ -125,6 +137,7 @@ class TestPilatus1M(unittest.TestCase): def setUp(self): import fabio.utils.pilutils + if fabio.utils.pilutils.Image is None: self.skipTest("No TIFF decoder available for LZW") @@ -175,7 +188,8 @@ def test_header(self): "Phi", "Chi", "Oscillation_axis", - "N_oscillations"] + "N_oscillations", + ] self.assertEqual(list(obj.header.keys()), expected_keys) @@ -187,6 +201,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/codecs/test_pixiimage.py b/src/fabio/test/codecs/test_pixiimage.py index b9b2ac7c..03719d59 100644 --- a/src/fabio/test/codecs/test_pixiimage.py +++ b/src/fabio/test/codecs/test_pixiimage.py @@ -33,13 +33,12 @@ import unittest import os import logging - -logger = logging.getLogger(__name__) - import fabio from ..utilstest import UtilsTest from ..test_frames import _CommonTestFrames +logger = logging.getLogger(__name__) + class TestPixiImage(_CommonTestFrames): """Test the class format""" @@ -51,7 +50,6 @@ def setUpClass(cls): @classmethod def getMeta(cls): - class Meta(object): pass @@ -74,12 +72,12 @@ def create_fake_images(cls): header = b"\n\xb8\x03\x00" + b"\x00" * 20 cls.single_frame = os.path.join(UtilsTest.tempdir, "pixi_1frame.dat") - with open(cls.single_frame, 'wb') as f: + with open(cls.single_frame, "wb") as f: f.write(header) f.write(frame1) cls.multi_frame = os.path.join(UtilsTest.tempdir, "pixi_3frame.dat") - with open(cls.multi_frame, 'wb') as f: + with open(cls.multi_frame, "wb") as f: f.write(header) f.write(frame1) f.write(header) @@ -92,7 +90,7 @@ def create_fake_images(cls): frames = [frame1, frame2, frame3] for num, frame in enumerate(frames): filename = template % num - with open(filename, 'wb') as f: + with open(filename, "wb") as f: f.write(header) f.write(frame) @@ -147,6 +145,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/codecs/test_pnmimage.py b/src/fabio/test/codecs/test_pnmimage.py index 832adcc5..4a2c711f 100755 --- a/src/fabio/test/codecs/test_pnmimage.py +++ b/src/fabio/test/codecs/test_pnmimage.py @@ -32,23 +32,27 @@ Jerome Kieffer, 04/12/2014 """ -__author__ = "Jerome Kieffer" -__date__ = "03/04/2020" + +__author__ = "Jérôme Kieffer" +__date__ = "27/10/2025" + import os import unittest import numpy import logging - -logger = logging.getLogger(__name__) - from fabio.pnmimage import pnmimage from fabio.openimage import openimage from ..utilstest import UtilsTest +logger = logging.getLogger(__name__) + class TestPNM(unittest.TestCase): """basic test""" - results = """image0001.pgm 1024 1024 0 28416 353.795654296875 2218.0290682517543""" + + results = ( + """image0001.pgm 1024 1024 0 28416 353.795654296875 2218.0290682517543""" + ) def setUp(self): """Download files""" @@ -60,7 +64,7 @@ def setUp(self): assert os.path.exists(self.fn[i]) def test_read(self): - """ check we can read pnm images""" + """check we can read pnm images""" vals = self.results.split() name = vals[0] dim1, dim2 = [int(x) for x in vals[1:3]] @@ -92,6 +96,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/codecs/test_raxisimage.py b/src/fabio/test/codecs/test_raxisimage.py index 02d667f7..b68c8d4d 100644 --- a/src/fabio/test/codecs/test_raxisimage.py +++ b/src/fabio/test/codecs/test_raxisimage.py @@ -30,17 +30,17 @@ # Unit tests for raxis images 28/11/2014 """ -__date__ = "03/04/2020" + +__date__ = "27/10/2025" import unittest import os import logging - -logger = logging.getLogger(__name__) - import fabio from fabio.raxisimage import raxisimage from ..utilstest import UtilsTest +logger = logging.getLogger(__name__) + # filename dim1 dim2 min max mean stddev TESTIMAGES = """mgzn-20hpt.img 2300 1280 16 15040 287.82 570.72 mgzn-20hpt.img.bz2 2300 1280 16 15040 287.82 570.72 @@ -49,7 +49,6 @@ class TestRaxisImage(unittest.TestCase): - def setUp(self): """ download images @@ -60,7 +59,7 @@ def test_read(self): """ Test the reading of Mar345 images """ - for line in TESTIMAGES.split('\n'): + for line in TESTIMAGES.split("\n"): vals = line.strip().split() name = vals[0] logger.debug("Testing file %s" % name) @@ -70,10 +69,21 @@ def test_read(self): obj = raxisimage() obj.read(os.path.join(os.path.dirname(self.mar), name)) - self.assertAlmostEqual(mini, obj.getmin(), 2, "getmin [%s,%s]" % (mini, obj.getmin())) - self.assertAlmostEqual(maxi, obj.getmax(), 2, "getmax [%s,%s]" % (maxi, obj.getmax())) - self.assertAlmostEqual(mean, obj.getmean(), 2, "getmean [%s,%s]" % (mean, obj.getmean())) - self.assertAlmostEqual(stddev, obj.getstddev(), 2, "getstddev [%s,%s]" % (stddev, obj.getstddev())) + self.assertAlmostEqual( + mini, obj.getmin(), 2, "getmin [%s,%s]" % (mini, obj.getmin()) + ) + self.assertAlmostEqual( + maxi, obj.getmax(), 2, "getmax [%s,%s]" % (maxi, obj.getmax()) + ) + self.assertAlmostEqual( + mean, obj.getmean(), 2, "getmean [%s,%s]" % (mean, obj.getmean()) + ) + self.assertAlmostEqual( + stddev, + obj.getstddev(), + 2, + "getstddev [%s,%s]" % (stddev, obj.getstddev()), + ) self.assertEqual(shape, obj.shape) def _test_write(self): @@ -93,7 +103,12 @@ def _test_write(self): if key == "filename": continue self.assertTrue(key in other.header, "Key %s is in header" % key) - self.assertEqual(obj.header[key], other.header[key], "value are the same for key %s: [%s|%s]" % (key, obj.header[key], other.header[key])) + self.assertEqual( + obj.header[key], + other.header[key], + "value are the same for key %s: [%s|%s]" + % (key, obj.header[key], other.header[key]), + ) os.unlink(os.path.join(UtilsTest.tempdir, name)) def test_memoryleak(self): @@ -114,6 +129,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/codecs/test_speimage.py b/src/fabio/test/codecs/test_speimage.py index 5fc890a8..a108d59d 100644 --- a/src/fabio/test/codecs/test_speimage.py +++ b/src/fabio/test/codecs/test_speimage.py @@ -32,29 +32,27 @@ __contact__ = "c.prescher@uni-koeln.de" __license__ = "MIT" __copyright__ = "Clemens Prescher/Univeristy Köln, Germany" -__date__ = "03/04/2020" +__date__ = "27/10/2025" import unittest import numpy import logging - -logger = logging.getLogger(__name__) - import fabio from fabio.speimage import SpeImage from ..utilstest import UtilsTest +logger = logging.getLogger(__name__) -class TestSpeImage(unittest.TestCase): +class TestSpeImage(unittest.TestCase): @classmethod def setUpClass(cls): super(TestSpeImage, cls).setUpClass() - cls.v2_spe_filename = UtilsTest.getimage('v2.SPE.bz2')[:-4] - cls.v2_converted_spe_filename = UtilsTest.getimage('v2_converted.SPE.bz2')[:-4] - cls.v3_spe_filename = UtilsTest.getimage('v3.spe.bz2')[:-4] - cls.v3_custom_roi_filename = UtilsTest.getimage('v3_custom_roi.spe.bz2')[:-4] - cls.v3_2frames_filename = UtilsTest.getimage('v3_2frames.spe.bz2')[:-4] + cls.v2_spe_filename = UtilsTest.getimage("v2.SPE.bz2")[:-4] + cls.v2_converted_spe_filename = UtilsTest.getimage("v2_converted.SPE.bz2")[:-4] + cls.v3_spe_filename = UtilsTest.getimage("v3.spe.bz2")[:-4] + cls.v3_custom_roi_filename = UtilsTest.getimage("v3_custom_roi.spe.bz2")[:-4] + cls.v3_2frames_filename = UtilsTest.getimage("v3_2frames.spe.bz2")[:-4] @classmethod def tearDownClass(cls): @@ -76,48 +74,52 @@ def tearDown(self): self.v2_spe_file = self.v3_spe_file = self.v2_converted_spe_file = None def test_reading_version2_spe(self): - self.assertEqual(self.v2_spe_file.header['version'], 2) - self.assertEqual(self.v3_spe_file.header['version'], 3) - self.assertEqual(self.v2_converted_spe_file.header['version'], 3) + self.assertEqual(self.v2_spe_file.header["version"], 2) + self.assertEqual(self.v3_spe_file.header["version"], 3) + self.assertEqual(self.v2_converted_spe_file.header["version"], 3) def test_calibration(self): - self.assertGreater(len(self.v2_spe_file.header['x_calibration']), 0) - self.assertGreater(len(self.v3_spe_file.header['x_calibration']), 0) - self.assertGreater(len(self.v2_converted_spe_file.header['x_calibration']), 0) + self.assertGreater(len(self.v2_spe_file.header["x_calibration"]), 0) + self.assertGreater(len(self.v3_spe_file.header["x_calibration"]), 0) + self.assertGreater(len(self.v2_converted_spe_file.header["x_calibration"]), 0) # def test_time(self): - self.assertEqual(self.v2_spe_file.header['time'], "07/13/2013 19:42:23") - self.assertEqual(self.v3_spe_file.header['time'], "09/06/2013 16:50:39.445678") - self.assertEqual(self.v2_converted_spe_file.header['time'], "05/10/2013 10:34:27") + self.assertEqual(self.v2_spe_file.header["time"], "07/13/2013 19:42:23") + self.assertEqual(self.v3_spe_file.header["time"], "09/06/2013 16:50:39.445678") + self.assertEqual( + self.v2_converted_spe_file.header["time"], "05/10/2013 10:34:27" + ) def test_exposure_time(self): - self.assertEqual(self.v2_spe_file.header['exposure_time'], 0.5) - self.assertEqual(self.v3_spe_file.header['exposure_time'], 0.1) - self.assertEqual(self.v2_converted_spe_file.header['exposure_time'], 0.18) + self.assertEqual(self.v2_spe_file.header["exposure_time"], 0.5) + self.assertEqual(self.v3_spe_file.header["exposure_time"], 0.1) + self.assertEqual(self.v2_converted_spe_file.header["exposure_time"], 0.18) def test_detector(self): - self.assertEqual(self.v2_spe_file.header['detector'], 'unspecified') - self.assertEqual(self.v3_spe_file.header['detector'], "PIXIS: 100BR") - self.assertEqual(self.v2_converted_spe_file.header['detector'], 'unspecified') + self.assertEqual(self.v2_spe_file.header["detector"], "unspecified") + self.assertEqual(self.v3_spe_file.header["detector"], "PIXIS: 100BR") + self.assertEqual(self.v2_converted_spe_file.header["detector"], "unspecified") def test_grating(self): - self.assertEqual(self.v2_spe_file.header['grating'], '300.0') - self.assertEqual(self.v3_spe_file.header['grating'], '860nm 300') - self.assertEqual(self.v2_converted_spe_file.header['grating'], '300.0') + self.assertEqual(self.v2_spe_file.header["grating"], "300.0") + self.assertEqual(self.v3_spe_file.header["grating"], "860nm 300") + self.assertEqual(self.v2_converted_spe_file.header["grating"], "300.0") def test_center_wavelength(self): - self.assertEqual(self.v2_spe_file.header['center_wavelength'], 750) - self.assertEqual(self.v3_spe_file.header['center_wavelength'], 500) - self.assertEqual(self.v2_converted_spe_file.header['center_wavelength'], 750) + self.assertEqual(self.v2_spe_file.header["center_wavelength"], 750) + self.assertEqual(self.v3_spe_file.header["center_wavelength"], 500) + self.assertEqual(self.v2_converted_spe_file.header["center_wavelength"], 750) def test_roi(self): - self.assertEqual(self.v3_spe_file.header['roi'], (0, 1024, 0, 100)) + self.assertEqual(self.v3_spe_file.header["roi"], (0, 1024, 0, 100)) self.v3_custom_region = SpeImage() self.v3_custom_region.read(self.v3_custom_roi_filename) - self.assertEqual(self.v3_custom_region.header['roi'], (100, 600, 10, 60)) - self.assertEqual(len(self.v3_custom_region.header['x_calibration']), - self.v3_custom_region.header['x_dim']) + self.assertEqual(self.v3_custom_region.header["roi"], (100, 600, 10, 60)) + self.assertEqual( + len(self.v3_custom_region.header["x_calibration"]), + self.v3_custom_region.header["x_dim"], + ) def test_read_data(self): self.assertEqual(self.v2_spe_file.data.shape, (100, 1340)) @@ -156,6 +158,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/codecs/test_tifimage.py b/src/fabio/test/codecs/test_tifimage.py index 4476ea0e..67b2572f 100755 --- a/src/fabio/test/codecs/test_tifimage.py +++ b/src/fabio/test/codecs/test_tifimage.py @@ -27,23 +27,25 @@ # THE SOFTWARE. # """Tiff Unit tests""" + import unittest import os import logging - -logger = logging.getLogger(__name__) import numpy import fabio from fabio import tifimage from ..utilstest import UtilsTest +logger = logging.getLogger(__name__) + class TestTif(unittest.TestCase): # filename dim1 dim2 min max mean stddev TESTIMAGES = [ ("Feb09-bright-00.300s_WAXS.bz2", 1042, 1042, 0, 65535, 8546.6414, 1500.4198), ("Feb09-bright-00.300s_WAXS.gz", 1042, 1042, 0, 65535, 8546.6414, 1500.4198), - ("Feb09-bright-00.300s_WAXS", 1042, 1042, 0, 65535, 8546.6414, 1500.4198)] + ("Feb09-bright-00.300s_WAXS", 1042, 1042, 0, 65535, 8546.6414, 1500.4198), + ] def test_read(self): """ @@ -58,10 +60,21 @@ def test_read(self): obj = fabio.tifimage.TifImage() obj.read(UtilsTest.getimage(name)) - self.assertAlmostEqual(mini, obj.getmin(), 2, "getmin [%s,%s]" % (mini, obj.getmin())) - self.assertAlmostEqual(maxi, obj.getmax(), 2, "getmax [%s,%s]" % (maxi, obj.getmax())) - self.assertAlmostEqual(mean, obj.getmean(), 2, "getmean [%s,%s]" % (mean, obj.getmean())) - self.assertAlmostEqual(stddev, obj.getstddev(), 2, "getstddev [%s,%s]" % (stddev, obj.getstddev())) + self.assertAlmostEqual( + mini, obj.getmin(), 2, "getmin [%s,%s]" % (mini, obj.getmin()) + ) + self.assertAlmostEqual( + maxi, obj.getmax(), 2, "getmax [%s,%s]" % (maxi, obj.getmax()) + ) + self.assertAlmostEqual( + mean, obj.getmean(), 2, "getmean [%s,%s]" % (mean, obj.getmean()) + ) + self.assertAlmostEqual( + stddev, + obj.getstddev(), + 2, + "getstddev [%s,%s]" % (stddev, obj.getstddev()), + ) self.assertEqual(shape, obj.shape, "dim1") def test_header(self): @@ -72,22 +85,25 @@ def test_header(self): obj.read(UtilsTest.getimage(name)) # The key order is not the same depending on Python2 or 3 - expected_keys = set([ - 'info', - 'photometricInterpretation', - 'rowsPerStrip', - 'nColumns', - 'compression', - 'sampleFormat', - 'imageDescription', - 'nRows', - 'colormap', - 'nBits', - 'date', - 'software', - 'compression_type', - 'stripOffsets', - 'stripByteCounts']) + expected_keys = set( + [ + "info", + "photometricInterpretation", + "rowsPerStrip", + "nColumns", + "compression", + "sampleFormat", + "imageDescription", + "nRows", + "colormap", + "nBits", + "date", + "software", + "compression_type", + "stripOffsets", + "stripByteCounts", + ] + ) self.assertEqual(set(obj.header.keys()), expected_keys) def test_frame(self): @@ -107,7 +123,7 @@ def test_frame(self): def test_bug502(self): """ - Test the reading of a frame with wrong Photometric interpretation + Test the reading of a frame with wrong Photometric interpretation """ ref = fabio.open(UtilsTest.getimage("frame_00017.npy")) obt = fabio.open(UtilsTest.getimage("frame_00017.tif")) @@ -115,7 +131,6 @@ def test_bug502(self): class TestTifImage_Pilatus(unittest.TestCase): - def setUp(self): self.fn = {} for i in ["pilatus2M.tif", "pilatus2M.edf"]: @@ -133,7 +148,6 @@ def test1(self): class TestTifImage_Packbits(unittest.TestCase): - def setUp(self): self.fn = {} for i in ["oPPA_5grains_0001.tif", "oPPA_5grains_0001.edf"]: @@ -151,7 +165,6 @@ def test1(self): class TestTifImage_fit2d(unittest.TestCase): - def setUp(self): self.fn = {} for i in ["fit2d.tif", "fit2d.edf"]: @@ -170,10 +183,10 @@ def test1(self): class TestTifImage_A0009(unittest.TestCase): """ - test image from ??? with this error -a0009.tif TIFF 1024x1024 1024x1024+0+0 16-bit Grayscale DirectClass 2MiB 0.000u 0:00.010 -identify: a0009.tif: invalid TIFF directory; tags are not sorted in ascending order. `TIFFReadDirectory' @ tiff.c/TIFFWarnings/703. -identify: a0009.tif: TIFF directory is missing required "StripByteCounts" field, calculating from imagelength. `TIFFReadDirectory' @ tiff.c/TIFFWarnings/703. + test image from ??? with this error + a0009.tif TIFF 1024x1024 1024x1024+0+0 16-bit Grayscale DirectClass 2MiB 0.000u 0:00.010 + identify: a0009.tif: invalid TIFF directory; tags are not sorted in ascending order. `TIFFReadDirectory' @ tiff.c/TIFFWarnings/703. + identify: a0009.tif: TIFF directory is missing required "StripByteCounts" field, calculating from imagelength. `TIFFReadDirectory' @ tiff.c/TIFFWarnings/703. """ @@ -194,7 +207,6 @@ def test1(self): class TestGzipTif(unittest.TestCase): - def setUp(self): self.unzipped = UtilsTest.getimage("oPPA_5grains_0001.tif.bz2")[:-4] self.zipped = self.unzipped + ".gz" @@ -209,7 +221,6 @@ def test1(self): class TestTif_Rect(unittest.TestCase): - def setUp(self): self.fn = UtilsTest.getimage("testmap1_0002.tif.bz2")[:-4] @@ -220,7 +231,6 @@ def test1(self): class TestTif_Colormap(unittest.TestCase): - def setUp(self): self.fn = UtilsTest.getimage("indexed_color.tif.bz2")[:-4] @@ -278,6 +288,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/codecs/test_xcaliburimage.py b/src/fabio/test/codecs/test_xcaliburimage.py index 363cda14..63560ada 100644 --- a/src/fabio/test/codecs/test_xcaliburimage.py +++ b/src/fabio/test/codecs/test_xcaliburimage.py @@ -31,32 +31,43 @@ Unit tests for xcalibur struct used by crysalis to represent the mask """ + __authors__ = ["Jérôme Kieffer"] __contact__ = "jerome.kieffer@esrf.eu" __license__ = "MIT" __copyright__ = "2022 ESRF" -__date__ = "23/02/2023" +__date__ = "27/10/2025" import unittest -import os -import time import logging - -logger = logging.getLogger(__name__) import numpy import fabio from fabio.xcaliburimage import CcdCharacteristiscs, XcaliburImage from ..utilstest import UtilsTest +logger = logging.getLogger(__name__) + class TestCcdCharacteristiscs(unittest.TestCase): - """ test CcdCharacteristiscs struct reader """ + """test CcdCharacteristiscs struct reader""" + @classmethod - def setUpClass(cls)->None: - cls.ccdfiles = [UtilsTest.getimage(i) for i in ("scan0001.ccd", "scan0005.ccd", "scan0050.ccd", "scan0059.ccd", "scan0066.ccd")] + def setUpClass(cls) -> None: + cls.ccdfiles = [ + UtilsTest.getimage(i) + for i in ( + "scan0001.ccd", + "scan0005.ccd", + "scan0050.ccd", + "scan0059.ccd", + "scan0066.ccd", + ) + ] + @classmethod - def tearDownClass(cls)->None: - cls.ccfiles = None + def tearDownClass(cls) -> None: + cls.ccfiles = None + def test_parse(self): for afile in self.ccdfiles: ref = CcdCharacteristiscs.read(afile) @@ -66,21 +77,26 @@ def test_parse(self): if what.__getattribute__(key) == tuple(): what.__setattr__(key, []) self.assertEqual(ref, obt, f"{afile} matches ") - + + class testXcalibureImage(unittest.TestCase): @classmethod - def setUpClass(cls)->None: + def setUpClass(cls) -> None: cls.filename = UtilsTest.getimage("Pilatus1M.cbf") + @classmethod - def tearDownClass(cls)->None: - cls.filename = None + def tearDownClass(cls) -> None: + cls.filename = None + def test_decomposition(self): - ref = (fabio.open(self.filename).data<0).astype("int8") + ref = (fabio.open(self.filename).data < 0).astype("int8") xcal = XcaliburImage(data=ref) try: - import pyFAI.ext.dynamic_rectangle + import pyFAI.ext.dynamic_rectangle # noqa except ImportError: - logger.warning("PyFAI not available: only a coarse description of the mask is provided") + logger.warning( + "PyFAI not available: only a coarse description of the mask is provided" + ) precise = False else: precise = True @@ -88,19 +104,17 @@ def test_decomposition(self): ccd = xcal.decompose(full=True) obt = ccd.build_mask(ref.shape) if precise: - self.assertTrue(numpy.allclose(ref, obt), "mask is the same") + self.assertTrue(numpy.allclose(ref, obt), "mask is the same") + - def suite(): loadTests = unittest.defaultTestLoader.loadTestsFromTestCase testsuite = unittest.TestSuite() testsuite.addTest(loadTests(TestCcdCharacteristiscs)) - testsuite.addTest(loadTests(testXcalibureImage)) + testsuite.addTest(loadTests(testXcalibureImage)) return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) - - \ No newline at end of file diff --git a/src/fabio/test/codecs/test_xsdimage.py b/src/fabio/test/codecs/test_xsdimage.py index 9043c577..e43ca7ff 100755 --- a/src/fabio/test/codecs/test_xsdimage.py +++ b/src/fabio/test/codecs/test_xsdimage.py @@ -35,19 +35,17 @@ import unittest import numpy import logging - -logger = logging.getLogger(__name__) - import fabio.xsdimage from ..utilstest import UtilsTest +logger = logging.getLogger(__name__) + # filename dim1 dim2 min max mean stddev values are from OD Sapphire 3.0 TESTIMAGES = """XSDataImage.xml 512 512 86 61204 511.63 667.15 XSDataImageInv.xml 512 512 -0.2814 0.22705039 2.81e-08 0.010""" class TestXSD(unittest.TestCase): - def setUp(self): if fabio.xsdimage.etree is None: self.skipTest("etree is not available") @@ -75,18 +73,25 @@ def test_read(self): self.assertEqual(shape, obj.shape) def test_same(self): - """ test if an image is the same as the EDF equivalent""" + """test if an image is the same as the EDF equivalent""" xsd = fabio.open(self.fn["XSDataImage.xml"]) edf = fabio.open(self.fn["XSDataImage.edf"]) - self.assertAlmostEqual(0, abs(xsd.data - edf.data).max(), 1, "images are the same") + self.assertAlmostEqual( + 0, abs(xsd.data - edf.data).max(), 1, "images are the same" + ) def test_invert(self): - """ Tests that 2 matrixes are invert """ + """Tests that 2 matrixes are invert""" m1 = fabio.open(self.fn["XSDataImage.xml"]) m2 = fabio.open(self.fn["XSDataImageInv.xml"]) - delta = abs(numpy.dot(m1.data, m2.data) - numpy.identity(m1.data.shape[0])).max() + delta = abs( + numpy.dot(m1.data, m2.data) - numpy.identity(m1.data.shape[0]) + ).max() if delta >= 1e-3: - logger.error("Matrices are not invert of each other !!! prod = %s", numpy.matrix(m1.data) * numpy.matrix(m2.data)) + logger.error( + "Matrices are not invert of each other !!! prod = %s", + numpy.matrix(m1.data) * numpy.matrix(m2.data), + ) self.assertTrue(delta < 1e-3, "matrices are invert of each other") @@ -97,6 +102,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/profile_all.py b/src/fabio/test/profile_all.py index bd8c116c..a8e88d8e 100755 --- a/src/fabio/test/profile_all.py +++ b/src/fabio/test/profile_all.py @@ -41,6 +41,7 @@ from . import test_all import logging + profiler = logging.getLogger("memProf") profiler.setLevel(logging.DEBUG) profiler.handlers.append(logging.FileHandler("profile.log")) @@ -55,7 +56,6 @@ class TestResult(unittest.TestResult): - def startTest(self, test): if sys.platform == "win32": raise RuntimeError(WIN32_ERROR) @@ -67,17 +67,20 @@ def stopTest(self, test): unittest.TestResult.stopTest(self, test) if sys.platform == "win32": raise RuntimeError(WIN32_ERROR) - mem = (resource.getrusage(resource.RUSAGE_SELF).ru_maxrss - self.__mem_start) / 1e3 - profiler.info(f"Time: {time.perf_counter() - self.__time_start:.3f}s \t RAM: {mem:.3f}Mb\t{test.id()}") + mem = ( + resource.getrusage(resource.RUSAGE_SELF).ru_maxrss - self.__mem_start + ) / 1e3 + profiler.info( + f"Time: {time.perf_counter() - self.__time_start:.3f}s \t RAM: {mem:.3f}Mb\t{test.id()}" + ) class ProfileTestRunner(unittest.TextTestRunner): - def _makeResult(self): return TestResult(stream=sys.stderr, descriptions=True, verbosity=1) -if __name__ == '__main__': +if __name__ == "__main__": suite = test_all.suite() runner = ProfileTestRunner() testresult = runner.run(suite) diff --git a/src/fabio/test/test_all.py b/src/fabio/test/test_all.py index fc2038c4..8b6d4636 100755 --- a/src/fabio/test/test_all.py +++ b/src/fabio/test/test_all.py @@ -27,9 +27,6 @@ import sys import logging import unittest - -logger = logging.getLogger(__name__) - from . import test_fabio_image from . import test_filenames from . import test_file_series @@ -54,6 +51,8 @@ from . import test_utils_cli from . import test_import +logger = logging.getLogger(__name__) + def suite(): testSuite = unittest.TestSuite() @@ -83,7 +82,7 @@ def suite(): return testSuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() if not runner.run(suite()).wasSuccessful(): sys.exit(1) diff --git a/src/fabio/test/test_compression.py b/src/fabio/test/test_compression.py index 139f516c..f8e753b2 100755 --- a/src/fabio/test/test_compression.py +++ b/src/fabio/test/test_compression.py @@ -32,16 +32,15 @@ __contact__ = "Jerome.Kieffer@esrf.fr" __license__ = "MIT" __copyright__ = "2011-2016 ESRF" -__date__ = "03/04/2020" +__date__ = "27/10/2025" import unittest import numpy import logging +from fabio import compression logger = logging.getLogger(__name__) -from fabio import compression - class TestByteOffset(unittest.TestCase): """ @@ -49,67 +48,125 @@ class TestByteOffset(unittest.TestCase): """ def setUp(self): - self.ds = numpy.array([0, 1, 2, 127, 0, 1, 2, 128, 0, 1, 2, 32767, 0, 1, 2, 32768, 0, 1, 2, 2147483647, 0, 1, 2, 2147483648, 0, 1, 2, 128, 129, 130, 32767, 32768, 128, 129, 130, 32768, 2147483647, 2147483648]) - self.ref = b'\x00\x01\x01}\x81\x01\x01~\x80\x80\xff\x01\x01\x80\xfd\x7f\x80\x01\x80\x01\x01\x80\xfe\x7f\x80\x00\x80\x00\x80\xff\xff\x01\x01\x80\x00\x80\xfd\xff\xff\x7f\x80\x00\x80\x01\x00\x00\x80\x01\x01\x80\x00\x80\xfe\xff\xff\x7f\x80\x00\x80\x00\x00\x00\x80\x00\x00\x00\x80\xff\xff\xff\xff\x01\x01~\x01\x01\x80}\x7f\x01\x80\x80\x80\x01\x01\x80~\x7f\x80\x00\x80\xff\x7f\xff\x7f\x01' + self.ds = numpy.array( + [ + 0, + 1, + 2, + 127, + 0, + 1, + 2, + 128, + 0, + 1, + 2, + 32767, + 0, + 1, + 2, + 32768, + 0, + 1, + 2, + 2147483647, + 0, + 1, + 2, + 2147483648, + 0, + 1, + 2, + 128, + 129, + 130, + 32767, + 32768, + 128, + 129, + 130, + 32768, + 2147483647, + 2147483648, + ] + ) + self.ref = b"\x00\x01\x01}\x81\x01\x01~\x80\x80\xff\x01\x01\x80\xfd\x7f\x80\x01\x80\x01\x01\x80\xfe\x7f\x80\x00\x80\x00\x80\xff\xff\x01\x01\x80\x00\x80\xfd\xff\xff\x7f\x80\x00\x80\x01\x00\x00\x80\x01\x01\x80\x00\x80\xfe\xff\xff\x7f\x80\x00\x80\x00\x00\x00\x80\x00\x00\x00\x80\xff\xff\xff\xff\x01\x01~\x01\x01\x80}\x7f\x01\x80\x80\x80\x01\x01\x80~\x7f\x80\x00\x80\xff\x7f\xff\x7f\x01" def tearDown(self): unittest.TestCase.tearDown(self) self.ds = self.ref = None def testComp(self): - """ - """ + """ """ # first with numpy ds = numpy.array([0, 128]) ref = b"\x00\x80\x80\00" self.assertEqual(ref, compression.compByteOffset_numpy(ds), "test +128") ds = numpy.array([0, -128]) - ref = b'\x00\x80\x80\xff' + ref = b"\x00\x80\x80\xff" self.assertEqual(ref, compression.compByteOffset_numpy(ds), "test -128") ds = numpy.array([10, -128]) - ref = b'\n\x80v\xff' + ref = b"\n\x80v\xff" self.assertEqual(ref, compression.compByteOffset_numpy(ds), "test +10 -128") - self.assertEqual(self.ref, compression.compByteOffset_numpy(self.ds), "test larger") + self.assertEqual( + self.ref, compression.compByteOffset_numpy(self.ds), "test larger" + ) # Then with cython 32 bits ds = numpy.array([0, 128], dtype="int32") ref = b"\x00\x80\x80\00" self.assertEqual(ref, compression.compByteOffset_cython(ds), "test +128") ds = numpy.array([0, -128], dtype="int32") - ref = b'\x00\x80\x80\xff' + ref = b"\x00\x80\x80\xff" self.assertEqual(ref, compression.compByteOffset_cython(ds), "test -128") ds = numpy.array([10, -128], dtype="int32") - ref = b'\n\x80v\xff' + ref = b"\n\x80v\xff" self.assertEqual(ref, compression.compByteOffset_cython(ds), "test +10 -128") - self.assertEqual(self.ref, compression.compByteOffset_cython(self.ds), "test larger") + self.assertEqual( + self.ref, compression.compByteOffset_cython(self.ds), "test larger" + ) # Then with cython 64bits ds = numpy.array([0, 128], dtype="int64") ref = b"\x00\x80\x80\00" self.assertEqual(ref, compression.compByteOffset_cython(ds), "test +128") ds = numpy.array([0, -128], dtype="int64") - ref = b'\x00\x80\x80\xff' + ref = b"\x00\x80\x80\xff" self.assertEqual(ref, compression.compByteOffset_cython(ds), "test -128") ds = numpy.array([10, -128], dtype="int64") - ref = b'\n\x80v\xff' + ref = b"\n\x80v\xff" self.assertEqual(ref, compression.compByteOffset_cython(ds), "test +10 -128") - self.assertEqual(self.ref, compression.compByteOffset_cython(self.ds), "test larger") + self.assertEqual( + self.ref, compression.compByteOffset_cython(self.ds), "test larger" + ) def testSC(self): """test that datasets are unchanged after various compression/decompressions""" - obt_np = compression.decByteOffset_numpy(compression.compByteOffset_numpy(self.ds)) + obt_np = compression.decByteOffset_numpy( + compression.compByteOffset_numpy(self.ds) + ) self.assertEqual(abs(self.ds - obt_np).max(), 0.0, "numpy-numpy algo") - obt_cy = compression.decByteOffset_cython(compression.compByteOffset_numpy(self.ds)) + obt_cy = compression.decByteOffset_cython( + compression.compByteOffset_numpy(self.ds) + ) self.assertEqual(abs(self.ds - obt_cy).max(), 0.0, "cython-numpy algo") - obt_cy2 = compression.decByteOffset_cython(compression.compByteOffset_numpy(self.ds), self.ds.size) + obt_cy2 = compression.decByteOffset_cython( + compression.compByteOffset_numpy(self.ds), self.ds.size + ) self.assertEqual(abs(self.ds - obt_cy2).max(), 0.0, "cython2-numpy algo_orig") - obt_np = compression.decByteOffset_numpy(compression.compByteOffset_cython(self.ds)) + obt_np = compression.decByteOffset_numpy( + compression.compByteOffset_cython(self.ds) + ) self.assertEqual(abs(self.ds - obt_np).max(), 0.0, "numpy-numpy algo") - obt_cy = compression.decByteOffset_cython(compression.compByteOffset_cython(self.ds)) + obt_cy = compression.decByteOffset_cython( + compression.compByteOffset_cython(self.ds) + ) self.assertEqual(abs(self.ds - obt_cy).max(), 0.0, "cython-numpy algo") - obt_cy2 = compression.decByteOffset_cython(compression.compByteOffset_cython(self.ds), self.ds.size) + obt_cy2 = compression.decByteOffset_cython( + compression.compByteOffset_cython(self.ds), self.ds.size + ) self.assertEqual(abs(self.ds - obt_cy2).max(), 0.0, "cython2-numpy algo_orig") @@ -120,6 +177,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/test_densification.py b/src/fabio/test/test_densification.py index 54c14f60..fd20d330 100644 --- a/src/fabio/test/test_densification.py +++ b/src/fabio/test/test_densification.py @@ -32,18 +32,18 @@ __contact__ = "Jerome.Kieffer@esrf.fr" __license__ = "MIT" __copyright__ = "2020 ESRF" -__date__ = "02/09/2024" +__date__ = "27/10/2025" import unittest import numpy import logging -logger = logging.getLogger(__name__) from ..sparseimage import densify, cython_densify from ..ext.dense import distribution_uniform_mtc, distribution_normal_mtc +logger = logging.getLogger(__name__) + class TestDensification(unittest.TestCase): - def test_rng_uniform(self): shape = (100, 100) U = distribution_uniform_mtc(shape).ravel() @@ -70,8 +70,10 @@ def test_cython(self): shape = 256, 256 nframes = 8 vsize = 181 # This is cheated to avoid interpolation issues with rounding 128*sqrt(2) - y, x = numpy.ogrid[-shape[0] // 2:-shape[0] // 2 + shape[0], - -shape[1] // 2:-shape[1] // 2 + shape[1]] + y, x = numpy.ogrid[ + -shape[0] // 2 : -shape[0] // 2 + shape[0], + -shape[1] // 2 : -shape[1] // 2 + shape[1], + ] # To make this test "robust", those two radial position arrays needs to be in float64 ... in production float32 is more common r2d = numpy.sqrt(x * x + y * y).astype(numpy.float64) radius = numpy.linspace(0, r2d.max(), vsize).astype(numpy.float64) @@ -80,7 +82,9 @@ def test_cython(self): osc = numpy.random.randint(40, 100, size=nframes) indptr = numpy.zeros(nframes + 1, dtype=int) indptr[1:] = numpy.cumsum(npeak) - index = numpy.random.randint(0, numpy.prod(shape), size=npeak.sum()).astype(numpy.uint32) + index = numpy.random.randint(0, numpy.prod(shape), size=npeak.sum()).astype( + numpy.uint32 + ) intensity = numpy.random.randint(0, 10, size=npeak.sum()).astype(numpy.uint16) frames = numpy.empty((nframes, *shape), dtype=numpy.uint16) background = numpy.empty((nframes, vsize), dtype=numpy.float32) @@ -91,24 +95,70 @@ def test_cython(self): background[i] = numpy.arcsinh(scale[i] * numpy.sinc(radius / osc[i]) ** 2) noise[i] = abs(numpy.diff(background[i], prepend=background[i, 0])) f[...] = numpy.arcsinh(scale[i] * numpy.sinc(r2d / osc[i]) ** 2).round() - f.ravel()[index[indptr[i]:indptr[i + 1]]] = intensity[indptr[i]:indptr[i + 1]] - python = densify(r2d, radius, index[indptr[i]:indptr[i + 1]], intensity[indptr[i]:indptr[i + 1]], 0, background[i]) - cython = cython_densify.densify(r2d, radius, index[indptr[i]:indptr[i + 1]], intensity[indptr[i]:indptr[i + 1]], 0, intensity.dtype, background[i]) + f.ravel()[index[indptr[i] : indptr[i + 1]]] = intensity[ + indptr[i] : indptr[i + 1] + ] + python = densify( + r2d, + radius, + index[indptr[i] : indptr[i + 1]], + intensity[indptr[i] : indptr[i + 1]], + 0, + background[i], + ) + cython = cython_densify.densify( + r2d, + radius, + index[indptr[i] : indptr[i + 1]], + intensity[indptr[i] : indptr[i + 1]], + 0, + intensity.dtype, + background[i], + ) if not numpy.all(python == cython): print(python) print(cython) self.assertTrue(numpy.all(python == cython), "python == cython #" + str(i)) # Rounding errors: - delta = (python.astype(int) - f) - self.assertLessEqual(abs(delta).max(), 1, "Maximum difference is 1 due to rounding errors") + delta = python.astype(int) - f + self.assertLessEqual( + abs(delta).max(), 1, "Maximum difference is 1 due to rounding errors" + ) bad = numpy.where(delta) print("#####", i) - self.assertLess(len(bad[0]), numpy.prod(shape) / 500, "python differs from reference on less then 0.2% of the pixel #" + str(i)) + self.assertLess( + len(bad[0]), + numpy.prod(shape) / 500, + "python differs from reference on less then 0.2% of the pixel #" + + str(i), + ) # Now consider the noise ... - python = densify(r2d, radius, index[indptr[i]:indptr[i + 1]], intensity[indptr[i]:indptr[i + 1]], 0, background[i], noise[i], seed=seed) - cython = cython_densify.densify(r2d, radius, index[indptr[i]:indptr[i + 1]], intensity[indptr[i]:indptr[i + 1]], 0, intensity.dtype, background[i], noise[i], seed=seed) - self.assertTrue(abs(python.astype(int) - cython).max() <= 2 * max(1, noise[i].max()), "python is close to cython #" + str(i)) + python = densify( + r2d, + radius, + index[indptr[i] : indptr[i + 1]], + intensity[indptr[i] : indptr[i + 1]], + 0, + background[i], + noise[i], + seed=seed, + ) + cython = cython_densify.densify( + r2d, + radius, + index[indptr[i] : indptr[i + 1]], + intensity[indptr[i] : indptr[i + 1]], + 0, + intensity.dtype, + background[i], + noise[i], + seed=seed, + ) + self.assertTrue( + abs(python.astype(int) - cython).max() <= 2 * max(1, noise[i].max()), + "python is close to cython #" + str(i), + ) def suite(): @@ -118,6 +168,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/test_fabio.py b/src/fabio/test/test_fabio.py index 42f6c292..b0acda55 100644 --- a/src/fabio/test/test_fabio.py +++ b/src/fabio/test/test_fabio.py @@ -31,15 +31,13 @@ import unittest import logging import io - -logger = logging.getLogger(__name__) - from .utilstest import UtilsTest import fabio +logger = logging.getLogger(__name__) -class TestFabio(unittest.TestCase): +class TestFabio(unittest.TestCase): def test_open(self): filename = UtilsTest.getimage("multiframes.edf.bz2") filename = filename.replace(".bz2", "") @@ -103,6 +101,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/test_fabio_convert.py b/src/fabio/test/test_fabio_convert.py index 8a1a8f2d..79f62dab 100644 --- a/src/fabio/test/test_fabio_convert.py +++ b/src/fabio/test/test_fabio_convert.py @@ -20,15 +20,13 @@ import time import unittest import logging - -_logger = logging.getLogger(__name__) - import fabio.app.convert from .utilstest import UtilsTest +_logger = logging.getLogger(__name__) -class TestFabioConvert(unittest.TestCase): +class TestFabioConvert(unittest.TestCase): def create_test_env(self): path = os.path.join(UtilsTest.tempdir, self.id()) os.makedirs(path) @@ -91,10 +89,14 @@ def subprocessFabioConvert(self, *args): commandLine = [sys.executable, self.__script] commandLine.extend(args) _logger.info("Execute: %s", " ".join(commandLine)) - p = subprocess.Popen(commandLine, env=self.__env, shell=False, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + p = subprocess.Popen( + commandLine, + env=self.__env, + shell=False, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) return p def logCommunicate(self, p): @@ -114,98 +116,104 @@ def logCommunicate(self, p): def testSingleFile(self): p = self.subprocessFabioConvert("input/03.edf", "-o=output/03.msk") self.logCommunicate(p) - assert(os.path.exists("output/03.msk")) + assert os.path.exists("output/03.msk") image = fabio.open("output/03.msk") - assert(isinstance(image, fabio.fit2dmaskimage.Fit2dMaskImage)) - assert(image.data.shape == (100, 100)) + assert isinstance(image, fabio.fit2dmaskimage.Fit2dMaskImage) + assert image.data.shape == (100, 100) def testSingleFileToDir(self): p = self.subprocessFabioConvert("input/03.edf", "-F=msk", "-o=output") self.logCommunicate(p) - assert(os.path.exists("output/03.msk")) + assert os.path.exists("output/03.msk") def testSingleFileWithWildcardToDir(self): p = self.subprocessFabioConvert("input/03.*", "-F=msk", "-o=output") self.logCommunicate(p) - assert(os.path.exists("output/03.msk")) + assert os.path.exists("output/03.msk") def testFullFormatName(self): p = self.subprocessFabioConvert("input/03.*", "-F=numpyimage", "-o=output") self.logCommunicate(p) - assert(os.path.exists("output/03.npy")) + assert os.path.exists("output/03.npy") image = fabio.open("output/03.npy") - assert(isinstance(image, fabio.numpyimage.NumpyImage)) - assert(image.data.shape == (100, 100)) + assert isinstance(image, fabio.numpyimage.NumpyImage) + assert image.data.shape == (100, 100) def testForceOption(self): date1 = os.path.getmtime("output/01.msk") date2 = os.path.getmtime("output/02.msk") p = self.subprocessFabioConvert("input/*.edf", "-f", "-F=msk", "-o=output") self.logCommunicate(p) - assert(os.path.exists("output/01.msk")) - assert(date1 < os.path.getmtime("output/01.msk")) - assert(os.path.exists("output/02.msk")) - assert(date2 < os.path.getmtime("output/02.msk")) - assert(os.path.exists("output/03.msk")) + assert os.path.exists("output/01.msk") + assert date1 < os.path.getmtime("output/01.msk") + assert os.path.exists("output/02.msk") + assert date2 < os.path.getmtime("output/02.msk") + assert os.path.exists("output/03.msk") def testRemoveDestinationOption(self): date1 = os.path.getmtime("output/01.msk") date2 = os.path.getmtime("output/02.msk") - p = self.subprocessFabioConvert("input/*.edf", "--remove-destination", "-F=msk", "-o=output") + p = self.subprocessFabioConvert( + "input/*.edf", "--remove-destination", "-F=msk", "-o=output" + ) self.logCommunicate(p) - assert(os.path.exists("output/01.msk")) - assert(date1 < os.path.getmtime("output/01.msk")) - assert(os.path.exists("output/02.msk")) - assert(date2 < os.path.getmtime("output/02.msk")) - assert(os.path.exists("output/03.msk")) + assert os.path.exists("output/01.msk") + assert date1 < os.path.getmtime("output/01.msk") + assert os.path.exists("output/02.msk") + assert date2 < os.path.getmtime("output/02.msk") + assert os.path.exists("output/03.msk") def testNoClobberOption(self): date1 = os.path.getmtime("output/01.msk") date2 = os.path.getmtime("output/02.msk") p = self.subprocessFabioConvert("input/*.edf", "-n", "-F=msk", "-o=output") self.logCommunicate(p) - assert(os.path.exists("output/01.msk")) - assert(date1 == os.path.getmtime("output/01.msk")) - assert(os.path.exists("output/02.msk")) - assert(date2 == os.path.getmtime("output/02.msk")) - assert(os.path.exists("output/03.msk")) + assert os.path.exists("output/01.msk") + assert date1 == os.path.getmtime("output/01.msk") + assert os.path.exists("output/02.msk") + assert date2 == os.path.getmtime("output/02.msk") + assert os.path.exists("output/03.msk") def testUpdateOption(self): date1 = os.path.getmtime("output/01.msk") date2 = os.path.getmtime("output/02.msk") - p = self.subprocessFabioConvert("input/*.edf", "--update", "-F=msk", "-o=output") + p = self.subprocessFabioConvert( + "input/*.edf", "--update", "-F=msk", "-o=output" + ) self.logCommunicate(p) - assert(os.path.exists("output/01.msk")) - assert(date1 < os.path.getmtime("output/01.msk")) - assert(os.path.exists("output/02.msk")) - assert(date2 == os.path.getmtime("output/02.msk")) - assert(os.path.exists("output/03.msk")) + assert os.path.exists("output/01.msk") + assert date1 < os.path.getmtime("output/01.msk") + assert os.path.exists("output/02.msk") + assert date2 == os.path.getmtime("output/02.msk") + assert os.path.exists("output/03.msk") def testDefaultOption(self): date1 = os.path.getmtime("output/01.msk") date2 = os.path.getmtime("output/02.msk") p = self.subprocessFabioConvert("input/*.edf", "-F=msk", "-o=output") - p.stdin.write(b'yes\n') - p.stdin.write(b'no\n') + p.stdin.write(b"yes\n") + p.stdin.write(b"no\n") self.logCommunicate(p) - assert(os.path.exists("output/01.msk")) - assert(date1 < os.path.getmtime("output/01.msk")) - assert(os.path.exists("output/02.msk")) - assert(date2 == os.path.getmtime("output/02.msk")) - assert(os.path.exists("output/03.msk")) + assert os.path.exists("output/01.msk") + assert date1 < os.path.getmtime("output/01.msk") + assert os.path.exists("output/02.msk") + assert date2 == os.path.getmtime("output/02.msk") + assert os.path.exists("output/03.msk") def testInteractiveOption(self): date1 = os.path.getmtime("output/01.msk") date2 = os.path.getmtime("output/02.msk") - p = self.subprocessFabioConvert("input/*.edf", "-n", "-i", "-F=msk", "-o=output") - p.stdin.write(b'yes\n') - p.stdin.write(b'no\n') + p = self.subprocessFabioConvert( + "input/*.edf", "-n", "-i", "-F=msk", "-o=output" + ) + p.stdin.write(b"yes\n") + p.stdin.write(b"no\n") self.logCommunicate(p) - assert(os.path.exists("output/01.msk")) - assert(date1 < os.path.getmtime("output/01.msk")) - assert(os.path.exists("output/02.msk")) - assert(date2 == os.path.getmtime("output/02.msk")) - assert(os.path.exists("output/03.msk")) + assert os.path.exists("output/01.msk") + assert date1 < os.path.getmtime("output/01.msk") + assert os.path.exists("output/02.msk") + assert date2 == os.path.getmtime("output/02.msk") + assert os.path.exists("output/03.msk") def suite(): @@ -215,6 +223,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/test_fabio_image.py b/src/fabio/test/test_fabio_image.py index 0cc2a6c1..ec258197 100644 --- a/src/fabio/test/test_fabio_image.py +++ b/src/fabio/test/test_fabio_image.py @@ -27,21 +27,19 @@ testsuite by Jerome Kieffer (Jerome.Kieffer@esrf.eu) 28/11/2014 """ + import unittest -import sys import os import numpy import copy import logging -import numbers - -logger = logging.getLogger(__name__) - from ..fabioimage import FabioImage from .. import fabioutils from ..utils import pilutils from .utilstest import UtilsTest +logger = logging.getLogger(__name__) + try: import pathlib except ImportError: @@ -52,7 +50,7 @@ class Test50000(unittest.TestCase): - """ test with 50000 everywhere""" + """test with 50000 everywhere""" def setUp(self): """make the image""" @@ -105,8 +103,7 @@ def setUp(self): dat2[slic] = dat2[slic] + 100 assert self.obj.maxval is None assert self.obj.minval is None - self.npix = (slic[0].stop - slic[0].start) * \ - (slic[1].stop - slic[1].start) + self.npix = (slic[0].stop - slic[0].start) * (slic[1].stop - slic[1].start) def testgetmax(self): """check max""" @@ -117,7 +114,7 @@ def testgetmin(self): self.assertEqual(self.obj.getmin(), 0) def testintegratearea(self): - """ check integrations""" + """check integrations""" self.obj.resetvals() area1 = self.obj.integrate_area(self.cord) self.obj.resetvals() @@ -131,15 +128,18 @@ def testRebin(self): res = numpy.array([[13, 17], [45, 49]]) fabimg = FabioImage(data=big, header={}) fabimg.rebin(4, 4) - self.assertEqual(abs(res - fabimg.data).max(), 0, "data are the same after rebin") + self.assertEqual( + abs(res - fabimg.data).max(), 0, "data are the same after rebin" + ) class TestOpen(unittest.TestCase): """check opening compressed files""" + testfile = os.path.join(UtilsTest.tempdir, "testfile") def setUp(self): - """ create test files""" + """create test files""" if not os.path.isfile(self.testfile): with open(self.testfile, "wb") as f: f.write(b"{ hello }") @@ -152,19 +152,19 @@ def setUp(self): self.obj = FabioImage() def testFlat(self): - """ no compression""" + """no compression""" res = self.obj._open(self.testfile) self.assertEqual(res.read(), b"{ hello }") res.close() def testgz(self): - """ gzipped """ + """gzipped""" res = self.obj._open(self.testfile + ".gz") self.assertEqual(res.read(), b"{ hello }") res.close() def testbz2(self): - """ bzipped""" + """bzipped""" res = self.obj._open(self.testfile + ".bz2") self.assertEqual(res.read(), b"{ hello }") res.close() @@ -182,27 +182,28 @@ def test_pathlib(self): class TestPilImage(unittest.TestCase): - """ check PIL creation""" + """check PIL creation""" def setUp(self): if pilutils.Image is None: self.skipTest("PIL is not available") """ list of working numeric types""" - self.okformats = [numpy.uint8, - numpy.int8, - numpy.uint16, - numpy.int16, - numpy.uint32, - numpy.int32, - numpy.float32] + self.okformats = [ + numpy.uint8, + numpy.int8, + numpy.uint16, + numpy.int16, + numpy.uint32, + numpy.int32, + numpy.float32, + ] def mkdata(self, shape, typ): - """ generate [01] testdata """ + """generate [01] testdata""" return (numpy.random.random(shape)).astype(typ) def testpil(self): - for typ in self.okformats: for shape in [(10, 20), (431, 1325)]: testdata = self.mkdata(shape, typ) @@ -211,13 +212,18 @@ def testpil(self): for i in [0, 5, 6, shape[1] - 1]: for j in [0, 5, 7, shape[0] - 1]: errstr = str(typ) + " %d %d %f %f t=%s" % ( - i, j, testdata[j, i], pim.getpixel((i, j)), typ) + i, + j, + testdata[j, i], + pim.getpixel((i, j)), + typ, + ) er1 = img.data[j, i] - pim.getpixel((i, j)) er2 = img.data[j, i] + pim.getpixel((i, j)) # difference as % error in case of rounding - if er2 != 0.: + if er2 != 0.0: err = er1 / er2 else: err = er1 @@ -226,10 +232,10 @@ def testpil(self): class TestPilImage2(TestPilImage): - """ check with different numbers""" + """check with different numbers""" def mkdata(self, shape, typ): - """ positive and big""" + """positive and big""" if numpy.issubdtype(typ, numpy.integer): maxi = numpy.iinfo(typ).max else: @@ -238,10 +244,10 @@ def mkdata(self, shape, typ): class TestPilImage3(TestPilImage): - """ check with different numbers""" + """check with different numbers""" def mkdata(self, shape, typ): - """ positive, negative and big""" + """positive, negative and big""" if numpy.issubdtype(typ, numpy.integer): maxi = numpy.iinfo(typ).max else: @@ -250,7 +256,6 @@ def mkdata(self, shape, typ): class TestDeprecatedFabioImage(unittest.TestCase): - def test_patch_dim(self): data = numpy.array(numpy.arange(3 * 10)).reshape(3, 10) image = FabioImage(data=data) @@ -267,7 +272,6 @@ def test_cleanup_pilimage_cache(self): class TestFabioImage(unittest.TestCase): - def test_iter_abort_iteration(self): data = numpy.zeros((2, 2)) image = FabioImage(data=data) @@ -289,6 +293,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/test_failing_files.py b/src/fabio/test/test_failing_files.py index 981c234b..08afd434 100644 --- a/src/fabio/test/test_failing_files.py +++ b/src/fabio/test/test_failing_files.py @@ -22,15 +22,13 @@ # along with this program. If not, see . # -"""Test failing files -""" +"""Test failing files""" import unittest import os import io import fabio import shutil - from .utilstest import UtilsTest @@ -45,35 +43,34 @@ def setUpClass(cls): @classmethod def createResources(cls, directory): - cls.txt_filename = os.path.join(directory, "test.txt") with io.open(cls.txt_filename, "w+t") as f: - f.write(u"Kikoo") + f.write("Kikoo") cls.bad_edf_filename = os.path.join(directory, "bad_edf.edf") with io.open(cls.bad_edf_filename, "w+b") as f: f.write(b"\r{") - f.write(b"\x00\xFF\x99" * 10) + f.write(b"\x00\xff\x99" * 10) cls.bad_edf2_filename = os.path.join(directory, "bad_edf2.edf") with io.open(cls.bad_edf2_filename, "w+b") as f: f.write(b"\n{\n\n}\n") - f.write(b"\xFF\x00\x99" * 10) + f.write(b"\xff\x00\x99" * 10) cls.bad_msk_filename = os.path.join(directory, "bad_msk.msk") with io.open(cls.bad_msk_filename, "w+b") as f: - f.write(b'M\x00\x00\x00A\x00\x00\x00S\x00\x00\x00K\x00\x00\x00') - f.write(b"\x00\xFF\x99" * 10) + f.write(b"M\x00\x00\x00A\x00\x00\x00S\x00\x00\x00K\x00\x00\x00") + f.write(b"\x00\xff\x99" * 10) cls.bad_dm3_filename = os.path.join(directory, "bad_dm3.dm3") with io.open(cls.bad_dm3_filename, "w+b") as f: - f.write(b'\x00\x00\x00\x03') - f.write(b"\x00\xFF\x99" * 10) + f.write(b"\x00\x00\x00\x03") + f.write(b"\x00\xff\x99" * 10) cls.bad_npy_filename = os.path.join(directory, "bad_numpy.npy") with io.open(cls.bad_npy_filename, "w+b") as f: f.write(b"\x93NUMPY") - f.write(b"\x00\xFF\x99" * 10) + f.write(b"\x00\xff\x99" * 10) cls.missing_filename = os.path.join(directory, "test.missing") @@ -110,6 +107,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/test_file_series.py b/src/fabio/test/test_file_series.py index 4a0eec84..68bd7249 100644 --- a/src/fabio/test/test_file_series.py +++ b/src/fabio/test/test_file_series.py @@ -33,14 +33,13 @@ import os import shutil import numpy - -logger = logging.getLogger(__name__) - import fabio from fabio.file_series import numbered_file_series, file_series, filename_series from fabio.file_series import FileSeries from .utilstest import UtilsTest +logger = logging.getLogger(__name__) + class TestRandomSeries(unittest.TestCase): """arbitrary series""" @@ -68,19 +67,19 @@ class TestEdfNumbered(unittest.TestCase): """ def setUp(self): - """ note extension has the . in it""" + """note extension has the . in it""" self.fso = numbered_file_series("mydata", 0, 10005, ".edf") def testfirst(self): - """ first in series""" + """first in series""" self.assertEqual(self.fso.first(), "mydata0000.edf") def testlast(self): - """ last in series""" + """last in series""" self.assertEqual(self.fso.last(), "mydata10005.edf") def testnext(self): - """ check all in order """ + """check all in order""" mylist = ["mydata%04d.edf" % (i) for i in range(0, 10005)] i = 1 while i < len(mylist): @@ -88,7 +87,7 @@ def testnext(self): i += 1 def testprevious(self): - """ check all in order """ + """check all in order""" mylist = ["mydata%04d.edf" % (i) for i in range(0, 10005)] i = 10003 self.fso.jump(10004) @@ -114,7 +113,6 @@ def testlen(self): class TestFileSeries(unittest.TestCase): - @classmethod def setUpClass(cls): cls.tmp_directory = os.path.join(UtilsTest.tempdir, cls.__name__) @@ -176,7 +174,12 @@ def get_multiframe_files(self): return filenames def get_anyframe_files(self): - filenames = ["image_c_000.edf", "image_c_001.edf", "image_c_002.edf", "image_c_003.edf"] + filenames = [ + "image_c_000.edf", + "image_c_001.edf", + "image_c_002.edf", + "image_c_003.edf", + ] filenames = [self.get_filename(f) for f in filenames] return filenames @@ -342,7 +345,6 @@ def test_filename_iterator(self): serie.close() def test_filename_generator(self): - def generator(): filenames = self.get_anyframe_files() for filename in filenames: @@ -353,7 +355,9 @@ def generator(): serie.close() def test_with_numbered_file_series(self): - filenames = numbered_file_series(self.get_filename("image_c_"), 0, 3, ".edf", digits=3) + filenames = numbered_file_series( + self.get_filename("image_c_"), 0, 3, ".edf", digits=3 + ) serie = FileSeries(filenames=filenames) self.assertEqual(serie.nframes, 10) serie.close() @@ -375,6 +379,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/test_filename_steps.py b/src/fabio/test/test_filename_steps.py index ca646c23..d5dc1dd6 100644 --- a/src/fabio/test/test_filename_steps.py +++ b/src/fabio/test/test_filename_steps.py @@ -29,14 +29,12 @@ import unittest import logging +import fabio logger = logging.getLogger(__name__) -import fabio - class TestNext(unittest.TestCase): - def test_next1(self): files = [ ["data0001.edf", "data0002.edf"], @@ -49,7 +47,6 @@ def test_next1(self): class TestPrev(unittest.TestCase): - def test_prev1(self): files = [ ["data0001.edf", "data0000.edf"], @@ -62,7 +59,6 @@ def test_prev1(self): class TestJump(unittest.TestCase): - def test_jump1(self): files = [ ["data0001.edf", "data99993.edf", 99993], @@ -83,6 +79,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/test_filenames.py b/src/fabio/test/test_filenames.py index 81823d14..fe36b572 100644 --- a/src/fabio/test/test_filenames.py +++ b/src/fabio/test/test_filenames.py @@ -34,39 +34,37 @@ import os import logging import tempfile - - -logger = logging.getLogger(__name__) - import fabio import numpy +logger = logging.getLogger(__name__) + CASES = [ - (1, 'edf', "data0001.edf"), - (10001, 'edf', "data10001.edf"), - (10001, 'edf', "data10001.edf.gz"), - (10001, 'edf', "data10001.edf.bz2"), - (2, 'marccd', "data0002.mccd"), - (12345, 'marccd', "data12345.mccd"), - (10001, 'marccd', "data10001.mccd.gz"), - (10001, 'marccd', "data10001.mccd.bz2"), - (123, 'marccd', "data123.mccd.gz"), - (3, 'tif_or_pilatus', "data0003.tif"), - (4, 'tif_or_pilatus', "data0004.tiff"), - (12, 'bruker', "sucrose101.012.gz"), - (99, 'bruker', "sucrose101.099"), - (99, 'bruker', "sucrose101.0099"), - (99, 'bruker', "sucrose101.0099.bz2"), - (99, 'bruker', "sucrose101.0099.gz"), - (2, 'fit2dmask', "fit2d.msk"), - (None, 'fit2dmask', "mymask.msk"), - (670005, 'edf', 'S82P670005.edf'), - (670005, 'edf', 'S82P670005.edf.gz'), + (1, "edf", "data0001.edf"), + (10001, "edf", "data10001.edf"), + (10001, "edf", "data10001.edf.gz"), + (10001, "edf", "data10001.edf.bz2"), + (2, "marccd", "data0002.mccd"), + (12345, "marccd", "data12345.mccd"), + (10001, "marccd", "data10001.mccd.gz"), + (10001, "marccd", "data10001.mccd.bz2"), + (123, "marccd", "data123.mccd.gz"), + (3, "tif_or_pilatus", "data0003.tif"), + (4, "tif_or_pilatus", "data0004.tiff"), + (12, "bruker", "sucrose101.012.gz"), + (99, "bruker", "sucrose101.099"), + (99, "bruker", "sucrose101.0099"), + (99, "bruker", "sucrose101.0099.bz2"), + (99, "bruker", "sucrose101.0099.gz"), + (2, "fit2dmask", "fit2d.msk"), + (None, "fit2dmask", "mymask.msk"), + (670005, "edf", "S82P670005.edf"), + (670005, "edf", "S82P670005.edf.gz"), # based on only the name it can be either img or oxd - (1, 'dtrek_or_oxd_or_hipic_or_raxis', 'mb_LP_1_001.img'), - (2, 'dtrek_or_oxd_or_hipic_or_raxis', 'mb_LP_1_002.img.gz'), - (3, 'dtrek_or_oxd_or_hipic_or_raxis', 'mb_LP_1_003.img.bz2'), - (3, 'dtrek_or_oxd_or_hipic_or_raxis', os.path.join("data", 'mb_LP_1_003.img.bz2')), + (1, "dtrek_or_oxd_or_hipic_or_raxis", "mb_LP_1_001.img"), + (2, "dtrek_or_oxd_or_hipic_or_raxis", "mb_LP_1_002.img.gz"), + (3, "dtrek_or_oxd_or_hipic_or_raxis", "mb_LP_1_003.img.bz2"), + (3, "dtrek_or_oxd_or_hipic_or_raxis", os.path.join("data", "mb_LP_1_003.img.bz2")), ] MORE_CASES = [ @@ -75,29 +73,37 @@ ("data0999.pnm", "data1000.pnm", 999), ("data123457.edf", "data123456.edf", 123457), ("d0ata000100.mccd", "d0ata000012.mccd", 100), - (os.path.join("images/sampledir", "P33S670003.edf"), - os.path.join("images/sampledir", "P33S670002.edf"), 670003), - (os.path.join("images/P33S67", "P33S670003.edf"), - os.path.join("images/P33S67", "P33S670002.edf"), 670003), + ( + os.path.join("images/sampledir", "P33S670003.edf"), + os.path.join("images/sampledir", "P33S670002.edf"), + 670003, + ), + ( + os.path.join("images/P33S67", "P33S670003.edf"), + os.path.join("images/P33S67", "P33S670002.edf"), + 670003, + ), ("image2301.mar2300", "image2300.mar2300", 2301), ("image2300.mar2300", "image2301.mar2300", 2300), ("image.0123", "image.1234", 123), ("mymask.msk", "mymask.msk", None), - ("data_123.mccd.bz2", "data_001.mccd.bz2", 123) + ("data_123.mccd.bz2", "data_001.mccd.bz2", 123), ] class TestFilenames(unittest.TestCase): - """ check the name -> number, type conversions """ + """check the name -> number, type conversions""" def test_many_cases(self): - """ loop over CASES """ + """loop over CASES""" for num, typ, name in CASES: obj = fabio.FilenameObject(filename=name) - self.assertEqual(num, obj.num, name + " num=" + str(num) + - " != obj.num=" + str(obj.num)) - self.assertEqual(typ, "_or_".join(obj.format), - name + " " + "_or_".join(obj.format)) + self.assertEqual( + num, obj.num, name + " num=" + str(num) + " != obj.num=" + str(obj.num) + ) + self.assertEqual( + typ, "_or_".join(obj.format), name + " " + "_or_".join(obj.format) + ) self.assertEqual(name, obj.tostring(), name + " " + obj.tostring()) def test_more_cases(self): @@ -112,15 +118,16 @@ def test_more_cases_jump(self): class TestFilenameObjects(unittest.TestCase): - def setUp(self): - """ make a small test dataset """ - self.datashape = (10,11) + """make a small test dataset""" + self.datashape = (10, 11) self.nframes = 5 self.tempdir = tempfile.mkdtemp() - self.fnames = [os.path.join(self.tempdir, "FNO%04d.edf" % iframe) - for iframe in range(self.nframes)] - data = numpy.zeros(self.datashape,numpy.uint16) + self.fnames = [ + os.path.join(self.tempdir, "FNO%04d.edf" % iframe) + for iframe in range(self.nframes) + ] + data = numpy.zeros(self.datashape, numpy.uint16) im = fabio.edfimage.edfimage(data) for iframe, fname in enumerate(self.fnames): im.header["checkthing"] = str(iframe) @@ -136,13 +143,13 @@ def test_files_are_being_opened(self): for iframe, fname in enumerate(self.fnames): obj = fabio.FilenameObject(filename=fname) # read via the FilenameObject - o1 = fabio.open(obj) + o1 = fabio.open(obj) self.assertEqual(o1.data.shape, self.datashape) - self.assertEqual(o1.header['checkthing'], str(iframe)) + self.assertEqual(o1.header["checkthing"], str(iframe)) # And via the tostring - o2 = fabio.open(obj.tostring()) + o2 = fabio.open(obj.tostring()) self.assertEqual(o2.data.shape, self.datashape) - self.assertEqual(o2.header['checkthing'], str(iframe)) + self.assertEqual(o2.header["checkthing"], str(iframe)) def test_FileNameObject_can_iterate(self): """Regression test for Fable""" @@ -150,17 +157,17 @@ def test_FileNameObject_can_iterate(self): for iframe, fname in enumerate(self.fnames): obj.num = iframe # read via the FilenameObject - o1 = fabio.open(obj) + o1 = fabio.open(obj) self.assertEqual(o1.data.shape, self.datashape) - self.assertEqual(o1.header['checkthing'], str(iframe)) + self.assertEqual(o1.header["checkthing"], str(iframe)) # And via the tostring - o2 = fabio.open(obj.tostring()) + o2 = fabio.open(obj.tostring()) self.assertEqual(o2.data.shape, self.datashape) - self.assertEqual(o2.header['checkthing'], str(iframe)) + self.assertEqual(o2.header["checkthing"], str(iframe)) # And the real name - o3 = fabio.open(fname) + o3 = fabio.open(fname) self.assertEqual(o3.data.shape, self.datashape) - self.assertEqual(o3.header['checkthing'], str(iframe)) + self.assertEqual(o3.header["checkthing"], str(iframe)) def suite(): @@ -171,6 +178,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/test_flat_binary.py b/src/fabio/test/test_flat_binary.py index c1f2bace..6c8adfab 100644 --- a/src/fabio/test/test_flat_binary.py +++ b/src/fabio/test/test_flat_binary.py @@ -27,23 +27,26 @@ testsuite by Jerome Kieffer (Jerome.Kieffer@esrf.eu) 28/11/2014 """ + import unittest import os import logging - from .utilstest import UtilsTest +import fabio logger = logging.getLogger(__name__) -import fabio class TestFlatBinary(unittest.TestCase): - - filenames = [os.path.join(UtilsTest.tempdir, i) - for i in ("not.a.file", - "bad_news_1234", - "empty_files_suck_1234.edf", - "notRUBY_1234.dat")] + filenames = [ + os.path.join(UtilsTest.tempdir, i) + for i in ( + "not.a.file", + "bad_news_1234", + "empty_files_suck_1234.edf", + "notRUBY_1234.dat", + ) + ] def setUp(self): for filename in self.filenames: @@ -67,7 +70,9 @@ def test_openimage(self): except Exception: logger.warning("failed for: %s" % filename) nfail += 1 - self.assertEqual(nfail, 0, " %s failures out of %s" % (nfail, len(self.filenames))) + self.assertEqual( + nfail, 0, " %s failures out of %s" % (nfail, len(self.filenames)) + ) def tearDown(self): for filename in self.filenames: @@ -81,6 +86,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/test_formats.py b/src/fabio/test/test_formats.py index 9bb5a879..06a8f4ed 100755 --- a/src/fabio/test/test_formats.py +++ b/src/fabio/test/test_formats.py @@ -30,16 +30,15 @@ import unittest import logging - -logger = logging.getLogger(__name__) import fabio from .. import fabioformats from ..utils import deprecation from ..utils import testutils +logger = logging.getLogger(__name__) -class TestRegistration(unittest.TestCase): +class TestRegistration(unittest.TestCase): def test_fabio_factory(self): image = fabio.factory("edfimage") self.assertIsNotNone(image) @@ -63,13 +62,14 @@ def test_deprecated_fabioimage_factory(self): @testutils.test_logging(deprecation.depreclog, warning=1) def test_deprecated_fabioimage_factory_missing_format(self): """Check that it is still working""" - self.assertRaises(RuntimeError, fabio.fabioimage.FabioImage.factory, "foobarimage") + self.assertRaises( + RuntimeError, fabio.fabioimage.FabioImage.factory, "foobarimage" + ) def test_not_existing(self): self.assertIsNone(fabioformats.get_class_by_name("myformat0")) def test_annotation(self): - @fabio.register class MyFormat1(fabio.fabioimage.FabioImage): pass @@ -77,7 +77,6 @@ class MyFormat1(fabio.fabioimage.FabioImage): self.assertIsNotNone(fabioformats.get_class_by_name("myformat1")) def test_function(self): - class MyFormat2(fabio.fabioimage.FabioImage): pass @@ -88,6 +87,7 @@ def test_extenstion_registry(self): for ext in fabioformats._get_extension_mapping(): self.assertFalse("." in ext) + def suite(): loadTests = unittest.defaultTestLoader.loadTestsFromTestCase testsuite = unittest.TestSuite() @@ -95,6 +95,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/test_frames.py b/src/fabio/test/test_frames.py index 9dd3772f..caa6d350 100644 --- a/src/fabio/test/test_frames.py +++ b/src/fabio/test/test_frames.py @@ -31,14 +31,13 @@ import logging import numpy import contextlib - -logger = logging.getLogger(__name__) - import fabio.fabioimage import fabio.edfimage import fabio.file_series from .utilstest import UtilsTest +logger = logging.getLogger(__name__) + class _CommonTestFrames(unittest.TestCase): """Test generic tests which could append on frame iteration""" @@ -120,7 +119,6 @@ def test_frames_random_access(self): class TestVirtualEdf(_CommonTestFrames): - @classmethod def getMeta(cls): header1 = {"foo": "bar"} @@ -162,7 +160,6 @@ def test_content(self): class TestEdf(_CommonTestFrames): - @classmethod def getMeta(cls): filename = UtilsTest.getimage("multiframes.edf.bz2") @@ -193,7 +190,6 @@ def test_content(self): class TestTiff(_CommonTestFrames): - @classmethod def getMeta(cls): filename = UtilsTest.getimage("multiframes.tif.bz2") @@ -224,7 +220,6 @@ def test_content(self): class TestFabioImage(unittest.TestCase): - def test_single_frame_iterator(self): data = numpy.array([[1, 2], [3, 4]], dtype=numpy.uint16) image = fabio.fabioimage.FabioImage(data=data) @@ -241,7 +236,6 @@ def test_single_frame_iterator(self): class TestFileSeries(_CommonTestFrames): - @classmethod def getMeta(cls): filenames = [] @@ -273,6 +267,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/test_header_not_singleton.py b/src/fabio/test/test_header_not_singleton.py index e687d069..3770e1a4 100644 --- a/src/fabio/test/test_header_not_singleton.py +++ b/src/fabio/test/test_header_not_singleton.py @@ -31,16 +31,14 @@ import unittest import os import logging - -logger = logging.getLogger(__name__) - import fabio import shutil from .utilstest import UtilsTest +logger = logging.getLogger(__name__) -class TestHeaderNotSingleton(unittest.TestCase): +class TestHeaderNotSingleton(unittest.TestCase): def setUp(self): """ download images @@ -74,6 +72,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/test_image_convert.py b/src/fabio/test/test_image_convert.py index d10c16d8..dfb10c97 100644 --- a/src/fabio/test/test_image_convert.py +++ b/src/fabio/test/test_image_convert.py @@ -28,12 +28,11 @@ import unittest import os import logging - -logger = logging.getLogger(__name__) - from .utilstest import UtilsTest import fabio +logger = logging.getLogger(__name__) + class TestImageConvert(unittest.TestCase): """Test image convertions""" @@ -60,6 +59,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/test_import.py b/src/fabio/test/test_import.py index 27d81d7a..6998e027 100644 --- a/src/fabio/test/test_import.py +++ b/src/fabio/test/test_import.py @@ -11,15 +11,17 @@ logger = logging.getLogger(__name__) - class TestImport(unittest.TestCase): def test_import_all(self): import fabio - base = os.path.split(fabio.__path__[0])[0]+"/" + + base = os.path.split(fabio.__path__[0])[0] + "/" for root, dirs, files in os.walk(fabio.__path__[0]): for f in files: if f.endswith(".py"): - module = os.path.join(root, f[:-3])[len(base):].replace(os.sep,".") + module = os.path.join(root, f[:-3])[len(base) :].replace( + os.sep, "." + ) __import__(module) diff --git a/src/fabio/test/test_io_limits.py b/src/fabio/test/test_io_limits.py index 479c9be5..25732d80 100644 --- a/src/fabio/test/test_io_limits.py +++ b/src/fabio/test/test_io_limits.py @@ -10,12 +10,11 @@ import numpy import tempfile import shutil - -logger = logging.getLogger(__name__) - import fabio.edfimage import fabio.tifimage +logger = logging.getLogger(__name__) + class _CommonIOLimitTest(unittest.TestCase): IMAGETYPE = None @@ -27,7 +26,7 @@ def setUpClass(cls): cls.test_filename = os.path.join(cls.test_dir, "data") # A single IO operation is limited to `nbytes_single_io` bytes - nbytes_single_io = 2 ** 31 + nbytes_single_io = 2**31 cls.data_dtype = numpy.uint32 itemsize = 4 diff --git a/src/fabio/test/test_nexus.py b/src/fabio/test/test_nexus.py index 50e6f05d..f9d7aa66 100644 --- a/src/fabio/test/test_nexus.py +++ b/src/fabio/test/test_nexus.py @@ -21,22 +21,18 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -"""Unit tests for nexus file reader -""" +"""Unit tests for nexus file reader""" import unittest import os import logging - -logger = logging.getLogger(__name__) - from .utilstest import UtilsTest from .. import nexus -import numpy +logger = logging.getLogger(__name__) -class TestNexus(unittest.TestCase): +class TestNexus(unittest.TestCase): def setUp(self): if nexus.h5py is None: self.skipTest("h5py library is not available. Skipping Nexus test") @@ -74,6 +70,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/test_open_header.py b/src/fabio/test/test_open_header.py index 7d427502..9a5d86cf 100644 --- a/src/fabio/test/test_open_header.py +++ b/src/fabio/test/test_open_header.py @@ -28,12 +28,11 @@ import unittest import logging - -logger = logging.getLogger(__name__) - from fabio.openimage import openheader from .utilstest import UtilsTest +logger = logging.getLogger(__name__) + class Test1(unittest.TestCase): """openheader opening edf""" @@ -42,14 +41,14 @@ def setUp(self): self.name = UtilsTest.getimage("F2K_Seb_Lyso0675_header_only.edf.bz2")[:-4] def testcase(self): - """ check openheader can read edf headers""" + """check openheader can read edf headers""" for ext in ["", ".bz2", ".gz"]: name = self.name + ext obj = openheader(name) logger.debug(" %s obj = %s" % (name, obj.header)) - self.assertEqual(obj.header["title"], - "ESPIA FRELON Image", - "Error on file %s" % name) + self.assertEqual( + obj.header["title"], "ESPIA FRELON Image", "Error on file %s" % name + ) def suite(): @@ -59,6 +58,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite) diff --git a/src/fabio/test/test_open_image.py b/src/fabio/test/test_open_image.py index 9a57b638..7ec7c492 100644 --- a/src/fabio/test/test_open_image.py +++ b/src/fabio/test/test_open_image.py @@ -30,9 +30,6 @@ import unittest import logging - -logger = logging.getLogger(__name__) - from fabio.openimage import openimage from fabio.edfimage import EdfImage from fabio.marccdimage import MarccdImage @@ -42,12 +39,14 @@ from fabio.dtrekimage import DtrekImage from .utilstest import UtilsTest +logger = logging.getLogger(__name__) + class TestOpenEdf(unittest.TestCase): """openimage opening edf""" def checkFile(self, filename): - """ check we can read EDF image with openimage""" + """check we can read EDF image with openimage""" obj = openimage(filename) obj2 = EdfImage() obj2.read(filename) @@ -75,7 +74,7 @@ class TestOpenMccd(unittest.TestCase): """openimage opening mccd""" def checkFile(self, filename): - """ check we can read it""" + """check we can read it""" obj = openimage(filename) obj2 = MarccdImage() obj2.read(filename) @@ -103,7 +102,7 @@ class TestOpenMask(unittest.TestCase): """openimage opening fit2d msk""" def checkFile(self, filename): - """ check we can read Fit2D mask with openimage""" + """check we can read Fit2D mask with openimage""" obj = openimage(filename) obj2 = Fit2dMaskImage() obj2.read(filename) @@ -135,7 +134,7 @@ class TestOpenBruker(unittest.TestCase): """openimage opening bruker""" def checkFile(self, filename): - """ check we can read it""" + """check we can read it""" obj = openimage(filename) obj2 = BrukerImage() obj2.read(filename) @@ -166,7 +165,7 @@ class TestOpenDtrek(unittest.TestCase): """openimage opening adsc""" def checkFile(self, filename): - """ check we can read it""" + """check we can read it""" obj = openimage(filename) obj2 = DtrekImage() obj2.read(filename) @@ -197,7 +196,7 @@ class TestOpenOxd(unittest.TestCase): """openimage opening adsc""" def checkFile(self, filename): - """ check we can read OXD images with openimage""" + """check we can read OXD images with openimage""" obj = openimage(filename) obj2 = OXDimage() obj2.read(filename) @@ -230,6 +229,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/test_tiffio.py b/src/fabio/test/test_tiffio.py index 52f200de..43fa2962 100644 --- a/src/fabio/test/test_tiffio.py +++ b/src/fabio/test/test_tiffio.py @@ -29,22 +29,21 @@ import os import logging import numpy - -logger = logging.getLogger(__name__) - from .utilstest import UtilsTest from ..TiffIO import TiffIO +logger = logging.getLogger(__name__) + class TestTiffIO(unittest.TestCase): """Test the class format""" def write(self, filename): - tif = TiffIO(filename, mode='wb+') + tif = TiffIO(filename, mode="wb+") dtype = numpy.uint16 data = numpy.arange(10000).astype(dtype) data.shape = 100, 100 - tif.writeImage(data, info={'Title': '1st'}) + tif.writeImage(data, info={"Title": "1st"}) tif = None def test_write(self): @@ -57,11 +56,11 @@ def test_append(self): filename = os.path.join(UtilsTest.tempdir, filename) self.write(filename) # append - tif = TiffIO(filename, mode='rb+') + tif = TiffIO(filename, mode="rb+") dtype = numpy.uint16 data = numpy.arange(100).astype(dtype) data.shape = 10, 10 - tif.writeImage((data * 2).astype(dtype), info={'Title': '2nd'}) + tif.writeImage((data * 2).astype(dtype), info={"Title": "2nd"}) self.assertEqual(tif.getNumberOfImages(), 2) tif = None @@ -77,7 +76,7 @@ def test_read(self): for key in info: if key not in ["colormap"]: logger.info("%s = %s", key, info[key]) - elif info['colormap'] is not None: + elif info["colormap"] is not None: logger.info("RED %s = %s", key, info[key][0:10, 0]) logger.info("GREEN %s = %s", key, info[key][0:10, 1]) logger.info("BLUE %s = %s", key, info[key][0:10, 2]) @@ -92,6 +91,6 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) diff --git a/src/fabio/test/test_utils_cli.py b/src/fabio/test/test_utils_cli.py index c5c64b86..f2257978 100644 --- a/src/fabio/test/test_utils_cli.py +++ b/src/fabio/test/test_utils_cli.py @@ -3,7 +3,7 @@ # Project: Azimuthal integration # https://github.com/silx-kit/pyFAI # -# Copyright (C) 2015-2018 European Synchrotron Radiation Facility, Grenoble, France +# Copyright (C) 2015-2025 European Synchrotron Radiation Facility, Grenoble, France # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -28,19 +28,19 @@ __author__ = "valentin.valls@esrf.eu" __license__ = "MIT" __copyright__ = "European Synchrotron Radiation Facility, Grenoble, France" -__date__ = "12/12/2022" +__date__ = "27/10/2025" __status__ = "development" -__docformat__ = 'restructuredtext' +__docformat__ = "restructuredtext" import unittest import logging from .utilstest import UtilsTest -logger = logging.getLogger(__name__) from ..utils.cli import ProgressBar, expand_args +logger = logging.getLogger(__name__) -class TestUtilShell(unittest.TestCase): +class TestUtilShell(unittest.TestCase): def test_coverage(self): """ test function coverage @@ -52,8 +52,9 @@ def test_coverage(self): progressbar.clear() def test_expand_args(self): - _ = expand_args(["*.tif","*.edf", "*.cbf", "*.img"]) - + _ = expand_args(["*.tif", "*.edf", "*.cbf", "*.img"]) + + def suite(): loader = unittest.defaultTestLoader.loadTestsFromTestCase testsuite = unittest.TestSuite() @@ -61,7 +62,7 @@ def suite(): return testsuite -if __name__ == '__main__': +if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(suite()) UtilsTest.clean_up() diff --git a/src/fabio/test/testutils.py b/src/fabio/test/testutils.py index 360de676..fdb115be 100755 --- a/src/fabio/test/testutils.py +++ b/src/fabio/test/testutils.py @@ -46,9 +46,11 @@ if sys.hexversion >= 0x030400F0: # Python >= 3.4 + class ParametricTestCase(unittest.TestCase): pass else: + class ParametricTestCase(unittest.TestCase): """TestCase with subTest support for Python < 3.4. @@ -63,8 +65,8 @@ class ParametricTestCase(unittest.TestCase): def subTest(self, msg=None, **params): """Use as unittest.TestCase.subTest method in Python >= 3.4.""" # Format arguments as: '[msg] (key=value, ...)' - param_str = ', '.join(['%s=%s' % (k, v) for k, v in params.items()]) - self._subtest_msg = '[%s] (%s)' % (msg or '', param_str) + param_str = ", ".join(["%s=%s" % (k, v) for k, v in params.items()]) + self._subtest_msg = "[%s] (%s)" % (msg or "", param_str) yield self._subtest_msg = None @@ -72,8 +74,9 @@ def shortDescription(self): short_desc = super(ParametricTestCase, self).shortDescription() if self._subtest_msg is not None: # Append subTest message to shortDescription - short_desc = ' '.join( - [msg for msg in (short_desc, self._subtest_msg) if msg]) + short_desc = " ".join( + [msg for msg in (short_desc, self._subtest_msg) if msg] + ) return short_desc if short_desc else None @@ -143,8 +146,16 @@ class LoggingValidator(logging.Handler): :raises RuntimeError: If the message counts are the expected ones. """ - def __init__(self, logger=None, critical=None, error=None, - warning=None, info=None, debug=None, notset=None): + def __init__( + self, + logger=None, + critical=None, + error=None, + warning=None, + info=None, + debug=None, + notset=None, + ): if logger is None: logger = logging.getLogger() elif not isinstance(logger, logging.Logger): @@ -159,10 +170,12 @@ def __init__(self, logger=None, critical=None, error=None, logging.WARNING: warning, logging.INFO: info, logging.DEBUG: debug, - logging.NOTSET: notset + logging.NOTSET: notset, } - self._expected_count = sum([v for k, v in self.expected_count_by_level.items() if v is not None]) + self._expected_count = sum( + [v for k, v in self.expected_count_by_level.items() if v is not None] + ) """Amount of any logging expected""" super(LoggingValidator, self).__init__() @@ -189,15 +202,14 @@ def can_be_checked(self): return len(self.records) >= self._expected_count def get_count_by_level(self): - """Returns the current message count by level. - """ + """Returns the current message count by level.""" count = { logging.CRITICAL: 0, logging.ERROR: 0, logging.WARNING: 0, logging.INFO: 0, logging.DEBUG: 0, - logging.NOTSET: 0 + logging.NOTSET: 0, } for record in self.records: level = record.levelno @@ -230,18 +242,30 @@ def __exit__(self, exc_type, exc_value, traceback): message += ", " count = count_by_level[level] expected_count = expected_count_by_level[level] - message += "%d %s (got %d)" % (expected_count, logging.getLevelName(level), count) + message += "%d %s (got %d)" % ( + expected_count, + logging.getLevelName(level), + count, + ) raise LoggingRuntimeError( - 'Expected %s' % message, records=list(self.records)) + "Expected %s" % message, records=list(self.records) + ) def emit(self, record): """Override :meth:`logging.Handler.emit`""" self.records.append(record) -def validate_logging(logger=None, critical=None, error=None, - warning=None, info=None, debug=None, notset=None): +def validate_logging( + logger=None, + critical=None, + error=None, + warning=None, + info=None, + debug=None, + notset=None, +): """Decorator checking number of logging messages. Propagation of logging messages is disabled by this decorator. @@ -270,16 +294,20 @@ def validate_logging(logger=None, critical=None, error=None, :param int notset: Expected number of NOTSET messages. Default: Do not check. """ + def decorator(func): test_context = LoggingValidator( - logger, critical, error, warning, info, debug, notset) + logger, critical, error, warning, info, debug, notset + ) @functools.wraps(func) def wrapper(*args, **kwargs): with test_context: result = func(*args, **kwargs) return result + return wrapper + return decorator @@ -309,6 +337,7 @@ class EnsureImportError(object): if it is already imported. It only ensures that any attempt to import it again will cause an ImportError to be raised. """ + def __init__(self, name): """ diff --git a/src/fabio/test/utilstest.py b/src/fabio/test/utilstest.py index 73bf11e5..fd5e9427 100644 --- a/src/fabio/test/utilstest.py +++ b/src/fabio/test/utilstest.py @@ -3,7 +3,7 @@ # # Project: FabIO tests class utilities # -# Copyright (C) 2010-2016 European Synchrotron Radiation Facility +# Copyright (C) 2010-2025 European Synchrotron Radiation Facility # Grenoble, France # # Principal authors: Jérôme KIEFFER (jerome.kieffer@esrf.fr) @@ -30,10 +30,7 @@ __contact__ = "jerome.kieffer@esrf.eu" __license__ = "MIT" __copyright__ = "European Synchrotron Radiation Facility, Grenoble, France" -__date__ = "03/04/2020" - -PACKAGE = "fabio" -DATA_KEY = "FABIO_DATA" +__date__ = "27/10/2025" import os import sys @@ -45,20 +42,20 @@ logger = logging.getLogger(__name__) +PACKAGE = "fabio" +DATA_KEY = "FABIO_DATA" TEST_HOME = os.path.dirname(os.path.abspath(__file__)) class TestOptions(object): - def __init__(self): self.options = None self.timeout = 60 # timeout in seconds for downloading images # url_base = "http://forge.epn-campus.eu/attachments/download" self.url_base = "http://www.edna-site.org/pub/fabio/testimages" - self.resources = ExternalResources(PACKAGE, - timeout=self.timeout, - env_key=DATA_KEY, - url_base=self.url_base) + self.resources = ExternalResources( + PACKAGE, timeout=self.timeout, env_key=DATA_KEY, url_base=self.url_base + ) self.sem = threading.Semaphore() self.recompiled = False self.reloaded = False @@ -89,6 +86,7 @@ def script_path(self, script_name, module_name): script = self.get_installed_script_path(script_name) else: import importlib + module = importlib.import_module(module_name) script = module.__file__ return script @@ -100,7 +98,7 @@ def get_installed_script_path(self, script): In Windows, it checks availability of script using .py .bat, and .exe file extensions. """ - if (sys.platform == "win32"): + if sys.platform == "win32": available_extensions = [".py", ".bat", ".exe"] else: available_extensions = [""] @@ -126,8 +124,9 @@ def _initialize_tmpdir(self): if not self._tempdir: with self.sem: if not self._tempdir: - self._tempdir = tempfile.mkdtemp("_" + getpass.getuser(), - self.name + "_") + self._tempdir = tempfile.mkdtemp( + "_" + getpass.getuser(), self.name + "_" + ) @property def tempdir(self): diff --git a/src/fabio/third_party/__init__.py b/src/fabio/third_party/__init__.py index 68ef9905..072572d1 100644 --- a/src/fabio/third_party/__init__.py +++ b/src/fabio/third_party/__init__.py @@ -1 +1 @@ -# Place holder \ No newline at end of file +# Place holder diff --git a/src/fabio/third_party/_local/__init__.py b/src/fabio/third_party/_local/__init__.py index 68ef9905..072572d1 100644 --- a/src/fabio/third_party/_local/__init__.py +++ b/src/fabio/third_party/_local/__init__.py @@ -1 +1 @@ -# Place holder \ No newline at end of file +# Place holder diff --git a/src/fabio/tifimage.py b/src/fabio/tifimage.py index 8716fa05..f1dc7544 100644 --- a/src/fabio/tifimage.py +++ b/src/fabio/tifimage.py @@ -2,7 +2,7 @@ # # Project: FabIO X-ray image reader # -# Copyright (C) 2010-2016 European Synchrotron Radiation Facility +# Copyright (C) 2010-2025 European Synchrotron Radiation Facility # Grenoble, France # # Permission is hereby granted, free of charge, to any person obtaining a copy @@ -42,13 +42,18 @@ """ __authors__ = ["Jérôme Kieffer", "Henning O. Sorensen", "Erik Knudsen"] -__date__ = "03/04/2020" +__date__ = "27/10/2025" __license__ = "MIT" __copyright__ = "ESRF, Grenoble & Risoe National Laboratory" __status__ = "stable" import time import logging +import numpy +from .utils import pilutils +from . import fabioimage +from . import TiffIO + logger = logging.getLogger(__name__) try: @@ -56,10 +61,6 @@ except ImportError: PIL = None -import numpy -from .utils import pilutils -from . import fabioimage -from . import TiffIO _USE_TIFFIO = True """Uses TiffIO library if available""" @@ -82,6 +83,7 @@ class TifImage(fabioimage.FabioImage): Images in TIF format Wraps TiffIO """ + DESCRIPTION = "Tagged image file format" DEFAULT_EXTENSIONS = ["tif", "tiff"] @@ -89,7 +91,7 @@ class TifImage(fabioimage.FabioImage): _need_a_seek_to_read = True def __init__(self, *args, **kwds): - """ Tifimage constructor adds an nbits member attribute """ + """Tifimage constructor adds an nbits member attribute""" self.nbits = None fabioimage.FabioImage.__init__(self, *args, **kwds) self._tiffio = None @@ -141,7 +143,11 @@ def _read_with_tiffio(self, infile): logger.warning("Third dimension is the color") self._shape = None else: - logger.warning("dataset has %s dimensions (%s), check for errors !!!!", self.data.ndim, self.data.shape) + logger.warning( + "dataset has %s dimensions (%s), check for errors !!!!", + self.data.ndim, + self.data.shape, + ) self.lib = "TiffIO" def _read_with_pil(self, infile): @@ -167,7 +173,9 @@ def read(self, fname, frame=None): try: self._read_with_tiffio(infile) except Exception as error: - logger.warning("Unable to read %s with TiffIO due to %s, trying PIL", fname, error) + logger.warning( + "Unable to read %s with TiffIO due to %s, trying PIL", fname, error + ) logger.debug("Backtrace", exc_info=True) infile.seek(0) @@ -184,7 +192,9 @@ def read(self, fname, frame=None): infile.close() if self.lib is None: - logger.error("Error in opening %s: no tiff reader managed to read the file.", fname) + logger.error( + "Error in opening %s: no tiff reader managed to read the file.", fname + ) self.resetvals() return self @@ -196,10 +206,12 @@ def write(self, fname): :param str fname: name of the file to save the image to """ with TiffIO.TiffIO(fname, mode="w") as tiff_file: - tiff_file.writeImage(self.data, - info=self.header, - software="fabio.tifimage", - date=time.ctime()) + tiff_file.writeImage( + self.data, + info=self.header, + software="fabio.tifimage", + date=time.ctime(), + ) def close(self): if self._tiffio is not None: diff --git a/src/fabio/utils/ExternalResources.py b/src/fabio/utils/ExternalResources.py index 4d9d9e3b..92b0e5a4 100644 --- a/src/fabio/utils/ExternalResources.py +++ b/src/fabio/utils/ExternalResources.py @@ -37,7 +37,8 @@ import logging import tempfile import unittest -import urllib.request, urllib.error +import urllib.request +import urllib.error import bz2 import gzip @@ -50,10 +51,7 @@ class ExternalResources(object): """ - def __init__(self, project, - url_base, - env_key=None, - timeout=60): + def __init__(self, project, url_base, env_key=None, timeout=60): """Constructor of the class :param str project: name of the project, like "silx" @@ -88,6 +86,7 @@ def data_home(self): if data_home is None: try: import getpass + name = getpass.getuser() except Exception: if "getlogin" in dir(os): @@ -135,14 +134,17 @@ def getfile(self, filename): fullfilename = os.path.abspath(os.path.join(self.data_home, filename)) if not os.path.isfile(fullfilename): - logger.debug("Trying to download image %s, timeout set to %ss", - filename, self.timeout) + logger.debug( + "Trying to download image %s, timeout set to %ss", + filename, + self.timeout, + ) dictProxies = {} if "http_proxy" in os.environ: - dictProxies['http'] = os.environ["http_proxy"] - dictProxies['https'] = os.environ["http_proxy"] + dictProxies["http"] = os.environ["http_proxy"] + dictProxies["https"] = os.environ["http_proxy"] if "https_proxy" in os.environ: - dictProxies['https'] = os.environ["https_proxy"] + dictProxies["https"] = os.environ["https_proxy"] if dictProxies: proxy_handler = urllib.request.ProxyHandler(dictProxies) opener = urllib.request.build_opener(proxy_handler).open @@ -151,8 +153,9 @@ def getfile(self, filename): logger.debug("wget %s/%s", self.url_base, filename) try: - data = opener("%s/%s" % (self.url_base, filename), - data=None, timeout=self.timeout).read() + data = opener( + "%s/%s" % (self.url_base, filename), data=None, timeout=self.timeout + ).read() logger.info("Image %s successfully downloaded.", filename) except urllib.error.URLError: raise unittest.SkipTest("network unreachable.") @@ -165,16 +168,20 @@ def getfile(self, filename): with open(fullfilename, "wb") as outfile: outfile.write(data) except IOError: - raise IOError("unable to write downloaded \ - data to disk at %s" % self.data_home) + raise IOError( + "unable to write downloaded \ + data to disk at %s" + % self.data_home + ) if not os.path.isfile(fullfilename): raise RuntimeError( - ("Could not automatically download test images %s!" % filename) + - "If you are behind a firewall, please set both environment variable http_proxy and https_proxy." + - "This works even under windows !" + - "Otherwise please try to download the images manually from" + - "%s/%s" % (self.url_base, filename)) + ("Could not automatically download test images %s!" % filename) + + "If you are behind a firewall, please set both environment variable http_proxy and https_proxy." + + "This works even under windows !" + + "Otherwise please try to download the images manually from" + + "%s/%s" % (self.url_base, filename) + ) if filename not in self.all_data: self.all_data.add(filename) @@ -197,17 +204,24 @@ def getdir(self, dirname): :return: list of files with their full path. """ lodn = dirname.lower() - if (lodn.endswith("tar") or lodn.endswith("tgz") or - lodn.endswith("tbz2") or lodn.endswith("tar.gz") or - lodn.endswith("tar.bz2")): + if ( + lodn.endswith("tar") + or lodn.endswith("tgz") + or lodn.endswith("tbz2") + or lodn.endswith("tar.gz") + or lodn.endswith("tar.bz2") + ): import tarfile + engine = tarfile.TarFile.open elif lodn.endswith("zip"): import zipfile + engine = zipfile.ZipFile else: - raise RuntimeError("Unsupported archive format. Only tar and zip " - "are currently supported") + raise RuntimeError( + "Unsupported archive format. Only tar and zip are currently supported" + ) full_path = self.getfile(dirname) with engine(full_path, mode="r") as fd: output = os.path.join(self.data_home, dirname + "__content") @@ -267,10 +281,11 @@ def get_file_and_repack(self, filename): self.getfile(bzip2name) if not os.path.isfile(fullimagename_bz2): raise RuntimeError( - ("Could not automatically download test images %s!" % filename) + - "If you are behind a firewall, please set the environment variable http_proxy and https_proxy." + - "Otherwise please try to download the images manually from" + - "%s/%s" % (self.url_base, filename)) + ("Could not automatically download test images %s!" % filename) + + "If you are behind a firewall, please set the environment variable http_proxy and https_proxy." + + "Otherwise please try to download the images manually from" + + "%s/%s" % (self.url_base, filename) + ) raw_file_exists = os.path.isfile(fullimagename_raw) gz_file_exists = os.path.isfile(fullimagename_gz) @@ -284,16 +299,22 @@ def get_file_and_repack(self, filename): with open(fullimagename_raw, "wb") as fullimage: fullimage.write(decompressed) except IOError: - raise IOError("unable to write decompressed \ - data to disk at %s" % self.data_home) + raise IOError( + "unable to write decompressed \ + data to disk at %s" + % self.data_home + ) if not gz_file_exists: try: - with gzip.open(fullimagename_gz, "wb") as g: + with gzip.open(fullimagename_gz, "wb") as g: g.write(decompressed) except IOError: - raise IOError("unable to write gzipped \ - data to disk at %s" % self.data_home) + raise IOError( + "unable to write gzipped \ + data to disk at %s" + % self.data_home + ) return fullimagename diff --git a/src/fabio/utils/cli.py b/src/fabio/utils/cli.py index 853f3d2e..cc7ade64 100644 --- a/src/fabio/utils/cli.py +++ b/src/fabio/utils/cli.py @@ -88,26 +88,27 @@ def __init__(self, title, max_value, bar_width): encoding = sys.stdout.encoding if encoding is None: # We uses the safer aproch: a valid ASCII character. - self.progress_char = '#' + self.progress_char = "#" else: try: import datetime + if str(datetime.datetime.now())[5:10] == "02-14": - self.progress_char = u'\u2665' + self.progress_char = "\u2665" else: - self.progress_char = u'\u25A0' + self.progress_char = "\u25a0" _byte = codecs.encode(self.progress_char, encoding) except (ValueError, TypeError, LookupError): # In case the char is not supported by the encoding, # or if the encoding does not exists - self.progress_char = '#' + self.progress_char = "#" def clear(self): """ Remove the progress bar from the display and move the cursor at the beginning of the line using carriage return. """ - sys.stdout.write('\r' + " " * self.last_size + "\r") + sys.stdout.write("\r" + " " * self.last_size + "\r") sys.stdout.flush() def display(self): @@ -145,7 +146,13 @@ def update(self, value, message="", max_value=None): bar_position = self.bar_width # line to display - line = '\r%15s [%s%s] % 3d%% %s' % (self.title, self.progress_char * bar_position, ' ' * (self.bar_width - bar_position), percent, message) + line = "\r%15s [%s%s] % 3d%% %s" % ( + self.title, + self.progress_char * bar_position, + " " * (self.bar_width - bar_position), + percent, + message, + ) # trailing to mask the previous message line_size = len(line) diff --git a/src/fabio/utils/deprecation.py b/src/fabio/utils/deprecation.py index b809a018..c06fadae 100644 --- a/src/fabio/utils/deprecation.py +++ b/src/fabio/utils/deprecation.py @@ -44,8 +44,7 @@ def hexversion_fromstring(string): - """Calculate the hexadecimal version number from a string: - """ + """Calculate the hexadecimal version number from a string:""" if string is not None: result = _PATTERN.match(string) if result is None: @@ -58,9 +57,15 @@ def hexversion_fromstring(string): return calc_hexversion(major, minor, micro, releaselevel, serial=0) -def deprecated(func=None, reason=None, replacement=None, since_version=None, - only_once=True, skip_backtrace_count=1, - deprecated_since=None): +def deprecated( + func=None, + reason=None, + replacement=None, + since_version=None, + only_once=True, + skip_backtrace_count=1, + deprecated_since=None, +): """ Decorator that deprecates the use of a function @@ -79,19 +84,20 @@ def deprecated(func=None, reason=None, replacement=None, since_version=None, """ def decorator(func): - @functools.wraps(func) def wrapper(*args, **kwargs): name = func.__name__ - deprecated_warning(type_='Function', - name=name, - reason=reason, - replacement=replacement, - since_version=since_version, - only_once=only_once, - skip_backtrace_count=skip_backtrace_count, - deprecated_since=deprecated_since) + deprecated_warning( + type_="Function", + name=name, + reason=reason, + replacement=replacement, + since_version=since_version, + only_once=only_once, + skip_backtrace_count=skip_backtrace_count, + deprecated_since=deprecated_since, + ) return func(*args, **kwargs) return wrapper @@ -101,10 +107,16 @@ def wrapper(*args, **kwargs): return decorator -def deprecated_warning(type_, name, reason=None, replacement=None, - since_version=None, only_once=True, - skip_backtrace_count=0, - deprecated_since=None): +def deprecated_warning( + type_, + name, + reason=None, + replacement=None, + since_version=None, + only_once=True, + skip_backtrace_count=0, + deprecated_since=None, +): """ Function to log a deprecation warning diff --git a/src/fabio/utils/pilutils.py b/src/fabio/utils/pilutils.py index 3b37b2a4..55c5d7be 100644 --- a/src/fabio/utils/pilutils.py +++ b/src/fabio/utils/pilutils.py @@ -26,8 +26,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE -"""Helper functions using Python Imaging Library (PIL) -""" +"""Helper functions using Python Imaging Library (PIL)""" __authors__ = ["Jérôme Kieffer", "Jon Wright"] __date__ = "25/06/2018" @@ -67,13 +66,13 @@ NUMPY_TO_PIL = { - 'float32': "F", - 'int32': "F;32NS", - 'uint32': "F;32N", - 'int16': "F;16NS", - 'uint16': "F;16N", - 'int8': "F;8S", - 'uint8': "F;8" + "float32": "F", + "int32": "F;32NS", + "uint32": "F;32N", + "int16": "F;16NS", + "uint16": "F;16N", + "int8": "F;8S", + "uint8": "F;8", } @@ -90,7 +89,7 @@ def get_numpy_array(pil_image): dtype = numpy.float32 pil_image = pil_image.convert("F") try: - if pil_image.mode == 'P': + if pil_image.mode == "P": # Indexed color data = numpy.asarray(pil_image.convert("RGB"), dtype) else: diff --git a/src/fabio/utils/testutils.py b/src/fabio/utils/testutils.py index 82c2ce39..2016a151 100644 --- a/src/fabio/utils/testutils.py +++ b/src/fabio/utils/testutils.py @@ -45,9 +45,11 @@ if sys.hexversion >= 0x030400F0: # Python >= 3.4 + class ParametricTestCase(unittest.TestCase): pass else: + class ParametricTestCase(unittest.TestCase): """TestCase with subTest support for Python < 3.4. @@ -62,8 +64,8 @@ class ParametricTestCase(unittest.TestCase): def subTest(self, msg=None, **params): """Use as unittest.TestCase.subTest method in Python >= 3.4.""" # Format arguments as: '[msg] (key=value, ...)' - param_str = ', '.join(['%s=%s' % (k, v) for k, v in params.items()]) - self._subtest_msg = '[%s] (%s)' % (msg or '', param_str) + param_str = ", ".join(["%s=%s" % (k, v) for k, v in params.items()]) + self._subtest_msg = "[%s] (%s)" % (msg or "", param_str) yield self._subtest_msg = None @@ -71,8 +73,9 @@ def shortDescription(self): short_desc = super(ParametricTestCase, self).shortDescription() if self._subtest_msg is not None: # Append subTest message to shortDescription - short_desc = ' '.join( - [msg for msg in (short_desc, self._subtest_msg) if msg]) + short_desc = " ".join( + [msg for msg in (short_desc, self._subtest_msg) if msg] + ) return short_desc if short_desc else None @@ -131,8 +134,16 @@ class TestLogging(logging.Handler): :raises RuntimeError: If the message counts are the expected ones. """ - def __init__(self, logger=None, critical=None, error=None, - warning=None, info=None, debug=None, notset=None): + def __init__( + self, + logger=None, + critical=None, + error=None, + warning=None, + info=None, + debug=None, + notset=None, + ): if logger is None: logger = logging.getLogger() elif not isinstance(logger, logging.Logger): @@ -147,7 +158,7 @@ def __init__(self, logger=None, critical=None, error=None, logging.WARNING: warning, logging.INFO: info, logging.DEBUG: debug, - logging.NOTSET: notset + logging.NOTSET: notset, } super(TestLogging, self).__init__() @@ -179,16 +190,24 @@ def __exit__(self, exc_type, exc_value, traceback): for record in self.records: self.logger.handle(record) raise RuntimeError( - 'Expected %d %s logging messages, got %d' % ( - expected_count, logging.getLevelName(level), count)) + "Expected %d %s logging messages, got %d" + % (expected_count, logging.getLevelName(level), count) + ) def emit(self, record): """Override :meth:`logging.Handler.emit`""" self.records.append(record) -def test_logging(logger=None, critical=None, error=None, - warning=None, info=None, debug=None, notset=None): +def test_logging( + logger=None, + critical=None, + error=None, + warning=None, + info=None, + debug=None, + notset=None, +): """Decorator checking number of logging messages. Propagation of logging messages is disabled by this decorator. @@ -217,16 +236,20 @@ def test_logging(logger=None, critical=None, error=None, :param int notset: Expected number of NOTSET messages. Default: Do not check. """ + def decorator(func): - test_context = TestLogging(logger, critical, error, - warning, info, debug, notset) + test_context = TestLogging( + logger, critical, error, warning, info, debug, notset + ) @functools.wraps(func) def wrapper(*args, **kwargs): with test_context: result = func(*args, **kwargs) return result + return wrapper + return decorator @@ -256,6 +279,7 @@ class EnsureImportError(object): if it is already imported. It only ensures that any attempt to import it again will cause an ImportError to be raised. """ + def __init__(self, name): """ diff --git a/src/fabio/xcaliburimage.py b/src/fabio/xcaliburimage.py index eae12353..859553ec 100644 --- a/src/fabio/xcaliburimage.py +++ b/src/fabio/xcaliburimage.py @@ -34,10 +34,9 @@ __contact__ = "jerome.kieffer@esrf.eu" __license__ = "MIT" __copyright__ = "2022 ESRF" -__date__ = "03/03/2023" +__date__ = "27/10/2025" import logging -logger = logging.getLogger(__name__) import time import os import numpy @@ -45,6 +44,9 @@ from dataclasses import dataclass from enum import Enum import struct + +logger = logging.getLogger(__name__) + # Some constants used in the file: PAR_TEMPLATE = """#XCALIBUR SYSTEM #XCALIBUR PARAMETER FILE - GENERATED BY THE COMMANDS WD PAR OR WD CAL @@ -282,9 +284,9 @@ class CCD_FILEVERSION(Enum): VERS_1_7 = 0x00010007 VERS_1_8 = 0x00010008 VERS_1_9 = 0x00010009 - VERS_1_10 = 0x0001000a - VERS_1_11 = 0x0001000b - VERS_1_12 = 0x0001000c + VERS_1_10 = 0x0001000A + VERS_1_11 = 0x0001000B + VERS_1_12 = 0x0001000C CCD_FILEVERSION_VERS_HIGHEST = CCD_FILEVERSION.VERS_1_12 @@ -321,12 +323,14 @@ class CHIPCHARACTERISTICS_SCINTILLATORID(Enum): CHIPCHARACTERISTICS_SCINTILLATORID_FIRST = CHIPCHARACTERISTICS_SCINTILLATORID.GREEN400 -CHIPCHARACTERISTICS_SCINTILLATORID_LAST = CHIPCHARACTERISTICS_SCINTILLATORID.GREEN400_NEW +CHIPCHARACTERISTICS_SCINTILLATORID_LAST = ( + CHIPCHARACTERISTICS_SCINTILLATORID.GREEN400_NEW +) class SCAN_TYPE(Enum): - Omega = 0 - Phi = 4 + Omega = 0 + Phi = 4 @dataclass @@ -338,7 +342,7 @@ class ChipPoint: @classmethod def loads(cls, buffer): assert len(buffer) >= cls.SIZE - return cls(*struct.unpack("= cls.SIZE - return cls(ChipPoint.loads(buffer[:4]), - ChipPoint.loads(buffer[4:8]), - ChipPoint.loads(buffer[8:12]), - struct.unpack("= cls.SIZE - return cls(ChipPoint.loads(buffer[:4]), - ChipPoint.loads(buffer[4:8]), - ChipPoint.loads(buffer[8:12]), - ChipPoint.loads(buffer[12:16]), - struct.unpack("= cls.SIZE - return cls(ChipPoint.loads(buffer[:4]), - ChipPoint.loads(buffer[4:8]), - ChipPoint.loads(buffer[8:12]), - ChipPoint.loads(buffer[12:16]), - *struct.unpack("= cls.SIZE - lst = struct.unpack("= cls.SIZE - lst = struct.unpack("= cls.SIZE - lst = struct.unpack("= cls.SIZE prefix = buffer[:256].decode().strip("\x00") path = buffer[256:512].decode().strip("\x00") - lst = struct.unpack(" 256: + if len(value) > 256: self.__setattr__(key, value[:256]) @classmethod @@ -666,22 +718,46 @@ def loads(cls, bytestream): dico = {} if length > 1854: dico["dwversion"] = struct.unpack("I", bytestream[:4])[0] # VERSION - dico["ddarkcurrentinADUpersec"] = struct.unpack("d", bytestream[4:12])[0] # DARK CURRENT IN ADU - dico["dreadnoiseinADU"] = struct.unpack("d", bytestream[12:20])[0] # READ NOSE IN ADU - dico["ccharacteristicsfil"] = bytestream[20:276].decode().strip("\x00") # CHARACTISTICS FILE NAME - dico["cccdproducer"] = bytestream[276:532].decode().strip("\x00") # PRODUCER - dico["cccdchiptype"] = bytestream[532:788].decode().strip("\x00") # CHIP TYPE - dico["cccdchipserial"] = bytestream[788:1044].decode().strip("\x00") # CHIP SERIAL - dico["ctaperproducer"] = bytestream[1044:1300].decode().strip("\x00") # TAPER PRODUCER - dico["ctapertype"] = bytestream[1300:1556].decode().strip("\x00") # TAPER TYPE - dico["ctaperserial"] = bytestream[1556:1812].decode().strip("\x00") # TAPER SERIAL - dico["iisfip60origin"], dico["ifip60xorigin"], dico["ifip60yorigin"] = struct.unpack("HHH", bytestream[1812:1818]) # FIP60ORIGIN - - dico["inumofcornermasks"] = struct.unpack("H", bytestream[1818:1820]) # CORNER MASKS + dico["ddarkcurrentinADUpersec"] = struct.unpack("d", bytestream[4:12])[ + 0 + ] # DARK CURRENT IN ADU + dico["dreadnoiseinADU"] = struct.unpack("d", bytestream[12:20])[ + 0 + ] # READ NOSE IN ADU + dico["ccharacteristicsfil"] = ( + bytestream[20:276].decode().strip("\x00") + ) # CHARACTISTICS FILE NAME + dico["cccdproducer"] = ( + bytestream[276:532].decode().strip("\x00") + ) # PRODUCER + dico["cccdchiptype"] = ( + bytestream[532:788].decode().strip("\x00") + ) # CHIP TYPE + dico["cccdchipserial"] = ( + bytestream[788:1044].decode().strip("\x00") + ) # CHIP SERIAL + dico["ctaperproducer"] = ( + bytestream[1044:1300].decode().strip("\x00") + ) # TAPER PRODUCER + dico["ctapertype"] = ( + bytestream[1300:1556].decode().strip("\x00") + ) # TAPER TYPE + dico["ctaperserial"] = ( + bytestream[1556:1812].decode().strip("\x00") + ) # TAPER SERIAL + dico["iisfip60origin"], dico["ifip60xorigin"], dico["ifip60yorigin"] = ( + struct.unpack("HHH", bytestream[1812:1818]) + ) # FIP60ORIGIN + + dico["inumofcornermasks"] = struct.unpack( + "H", bytestream[1818:1820] + ) # CORNER MASKS dico["iacornermaskx"] = struct.unpack("HHHH", bytestream[1820:1828]) dico["iacornermasky"] = struct.unpack("HHHH", bytestream[1828:1836]) - dico["inumofglowingcornermasks"] = struct.unpack("H", bytestream[1836:1838]) # GLOWINGCORNER MASKS + dico["inumofglowingcornermasks"] = struct.unpack( + "H", bytestream[1836:1838] + ) # GLOWINGCORNER MASKS dico["iaglowingcornermaskx"] = struct.unpack("HHHH", bytestream[1838:1846]) dico["iaglowingcornermasky"] = struct.unpack("HHHH", bytestream[1846:1854]) else: @@ -700,7 +776,9 @@ def loads(cls, bytestream): # NUMBER OF BAD POINTS if not ended and start + 2 <= length: try: - dico["ibadpoints"] = struct.unpack("H", bytestream[start:start + 2])[0] + dico["ibadpoints"] = struct.unpack("H", bytestream[start : start + 2])[ + 0 + ] start += 2 points = dico["pschipbadpoint"] = [] for _ in range(dico["ibadpoints"]): # LOOP OVER BAD POINTS @@ -712,7 +790,9 @@ def loads(cls, bytestream): # NUMBER OF BAD COLS if not ended and start + 2 <= length: try: - dico["ibadcolumns"] = struct.unpack("H", bytestream[start:start + 2])[0] + dico["ibadcolumns"] = struct.unpack("H", bytestream[start : start + 2])[ + 0 + ] start += 2 columns = dico["pschipbadcolumn"] = [] for _ in range(dico["ibadcolumns"]): # LOOP OVER BAD COLS @@ -724,7 +804,9 @@ def loads(cls, bytestream): # NUMBER OF BAD COLS 1X1 if not ended and start + 2 <= length: try: - dico["ibadcolumns1x1"] = struct.unpack("H", bytestream[start:start + 2])[0] + dico["ibadcolumns1x1"] = struct.unpack( + "H", bytestream[start : start + 2] + )[0] start += 2 columns = dico["pschipbadcolumn1x1"] = [] for _ in range(dico["ibadcolumns1x1"]): # LOOP OVER BAD COLS @@ -736,7 +818,9 @@ def loads(cls, bytestream): # NUMBER OF BAD COLS 2X2 if not ended and start + 2 <= length: try: - dico["ibadcolumns2x2"] = struct.unpack("H", bytestream[start:start + 2])[0] + dico["ibadcolumns2x2"] = struct.unpack( + "H", bytestream[start : start + 2] + )[0] start += 2 columns = dico["pschipbadcolumn2x2"] = [] for _ in range(dico["ibadcolumns2x2"]): # LOOP OVER BAD COLS @@ -748,7 +832,9 @@ def loads(cls, bytestream): # NUMBER OF BAD COLS 4X4 if not ended and start + 2 <= length: try: - dico["ibadcolumns4x4"] = struct.unpack("H", bytestream[start:start + 2])[0] + dico["ibadcolumns4x4"] = struct.unpack( + "H", bytestream[start : start + 2] + )[0] start += 2 columns = dico["pschipbadcolumn4x4"] = [] for _ in range(dico["ibadcolumns4x4"]): # LOOP OVER BAD COLS @@ -760,7 +846,7 @@ def loads(cls, bytestream): # NUMBER OF BAD ROWS if not ended and start + 2 <= length: try: - dico["ibadrows"] = struct.unpack("H", bytestream[start:start + 2])[0] + dico["ibadrows"] = struct.unpack("H", bytestream[start : start + 2])[0] start += 2 columns = dico["pschipbadrow"] = [] for _ in range(dico["ibadrows"]): # LOOP OVER BAD COLS @@ -771,11 +857,19 @@ def loads(cls, bytestream): if not ended and start + 18 <= length: try: - dico["iscintillatorid"] = struct.unpack("H", bytestream[start:start + 2])[0] # SCINTILLATOR DESCRIPTION - dico["dgain_mo"] = struct.unpack("d", bytestream[start + 2:start + 10])[0] # GAINMO - dico["dgain_cu"] = struct.unpack("d", bytestream[start + 10:start + 18])[0] # GAINCU + dico["iscintillatorid"] = struct.unpack( + "H", bytestream[start : start + 2] + )[0] # SCINTILLATOR DESCRIPTION + dico["dgain_mo"] = struct.unpack( + "d", bytestream[start + 2 : start + 10] + )[0] # GAINMO + dico["dgain_cu"] = struct.unpack( + "d", bytestream[start + 10 : start + 18] + )[0] # GAINCU start += 18 - dico["chipmachinefunction"] = ChipMachineFunction.loads(bytestream[start:]) # IISMACHINEFUNCTION + dico["chipmachinefunction"] = ChipMachineFunction.loads( + bytestream[start:] + ) # IISMACHINEFUNCTION start += ChipMachineFunction.SIZE except AssertionError: ended = True @@ -783,7 +877,9 @@ def loads(cls, bytestream): # NUMBER OF BAD ROWS 1X1 if not ended and start + 2 <= length: try: - dico["ibadrows1x1"] = struct.unpack("H", bytestream[start:start + 2])[0] + dico["ibadrows1x1"] = struct.unpack("H", bytestream[start : start + 2])[ + 0 + ] start += 2 columns = dico["pschipbadrow1x1"] = [] for _ in range(dico["ibadcolumns1x1"]): # LOOP OVER BAD COLS @@ -795,7 +891,9 @@ def loads(cls, bytestream): # NUMBER OF BAD ROWS 2X2 if not ended and start + 2 <= length: try: - dico["ibadrows2x2"] = struct.unpack("H", bytestream[start:start + 2])[0] + dico["ibadrows2x2"] = struct.unpack("H", bytestream[start : start + 2])[ + 0 + ] start += 2 columns = dico["pschipbadrow2x2"] = [] for _ in range(dico["ibadrows2x2"]): # LOOP OVER BAD COLS @@ -807,7 +905,9 @@ def loads(cls, bytestream): # NUMBER OF BAD ROWS 4X4 if not ended and start + 2 <= length: try: - dico["ibadrows4x4"] = struct.unpack("H", bytestream[start:start + 2])[0] + dico["ibadrows4x4"] = struct.unpack("H", bytestream[start : start + 2])[ + 0 + ] start += 2 columns = dico["pschipbadrow4x4"] = [] for _ in range(dico["ibadrows4x4"]): # LOOP OVER BAD COLS @@ -820,7 +920,7 @@ def loads(cls, bytestream): return self def save(self, filename): - with open(filename, "wb") as w: + with open(filename, "wb") as w: w.write(self.dumps()) def dumps(self): @@ -833,8 +933,12 @@ def dumps(self): self._clip_string() self.ibadpolygons = len(self.pschipbadpolygon) self.ibadpoints = len(self.pschipbadpoint) - for empty_4_tuple in ("iacornermaskx", "iacornermasky", - "iaglowingcornermaskx", "iaglowingcornermasky"): + for empty_4_tuple in ( + "iacornermaskx", + "iacornermasky", + "iaglowingcornermaskx", + "iaglowingcornermasky", + ): value = self.__getattribute__(empty_4_tuple) if len(value) == 0: self.__setattr__(empty_4_tuple, [0, 0, 0, 0]) @@ -844,23 +948,23 @@ def dumps(self): # Some helper functions def record_str(key): value = self.__getattribute__(key) - buffer[end:end + len(value)] = value.encode() + buffer[end : end + len(value)] = value.encode() return 256 def record_struct(key, dtype): value = self.__getattribute__(key) size = struct.calcsize(dtype) if isinstance(value, (list, tuple)): - buffer[end:end + size] = struct.pack(dtype, *value) + buffer[end : end + size] = struct.pack(dtype, *value) else: - buffer[end:end + size] = struct.pack(dtype, value) + buffer[end : end + size] = struct.pack(dtype, value) return size def record_object(key): value = self.__getattribute__(key) tmp = value.dumps() ltmp = len(tmp) - buffer[end:end + ltmp] = tmp + buffer[end : end + ltmp] = tmp return ltmp def record_variable(key, subkey, dtype="H"): @@ -868,11 +972,11 @@ def record_variable(key, subkey, dtype="H"): values = self.__getattribute__(subkey) nitems = len(values) self.__setattr__(key, nitems) - buffer[end:end + size] = struct.pack(dtype, nitems) + buffer[end : end + size] = struct.pack(dtype, nitems) for i in values: tmp = i.dumps() ltmp = len(tmp) - buffer[end + size:end + size + ltmp] = tmp + buffer[end + size : end + size + ltmp] = tmp size += ltmp return size @@ -916,23 +1020,21 @@ def build_mask(self, shape): mask = numpy.zeros(shape, dtype=numpy.int8) for poly in self.pschipbadpolygon: if poly.itype == CHIPCHARACTERISTICS_POLYGONTYPE.RECTANGLE.value: - mask[poly.iay[0]:1 + poly.iay[1], poly.iax[0]:1 + poly.iax[1]] = 1 + mask[poly.iay[0] : 1 + poly.iay[1], poly.iax[0] : 1 + poly.iax[1]] = 1 for pt in self.pschipbadpoint: mask[pt.spt.iy, pt.spt.ix] = 1 return mask class XcaliburImage(FabioImage): - """FabIO image class for CrysalisPro mask image - """ + """FabIO image class for CrysalisPro mask image""" DESCRIPTION = "Xcalibur binary struct of masked pixels" DEFAULT_EXTENSIONS = [] def __init__(self, *arg, **kwargs): - """ - """ + """ """ FabioImage.__init__(self, *arg, **kwargs) def _readheader(self, infile): @@ -963,18 +1065,18 @@ def read(self, fname, frame=None): return self def decompose(self, full=False): - """Decompose a mask defined as a 2D binary image in - + """Decompose a mask defined as a 2D binary image in + * vertical+horizontal gaps * rectangles - * bad pixels - - :param: full: deal only with gaps (False) or perform the complete analysis (True) + * bad pixels + + :param: full: deal only with gaps (False) or perform the complete analysis (True) :return: CcdCharacteristiscs struct. """ - ccd = CcdCharacteristiscs(CCD_FILEVERSION_VERS_HIGHEST.value, - pschipbadpolygon=[], - pschipbadpoint=[]) + ccd = CcdCharacteristiscs( + CCD_FILEVERSION_VERS_HIGHEST.value, pschipbadpolygon=[], pschipbadpoint=[] + ) mask = numpy.array(self.data, dtype=bool) shape = mask.shape @@ -984,18 +1086,28 @@ def decompose(self, full=False): ccd.ibadpolygons = len(row_gaps) + len(col_gaps) ccd.pschipbadpolygon = [] for gap in row_gaps: - polygon = ChipBadPolygon(CHIPCHARACTERISTICS_POLYGONTYPE.RECTANGLE.value, 4, - [0, shape[1] - 1], [gap[0], gap[1] - 1]) + polygon = ChipBadPolygon( + CHIPCHARACTERISTICS_POLYGONTYPE.RECTANGLE.value, + 4, + [0, shape[1] - 1], + [gap[0], gap[1] - 1], + ) ccd.pschipbadpolygon.append(polygon) for gap in col_gaps: - polygon = ChipBadPolygon(CHIPCHARACTERISTICS_POLYGONTYPE.RECTANGLE.value, 4, - [gap[0], gap[1] - 1], [0, shape[0] - 1]) + polygon = ChipBadPolygon( + CHIPCHARACTERISTICS_POLYGONTYPE.RECTANGLE.value, + 4, + [gap[0], gap[1] - 1], + [0, shape[0] - 1], + ) ccd.pschipbadpolygon.append(polygon) try: import pyFAI.ext.dynamic_rectangle except ImportError: - logger.warning("PyFAI not available: only a coarse description of the mask is provided") + logger.warning( + "PyFAI not available: only a coarse description of the mask is provided" + ) full = False if not full: return ccd @@ -1004,20 +1116,28 @@ def decompose(self, full=False): for cg in col_gaps + [(self.shape[1], self.shape[1])]: r = 0 for rg in row_gaps + [(self.shape[0], self.shape[0])]: - mm = mask[r:rg[0], c:cg[0]] + mm = mask[r : rg[0], c : cg[0]] if mm.size: rectangles = pyFAI.ext.dynamic_rectangle.decompose_mask(mm) for rec in rectangles: if rec.area == 1: point = ChipPoint(c + rec.col, r + rec.row) - bad_point = ChipBadPoint(point, point, point, CHIPCHARACTERISTICS_TREATMENT.IGNORE.value) + bad_point = ChipBadPoint( + point, + point, + point, + CHIPCHARACTERISTICS_TREATMENT.IGNORE.value, + ) ccd.ibadpoints += 1 ccd.pschipbadpoint.append(bad_point) else: ccd.ibadpolygons += 1 - polygon = ChipBadPolygon(CHIPCHARACTERISTICS_POLYGONTYPE.RECTANGLE.value, 4, - [c + rec.col, c + rec.col + rec.width - 1], - [r + rec.row, r + rec.row + rec.height - 1]) + polygon = ChipBadPolygon( + CHIPCHARACTERISTICS_POLYGONTYPE.RECTANGLE.value, + 4, + [c + rec.col, c + rec.col + rec.width - 1], + [r + rec.row, r + rec.row + rec.height - 1], + ) ccd.pschipbadpolygon.append(polygon) r = rg[1] c = cg[1] @@ -1025,7 +1145,6 @@ def decompose(self, full=False): @staticmethod def _search_gap(mask, dim=0): - shape = mask.shape m0 = numpy.sum(mask, axis=dim, dtype="int") == shape[dim] if m0.any(): @@ -1033,39 +1152,44 @@ def _search_gap(mask, dim=0): d0 = m0[1:] - m0[:-1] starts = numpy.where(d0 == 1)[0] stops = numpy.where(d0 == -1)[0] - if (len(starts) == 0): + if len(starts) == 0: starts = numpy.array([-1]) - if (len(stops) == 0): + if len(stops) == 0: stops = numpy.array([len(m0) - 1]) - if (stops[0] < starts[0]): + if stops[0] < starts[0]: starts = numpy.concatenate(([-1], starts)) - if (stops[-1] < starts[-1]): + if stops[-1] < starts[-1]: stops = numpy.concatenate((stops, [len(m0) - 1])) - r0 = [ (start + 1, stop + 1) for start, stop in zip(starts, stops)] + r0 = [(start + 1, stop + 1) for start, stop in zip(starts, stops)] else: r0 = [] return r0 def save_par(self, path, prefix, **kwargs): - """Generate a *.par" file which contains the parameters of the scan - """ - - dico = {"alpha": kwargs.get("alpha", 50), - "beta": kwargs.get("beta", 0), - "wavelength": kwargs.get("wavelength", 1), - "polarization": kwargs.get("polarization", 0.98), - "shape_x": self.shape[1], - "shape_y": self.shape[0], - "distance": kwargs.get("distance", 100), # mm - "path": path, - "ccd_file": prefix + ".ccd", - "oscil": kwargs.get("oscil", 1), - "center_x":kwargs.get("center_x", self.shape[1] / 2), - "center_y":kwargs.get("center_y", self.shape[1] / 2), - "date": time.ctime() - } + """Generate a *.par" file which contains the parameters of the scan""" + + dico = { + "alpha": kwargs.get("alpha", 50), + "beta": kwargs.get("beta", 0), + "wavelength": kwargs.get("wavelength", 1), + "polarization": kwargs.get("polarization", 0.98), + "shape_x": self.shape[1], + "shape_y": self.shape[0], + "distance": kwargs.get("distance", 100), # mm + "path": path, + "ccd_file": prefix + ".ccd", + "oscil": kwargs.get("oscil", 1), + "center_x": kwargs.get("center_x", self.shape[1] / 2), + "center_y": kwargs.get("center_y", self.shape[1] / 2), + "date": time.ctime(), + } string = PAR_TEMPLATE.format(**dico) - blines = [b"\xa7" + i[1:].rstrip().encode() if i.startswith("#") else i.rstrip().encode() for i in string.split("\n")] + blines = [ + b"\xa7" + i[1:].rstrip().encode() + if i.startswith("#") + else i.rstrip().encode() + for i in string.split("\n") + ] with open(os.path.join(path, prefix + ".par"), "wb") as w: w.write(b"\r\n".join(blines)) diff --git a/src/fabio/xsdimage.py b/src/fabio/xsdimage.py index 73160ea6..c6851277 100644 --- a/src/fabio/xsdimage.py +++ b/src/fabio/xsdimage.py @@ -52,7 +52,9 @@ # Try using the standard library import xml.etree.ElementTree as etree except ImportError: - logger.warning("xml/lxml library is probably not part of your python installation: disabling xsdimage format") + logger.warning( + "xml/lxml library is probably not part of your python installation: disabling xsdimage format" + ) etree = None @@ -83,8 +85,7 @@ def __init__(self, data=None, header=None, fname=None): self.read(fname) def read(self, fname, frame=None): - """ - """ + """ """ self.header = {} self.resetvals() self.filename = fname @@ -105,7 +106,10 @@ def read(self, fname, frame=None): elif self.coding == "base16": decData = base64.b16decode(self.rawData) else: - logger.warning("Unable to recognize the encoding of the data !!! got %s, expected base64, base32 or base16, I assume it is base64 " % self.coding) + logger.warning( + "Unable to recognize the encoding of the data !!! got %s, expected base64, base32 or base16, I assume it is base64 " + % self.coding + ) decData = base64.b64decode(self.rawData) if self.md5: assert hashlib.md5(decData).hexdigest() == self.md5 @@ -131,14 +135,20 @@ def _readheader(self, infile): try: self._shape.insert(0, int(i.text)) except ValueError as error: - logger.warning("%s Shape: Unable to convert %s to integer in %s" % (error, i.text, i)) + logger.warning( + "%s Shape: Unable to convert %s to integer in %s" + % (error, i.text, i) + ) self._shape = tuple(self._shape) for i in xml.findall(".//size"): try: self.size = int(i.text) except Exception as error: - logger.warning("%s Size: Unable to convert %s to integer in %s" % (error, i.text, i)) + logger.warning( + "%s Size: Unable to convert %s to integer in %s" + % (error, i.text, i) + ) self._dtype = None for i in xml.findall(".//dtype"): diff --git a/version.py b/version.py index f62ef834..bd0878d5 100755 --- a/version.py +++ b/version.py @@ -4,7 +4,7 @@ # Project: X-ray image reader # https://github.com/silx-kit/fabio # -# Copyright (C) 2015-2024 European Synchrotron Radiation Facility, Grenoble, France +# Copyright (C) 2015-2025 European Synchrotron Radiation Facility, Grenoble, France # # Principal author: Jérôme Kieffer (Jerome.Kieffer@ESRF.eu) # @@ -58,32 +58,36 @@ __contact__ = "Jerome.Kieffer@ESRF.eu" __license__ = "MIT" __copyright__ = "European Synchrotron Radiation Facility, Grenoble, France" -__date__ = "03/10/2024" +__date__ = "27/10/2025" __status__ = "production" -__docformat__ = 'restructuredtext' -__all__ = ["date", "version_info", "strictversion", "hexversion", "debianversion", - "calc_hexversion"] +__docformat__ = "restructuredtext" +__all__ = [ + "date", + "version_info", + "strictversion", + "hexversion", + "debianversion", + "calc_hexversion", +] -RELEASE_LEVEL_VALUE = {"dev": 0, - "alpha": 10, - "beta": 11, - "candidate": 12, - "final": 15} -PRERELEASE_NORMALIZED_NAME = {"dev": "a", - "alpha": "a", - "beta": "b", - "candidate": "rc"} +from collections import namedtuple + + +RELEASE_LEVEL_VALUE = {"dev": 0, "alpha": 10, "beta": 11, "candidate": 12, "final": 15} + +PRERELEASE_NORMALIZED_NAME = {"dev": "a", "alpha": "a", "beta": "b", "candidate": "rc"} MAJOR = 2025 MINOR = 10 MICRO = 0 -RELEV = "dev" # <16 -SERIAL = 0 # <16 +RELEV = "beta" # <16 +SERIAL = 1 # <16 date = __date__ -from collections import namedtuple -_version_info = namedtuple("version_info", ["major", "minor", "micro", "releaselevel", "serial"]) +_version_info = namedtuple( + "version_info", ["major", "minor", "micro", "releaselevel", "serial"] +) version_info = _version_info(MAJOR, MINOR, MICRO, RELEV, SERIAL) @@ -91,7 +95,11 @@ if version_info.releaselevel != "final": _prerelease = PRERELEASE_NORMALIZED_NAME[version_info[3]] version += "%s%s" % (_prerelease, version_info[-1]) - debianversion += "~adev%i" % version_info[-1] if RELEV == "dev" else "~%s%i" % (_prerelease, version_info[-1]) + debianversion += ( + "~adev%i" % version_info[-1] + if RELEV == "dev" + else "~%s%i" % (_prerelease, version_info[-1]) + ) strictversion += _prerelease + str(version_info[-1])