Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-lfdfiles for openSUSE:Factory 
checked in at 2026-06-28 21:07:21
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-lfdfiles (Old)
 and      /work/SRC/openSUSE:Factory/.python-lfdfiles.new.11887 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-lfdfiles"

Sun Jun 28 21:07:21 2026 rev:12 rq:1362043 version:2026.6.24

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-lfdfiles/python-lfdfiles.changes  
2026-05-18 17:47:15.178408955 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-lfdfiles.new.11887/python-lfdfiles.changes   
    2026-06-28 21:08:00.869067006 +0200
@@ -1,0 +2,7 @@
+Sat Jun 27 20:54:28 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 2026.6.24:
+  * Add VistaTdflim class for reading ISS Vista TDFLIM files.
+  * Support Python 3.15.
+
+-------------------------------------------------------------------

Old:
----
  lfdfiles-2026.4.30.tar.gz

New:
----
  lfdfiles-2026.6.24.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-lfdfiles.spec ++++++
--- /var/tmp/diff_new_pack.GEMl1Y/_old  2026-06-28 21:08:01.369083854 +0200
+++ /var/tmp/diff_new_pack.GEMl1Y/_new  2026-06-28 21:08:01.369083854 +0200
@@ -25,7 +25,7 @@
 %{?sle15_python_module_pythons}
 %global skip_python311 1
 Name:           python-lfdfiles
-Version:        2026.4.30
+Version:        2026.6.24
 Release:        0
 Summary:        Laboratory for Fluorescence Dynamics (LFD) file formats
 License:        BSD-3-Clause

++++++ lfdfiles-2026.4.30.tar.gz -> lfdfiles-2026.6.24.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lfdfiles-2026.4.30/CHANGES.rst 
new/lfdfiles-2026.6.24/CHANGES.rst
--- old/lfdfiles-2026.4.30/CHANGES.rst  2026-04-29 20:10:59.000000000 +0200
+++ new/lfdfiles-2026.6.24/CHANGES.rst  2026-06-24 20:08:13.000000000 +0200
@@ -1,6 +1,11 @@
 Revisions
 =========
 
+2026.6.24
+
+- Add VistaTdflim class for reading ISS Vista TDFLIM files.
+- Support Python 3.15.
+
 2026.4.30
 
 - Remove FlimboxFbd, FlimboxFbf, and FlimboxFbs (breaking; use fbdfile).
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lfdfiles-2026.4.30/README.rst 
new/lfdfiles-2026.6.24/README.rst
--- old/lfdfiles-2026.4.30/README.rst   2026-04-29 20:10:59.000000000 +0200
+++ new/lfdfiles-2026.6.24/README.rst   2026-06-24 20:08:13.000000000 +0200
@@ -15,12 +15,12 @@
 - CCP4 MAP
 - Vaa3D RAW
 - Bio-Rad(r) PIC
-- ISS Vista IFLI, IFI
+- ISS Vista IFLI, IFI, TDFLIM
 - FlimFast FLIF
 
 :Author: `Christoph Gohlke <https://www.cgohlke.com>`_
 :License: BSD-3-Clause
-:Version: 2026.4.30
+:Version: 2026.6.24
 :DOI: `10.5281/zenodo.8384166 <https://doi.org/10.5281/zenodo.8384166>`_
 
 Quickstart
@@ -48,17 +48,22 @@
 This revision was tested with the following requirements and dependencies
 (other versions may work):
 
-- `CPython <https://www.python.org>`_ 3.12.10, 3.13.13, 3.14.4 64-bit
-- `NumPy <https://pypi.org/project/numpy>`_ 2.4.4
-- `Tifffile <https://pypi.org/project/tifffile/>`_ 2026.4.11
-- `Matplotlib <https://pypi.org/project/matplotlib/>`_ 3.10.9
+- `CPython <https://www.python.org>`_ 3.12.10, 3.13.14, 3.14.6, 3.15.0b3 64-bit
+- `Numpy <https://pypi.org/project/numpy>`_ 2.5.0
+- `Tifffile <https://pypi.org/project/tifffile/>`_ 2026.6.1
+- `Matplotlib <https://pypi.org/project/matplotlib/>`_ 3.11.0
   (optional, for plotting)
