Search code examples
pythonopencv

How to read and enhance contrast of 32bit .tiff image using opencv2 in python coding?


As I tried to code the opencv2 to read the tiff image file, It show up

OpenCV TIFF: TIFFRGBAImageOK: Sorry, can not handle images with 32-bit samples

I should expect that the opencv will read the file and process it with enhance contrast a bit.

here is the code:

import os
import cv2

input_folder = 'path'
output_folder = 'path'

# Loop through all TIFF files in the input folder
for filename in os.listdir(input_folder):
    if filename.endswith('.tiff'):
        image = cv2.imread(os.path.join(input_folder, filename), cv2.IMREAD_GRAYSCALE)

        alpha = 1.0035
        beta = 0
        enhanced_image = cv2.convertScaleAbs(image, alpha=alpha, beta=beta)

        output_filename = os.path.splitext(filename)[0] + '_enhanced.tif'
        cv2.imwrite(os.path.join(output_folder, output_filename), enhanced_image)

For the image tiff file, i have upload Here and please use ImageJ to view it.

Update (1), I have tried How do you read a 32-bit TIFF image in python? But it seem have another error which is this

can't open/read file: check file path/integrity

Followed by

cv2.error: OpenCV(4.7.0) D:\a\opencv-python\opencv-python\opencv\modules\imgcodecs\src\loadsave.cpp:783: error: (-215:Assertion failed) !_img.empty() in function 'cv::imwrite'


Solution

  • OpenCV doesn't support reading Tiff image in float format (at least not the standard Python version).
    We may use tifffile for reading the image:

    import tifffile
    ...
    image = tifffile.imread(filename)
    

    There are multiple ways to enhance the contrast of the image.
    Two examples: "Linear stretching" and CLAHE.

    • Linear stretching (from my following answer):
      Find minimal and maximal percentile, and apply linear "stretch" such that low percentile goes to 0, and high_prc percentile goes to 255:

       def lin_stretch_img(img, low_prc, high_prc):
           lo, hi = np.percentile(img, (low_prc, high_prc))  # Example: 1% - Low percentile, 99% - High percentile
      
           if lo == hi:
               return np.full(img.shape, 128, np.uint8)  # Protection: return gray image if lo = hi.
      
           stretch_img = (img.astype(np.float32) - lo) * (255/(hi-lo))  # Linear stretch: lo goes to 0, hi to 255.
           stretch_img = stretch_img.clip(0, 255).astype(np.uint8)  # Clip range to [0, 255] and convert to uint8
           return stretch_img
      
    • OpenCV CLAHE:
      Enhancing the contrast in each block, allows much higher contrast compared to the linear stretching.
      Since CLAHE in OpenCV does not support float32, we have to convert the image to uint16 type before applying CLAHE.

       image_as_uint16 = cv2.normalize(image, None, 0, 65535, cv2.NORM_MINMAX, cv2.CV_16U) # Convert to uint16 before applying CLAHE
       clahe = cv2.createCLAHE(clipLimit=20, tileGridSize=(8, 8))
       cl1 = clahe.apply(image_as_uint16)  # CLAHE in OpenCV does not support float32 apply CLAHE to the uint16 image.
       cl1 = cv2.convertScaleAbs(cl1, alpha=255/65535)  # Convert from uint16 to uint8
      

    Code sample:

    import os
    import cv2
    import tifffile
    import numpy as np
    
    def lin_stretch_img(img, low_prc, high_prc):
        """ 
        Apply linear "stretch" - low_prc percentile goes to 0, 
        and high_prc percentile goes to 255.
        The result is clipped to [0, 255] and converted to np.uint8
        """
        lo, hi = np.percentile(img, (low_prc, high_prc))  # Example: 1% - Low percentile, 99% - High percentile
    
        if lo == hi:
            return np.full(img.shape, 128, np.uint8)  # Protection: return gray image if lo = hi.
    
        stretch_img = (img.astype(np.float32) - lo) * (255/(hi-lo))  # Linear stretch: lo goes to 0, hi to 255.
        stretch_img = stretch_img.clip(0, 255).astype(np.uint8)  # Clip range to [0, 255] and convert to uint8
        return stretch_img
    
    
    filename = 'top_high_1493_2065_132_132_0.tiff'
    
    #image = cv2.imread(filename, cv2.IMREAD_GRAYSCALE)
    image = tifffile.imread(filename)
    
    alpha = 1.0035
    beta = 0
    enhanced_image = cv2.convertScaleAbs(image, alpha=alpha, beta=beta)
    
    # Apply "linear stretching" (lower percentile 0.1 goes to 0, and percentile 99.9 to 255).
    lin_enhanced_image = lin_stretch_img(image, 0.1, 99.9)
    
    # Normalizing frame to range [0, 65535], and get the result as type uint16. (65535 = 2**16-1)
    image_as_uint16 = cv2.normalize(image, None, 0, 65535, cv2.NORM_MINMAX, cv2.CV_16U) # Convert to uint16 before applying CLAHE
    clahe = cv2.createCLAHE(clipLimit=20, tileGridSize=(8, 8))
    cl1 = clahe.apply(image_as_uint16)  # CLAHE in OpenCV does not support float32 apply CLAHE to the uint16 image.
    cl1 = cv2.convertScaleAbs(cl1, alpha=255/65535)  # Convert from uint16 to uint8
    
    output_filename = os.path.splitext(filename)[0] + '_enhanced.tif'
    cv2.imwrite(output_filename, enhanced_image)
    
    cv2.imwrite(os.path.splitext(filename)[0] + '_lin_enhanced.tif', lin_enhanced_image)
    
    cv2.imwrite(os.path.splitext(filename)[0] + '_cl1_enhanced.tif', cl1)
    

    CLAHE output for example:
    enter image description here
    The contrast is enhanced, but the input image is mainly noise...