Search code examples
pythonimagematplotlibmatrixsave

How to Save a Matrix as a PNG Image and Reload it to Recover the Original Matrix


I would like to save a 96x96 matrix as an image. The values in the matrix range from -1 to 1. When I save the matrix using

plt.imsave(path, matrix, cmap='rainbow'),

it gets stored as a 96x96x4 RGBA image, and each channel's values are rescaled to the 0–255 range. This makes it difficult to retrieve the original values of the matrix. Is there a way to preserve the original matrix values when saving and reloading the image?

I attempted the following approach to retrieve the original matrix:

loaded_image = plt.imread(path)
restored_image = np.mean(loaded_image[:, :, :3], axis=2)
print(restored_image )

However, this method only reproduces a matrix with the same dimensions as the original matrix, but the values differ from those of the original matrix.


Solution

  • There is not a single good answer to your question, as it is not quite clear to me why you want to save your matrix as an image in the first place. Consider the following: Most images that are used in standard use cases (think of showing pictures online, creating plots of data, etc.) are 8-bit RGB(A) or grayscale images, meaning you have 3 or 4 channels for RGB (red, green, blue, and maybe opacity) or 1 channel for grayscale (intensity), each storing 256 integer values (0 to 255). Your data, on the other hand, is floating point data in a very different range (-1 to 1). So a standard, 8-bit image may not be the ideal medium to store your matrix.

    There are multiple options you could choose from, depending on the actual task that you are trying to solve:

    • Since you seem to work with Numpy, you might consider storing your data directly as a Numpy array – have a look at numpy.save() and numpy.load() for that.
    • If you want to store your data in a Numpy-independent format, you could even consider storing it as a text file, given your matrix only contains 96×96 values – have a look at numpy.savetxt() and numpy.loadtxt() for that. There are other, more advanced formats for storing array data in a language-agnostic way, e.g. HDF5, but I guess that would be a bit too much here.
    • If you really want to store your matrix as an image, but with minimum loss of information, you could consider using a bit of a less standard image format, such as floating-point TIFF. The tifffile library should support this.
    • Finally, if you want to use a "standard" (8-bit) image, you need to normalize your image data before saving (convert from [-1, 1] to [0, 255]) and after loading (convert from [0, 255] to [-1, 1]). You will lose precision there though, as already mentioned. The following code achieves this, for example:
      import numpy as np
      from PIL import Image
      
      rand = np.random.default_rng(seed=0xC0FFEE)
      
      # Produce some exemplary data in the given range
      mat = rand.uniform(low=-1., high=1., size=(96, 96))
      # Move to range [0, 255], then convert to integers
      mat_uint8 = np.round((255 / 2) * (mat + 1)).astype(np.uint8)
      # Save as 8-bit PNG image
      Image.fromarray(mat_uint8).save("matrix.png")
      # Load and reconstruct original matrix
      mat_rec = np.asarray(Image.open("matrix.png")).astype(float) * (2 / 255) - 1
      assert(np.allclose(mat, mat_rec, atol=1e-2))
      print(f"Max. abs. difference: {np.abs(mat_rec - mat).max():.5f}")
      # Prints 0.00392