Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-tifffile for openSUSE:Factory 
checked in at 2023-06-01 17:19:31
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-tifffile (Old)
 and      /work/SRC/openSUSE:Factory/.python-tifffile.new.2531 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-tifffile"

Thu Jun  1 17:19:31 2023 rev:12 rq:1090095 version:2023.4.12

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-tifffile/python-tifffile.changes  
2023-05-25 23:52:33.395617983 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-tifffile.new.2531/python-tifffile.changes    
    2023-06-01 17:19:39.134207279 +0200
@@ -1,0 +2,15 @@
+Wed May 31 08:27:38 UTC 2023 - Dirk Müller <[email protected]>
+
+- update to 2023.4.12:
+  * Pass 4988 tests.
+  * Do not write duplicate ImageDescription tags from extratags
+    (breaking).
+  * Support multifocal SVS files (#193).
+  * Log warning when filtering out extratags.
+  * Fix writing OME-TIFF with image description in extratags.
+  * Ignore invalid predictor tag value if prediction is not used.
+  * Raise KeyError if ZarrStore is missing requested chunk.
+  * 2023.3.21
+  * Fix reading MMstack with missing data (#187).
+
+-------------------------------------------------------------------

Old:
----
  tifffile-2023.3.15.tar.gz

New:
----
  tifffile-2023.4.12.tar.gz

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

Other differences:
------------------
++++++ python-tifffile.spec ++++++
--- /var/tmp/diff_new_pack.mVSmGg/_old  2023-06-01 17:19:39.850211523 +0200
+++ /var/tmp/diff_new_pack.mVSmGg/_new  2023-06-01 17:19:39.858211571 +0200
@@ -25,7 +25,7 @@
 %bcond_with test
 %endif
 Name:           python-tifffile%{psuffix}
-Version:        2023.3.15
+Version:        2023.4.12
 Release:        0
 Summary:        Read and write TIFF files
 License:        BSD-2-Clause
@@ -127,6 +127,7 @@
 donttest="$donttest or test_write_extrasamples_planar"
 donttest="$donttest or test_write_extrasamples_contig_rgb"
 donttest="$donttest or test_write_compression_args"
+donttest="$donttest or test_issue_invalid_predictor"
 %pytest -n auto -k "not ($donttest)"
 %endif
 

++++++ tifffile-2023.3.15.tar.gz -> tifffile-2023.4.12.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tifffile-2023.3.15/CHANGES.rst 
new/tifffile-2023.4.12/CHANGES.rst
--- old/tifffile-2023.3.15/CHANGES.rst  2023-03-15 23:42:04.000000000 +0100
+++ new/tifffile-2023.4.12/CHANGES.rst  2023-04-13 02:13:08.000000000 +0200
@@ -1,9 +1,22 @@
 Revisions
 ---------
 
+2023.4.12
+
+- Pass 4988 tests.
+- Do not write duplicate ImageDescription tags from extratags (breaking).
+- Support multifocal SVS files (#193).
+- Log warning when filtering out extratags.
+- Fix writing OME-TIFF with image description in extratags.
+- Ignore invalid predictor tag value if prediction is not used.
+- Raise KeyError if ZarrStore is missing requested chunk.
+
+2023.3.21
+
+- Fix reading MMstack with missing data (#187).
+
 2023.3.15
 
-- Pass 4980 tests.
 - Fix corruption using tile generators with prediction/compression (#185).
 - Add parser for Micro-Manager MMStack series (breaking).
 - Return micromanager_metadata IndexMap as numpy array (breaking).
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tifffile-2023.3.15/README.rst 
new/tifffile-2023.4.12/README.rst
--- old/tifffile-2023.3.15/README.rst   2023-03-15 23:42:04.000000000 +0100
+++ new/tifffile-2023.4.12/README.rst   2023-04-13 02:13:08.000000000 +0200
@@ -30,7 +30,7 @@
 
 :Author: `Christoph Gohlke <https://www.cgohlke.com>`_
 :License: BSD 3-Clause
-:Version: 2023.3.15
+:Version: 2023.4.12
 :DOI: `10.5281/zenodo.6795860 <https://doi.org/10.5281/zenodo.6795860>`_
 
 Quickstart
@@ -66,9 +66,9 @@
 This revision was tested with the following requirements and dependencies
 (other versions may work):
 
-- `CPython <https://www.python.org>`_ 3.8.10, 3.9.13, 3.10.10, 3.11.2, 64-bit
+- `CPython <https://www.python.org>`_ 3.8.10, 3.9.13, 3.10.11, 3.11.3, 64-bit
 - `NumPy <https://pypi.org/project/numpy/>`_ 1.23.5
-- `Imagecodecs <https://pypi.org/project/imagecodecs/>`_ 2023.1.23
+- `Imagecodecs <https://pypi.org/project/imagecodecs/>`_ 2023.3.16
   (required for encoding or decoding LZW, JPEG, etc. compressed segments)
 - `Matplotlib <https://pypi.org/project/matplotlib/>`_ 3.7.1
   (required for plotting)
@@ -76,15 +76,28 @@
   (required only for validating and printing XML)
 - `Zarr <https://pypi.org/project/zarr/>`_ 2.14.2
   (required only for opening Zarr stores)
-- `Fsspec <https://pypi.org/project/fsspec/>`_ 2023.3.0
+- `Fsspec <https://pypi.org/project/fsspec/>`_ 2023.4.0
   (required only for opening ReferenceFileSystem files)
 
 Revisions
 ---------
 
+2023.4.12
+
+- Pass 4988 tests.
+- Do not write duplicate ImageDescription tags from extratags (breaking).
+- Support multifocal SVS files (#193).
+- Log warning when filtering out extratags.
+- Fix writing OME-TIFF with image description in extratags.
+- Ignore invalid predictor tag value if prediction is not used.
+- Raise KeyError if ZarrStore is missing requested chunk.
+
+2023.3.21
+
+- Fix reading MMstack with missing data (#187).
+
 2023.3.15
 
-- Pass 4980 tests.
 - Fix corruption using tile generators with prediction/compression (#185).
 - Add parser for Micro-Manager MMStack series (breaking).
 - Return micromanager_metadata IndexMap as numpy array (breaking).
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tifffile-2023.3.15/tests/test_tifffile.py 
new/tifffile-2023.4.12/tests/test_tifffile.py
--- old/tifffile-2023.3.15/tests/test_tifffile.py       2023-03-15 
23:42:04.000000000 +0100
+++ new/tifffile-2023.4.12/tests/test_tifffile.py       2023-04-13 
02:13:08.000000000 +0200
@@ -37,7 +37,7 @@
 Public data files can be requested from the author.
 Private data files are not available due to size and copyright restrictions.
 
-:Version: 2023.3.15
+:Version: 2023.4.12
 
 """
 
@@ -1724,8 +1724,8 @@
             [[0]],
             description='1st description',
             extratags=[
-                (270, 1, None, b'\1\128\0', True),
-                (270, 1, None, b'\2\128\0', True),
+                ('ImageDescription', 1, None, b'\1\128\0', True),
+                ('ImageDescription', 1, None, b'\2\128\0', True),
             ],
             metadata=False,
         )
@@ -2481,7 +2481,9 @@
     if compression == 'none' and predictor != 'none':
         pytest.xfail('cannot use predictor without compression')
 
-    data = numpy.empty((32, 32, samples) if samples else (32, 32), numpy.uint8)
+    data = numpy.empty(
+        (27, 23, samples) if samples else (27, 23, 1), numpy.uint8
+    )
     data[:] = 199
     data[7:9, 11:13] = 13
     data[22:25, 19:22] = 11
@@ -2504,14 +2506,51 @@
             compression=compression,
             predictor=predictor,
         )
-        assert_array_equal(imread(fname), data[:27, :23].squeeze())
+        assert_array_equal(imread(fname), data.squeeze())
         if (
             imagecodecs is None
             or not imagecodecs.TIFF
             or (compression == 'packbits' and predictor == 'horizontal')
         ):
             return
-        assert_array_equal(imagecodecs.imread(fname), data[:27, :23].squeeze())
+        assert_array_equal(imagecodecs.imread(fname), data.squeeze())
+
+
+def test_issue_extratags_filter(caplog):
+    """Test filtering extratags."""
+    # https://github.com/cgohlke/tifffile/pull/188
+    with TempFileName('extratags_filter') as fname:
+        imwrite(
+            fname,
+            shape=(10, 10),
+            dtype='u1',
+            extratags=[
+                (322, 3, 1, 2, False),  # TileWidth is filtered
+                (34665, 13, 1, 0, False),  # ExifIFD is filtered
+                (270, 2, 0, 'second description', True),  # filtered
+                ('FillOrder', 3, 1, 1, True),  # by name should go through
+            ],
+        )
+        assert 'extratag 322' in caplog.text
+        assert 'extratag 34665' in caplog.text
+        with TiffFile(fname) as tif:
+            tags = tif.pages.first.tags
+            assert 322 not in tags
+            assert 34665 not in tags
+            assert tags.get(270, index=1) is None
+            assert tags[266].value == 1
+
+
+def test_issue_invalid_predictor(caplog):
+    """Test decoding JPEG compression with invalid predictor tag."""
+    fname = private_file('issues/invalid_predictor.tiff')
+    with TiffFile(fname) as tif:
+        page = tif.pages.first
+        assert page.predictor == 58240
+        assert page.compression == 7
+        data = page.asarray()
+        assert 'ignoring predictor' in caplog.text
+        assert data.shape == (1275, 1650, 4)
 
 
 class TestExceptions:
@@ -11924,6 +11963,7 @@
         assert meta['DisplaySettings'][0]['Name'] == 'Dual-GFP'
         # assert series properties
         series = tif.series[0]
+        assert series[-1] is None  # missing page
         assert len(series) == 126
         assert series.shape == (63, 2, 264, 320)
         assert series.axes == 'TCYX'
@@ -11935,7 +11975,14 @@
         assert data.shape == (63, 2, 264, 320)
         assert data.dtype == numpy.uint16
         assert data[59, 1, 151, 186] == 599
-        assert_aszarr_method(series, data)
+        # assert zarr
+        if not SKIP_ZARR and zarr is not None:
+            with series.aszarr(fillvalue=100) as store:
+                assert '1.1.0.0' in store
+                assert '62.1.0.0' not in store  # missing page
+                z = zarr.open(store, mode='r')
+                assert z[62, 1, 0, 0] == 100
+                assert_array_equal(data[:62], z[:62])
         # test OME and ImageJ
         assert_array_equal(data, imread(fname, is_mmstack=False))
         assert_array_equal(data, imread(fname, is_mmstack=False, is_ome=False))
@@ -11987,6 +12034,50 @@
 
 
 @pytest.mark.skipif(SKIP_PRIVATE or SKIP_LARGE, reason=REASON)
+def test_read_mmstack_missing_sbs(caplog):
+    """Test read MicroManager dataset with missing data."""
+    # https://github.com/cgohlke/tifffile/issues/187
+    fname = private_file('MMStack/10X_c1-SBS-1_A1_Tile-102.sbs.tif')
+    with TiffFile(fname) as tif:
+        assert tif.is_micromanager
+        assert tif.is_mmstack
+        assert tif.is_ome
+        assert tif.is_imagej
+        assert not tif.is_ndtiff
+        assert tif.byteorder == '<'
+        assert len(tif.pages) == 5
+        assert len(tif.series) == 1
+        assert 'MMStack file name is invalid' in caplog.text
+        assert 'MMStack series is missing files' in caplog.text
+        # assert metadata
+        meta = tif.micromanager_metadata
+        assert meta is not None
+        assert meta['MajorVersion'] == 0
+        assert meta['Summary']['MicroManagerVersion'].startswith('1.4.23 2019')
+        assert meta['Summary']['Prefix'] == '10X_c1-SBS-1_1'
+        assert meta['IndexMap'].shape == (5, 5)
+        assert meta['Comments']['Summary'] == ''
+        assert meta['DisplaySettings'][0]['Name'] == 'DAPI_10p'
+        # assert series properties
+        series = tif.series[0]
+        assert len(series) == 5
+        assert series.shape == (5, 1024, 1024)
+        assert series.axes == 'CYX'
+        assert series.kind == 'mmstack'
+        assert not series.is_multifile
+        # assert data
+        data = tif.asarray()
+        assert isinstance(data, numpy.ndarray)
+        assert data.shape == (5, 1024, 1024)
+        assert data.dtype == numpy.uint16
+        assert data[3, 151, 186] == 542
+        assert_aszarr_method(series, data)
+        # test OME
+        assert_array_equal(data, imread(fname, is_mmstack=False))
+        assert__str__(tif)
+
+
[email protected](SKIP_PRIVATE or SKIP_LARGE, reason=REASON)
 def test_read_mmstack_trzc():
     """Test read MicroManager 6 dimensional dataset."""
     fname = private_file(
@@ -13398,7 +13489,7 @@
     description = 'Created by TestTiffWriter\nLorem ipsum dolor...'
     pagename = 'Page name'
     extratags = [
-        (270, 's', 0, description, True),
+        ('ImageDescription', 's', 0, description, True),
         ('PageName', 's', 0, pagename, False),
         (50001, 'b', 1, b'1', True),
         (50002, 'b', 2, b'12', True),
@@ -13537,6 +13628,20 @@
             assert__str__(tif)
 
 
+def test_write_software_tag():
+    """Test write Software tag."""
+    data = random_data(numpy.uint8, (2, 219, 301))
+    software = 'test_tifffile.py'
+    with TempFileName('software_tag') as fname:
+        imwrite(fname, data, software=software)
+        assert_valid_tiff(fname)
+        with TiffFile(fname) as tif:
+            assert len(tif.pages) == 2
+            assert tif.pages.first.software == software
+            assert 'Software' not in tif.pages[1].tags
+            assert__str__(tif)
+
+
 def test_write_description_tag():
     """Test write two description tags."""
     data = random_data(numpy.uint8, (2, 219, 301))
@@ -13588,18 +13693,107 @@
             assert__str__(tif)
 
 
-def test_write_software_tag():
-    """Test write Software tag."""
-    data = random_data(numpy.uint8, (2, 219, 301))
-    software = 'test_tifffile.py'
-    with TempFileName('software_tag') as fname:
-        imwrite(fname, data, software=software)
-        assert_valid_tiff(fname)
+def test_write_description_ome():
+    """Test write multiple imagedescription tags to OME."""
+    # https://forum.image.sc/t/79471
+    with TempFileName('description_ome') as fname:
+        with pytest.warns(UserWarning):
+            imwrite(
+                fname,
+                shape=(2, 32, 32),
+                dtype='uint8',
+                ome=True,
+                description='description',  # not written
+                extratags=[('ImageDescription', 2, None, 'extratags', False)],
+            )
         with TiffFile(fname) as tif:
-            assert len(tif.pages) == 2
-            assert tif.pages.first.software == software
-            assert 'Software' not in tif.pages[1].tags
-            assert__str__(tif)
+            assert tif.is_ome
+            page = tif.pages.first
+            assert page.description.startswith('<?xml')
+            assert page.description1 == 'extratags'
+            assert tif.pages[1].description == 'extratags'
+
+
+def test_write_description_imagej():
+    """Test write multiple imagedescription tags."""
+    with TempFileName('description_imagej') as fname:
+        with pytest.warns(UserWarning):
+            imwrite(
+                fname,
+                shape=(2, 32, 32),
+                dtype='uint8',
+                imagej=True,
+                description='description',  # not written
+                extratags=[('ImageDescription', 2, None, 'extratags', False)],
+            )
+        with TiffFile(fname) as tif:
+            assert tif.is_imagej
+            page = tif.pages.first
+            assert page.description.startswith('ImageJ=')
+            assert page.description1 == 'extratags'
+            assert tif.pages[1].description == 'extratags'
+
+
+def test_write_description_shaped():
+    """Test write multiple imagedescription tags to shaped."""
+    with TempFileName('description_shaped') as fname:
+        imwrite(
+            fname,
+            shape=(2, 32, 32),
+            dtype='uint8',
+            description='description',  # written
+            extratags=[('ImageDescription', 2, None, 'extratags', False)],
+        )
+        with TiffFile(fname) as tif:
+            assert tif.is_shaped
+            page = tif.pages.first
+            assert page.description == 'description'
+            assert page.description1.startswith('{')
+            assert page.tags.getall(270)[2].value == 'extratags'
+            assert tif.pages[1].description == 'extratags'
+
+
+def test_write_description_overwrite():
+    """Test overwrite imagedescription tag."""
+    with TempFileName('description_overwrite') as fname:
+        with TiffWriter(fname) as tif:
+            tif.write(
+                shape=(2, 32, 32),
+                dtype='uint8',
+                description='description',  # overwritten
+                extratags=[('ImageDescription', 2, None, 'extratags', False)],
+                metadata=None,
+            )
+            tif.overwrite_description('overwritten description')
+        with TiffFile(fname) as tif:
+            assert not tif.is_shaped
+            page = tif.pages.first
+            assert page.description == 'overwritten description'
+            assert page.description1 == 'extratags'
+            assert tif.pages[1].description == 'extratags'
+
+
+def test_write_description_extratags():
+    """Test write imagedescription tags using extratag."""
+    with TempFileName('description_extratags') as fname:
+        with TiffWriter(fname) as tif:
+            tif.write(
+                shape=(2, 32, 32),
+                dtype='uint8',
+                extratags=[
+                    (270, 2, None, 'description 0', False),
+                    ('ImageDescription', 2, None, 'description 1', False),
+                ],
+                metadata=None,
+            )
+        with TiffFile(fname) as tif:
+            assert not tif.is_shaped
+            page = tif.pages.first
+            assert page.description == 'description 0'
+            assert page.description1 == 'description 1'
+            page = tif.pages[1]
+            assert page.description == 'description 0'
+            assert page.description1 == 'description 1'
 
 
 def test_write_resolution_float():
@@ -18253,7 +18447,7 @@
 
         import turbojpeg
         from opentile.geometry import Point
-        from opentile.ndpi_tiler import NdpiTiler
+        from opentile.formats import NdpiTiler
     except ImportError:
         pytest.skip('opentile missing')
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tifffile-2023.3.15/tifffile/tifffile.py 
new/tifffile-2023.4.12/tifffile/tifffile.py
--- old/tifffile-2023.3.15/tifffile/tifffile.py 2023-03-15 23:42:04.000000000 
+0100
+++ new/tifffile-2023.4.12/tifffile/tifffile.py 2023-04-13 02:13:08.000000000 
+0200
@@ -60,7 +60,7 @@
 
 :Author: `Christoph Gohlke <https://www.cgohlke.com>`_
 :License: BSD 3-Clause
-:Version: 2023.3.15
+:Version: 2023.4.12
 :DOI: `10.5281/zenodo.6795860 <https://doi.org/10.5281/zenodo.6795860>`_
 
 Quickstart
@@ -96,9 +96,9 @@
 This revision was tested with the following requirements and dependencies
 (other versions may work):
 
-- `CPython <https://www.python.org>`_ 3.8.10, 3.9.13, 3.10.10, 3.11.2, 64-bit
+- `CPython <https://www.python.org>`_ 3.8.10, 3.9.13, 3.10.11, 3.11.3, 64-bit
 - `NumPy <https://pypi.org/project/numpy/>`_ 1.23.5
-- `Imagecodecs <https://pypi.org/project/imagecodecs/>`_ 2023.1.23
+- `Imagecodecs <https://pypi.org/project/imagecodecs/>`_ 2023.3.16
   (required for encoding or decoding LZW, JPEG, etc. compressed segments)
 - `Matplotlib <https://pypi.org/project/matplotlib/>`_ 3.7.1
   (required for plotting)
@@ -106,15 +106,28 @@
   (required only for validating and printing XML)
 - `Zarr <https://pypi.org/project/zarr/>`_ 2.14.2
   (required only for opening Zarr stores)
-- `Fsspec <https://pypi.org/project/fsspec/>`_ 2023.3.0
+- `Fsspec <https://pypi.org/project/fsspec/>`_ 2023.4.0
   (required only for opening ReferenceFileSystem files)
 
 Revisions
 ---------
 
+2023.4.12
+
+- Pass 4988 tests.
+- Do not write duplicate ImageDescription tags from extratags (breaking).
+- Support multifocal SVS files (#193).
+- Log warning when filtering out extratags.
+- Fix writing OME-TIFF with image description in extratags.
+- Ignore invalid predictor tag value if prediction is not used.
+- Raise KeyError if ZarrStore is missing requested chunk.
+
+2023.3.21
+
+- Fix reading MMstack with missing data (#187).
+
 2023.3.15
 
-- Pass 4980 tests.
 - Fix corruption using tile generators with prediction/compression (#185).
 - Add parser for Micro-Manager MMStack series (breaking).
 - Return micromanager_metadata IndexMap as numpy array (breaking).
@@ -793,7 +806,7 @@
 
 from __future__ import annotations
 
-__version__ = '2023.3.15'
+__version__ = '2023.4.12'
 
 __all__ = [
     'TiffFile',
@@ -1827,7 +1840,9 @@
                 4. writeonce (bool): If *True*, write tag to first page
                    of a series only.
 
-                Duplicate and select tags in TIFF.TAG_FILTERED are not written.
+                Duplicate and select tags in TIFF.TAG_FILTERED are not written
+                if the extratag is specified by integer code.
+                Extratags cannot be used to write IFD type tags.
 
             contiguous:
                 If *False* (default), write data to a new series.
@@ -2584,7 +2599,8 @@
         self._metadata = {} if not metadata else metadata.copy()
         if self._omexml is not None:
             if len(self._omexml.images) == 0:
-                description = ''  # rewritten later at end of file
+                # rewritten later at end of file
+                description = '\x00\x00\x00\x00'
             else:
                 description = None
         elif self._imagej:
@@ -2624,7 +2640,12 @@
             description += '\x00' * 16  # add buffer for in-place update
         # elif metadata is None and self._truncate:
         #     raise ValueError('cannot truncate without writing metadata')
-        else:
+        elif description is not None:
+            if not isinstance(description, bytes):
+                description = description.encode('ascii')
+            self._descriptiontag = TiffTag(
+                self, 0, 270, 2, len(description), description, 0
+            )
             description = None
 
         if description is None:
@@ -2633,6 +2654,9 @@
         else:
             description = description.encode('ascii')
             addtag(tags, 270, 2, 0, description, True)
+            self._descriptiontag = TiffTag(
+                self, 0, 270, 2, len(description), description, 0
+            )
         del description
 
         if software is None:
@@ -2972,11 +2996,10 @@
         extratag: TagTuple
         tagset = {t[0] for t in tags}
         tagset.update(TIFF.TAG_FILTERED)
-        if 270 in tagset:
-            # allow duplicate ImageDescription
-            tagset.remove(270)
         for extratag in extratags:
-            if extratag[0] not in tagset:
+            if extratag[0] in tagset:
+                log_warning(f'{self!r} not writing extratag {extratag[0]}')
+            else:
                 addtag(tags, *extratag)
         del tagset
         del extratags
@@ -3143,11 +3166,17 @@
                         elif code == tagbytecounts:
                             databytecountsoffset = offset, pos
                         elif code == 270:
-                            assert self._descriptiontag is not None
-                            self._descriptiontag.offset = (
-                                ifdpos + tagoffset + tagindex * tagsize
-                            )
-                            self._descriptiontag.valueoffset = ifdpos + pos
+                            if (
+                                self._descriptiontag is not None
+                                and self._descriptiontag.offset == 0
+                                and value.startswith(
+                                    self._descriptiontag.value
+                                )
+                            ):
+                                self._descriptiontag.offset = (
+                                    ifdpos + tagoffset + tagindex * tagsize
+                                )
+                                self._descriptiontag.valueoffset = ifdpos + pos
                         elif code == 330:
                             subifdsoffsets = offset, pos
                     elif code == tagoffsets:
@@ -3155,13 +3184,17 @@
                     elif code == tagbytecounts:
                         databytecountsoffset = offset, None
                     elif code == 270:
-                        assert self._descriptiontag is not None
-                        self._descriptiontag.offset = (
-                            ifdpos + tagoffset + tagindex * tagsize
-                        )
-                        self._descriptiontag.valueoffset = (
-                            self._descriptiontag.offset + offsetsize + 4
-                        )
+                        if (
+                            self._descriptiontag is not None
+                            and self._descriptiontag.offset == 0
+                            and self._descriptiontag.value in tag[1][-4:]
+                        ):
+                            self._descriptiontag.offset = (
+                                ifdpos + tagoffset + tagindex * tagsize
+                            )
+                            self._descriptiontag.valueoffset = (
+                                self._descriptiontag.offset + offsetsize + 4
+                            )
                     elif code == 330:
                         subifdsoffsets = offset, None
                 ifdsize = ifd.tell()
@@ -3212,10 +3245,7 @@
 
             elif tile:
                 # write tiles
-                if storedshape.contig_samples == 1:
-                    tileshape = tile
-                else:
-                    tileshape = tile + (storedshape.contig_samples,)
+                tileshape = tile + (storedshape.contig_samples,)
                 tilesize = product(tileshape) * datadtype.itemsize
 
                 if dataiter is None:
@@ -3709,11 +3739,10 @@
             elif not isinstance(value, bytes):
                 raise ValueError('TIFF strings must be 7-bit ASCII')
 
-            if len(value) == 0 or value[-1] != b'\x00':
+            if len(value) == 0 or value[-1:] != b'\x00':
                 value += b'\x00'
             count = len(value)
             if code == 270:
-                self._descriptiontag = TiffTag(self, 0, 270, 2, count, None, 0)
                 rawcount = int(value.find(b'\x00\x00'))
                 if rawcount < 0:
                     rawcount = count
@@ -5068,53 +5097,74 @@
         self.pages.set_keyframe(0)
         self.pages._load()
 
-        # Baseline
-        index = 0
-        page = self.pages[index]
-        series.append(
-            TiffPageSeries(
-                [page],
-                page.shape,
-                page.dtype,
-                page.axes,
-                name='Baseline',
-                kind='svs',
-            )
-        )
-        # Thumbnail
-        index += 1
-        if index == len(self.pages):
+        # baseline
+        firstpage = self.pages.first
+        if len(self.pages) == 1:
             self.is_uniform = False
-            return series
-        page = self.pages[index]
-        series.append(
-            TiffPageSeries(
-                [page],
-                page.shape,
-                page.dtype,
-                page.axes,
-                name='Thumbnail',
-                kind='svs',
-            )
+            return [
+                TiffPageSeries(
+                    [firstpage],
+                    firstpage.shape,
+                    firstpage.dtype,
+                    firstpage.axes,
+                    name='Baseline',
+                    kind='svs',
+                )
+            ]
+
+        # thumbnail
+        page = self.pages[1]
+        thumnail = TiffPageSeries(
+            [page],
+            page.shape,
+            page.dtype,
+            page.axes,
+            name='Thumbnail',
+            kind='svs',
         )
-        # Resolutions
-        # TODO: resolutions not by two
-        index += 1
+
+        # resolutions and focal planes
+        levels = {firstpage.shape: [firstpage]}
+        index = 2
         while index < len(self.pages):
             page = cast(TiffPage, self.pages[index])
             if not page.is_tiled or page.is_reduced:
                 break
-            series[0].levels.append(
+            if page.shape in levels:
+                levels[page.shape].append(page)
+            else:
+                levels[page.shape] = [page]
+            index += 1
+
+        zsize = len(levels[firstpage.shape])
+        if not all(len(level) == zsize for level in levels.values()):
+            log_warning(f'{self!r} SVS series focal planes do not match')
+            zsize = 1
+        baseline = TiffPageSeries(
+            levels[firstpage.shape],
+            (zsize,) + firstpage.shape,
+            firstpage.dtype,
+            'Z' + firstpage.axes,
+            name='Baseline',
+            kind='svs',
+        )
+        for shape, level in levels.items():
+            if shape == firstpage.shape:
+                continue
+            page = level[0]
+            baseline.levels.append(
                 TiffPageSeries(
-                    [page],
-                    page.shape,
+                    level,
+                    (zsize,) + page.shape,
                     page.dtype,
-                    page.axes,
+                    'Z' + page.axes,
                     name='Resolution',
                     kind='svs',
                 )
             )
-            index += 1
+        series.append(baseline)
+        series.append(thumnail)
+
         # Label, Macro; subfiletype 1, 9
         for _ in range(2):
             if index == len(self.pages):
@@ -5661,20 +5711,31 @@
                 found_keyframe = False
                 ifds = []
                 for page in aseries.pages:
-                    if page is None or page.subifds is None:
+                    if (
+                        page is None
+                        or page.subifds is None
+                        or page.subifds[level] < 8
+                    ):
                         ifds.append(None)
                         continue
                     page.parent.filehandle.seek(page.subifds[level])
                     if page.keyframe == page:
-                        ifd = keyframe = TiffPage(self, (page.index, level))
+                        ifd = keyframe = TiffPage(
+                            self, (page.index, level + 1)
+                        )
                         found_keyframe = True
                     elif not found_keyframe:
                         raise RuntimeError('no keyframe found')
                     else:
                         ifd = TiffFrame(
-                            self, (page.index, level), keyframe=keyframe
+                            self, (page.index, level + 1), keyframe=keyframe
                         )
                     ifds.append(ifd)
+                if all(ifd_or_none is None for ifd_or_none in ifds):
+                    log_warning(
+                        f'{self!r} OME series level {level + 1} is empty'
+                    )
+                    break
                 # fix shape
                 shape = list(aseries.get_shape(False))
                 axes = aseries.get_axes(False)
@@ -5837,9 +5898,14 @@
         elif size > indexmap.shape[0]:
             # other files missing: squeeze shape
             old_shape = shape
-            min_index = numpy.min(indexmap[:, :4], axis=0).tolist()
-            max_index = numpy.max(indexmap[:, :4], axis=0).tolist()
-            shape = tuple(j - i + 1 for i, j in zip(min_index, max_index))
+            min_index = numpy.min(indexmap[:, :4], axis=0)
+            max_index = numpy.max(indexmap[:, :4], axis=0)
+            indexmap = indexmap.copy()
+            indexmap[:, :4] -= min_index
+            shape = tuple(
+                j - i + 1
+                for i, j in zip(min_index.tolist(), max_index.tolist())
+            )
             shape = tuple(shape[i] for i in indexmap_order)
             size = product(shape)
             pages = [None] * size
@@ -8131,11 +8197,17 @@
             else:
                 unpredict = TIFF.UNPREDICTORS[self.predictor]
         except KeyError as exc:
+            if self.compression not in TIFF.IMAGE_COMPRESSIONS:
 
-            def decode_raise_predictor(*args, exc=str(exc)[1:-1], **kwargs):
-                raise ValueError(f'{exc}')
+                def decode_raise_predictor(
+                    *args, exc=str(exc)[1:-1], **kwargs
+                ):
+                    raise ValueError(f'{exc}')
+
+                return cache(decode_raise_predictor)
 
-            return cache(decode_raise_predictor)
+            log_warning(f'{self!r} ignoring predictor {self.predictor}')
+            unpredict = None
 
         if self.tags.get(339) is not None:
             tag = self.tags[339]  # SampleFormat
@@ -10892,7 +10964,7 @@
                     ) from exc
             elif not isinstance(value, bytes):
                 raise ValueError('TIFF strings must be 7-bit ASCII')
-            if len(value) == 0 or value[-1] != b'\x00':
+            if len(value) == 0 or value[-1:] != b'\x00':
                 value += b'\x00'
             count = len(value)
             value = (value,)
@@ -12020,7 +12092,14 @@
         return len(self._store)
 
     def __contains__(self, key: object, /) -> bool:
-        return key in self._store
+        if key in self._store:
+            return True
+        assert isinstance(key, str)
+        return self._contains(key)
+
+    def _contains(self, key: str, /) -> bool:
+        """Return if key is in store."""
+        raise NotImplementedError
 
     def __delitem__(self, key: object, /) -> None:
         raise PermissionError('ZarrStore is read-only')
@@ -12656,23 +12735,26 @@
         if not hasattr(jsonfile, 'write'):
             fh.close()
 
+    def _contains(self, key: str, /) -> bool:
+        """Return if key is in store."""
+        try:
+            _, page, _, offset, bytecount = self._parse_key(key)
+        except KeyError:
+            return False
+        return (
+            page is not None
+            and offset is not None
+            and bytecount is not None
+            and offset > 0
+            and bytecount > 0
+        )
+
     def _getitem(self, key: str, /) -> NDArray[Any]:
         """Return chunk from file."""
         keyframe, page, chunkindex, offset, bytecount = self._parse_key(key)
 
-        if self._chunkmode:
-            chunks = keyframe.shape
-        else:
-            chunks = keyframe.chunks
-
         if page is None or offset == 0 or bytecount == 0:
-            assert keyframe.dtype is not None
-            chunk = ZarrStore._empty_chunk(
-                chunks, keyframe.dtype, self._fillvalue
-            )
-            if self._transform is not None:
-                chunk = self._transform(chunk)
-            return chunk
+            raise KeyError(key)
 
         fh = page.parent.filehandle
 
@@ -12703,6 +12785,10 @@
         if self._transform is not None:
             chunk = self._transform(chunk)
 
+        if self._chunkmode:
+            chunks = keyframe.shape
+        else:
+            chunks = keyframe.chunks
         if chunk.size != product(chunks):
             raise RuntimeError(f'{chunk.size} != {product(chunks)}')
         return chunk  # .tobytes()
@@ -12990,17 +13076,21 @@
             }
         )
 
+    def _contains(self, key: str, /) -> bool:
+        """Return if key is in store."""
+        try:
+            indices = tuple(int(i) for i in key.split('.'))
+        except Exception:
+            return False
+        return indices in self._lookup
+
     def _getitem(self, key: str, /) -> NDArray[Any]:
         """Return chunk from file."""
         indices = tuple(int(i) for i in key.split('.'))
         filename = self._lookup.get(indices, None)
         if filename is None:
-            chunk = ZarrStore._empty_chunk(
-                self._chunks, self._dtype, self._fillvalue
-            )
-        else:
-            chunk = self._imread(filename, **self._kwargs)
-        return chunk
+            raise KeyError(key)
+        return self._imread(filename, **self._kwargs)
 
     def _setitem(self, key: str, value: bytes, /) -> None:
         raise PermissionError('ZarrStore is read-only')
@@ -17182,8 +17272,12 @@
                 330,  # SubIFDs,
                 338,  # ExtraSamples
                 339,  # SampleFormat
+                400,  # GlobalParametersIFD
                 32997,  # ImageDepth
                 32998,  # TileDepth
+                34665,  # ExifTag
+                34853,  # GPSTag
+                40965,  # InteroperabilityTag
             )
         )
 

Reply via email to