Repository: pydicom/pydicom

Issue:
Floating point images data incorrectly require the BitsStored attribute to decode

**Describe the issue**

The new pixel decompression logic in pydicom 3 requires that the `BitsStored` attribute be present for all images. While `BitsStored` is required for all images that use the `Image Pixel` module, "floating point" images that use the [Floating Point Image Pixel](https://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.7.6.24.html#sect_C.7.6.24) and [Double Floating Point Image Pixel](https://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.7.6.25.html#sect_C.7.6.25) modules should *not* include this attribute (I suppose this is because for floating point number the number of bits stored is always equal to the number of bits allocated).

Essentially this means that floating point images that correctly omit the BitsStored cannot be decoded by pydicom 3 now (but the same images could be decoded in pydicom 2).

**Steps to reproduce**

I have attached a small parametric map image using Double Float Pixel Data that demonstrates the problem. If you read it and use the `.pixel_array` property, you get this error message on pydicom 3:
[parametric_map_example.zip](https://github.com/user-attachments/files/17006688/parametric_map_example.zip)


```
Traceback (most recent call last):
  File "/home/chris/repos/highdicom/pm_reading.py", line 5, in <module>
    dcm.pixel_array
  File "/home/chris/repos/pydicom/src/pydicom/dataset.py", line 918, in __getattr__
    return object.__getattribute__(self, name)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/chris/repos/pydicom/src/pydicom/dataset.py", line 2193, in pixel_array
    self.convert_pixel_data()
  File "/home/chris/repos/pydicom/src/pydicom/dataset.py", line 1726, in convert_pixel_data
    self._pixel_array = pixel_array(self, **opts)
                        ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/chris/repos/pydicom/src/pydicom/pixels/utils.py", line 1430, in pixel_array
    return decoder.as_array(
           ^^^^^^^^^^^^^^^^^
  File "/home/chris/repos/pydicom/src/pydicom/pixels/decoders/base.py", line 989, in as_array
    runner.validate()
  File "/home/chris/repos/pydicom/src/pydicom/pixels/decoders/base.py", line 752, in validate
    self._validate_options()
  File "/home/chris/repos/pydicom/src/pydicom/pixels/decoders/base.py", line 821, in _validate_options
    super()._validate_options()
  File "/home/chris/repos/pydicom/src/pydicom/pixels/common.py", line 576, in _validate_options
    raise AttributeError(f"{prefix},0101) 'Bits Stored'")
AttributeError: Missing required element: (0028,0101) 'Bits Stored'. Did you mean: '_pixel_array'?
```

**Possible solution**

The simplest solution (though not especially elegant) is just to copy the BitsAllocated to BitsStored in case it's missing. I.e. replacing this part of `src/pydicom/pixels/common.py` (line 575):

```python
        if self._opts.get("bits_stored") is None:
            raise AttributeError(f"{prefix},0101) 'Bits Stored'")
```

with something like this

```python
        if self._opts.get("bits_stored") is None:
            # The BitsStored attribute is not present for images using the
            # "Floating Point Image Pixel" and "Double Floating Point Image
            # Pixel" modules, but is implicitly equal to the BitsAllocated
            if self._opts.get("pixel_keyword") in (
                "FloatPixelData",
                "DoubleFloatPixelData"
            ):
                self.set_option('bits_stored', self.bits_allocated)
            else:
                raise AttributeError(f"{prefix},0101) 'Bits Stored'")

```

Though I suppose you may also want this to be fixed at a lower level. I'm happy to help draft a PR when we have the right direction.

Discussion:
@scaramallion: Oof, my bad. Completely overlooked that *Bits Stored* is optional for float.

I'm sure you're already aware, but as a workaround you can use the old pixel data handler via [Dataset.pixel_array_options()](https://pydicom.github.io/pydicom/stable/reference/generated/pydicom.dataset.Dataset.html#pydicom.dataset.Dataset.pixel_array_options):

```python
ds = dcmread(...)
ds.pixel_array_options(use_v2_backend=True)
arr = ds.pixel_array
```

Do you mind if we use your example dataset as one of our testing datasets?@CPBridge: Thanks for the workaround, I hadn't spotted that.

If you want to use this as test image you are welcome to, but maybe it would make more sense to use one with meaningful pixels (the one above is random noise). This example may be better (and is also tidier in terms of correctness of other attributes). You are welcome to include this in the test files: 
[parametric_map_example_2.zip](https://github.com/user-attachments/files/17014814/parametric_map_example_2.zip)
@CPBridge: If you want one using Float Pixel Data with 32 bit floats, you can use this 
[parametric_map_example_single.zip](https://github.com/user-attachments/files/17014885/parametric_map_example_single.zip)

@scaramallion: Thanks x2!

Solution patch:
diff --git a/doc/release_notes/v3.1.0.rst b/doc/release_notes/v3.1.0.rst
index 986843ac96..a2159496b4 100644
--- a/doc/release_notes/v3.1.0.rst
+++ b/doc/release_notes/v3.1.0.rst
@@ -7,3 +7,5 @@ Fixes
 * Changed logging of missing plugin imports to use :attr:`logging.DEBUG` (:issue:`2128`).
 * Include all :mod:`~pydicom.examples` module datasets with the package (:issue:`2128`, :issue:`2131`)
 * Fixed an invalid VR value in the private data dictionary (:issue:`2132`).
+* Fixed checking for *Bits Stored* when converting *Float Pixel Data* and *Double Float
+  Pixel Data* using the :mod:`~pydicom.pixels` backend (:issue:`2135`).
diff --git a/src/pydicom/data/hashes.json b/src/pydicom/data/hashes.json
index 958dc18326..45eed9002f 100644
--- a/src/pydicom/data/hashes.json
+++ b/src/pydicom/data/hashes.json
@@ -74,5 +74,7 @@
   "JLSL_RGB_ILV2.dcm": "f8d670e9988cbca207d3367d916aff3cb508c076495211a0d132692266e9546d",
   "JLSN_RGB_ILV0.dcm": "a377750d24bd3413d21faa343662dfff997db9acf65c0b095c5d8a95beb866fa",
   "JLSL_08_07_0_1F.dcm": "308fb028c8fbdd1e9a93e731978ea4da6b15cb55b40451cf6f21e7c9ba35dd8a",
-  "JLSL_16_15_1_1F.dcm": "61f38f250a7dc82c44529c0face2eeab3ffd02ca8b9dfc756dd818eb252104b6"
+  "JLSL_16_15_1_1F.dcm": "61f38f250a7dc82c44529c0face2eeab3ffd02ca8b9dfc756dd818eb252104b6",
+  "parametric_map_float.dcm": "957f34397c26d82f7a90cad7a653ce0f7238f4be6aa9dfa9a33bae5dc2ce7e23",
+  "parametric_map_double_float.dcm": "a41e0b78b05e543a2448e22435858f9ca8d5f94807d7b391b93b4bca80e23a22"
 }
diff --git a/src/pydicom/data/urls.json b/src/pydicom/data/urls.json
index 548a8916ce..62c61818e5 100644
--- a/src/pydicom/data/urls.json
+++ b/src/pydicom/data/urls.json
@@ -74,5 +74,7 @@
     "JLSL_RGB_ILV2.dcm": "https://github.com/pydicom/pydicom-data/raw/7358d21f75fa8cdc8ae4e0bb02f7612c52a140ec/data_store/data/JLSL_RGB_ILV2.dcm",
     "JLSN_RGB_ILV0.dcm": "https://github.com/pydicom/pydicom-data/raw/7358d21f75fa8cdc8ae4e0bb02f7612c52a140ec/data_store/data/JLSN_RGB_ILV0.dcm",
     "JLSL_08_07_0_1F.dcm": "https://github.com/pydicom/pydicom-data/raw/831d25636b0aff13916975c493b20d6fadc01d2e/data_store/data/JLSL_08_07_0_1F.dcm",
-    "JLSL_16_15_1_1F.dcm": "https://github.com/pydicom/pydicom-data/raw/eaaec0d930f5e578db5e56892da57a8e63af71d7/data_store/data/JLSL_16_15_1_1F.dcm"
+    "JLSL_16_15_1_1F.dcm": "https://github.com/pydicom/pydicom-data/raw/eaaec0d930f5e578db5e56892da57a8e63af71d7/data_store/data/JLSL_16_15_1_1F.dcm",
+    "parametric_map_float.dcm": "https://github.com/pydicom/pydicom-data/raw/812f8edacbb6a1f3606ff4a9c16c54b831e9fd3b/data_store/data/parametric_map_float.dcm",
+    "parametric_map_double_float.dcm": "https://github.com/pydicom/pydicom-data/raw/812f8edacbb6a1f3606ff4a9c16c54b831e9fd3b/data_store/data/parametric_map_double_float.dcm"
 }
diff --git a/src/pydicom/pixels/common.py b/src/pydicom/pixels/common.py
index 89852aabe3..737ebcf7fe 100644
--- a/src/pydicom/pixels/common.py
+++ b/src/pydicom/pixels/common.py
@@ -572,15 +572,17 @@ def _validate_options(self) -> None:
                 "is invalid, it must be 1 or a multiple of 8 and in the range (1, 64)"
             )
 
-        if self._opts.get("bits_stored") is None:
-            raise AttributeError(f"{prefix},0101) 'Bits Stored'")
+        if "Float" not in self.pixel_keyword:
+            if self._opts.get("bits_stored") is None:
+                raise AttributeError(f"{prefix},0101) 'Bits Stored'")
 
-        if not 1 <= self.bits_stored <= self.bits_allocated <= 64:
-            raise ValueError(
-                f"A (0028,0101) 'Bits Stored' value of '{self.bits_stored}' is "
-                "invalid, it must be in the range (1, 64) and no greater than "
-                f"the (0028,0100) 'Bits Allocated' value of '{self.bits_allocated}'"
-            )
+            if not 1 <= self.bits_stored <= self.bits_allocated <= 64:
+                raise ValueError(
+                    f"A (0028,0101) 'Bits Stored' value of '{self.bits_stored}' is "
+                    "invalid, it must be in the range (1, 64) and no greater than "
+                    "the (0028,0100) 'Bits Allocated' value of "
+                    f"'{self.bits_allocated}'"
+                )
 
         if self._opts.get("columns") is None:
             raise AttributeError(f"{prefix},0011) 'Columns'")
diff --git a/src/pydicom/pixels/decoders/base.py b/src/pydicom/pixels/decoders/base.py
index e3b3c7ef69..f75be168b2 100644
--- a/src/pydicom/pixels/decoders/base.py
+++ b/src/pydicom/pixels/decoders/base.py
@@ -537,7 +537,6 @@ def pixel_properties(self, as_frame: bool = False) -> dict[str, str | int]:
         """
         d = {
             "bits_allocated": self.bits_allocated,
-            "bits_stored": self.bits_stored,
             "columns": self.columns,
             "number_of_frames": self.number_of_frames if not as_frame else 1,
             "photometric_interpretation": str(self.photometric_interpretation),
@@ -549,6 +548,7 @@ def pixel_properties(self, as_frame: bool = False) -> dict[str, str | int]:
             d["planar_configuration"] = self.planar_configuration
 
         if self.pixel_keyword == "PixelData":
+            d["bits_stored"] = self.bits_stored
             d["pixel_representation"] = self.pixel_representation
 
         return cast(dict[str, str | int], d)


Test patch:
diff --git a/tests/pixels/pixels_reference.py b/tests/pixels/pixels_reference.py
index 16f24e0bfe..8291d2b633 100644
--- a/tests/pixels/pixels_reference.py
+++ b/tests/pixels/pixels_reference.py
@@ -604,6 +604,28 @@ def test(ref, arr, **kwargs):
 EXPL_32_3_2F = PixelReference("SC_rgb_32bit_2frame.dcm", "<u4", test)
 
 
+# EXPL, (32, NA), (1, 128, 128, 1), OD, MONOCHROME2
+# Float Pixel Data
+def test(ref, arr, **kwargs):
+    assert [0.92012787, 0.91510725, 0.9160201, 0.92104053] == [
+        round(x, 8) for x in arr[:4, 0]
+    ]
+
+
+EXPL_32_1F_FLOAT = PixelReference("parametric_map_float.dcm", "<f4", test)
+
+
+# EXPL, (64, NA), (1, 128, 128, 1), OD, MONOCHROME2
+# Double Float Pixel Data
+def test(ref, arr, **kwargs):
+    assert [0.9201278, 0.91510726, 0.91602008, 0.92104062] == [
+        round(x, 8) for x in arr[:4, 0]
+    ]
+
+
+EXPL_64_1F_DOUBLE_FLOAT = PixelReference("parametric_map_double_float.dcm", "<f8", test)
+
+
 PIXEL_REFERENCE[ExplicitVRLittleEndian] = [
     EXPL_1_1_1F,
     EXPL_1_1_3F,
@@ -622,6 +644,8 @@ def test(ref, arr, **kwargs):
     EXPL_16_3_2F,
     EXPL_32_3_1F,
     EXPL_32_3_2F,
+    EXPL_32_1F_FLOAT,  # Float Pixel Data
+    EXPL_64_1F_DOUBLE_FLOAT,  # Double Float Pixel Data
 ]
 PIXEL_REFERENCE[ImplicitVRLittleEndian] = [
     IMPL_08_08_3_0_1F_RGB,
diff --git a/tests/pixels/test_common.py b/tests/pixels/test_common.py
index da738d86f1..103926eea3 100644
--- a/tests/pixels/test_common.py
+++ b/tests/pixels/test_common.py
@@ -214,6 +214,7 @@ def test_validate_options(self):
         """Tests for validate_options()"""
         # Generic option validation
         runner = RunnerBase(ExplicitVRLittleEndian)
+        runner.set_option("pixel_keyword", "PixelData")
 
         msg = r"Missing required element: \(0028,0100\) 'Bits Allocated'"
         with pytest.raises(AttributeError, match=msg):
@@ -281,13 +282,15 @@ def test_validate_options(self):
         with pytest.raises(ValueError, match=msg):
             runner._validate_options()
 
+        runner.del_option("pixel_keyword")
+
         runner.set_option("photometric_interpretation", PI.RGB)
         msg = "No value for 'pixel_keyword' has been set"
         with pytest.raises(AttributeError, match=msg):
             runner._validate_options()
 
-        runner.set_option("pixel_keyword", -1)
-        msg = "Unknown 'pixel_keyword' value '-1'"
+        runner.set_option("pixel_keyword", "foo")
+        msg = "Unknown 'pixel_keyword' value 'foo'"
         with pytest.raises(ValueError, match=msg):
             runner._validate_options()
 
diff --git a/tests/pixels/test_decoder_base.py b/tests/pixels/test_decoder_base.py
index b53a299323..1d48d0040f 100644
--- a/tests/pixels/test_decoder_base.py
+++ b/tests/pixels/test_decoder_base.py
@@ -356,6 +356,14 @@ def test_validate_options(self):
         runner.set_option("extended_offsets", ([0], [10]))
         runner._validate_options()
 
+        # Float/Double Float Pixel Data
+        runner.del_option("bits_stored")
+        runner.set_option("pixel_keyword", "FloatPixelData")
+        runner._validate_options()
+
+        runner.set_option("pixel_keyword", "DoubleFloatPixelData")
+        runner._validate_options()
+
     @pytest.mark.skipif(not HAVE_NP, reason="Numpy is not available")
     def test_decode(self):
         """Test decode()"""
diff --git a/tests/pixels/test_decoder_native.py b/tests/pixels/test_decoder_native.py
index c731590b3f..e43c523ada 100644
--- a/tests/pixels/test_decoder_native.py
+++ b/tests/pixels/test_decoder_native.py
@@ -25,13 +25,31 @@
 except ImportError:
     HAVE_NP = False
 
-from .pixels_reference import PIXEL_REFERENCE, EXPL_16_1_1F_PAD, IMPL_32_1_1F
+from .pixels_reference import (
+    PIXEL_REFERENCE,
+    EXPL_16_1_1F_PAD,
+    IMPL_32_1_1F,
+    EXPL_64_1F_DOUBLE_FLOAT,
+)
 
 
 def name(ref):
     return f"{ref.name}"
 
 
+def get_pixel_keyword(ds):
+    if "PixelData" in ds:
+        return "PixelData"
+
+    if "FloatPixelData" in ds:
+        return "FloatPixelData"
+
+    if "DoubleFloatPixelData" in ds:
+        return "DoubleFloatPixelData"
+
+    raise ValueError("No pixel data element found")
+
+
 @pytest.mark.skipif(not HAVE_NP, reason="NumPy is not available")
 class TestAsArray:
     """Tests for decoder.as_array() with native transfer syntaxes"""
@@ -82,21 +100,25 @@ def test_reference_expl_binary(self, reference):
         """Test against the reference data for explicit little for binary IO."""
         decoder = get_decoder(ExplicitVRLittleEndian)
         ds = reference.ds
+        pixel_keyword = get_pixel_keyword(ds)
+
         opts = {
             "rows": ds.Rows,
             "columns": ds.Columns,
             "samples_per_pixel": ds.SamplesPerPixel,
             "photometric_interpretation": ds.PhotometricInterpretation,
-            "pixel_representation": ds.PixelRepresentation,
             "bits_allocated": ds.BitsAllocated,
-            "bits_stored": ds.BitsStored,
             "number_of_frames": ds.get("NumberOfFrames", 1),
             "planar_configuration": ds.get("PlanarConfiguration", 0),
-            "pixel_keyword": "PixelData",
+            "pixel_keyword": pixel_keyword,
         }
 
+        if pixel_keyword == "PixelData":
+            opts["bits_stored"] = ds.BitsStored
+            opts["pixel_representation"] = ds.PixelRepresentation
+
         with open(reference.path, "rb") as f:
-            file_offset = reference.ds["PixelData"].file_tell
+            file_offset = reference.ds[pixel_keyword].file_tell
             f.seek(file_offset)
             arr, _ = decoder.as_array(f, raw=True, **opts)
             assert f.tell() == file_offset
@@ -229,35 +251,6 @@ def test_reference_expb_binary(self, reference):
                 else:
                     assert arr.shape == reference.shape[1:]
 
-    def test_float_pixel_data(self):
-        """Test Float Pixel Data."""
-        # Only 1 sample per pixel allowed
-        ds = dcmread(IMPL_32_1_1F.path)
-        ds.FloatPixelData = ds.PixelData
-        del ds.PixelData
-        assert 32 == ds.BitsAllocated
-        decoder = get_decoder(ds.file_meta.TransferSyntaxUID)
-        arr, _ = decoder.as_array(ds, raw=True)
-        assert "float32" == arr.dtype
-
-        ref, _ = decoder.as_array(IMPL_32_1_1F.ds, raw=True)
-        assert np.array_equal(arr, ref.view("float32"))
-
-    def test_double_float_pixel_data(self):
-        """Test Double Float Pixel Data."""
-        # Only 1 sample per pixel allowed
-        ds = dcmread(IMPL_32_1_1F.path)
-        ds.DoubleFloatPixelData = ds.PixelData + ds.PixelData
-        del ds.PixelData
-        ds.BitsAllocated = 64
-        decoder = get_decoder(ds.file_meta.TransferSyntaxUID)
-        arr, _ = decoder.as_array(ds, raw=True)
-        assert "float64" == arr.dtype
-
-        ref, _ = decoder.as_array(IMPL_32_1_1F.ds, raw=True)
-        assert np.array_equal(arr.ravel()[:50], ref.view("float64").ravel())
-        assert np.array_equal(arr.ravel()[50:], ref.view("float64").ravel())
-
 
 @pytest.mark.skipif(not HAVE_NP, reason="NumPy is not available")
 class TestIterArray:
@@ -306,21 +299,25 @@ def test_reference_expl_binary(self, reference):
         """Test against the reference data for explicit little for binary IO."""
         decoder = get_decoder(ExplicitVRLittleEndian)
         ds = reference.ds
+        pixel_keyword = get_pixel_keyword(ds)
+
         opts = {
             "rows": ds.Rows,
             "columns": ds.Columns,
             "samples_per_pixel": ds.SamplesPerPixel,
             "photometric_interpretation": ds.PhotometricInterpretation,
-            "pixel_representation": ds.PixelRepresentation,
             "bits_allocated": ds.BitsAllocated,
-            "bits_stored": ds.BitsStored,
             "number_of_frames": ds.get("NumberOfFrames", 1),
             "planar_configuration": ds.get("PlanarConfiguration", 0),
-            "pixel_keyword": "PixelData",
+            "pixel_keyword": pixel_keyword,
         }
 
+        if pixel_keyword == "PixelData":
+            opts["bits_stored"] = ds.BitsStored
+            opts["pixel_representation"] = ds.PixelRepresentation
+
         with open(reference.path, "rb") as f:
-            file_offset = reference.ds["PixelData"].file_tell
+            file_offset = reference.ds[pixel_keyword].file_tell
             f.seek(file_offset)
 
             frame_generator = decoder.iter_array(f, raw=True, **opts)
@@ -492,21 +489,25 @@ def test_reference_expl_binary(self, reference):
             return
 
         ds = reference.ds
+        pixel_keyword = get_pixel_keyword(ds)
+
         opts = {
             "rows": ds.Rows,
             "columns": ds.Columns,
             "samples_per_pixel": ds.SamplesPerPixel,
             "photometric_interpretation": ds.PhotometricInterpretation,
-            "pixel_representation": ds.PixelRepresentation,
             "bits_allocated": ds.BitsAllocated,
-            "bits_stored": ds.BitsStored,
             "number_of_frames": ds.get("NumberOfFrames", 1),
             "planar_configuration": ds.get("PlanarConfiguration", 0),
-            "pixel_keyword": "PixelData",
+            "pixel_keyword": pixel_keyword,
         }
 
+        if pixel_keyword == "PixelData":
+            opts["bits_stored"] = ds.BitsStored
+            opts["pixel_representation"] = ds.PixelRepresentation
+
         with open(reference.path, "rb") as f:
-            file_offset = reference.ds["PixelData"].file_tell
+            file_offset = reference.ds[pixel_keyword].file_tell
             f.seek(file_offset)
             arr, _ = decoder.as_array(f, raw=True, **opts)
             buffer, _ = decoder.as_buffer(f, **opts)
@@ -705,28 +706,6 @@ def test_expb_8bit_ow_binary(self):
                     out[:27] = arr.ravel()
                     assert out.view(">u2").byteswap().tobytes() == buffer
 
-    def test_float_pixel_data(self):
-        """Test Float Pixel Data."""
-        ds = dcmread(IMPL_32_1_1F.path)
-        ref = ds.PixelData
-        ds.FloatPixelData = ref
-        del ds.PixelData
-        assert 32 == ds.BitsAllocated
-        decoder = get_decoder(ds.file_meta.TransferSyntaxUID)
-        buffer, _ = decoder.as_buffer(ds, raw=True)
-        assert buffer == ref
-
-    def test_double_float_pixel_data(self):
-        """Test Double Float Pixel Data."""
-        ds = dcmread(IMPL_32_1_1F.path)
-        ref = ds.PixelData + ds.PixelData
-        ds.DoubleFloatPixelData = ref
-        del ds.PixelData
-        ds.BitsAllocated = 64
-        decoder = get_decoder(ds.file_meta.TransferSyntaxUID)
-        buffer, _ = decoder.as_buffer(ds, raw=True)
-        assert buffer == ref
-
 
 @pytest.mark.skipif(not HAVE_NP, reason="NumPy is not available")
 class TestIterBuffer:
@@ -776,21 +755,25 @@ def test_reference_expl_binary(self, reference):
             return
 
         ds = reference.ds
+        pixel_keyword = get_pixel_keyword(ds)
+
         opts = {
             "rows": ds.Rows,
             "columns": ds.Columns,
             "samples_per_pixel": ds.SamplesPerPixel,
             "photometric_interpretation": ds.PhotometricInterpretation,
-            "pixel_representation": ds.PixelRepresentation,
             "bits_allocated": ds.BitsAllocated,
-            "bits_stored": ds.BitsStored,
             "number_of_frames": ds.get("NumberOfFrames", 1),
             "planar_configuration": ds.get("PlanarConfiguration", 0),
-            "pixel_keyword": "PixelData",
+            "pixel_keyword": pixel_keyword,
         }
 
+        if pixel_keyword == "PixelData":
+            opts["bits_stored"] = ds.BitsStored
+            opts["pixel_representation"] = ds.PixelRepresentation
+
         with open(reference.path, "rb") as f:
-            file_offset = reference.ds["PixelData"].file_tell
+            file_offset = reference.ds[pixel_keyword].file_tell
             f.seek(file_offset)
             arr_gen = decoder.iter_array(f, raw=True, **opts)
             buf_gen = decoder.iter_buffer(f, **opts)