-- `Click <https://pypi.python.org/pypi/click>`_ 8.3.3
+- `Click <https://pypi.python.org/pypi/click>`_ 8.4.1
   (optional, for command line apps)
 
 Revisions
 ---------
 
+2026.6.24
+
+- Add VistaTdflim class for reading ISS Vista TDFLIM files.
+- Support Python 3.15.
+
 2026.4.30
 
 - Remove FlimboxFbd, FlimboxFbf, and FlimboxFbs (breaking; use fbdfile).
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lfdfiles-2026.4.30/lfdfiles/lfdfiles.py 
new/lfdfiles-2026.6.24/lfdfiles/lfdfiles.py
--- old/lfdfiles-2026.4.30/lfdfiles/lfdfiles.py 2026-04-29 20:10:59.000000000 
+0200
+++ new/lfdfiles-2026.6.24/lfdfiles/lfdfiles.py 2026-06-24 20:08:13.000000000 
+0200
@@ -42,12 +42,12 @@
 - CCP4 MAP
 - Vaa3D RAW
 - Bio-Rad(r) PIC
-- ISS Vista IFLI, IFI
+- ISS Vista IFLI, IFI, TDFLIM
 - FlimFast FLIF
 
 :Author: `Christoph Gohlke <https://www.cgohlke.com>`_
 :License: BSD-3-Clause
-:Version: 2026.4.30
+:Version: 2026.6.24
 :DOI: `10.5281/zenodo.8384166 <https://doi.org/10.5281/zenodo.8384166>`_
 
 Quickstart
@@ -75,17 +75,22 @@
 This revision was tested with the following requirements and dependencies
 (other versions may work):
 
-- `CPython <https://www.python.org>`_ 3.12.10, 3.13.13, 3.14.4 64-bit
-- `NumPy <https://pypi.org/project/numpy>`_ 2.4.4
-- `Tifffile <https://pypi.org/project/tifffile/>`_ 2026.4.11
-- `Matplotlib <https://pypi.org/project/matplotlib/>`_ 3.10.9
+- `CPython <https://www.python.org>`_ 3.12.10, 3.13.14, 3.14.6, 3.15.0b3 64-bit
+- `Numpy <https://pypi.org/project/numpy>`_ 2.5.0
+- `Tifffile <https://pypi.org/project/tifffile/>`_ 2026.6.1
+- `Matplotlib <https://pypi.org/project/matplotlib/>`_ 3.11.0
   (optional, for plotting)
-- `Click <https://pypi.python.org/pypi/click>`_ 8.3.3
+- `Click <https://pypi.python.org/pypi/click>`_ 8.4.1
   (optional, for command line apps)
 
 Revisions
 ---------
 
+2026.6.24
+
+- Add VistaTdflim class for reading ISS Vista TDFLIM files.
+- Support Python 3.15.
+
 2026.4.30
 
 - Remove FlimboxFbd, FlimboxFbf, and FlimboxFbs (breaking; use fbdfile).
@@ -233,7 +238,7 @@
 
 from __future__ import annotations
 
-__version__ = '2026.4.30'
+__version__ = '2026.6.24'
 
 __all__ = [
     'FILE_EXTENSIONS',
@@ -270,6 +275,7 @@
     'Vaa3dRaw',
     'VistaIfi',
     'VistaIfli',
+    'VistaTdflim',
     'VoxxMap',
     '__version__',
     'bioradpic_write',
@@ -1038,7 +1044,7 @@
             def imread_func(
                 filename: str | os.PathLike[Any],
                 /,
-                lfdfile: type[LfdFile] = imread,  # type: ignore[assignment]
+                lfdfile: type[LfdFile] = imread,
                 **kwargs: Any,
             ) -> NDArray[Any]:
                 with lfdfile(filename) as lfdf:
@@ -3532,6 +3538,329 @@
         )
 
 
