32bit Tiff to Jpg in Python

98 views Asked by At

I am trying to save spectrograms to jpg in python to use in machine learning. I have been able to save the spectrograms as tiff files and recreate the audio from those tiff files at a quality I am happy with. But I need to convert the 32bit floating point tiff files to jpg and I have been unable to find a method that preserves the contrast of the original tiff image.

I have tried numerous ways of converting the tiff to jpg or saving the array directly as a jpg and none of them have resulted in the contrast matching the tiff image. Every example I have found, whether using PIL or OpenCV etc, always results in an image with significantly lowered contrast. I can open the tiff file in MS paint and save it as a jpg and it looks exactly how I would like it to, the contrast is the same with maybe slightly lower quality but good enough. These 2 images came from the code below, the 'tiff' jpg was converted to jpg using ms paint and the other jpg was exported using the code below.


        S = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=216, hop_length=1024, fmax=10000)

        from PIL import Image
        im =Image.fromarray(S).convert('F')
        im.show()
        im.save(f"sp{frame_id}.tiff")

        na   = np.array(S)
        norm = cv2.normalize(na, None, alpha=0, beta=1, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_32F)
        cv2.imwrite(f'sp{frame_id}.jpg', (norm*255).astype(np.uint8))

Here is the original Tiff file https://drive.google.com/file/d/1MCtoWfGSQsxSUfsHpZi6teLH6EkqkPxP/view?usp=sharing

Tiff image saved as jpg from MS paint:

Tiff image saved as jpg from MS paint

Jpg saved by opencv

Jpg saved by cv2

1

There are 1 answers

1
Mark Setchell On

As @MarkRansom suggests, there is probably some sort of non-linear signal compression going on, but I don't know enough about audio (RIAA curves?, mu-law companding? or librosa?) to tell you what.

I can offer some code that tries to give you a better contrast in your signal though. It applies a traditional gamma correction from image processing and it discards data samples lying outside 3 standard deviations from the mean - see Wikipedia 68-95-99.7 rule.

It is not definitive, so you should try to better understand what has happened to your signal and see if you can tweak/improve it:

#!/usr/bin/env python3

import cv2 as cv
import numpy as np
from PIL import Image

def applyGamma(im, gamma=1.0):
   """
   Apply gamma corrrection to an image.
   Gamma values <1 will darken the image.
   Gamma values >1 will brighten the image.
   """
   # Apply gamma
   im = im**(1.0/gamma)

   # Clip to 3 stddevs, so we only use 99.7% of the data, i.e. discard 0.3% of wildest data
   N = 3
   lolim = np.mean(im) - N*np.std(im)
   hilim = np.mean(im) + N*np.std(im)
   im = np.clip(im, lolim, hilim)

   # Normalize to 0..65535 and uint16
   res = cv.normalize(im, None, alpha=0, beta=65535, norm_type=cv.NORM_MINMAX, dtype=cv.CV_16U)
   print(f'DEBUG: dtype={res.dtype}, min={res.min()}, max={res.max()}, mean={res.mean()}, stddev={res.std()}')

   return res

# Load image and convert to Numpy array
im = Image.open('sp1875.tiff')
im = np.array(im)

# Quick debug output
print(f'DEBUG: Original image: min={im.min()}, max={im.max()}, mean={im.mean()}, stddev={im.std()}')

res = applyGamma(im, 3.0)
cv.imwrite('result.png', res)

enter image description here


To increase the contrast, you would need to either decrease N in the code so you retain only data nearer the mean and discard more outliers. Or you could increase the gamma parameter when calling the applyGamma() function.

Conversely, increasing N or decreasing gamma will decrease the contrast.