Source code for gain.genomic_resources.statistics.png_bytes

"""Make the bytes of a statistics image depend only on its pixels.

Matplotlib writes a ``tEXt`` chunk ``Software`` with its own version into
every PNG it saves.  Two hosts with different matplotlib versions then
write different bytes from the same pixels, and the md5 of the image
changes in ``.MANIFEST`` for no real change (gain#2048).

The chunk is removed after the image is drawn, not kept out with
``savefig(metadata=...)``: a resource ``plot_function`` calls ``savefig``
itself, and gain does not control that code.

A leaf that imports nothing from ``gain``, as ``chart_style`` is: both
``histogram`` and this package write images, and the statistics package
does not import ``histogram``.
"""
from __future__ import annotations

import io
import struct
from collections.abc import Callable
from typing import BinaryIO

PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n"

_SOFTWARE_TEXT_PREFIX = b"Software\x00"


[docs] def without_software_chunk(data: bytes) -> bytes: """Return ``data`` without its ``tEXt`` chunks keyed ``Software``. Every other chunk is copied byte for byte, so the image data and each CRC stay as they were. Bytes that are not a PNG come back unchanged: a ``plot_function`` can write anything. """ if not data.startswith(PNG_SIGNATURE): return data kept = [PNG_SIGNATURE] offset = len(PNG_SIGNATURE) while offset + 8 <= len(data): (length,) = struct.unpack(">I", data[offset:offset + 4]) end = offset + 12 + length chunk = data[offset:end] if not (chunk[4:8] == b"tEXt" and chunk[8:].startswith(_SOFTWARE_TEXT_PREFIX)): kept.append(chunk) offset = end kept.append(data[offset:]) return b"".join(kept)
[docs] def rendered_png(draw: Callable[[BinaryIO], object]) -> bytes: """Let ``draw`` write an image into memory, and return its bytes. The bytes come back without the ``Software`` chunk, ready for the caller to write into the resource. """ buffer = io.BytesIO() draw(buffer) return without_software_chunk(buffer.getvalue())