+class VistaTdflim(LfdFile):
+    """ISS Vista time-domain fluorescence lifetime image.
+
+    VistaTdflim files are ZIP archives containing acquisition metadata in
+    ``dataProps/Core.xml`` and raw photon histogram data in
+    ``data/PrimaryDecayData.bin``.
+
+    Two binary format variants exist, identified by the ``<FileVersion>``
+    element in the XML:
+
+    - Version 1 (``<FileVersion>`` absent):
+      ``uint16``, dimension order ``TCYXH``.
+    - Version 2 (``<iVersionNumber>2</iVersionNumber>``):
+      ``uint32``, dimension order ``TCYX(H+2)``.
+      The last dimension contains two trailing uint32 words per pixel
+      (reserved zero word and a pre-computed float32 lifetime preview).
+
+    The returned array has shape ``(T, C, Y, X, H)``, where T is the number
+    of time series points, C is the channel count, Y and X are spatial
+    dimensions, and H is the photon histogram bin count.
+
+    Parameters:
+        filename: Name of file to open.
+
+    Examples:
+        >>> with VistaTdflim(DATA / 'version1.iss-tdflim') as f:
+        ...     data = f.asarray()
+        ...     f.axes
+        ...
+        'TCYXH'
+        >>> data.shape
+        (1, 2, 128, 128, 1024)
+        >>> with VistaTdflim(DATA / 'version2.iss-tdflim') as f:
+        ...     data = f.asarray()
+        ...     data.shape
+        ...
+        (1, 1, 128, 128, 4096)
+
+    """
+
+    _filepattern = r'.*\.(iss-tdflim|tdflim)$'
+
+    attrs: dict[str, Any]
+    """Metadata from Core.xml."""
+
+    coords: dict[str, NDArray[Any]]
+    """Coordinate arrays keyed by one-letter axis codes."""
+
+    @override
+    def _init(self, **kwargs: Any) -> None:
+        """Read and parse metadata from Core.xml inside ZIP archive."""
+        from xml.etree import ElementTree
+
+        assert self._fh is not None
+        try:
+            with zipfile.ZipFile(self._fh) as zf:
+                xml_files = [
+                    n for n in zf.namelist() if n.endswith('Core.xml')
+                ]
+                if not xml_files:
+                    raise LfdFileError(self, 'Core.xml not found')
+                with zf.open(xml_files[0]) as fh:
+                    root = ElementTree.parse(fh).getroot()  # noqa: S314
+                bin_files = [
+                    n
+                    for n in zf.namelist()
+                    if n.endswith('PrimaryDecayData.bin')
+                ]
+                if not bin_files:
+                    raise LfdFileError(self, 'PrimaryDecayData.bin not found')
+                bin_size = zf.getinfo(bin_files[0]).file_size
+        except zipfile.BadZipFile as exc:
+            raise LfdFileError(self) from exc
+
+        data_type = root.findtext('DataType')
+        if data_type != 'TDFlimData':
+            raise LfdFileError(
+                self,
+                f'unsupported DataType {data_type!r}, expected TDFlimData',
+            )
+
+        # parse dimensions
+        dims = root.find('Dimensions')
+        pcs = root.find('PhotonCountingSettings')
+        if dims is None or pcs is None:
+            raise LfdFileError(self, 'missing required XML elements')
+
+        def _int(node: ElementTree.Element, tag: str) -> int:
+            el = node.find(tag)
+            if el is None or not el.text:
+                raise LfdFileError(self, f'<{tag}> not found')
+            return int(el.text.strip())
+
+        def _float(node: ElementTree.Element, tag: str) -> float:
+            el = node.find(tag)
+            if el is None or not el.text:
+                raise LfdFileError(self, f'<{tag}> not found')
+            return float(el.text.strip())
+
+        sizet = _int(dims, 'TimeSeriesCount')
+        sizec = _int(dims, 'ChannelCount')
+        sizey = _int(dims, 'FrameHeight')
+        sizex = _int(dims, 'FrameWidth')
+        sizeh = _int(pcs, 'AdcResolution')
+
+        fv_node = root.find('FileVersion')
+        if fv_node is not None:
+            iv = fv_node.find('iVersionNumber')
+            file_version = (
+                int(iv.text.strip()) if iv is not None and iv.text else 1
+            )
+        else:
+            file_version = 1
+
+        size = sizet * sizec * sizey * sizex
+        if file_version == 1:
+            expected = size * sizeh * 2
+            if bin_size != expected:
+                raise LfdFileError(
+                    self,
+                    f'binary size mismatch (version 1): '
+                    f'expected {expected}, got {bin_size}',
+                )
+            self.dtype = numpy.dtype(numpy.uint16)
+        elif file_version == 2:
+            expected = size * (sizeh + 2) * 4
+            if bin_size != expected:
+                raise LfdFileError(
+                    self,
+                    f'binary size mismatch (version 2): '
+                    f'expected {expected}, got {bin_size}',
+                )
+            self.dtype = numpy.dtype(numpy.uint32)
+        else:
+            raise LfdFileError(
+                self, f'unsupported file version {file_version!r}'
+            )
+
+        # parse metadata attributes
+        channel_ids_node = root.find('ChannelIds')
+        if channel_ids_node is not None:
+            channel_ids = [
+                int(el.text.strip())
+                for el in channel_ids_node.findall('ChannelId')
+                if el.text
+            ]
+        else:
+            channel_ids = list(range(sizec))
+        if len(channel_ids) != sizec:
+            channel_ids = list(range(sizec))
+
+        units = {
+            'micrometer': 'um',
+            'nanometer': 'nm',
+            'millimeter': 'mm',
+            'pixel': 'px',
+        }
+        coord_unit_type = root.findtext('CoordUnitType') or ''
+        spatial_unit = units.get(coord_unit_type.lower(), coord_unit_type)
+
+        tac_time_range = _float(pcs, 'TacTimeRange')
+
+        ef_node = root.find('.//ExcitationFrequency')
+        if ef_node is not None and ef_node.text:
+            frequency = float(ef_node.text.strip()) / 1e6
+        else:
+            frequency = 1e3 / tac_time_range
+
+        self.attrs = {
+            'file_version': file_version,
+            'datetime_stamp': root.findtext('DateTimeStamp') or '',
+            'channel_ids': channel_ids,
+            'frequency': frequency,
+            'tac_time_range': tac_time_range,
+            'macro_time_clock_frequency': _float(
+                pcs, 'MacroTimeClockFrequency'
+            ),
+            'pixel_dwell_time': _float(root, 'PixelDwellTime'),
+            'coord_unit_type': coord_unit_type,
+            'spatial_unit': spatial_unit,
+        }
+
+        # parse axes coordinates
+        self.coords = {}
+        time_series = root.find('TimeSeries')
+        if time_series is not None:
+            tags_node = time_series.find('TimeSeriesTags')
+            if tags_node is not None:
+                tags = [
+                    float(el.text.strip())
+                    for el in tags_node.findall('TimeTag')
+                    if el.text
+                ]
+                if len(tags) == sizet:
+                    self.coords['T'] = numpy.asarray(tags, dtype=numpy.float64)
+
+        self.coords['C'] = numpy.asarray(channel_ids, dtype=numpy.uint16)
+
+        boundary = root.find('Boundary')
+        if boundary is not None:
+            frame_top = boundary.findtext('FrameTop')
+            frame_bottom = boundary.findtext('FrameBottom')
+            frame_left = boundary.findtext('FrameLeft')
+            frame_right = boundary.findtext('FrameRight')
+
+            if frame_top is not None and frame_bottom is not None:
+                self.coords['Y'] = numpy.linspace(
+                    float(frame_top),
+                    float(frame_bottom),
+                    sizey,
+                    endpoint=False,
+                )
+            if frame_left is not None and frame_right is not None:
+                self.coords['X'] = numpy.linspace(
+                    float(frame_left),
+                    float(frame_right),
+                    sizex,
+                    endpoint=False,
+                )
+
+            bnd_width = boundary.findtext('FrameWidth')
+            bnd_height = boundary.findtext('FrameHeight')
+            if bnd_width is not None:
+                self.attrs['frame_width'] = float(bnd_width)
+            if bnd_height is not None:
+                self.attrs['frame_height'] = float(bnd_height)
+
+        self.coords['H'] = numpy.linspace(
+            0, tac_time_range, sizeh, endpoint=False
+        )
+
+        self.shape = (sizet, sizec, sizey, sizex, sizeh)
+        self.axes = 'TCYXH'
+
+    @override
+    def _asarray(self, **kwargs: Any) -> NDArray[Any]:
+        """Return photon histogram array of shape (T, C, Y, X, H)."""
+        assert self._fh is not None
+        assert self.shape is not None
+        assert self.dtype is not None
+        sizet, sizec, sizey, sizex, sizeh = self.shape
+        file_version = self.attrs['file_version']
+
+        with zipfile.ZipFile(self._fh) as zf:
+            bin_files = [
+                n for n in zf.namelist() if n.endswith('PrimaryDecayData.bin')
+            ]
+            with zf.open(bin_files[0]) as fh:
+                raw = fh.read()
+
+        if file_version == 1:
+            data = numpy.frombuffer(raw, dtype=numpy.uint16)
+            return data.reshape(self.shape).copy()
+        # version 2: each pixel has H uint32 counts + 2 uint32 footer words
+        data32 = numpy.frombuffer(raw, dtype=numpy.uint32)
+        counts = data32.reshape(sizet * sizec * sizey * sizex, sizeh + 2)[
+            :, :sizeh
+        ]
+        return counts.reshape(self.shape).copy()
+
+    def lifetime(self) -> NDArray[numpy.float32]:
+        """Return lifetime image from version 2 file as NumPy array.
+
+        The returned array has shape ``(T, C, Y, X)`` and type float32.
+        The lifetime values are pre-computed by the ISS Vista acquisition
+        software.
+
+        """
+        assert self._fh is not None
+        assert self.shape is not None
+        if self.attrs['file_version'] != 2:
+            msg = 'lifetime is only available for version 2 files'
+            raise ValueError(self, msg)
+        sizet, sizec, sizey, sizex, sizeh = self.shape
+        with zipfile.ZipFile(self._fh) as zf:
+            bin_file = next(
+                n for n in zf.namelist() if n.endswith('PrimaryDecayData.bin')
+            )
+            with zf.open(bin_file) as fh:
+                buffer = fh.read()
+        # layout: (pixels, H + 2); last column is float32 lifetime
+        lifetime = numpy.frombuffer(buffer, dtype=numpy.float32)
+        lifetime = lifetime.reshape((sizet, sizec, sizey, sizex, sizeh + 2))
+        return lifetime[..., -1].copy()
+
+    @override
+    def _plot(self, figure: Figure, /, **kwargs: Any) -> None:
+        """Display data in matplotlib figure."""
+        assert pyplot is not None
+        update_kwargs(kwargs, cmap='viridis')
+        pyplot.subplots_adjust(bottom=0.07, top=0.93)
+
+        channel = kwargs.pop('channel', 0)
+
+        data = self.asarray()
+        image = numpy.mean(data[:, channel], axis=(0, -1))
+        hist = numpy.mean(data, axis=(0, 2, 3))
+
+        ax = pyplot.subplot2grid((3, 1), (0, 0), colspan=2, rowspan=2)
+        ax.set_title(self._filename + f' (Ch{channel})')
+        ax.imshow(image, **kwargs)
+        ax = pyplot.subplot2grid((3, 1), (2, 0))
+        ax.set_title('Mean')
+        ax.set_xlim([0, hist.shape[-1] - 1])
+        ax.set_xlabel('Histogram bin')
+        for h in hist:
+            ax.plot(h)
+
+    @override
+    def _totiff(self, tif: TiffWriter, /, **kwargs: Any) -> None:
+        """Write image data to TIFF file."""
+        update_kwargs(kwargs, metadata={'axes': self.axes})
+        tif.write(self.asarray(), **kwargs)
+
+    @override
+    def _str(self) -> str | None:
+        """Return additional information about file."""
+        return indent(
+            'attrs:',
+            *(f'{k}: {v}' for k, v in self.attrs.items()),
+        )
+
+
 class FlimfastFlif(LfdFile):
     """FlimFast fluorescence lifetime image.
 
@@ -5156,6 +5485,8 @@
     '.z64': SimfcsZ64,
     '.cyl': SimfcsCyl,
     '.ifli': VistaIfli,
+    '.iss-tdflim': VistaTdflim,
+    '.tdflim': VistaTdflim,
     '.vpl': SimfcsVpl,
     '.vpp': SimfcsVpp,
     '.ifi': VistaIfi,
@@ -5163,6 +5494,7 @@
     '.flif': FlimfastFlif,
     '.v3draw': Vaa3dRaw,
 }
+"""Supported LFD file extensions."""
 
 if __name__ == '__main__':
     main()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lfdfiles-2026.4.30/setup.py 
new/lfdfiles-2026.6.24/setup.py
--- old/lfdfiles-2026.4.30/setup.py     2026-04-29 20:10:59.000000000 +0200
+++ new/lfdfiles-2026.6.24/setup.py     2026-06-24 20:08:13.000000000 +0200
@@ -96,7 +96,7 @@
     project_urls={
         'Bug Tracker': 'https://github.com/cgohlke/lfdfiles/issues',
         'Source Code': 'https://github.com/cgohlke/lfdfiles',
-        # 'Documentation': 'https://',
+        'Documentation': 'https://www.cgohlke.com/docs/lfdfiles',
     },
     packages=['lfdfiles'],
     package_data={'lfdfiles': ['py.typed']},
@@ -116,5 +116,6 @@
         'Programming Language :: Python :: 3.12',
         'Programming Language :: Python :: 3.13',
         'Programming Language :: Python :: 3.14',
+        'Programming Language :: Python :: 3.15',
     ],
 )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lfdfiles-2026.4.30/tests/test_lfdfiles.py 
new/lfdfiles-2026.6.24/tests/test_lfdfiles.py
--- old/lfdfiles-2026.4.30/tests/test_lfdfiles.py       2026-04-29 
20:10:59.000000000 +0200
+++ new/lfdfiles-2026.6.24/tests/test_lfdfiles.py       2026-06-24 
20:08:13.000000000 +0200
@@ -29,7 +29,7 @@
 
 """Unittests for the lfdfiles package.
 
