Search code examples
imageimage-processingcomputer-visiondata-visualization

How to visualize a .pfm file as image?


I have a .pfm which is a (.PF/.pf) file. I am trying to visualize it but I am unable to do so. Normally the .pfm files contain the header of the format.

  1. PF/pf
  2. width height
  3. scale=1

But my file has this header.I am unable to visualize it as the image can anyone help me out. Any help is appreciated

  1. Typ=Pic98::TPlane

  2. Lines=750

  3. Columns=1125

  4. FirstLine=0

  5. FirstColumn=0

    import re
    import numpy as np
    file = open("PF file.PF", 'rb')
    header = file.readline().rstrip().decode('utf-8')
    if header == 'PF':
       raise Exception('Only ONE channel image is supported.')
    elif header == 'Typ=Pic98::TPlane<float>':
        color = False
    else:
        raise Exception('Not a PFM file.')
    dim_match = re.match(r'(^(\w+).(\d+)$)\n(^(\w+).(\d+)\s$)', 
    file.readline().decode('ascii'))
    if dim_match:
      width, height = map(int, dim_match.groups())
    else:
      raise Exception('Malformed PFM header.')
    if header == 'Typ=Pic98::TPlane<float>':
      scale =1
      endian = '>'
    else:
      scale = -scale
      endian = '<'
    
    npImage = np.reshape(npImage, width,height)
    npImage = np.flipud(npImage)
    
    if ret_PIL:
      img = Image.fromarray(npImage, 'F')
      return img
    return npImage
    file.close()
    

Solution

  • Updated Answer

    I have re-written my answer below in a slightly different, hopefully clearer style.

    #!/usr/bin/env python3
    
    import re
    import cv2
    import numpy as np
    from PIL import Image
    
    def readPF(filename):
       """Read named PF file into Numpy array"""
    
       # Slurp entire file into memory as binary 'bytes'
       with open(filename, 'rb') as f:
          data = f.read()
    
       # Check correct header, return None if incorrect
       if not re.match(b'Typ=Pic98::TPlane<float>', data):
          return None
       
       # Get Lines and Columns, both must be present, else return None
       L = re.search(b'Lines=(\d+)',   data)
       C = re.search(b'Columns=(\d+)', data)
       if not (L and C):
          return None
       height = int(L.groups()[0])
       width  = int(C.groups()[0])
       print(f'DEBUG: Height={height}, width={width}')
    
       # Take the data from the END of the file in case other header lines added at start
       na = np.frombuffer(data[-4*height*width:], dtype=np.dtype('<f4')).reshape((height,width))
    
       # Some debug stuff
       min, max, mean = na.min(), na.max(), na.mean()
       print(f'DEBUG: min={min}, max={max}, mean={mean}')
    
       return na
    
    ################################################################################
    # Main
    ################################################################################
    na = readPF('PF file.PF')
    
    ################################################################################
    # Use either of the following to save the image:
    ################################################################################
    # Save with OpenCV as scaled PNG
    u16 = (65535*(na - np.min(na))/np.ptp(na)).astype(np.uint16)  
    cv2.imwrite('OpenCV.png', u16)
    
    # Convert to PIL Image and save as TIFF
    pi = Image.fromarray(na, mode='F')
    pi.save('PIL.tif')
    

    Original Answer

    Not too sure what I image I should be expecting, but here is a rough idea:

    #!/usr/bin/env python3
    
    import re
    import cv2
    import numpy as np
    from PIL import Image
    
    file = open("PF file.PF", 'rb')
    header = file.readline().rstrip().decode('utf-8')
    if header == 'PF':
       raise Exception('Only ONE channel image is supported.')
    elif header == 'Typ=Pic98::TPlane<float>':
        color = False
    else:
        raise Exception('Not a PFM file.')
    
    while True:
        line = file.readline().decode('ascii')
        match = re.match('(\w+)=(\d+)', line)
        n, v = match.groups()
        if n == 'Lines':
            height = int(v)
            print(f'Height: {height}')
        if n == 'Columns':
            width = int(v)
            print(f'Width: {width}')
            break
    
    # Seek backwards from the end of the file in case any clown has added something to the header
    file.seek(-height*width*4,2)
    
    # Read remainder of file into Numpy array of floats and reshape
    na = np.fromfile(file, dtype=np.float32).reshape((height,width))
    
    # Some debug stuff
    min, max, mean = na.min(), na.max(), na.mean()
    print(f'DEBUG: min={min}, max={max}, mean={mean}')
    
    ################################################################################
    # Use either of the following to save the image:
    ################################################################################
    # Save with OpenCV as scaled PNG
    u16 = (65535*(na - np.min(na))/np.ptp(na)).astype(np.uint16)  
    cv2.imwrite('OpenCV.png', u16)
    
    # Convert to PIL Image and save as TIFF
    pi = Image.fromarray(na, mode='F')
    pi.save('PIL.tif')
    

    enter image description here

    The output is as follows:

    Height: 750
    Width: 1125
    DEBUG: min=0.0, max=127881704.0, mean=1618343.625
    

    Another possibility is to use ImageMagick to make it into a PNG, I get the following, and ImageMagick defaults to little-endian, so if this is correct, your image is little endian.

    magick -define quantum:format=floating-point -depth 32 -size 1125x750+80 gray:"PF file.pf"  -auto-level image.png
    

    enter image description here

    Keywords: Python, ImageMagick, image processing, float, float32, Numpy, PFM