-:Version: 2026.4.30
+:Version: 2026.6.24
 
 """
 
@@ -59,6 +59,7 @@
     GlobalsAscii,
     GlobalsLif,
     LfdFile,
+    LfdFileError,
     LfdFileSequence,
     RawPal,
     SimfcsB64,
@@ -80,6 +81,7 @@
     Vaa3dRaw,
     VistaIfi,
     VistaIfli,
+    VistaTdflim,
     VoxxMap,
     bioradpic_write,
     ccp4map_write,
@@ -124,13 +126,13 @@
     ) as f:
         assert type(f) is SimfcsBin
 
-    with pytest.raises(lfdfiles.LfdFileError):
+    with pytest.raises(LfdFileError):
         LfdFile.open(DATA / 'flimfast.flif', registry=[])
 
 
 def test_lfdfileerror():
     """Test LfdFileError is a ValueError subclass."""
-    assert issubclass(lfdfiles.LfdFileError, ValueError)
+    assert issubclass(LfdFileError, ValueError)
 
 
 def test_module_docstring_example():
@@ -203,7 +205,7 @@
 
 def test_lfdfile_validate():
     """Test LfdFile raises LfdFileError on filename pattern mismatch."""
-    with pytest.raises(lfdfiles.LfdFileError):
+    with pytest.raises(LfdFileError):
         SimfcsRef('wrong_extension.xyz')
 
 
@@ -569,6 +571,65 @@
         assert_array_equal(tif.asarray()[0, 0], data[..., 0, 0])
 
 
+def test_vistatdflim_v1():
+    """Test VistaTdflim with version 1 file (uint16)."""
+    with VistaTdflim(DATA / 'version1.iss-tdflim') as f:
+        data = f.asarray()
+        f.totiff(TEMP / '_vista_tdflim_v1.tif')
+        assert f.axes == 'TCYXH'
+        assert f.shape == (1, 2, 128, 128, 1024)
+        assert f.dtype == numpy.uint16
+        assert f.attrs['file_version'] == 1
+        assert f.attrs['datetime_stamp'] == '2022-11-18T16:40:35.9871206+01:00'
+        assert f.attrs['channel_ids'] == [0, 2]
+        assert pytest.approx(f.attrs['frequency'], 0.001) == 79.9463
+        assert pytest.approx(f.attrs['tac_time_range'], 1e-6) == 12.5083933
+        assert sorted(f.coords.keys()) == ['C', 'H', 'T', 'X', 'Y']
+        assert f.coords['T'].shape == (1,)
+        assert f.coords['C'].tolist() == [0, 2]
+        assert f.coords['Y'].shape == (128,)
+        assert f.coords['X'].shape == (128,)
+        assert f.coords['H'].shape == (1024,)
+        assert data.shape == f.shape
+        assert int(data[0, 0, 3, 0, 97]) == 1980
+
+        with pytest.raises(ValueError):
+            f.lifetime()
+
+    with TiffFile(TEMP / '_vista_tdflim_v1.tif') as tif:
+        assert_array_equal(tif.asarray(), data)
+
+
+def test_vistatdflim_v2():
+    """Test VistaTdflim with version 2 file (uint32, with per-pixel footer)."""
+    with VistaTdflim(DATA / 'version2.iss-tdflim') as f:
+        data = f.asarray()
+        f.totiff(TEMP / '_vista_tdflim_v2.tif')
+        assert f.axes == 'TCYXH'
+        assert f.shape == (1, 1, 128, 128, 4096)
+        assert f.dtype == numpy.uint32
+        assert f.attrs['file_version'] == 2
+        assert f.attrs['datetime_stamp'] == '2026-04-22T13:00:42.4049325+02:00'
+        assert f.attrs['channel_ids'] == [2]
+        assert f.attrs['frequency'] == 20.0
+        assert sorted(f.coords.keys()) == ['C', 'H', 'T', 'X', 'Y']
+        assert f.coords['T'].shape == (1,)
+        assert f.coords['C'].tolist() == [2]
+        assert f.coords['Y'].shape == (128,)
+        assert f.coords['X'].shape == (128,)
+        assert f.coords['H'].shape == (4096,)
+        assert data.shape == f.shape
+        assert int(data[0, 0, 114, 34, 325]) == 11
+
+        lifetime = f.lifetime()
+        assert lifetime.shape == (1, 1, 128, 128)
+        assert lifetime.dtype == numpy.float32
+        assert lifetime.mean() == pytest.approx(3.007484)
+
+    with TiffFile(TEMP / '_vista_tdflim_v2.tif') as tif:
+        assert_array_equal(tif.asarray(), data)
+
+
 def test_flimfastflif():
     """Test FlimfastFlif."""
     f = FlimfastFlif(DATA / 'flimfast.flif')
@@ -830,9 +891,11 @@
 
 @pytest.mark.parametrize(
     'filename',
-    itertools.chain.from_iterable(
-        glob.glob(f'**/*{ext}', root_dir=DATA, recursive=True)
-        for ext in FILE_EXTENSIONS
+    tuple(
+        itertools.chain.from_iterable(
+            glob.glob(f'**/*{ext}', root_dir=DATA, recursive=True)
+            for ext in FILE_EXTENSIONS
+        )
     ),
 )
 def test_glob(filename):

Reply via email to