Skip to content

Instantly share code, notes, and snippets.

@soswow
Last active December 16, 2023 23:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save soswow/71a6f98a03b0da5aae479a258db32b6c to your computer and use it in GitHub Desktop.
Save soswow/71a6f98a03b0da5aae479a258db32b6c to your computer and use it in GitHub Desktop.
OpenCV and matplotlib driven script to display histogram for variety of file formats including raw like DNG, high dynamic range like EXR etc.
import cv2
import numpy as np
import matplotlib.pyplot as plt
import sys
import os
import argparse
os.environ["OPENCV_IO_ENABLE_OPENEXR"] = "true"
def generate_histogram(image_path, num_bins, y_scale, percentile):
# Load the image
image = cv2.imread(image_path, cv2.IMREAD_ANYCOLOR | cv2.IMREAD_ANYDEPTH)
# image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# image = cv2.imread(image_path, cv2.IMREAD_UNCHANGED)
if image is None:
print("Error: Unable to load the image.")
return
# Flatten the image into a 1D array
pixels = image.flatten()
print("%d values" % len(pixels))
min_value = pixels.min()
max_value = pixels.max()
print("%f - %f range of values" % (min_value, max_value))
# max_number = 65535
# range_multiplier = max_number / max_value
# BGR
blue_pixels = pixels[0::3]
print("%d blue values" % len(blue_pixels))
green_pixels = pixels[1::3]
print("%d green values" % len(green_pixels))
red_pixels = pixels[2::3]
print("%d red values" % len(red_pixels))
print(blue_pixels[:10])
# Create the histogram using NumPy
red_hist, bins = np.histogram(red_pixels, bins=num_bins, range=(0, max_value))
green_hist, bins = np.histogram(green_pixels, bins=num_bins, range=(0, max_value))
blue_hist, bins = np.histogram(blue_pixels, bins=num_bins, range=(0, max_value))
# Plot the histogram using Matplotlib
plt.figure(figsize=(14, 9))
plt.bar(range(num_bins), red_hist, width=1.0, align='edge', color='red', alpha=0.5)
plt.bar(range(num_bins), green_hist, width=1.0, align='edge', color='green', alpha=0.5)
plt.bar(range(num_bins), blue_hist, width=1.0, align='edge', color='blue', alpha=0.5)
plt.title('Color distribution histogram')
plt.xlabel('Pixel Value')
plt.yscale(y_scale)
if percentile is not None:
print("Percentile %d is defined. Applying yscale" % percentile)
y_limit = max(np.percentile(red_hist, percentile), np.percentile(blue_hist, percentile), np.percentile(green_hist, percentile))
plt.ylim(0, y_limit)
bin_width = int(max_value / num_bins * 1000000) / 1000000
print("bin width = %f" % bin_width)
plt.ylabel('Num of pixels per %f wide bin' % bin_width)
step = int(num_bins / 25)
print("step = %d" % step)
x_ticks = np.arange(num_bins, step=step)
x_ticks = np.append(x_ticks, num_bins)
print("x_ticks length = %d" % len(x_ticks))
x_tick_labels = [int(i * bin_width * 1000000)/1000000 for i in np.arange(num_bins, step=step)]
print('x_tick_labels', x_tick_labels)
x_tick_labels = np.append(x_tick_labels, max_value)
print("x_tick_labels length = %d" % len(x_tick_labels))
plt.xticks(x_ticks, x_tick_labels, rotation=90)
# Add text annotations for parameters
info_text = f'# Bins: {num_bins}\nScale: {y_scale}\n'
if percentile is not None:
info_text += f'Percentile: {percentile}%\n'
plt.text(0.95, 0.95, info_text,
verticalalignment='top', horizontalalignment='right',
transform=plt.gca().transAxes, bbox=dict(facecolor='white', alpha=0.8))
# Extract the directory path from the input image path
input_dir = os.path.dirname(image_path)
# Generate the output image path in the same directory as the input TIFF file
image_name = os.path.splitext(os.path.basename(image_path))[0]
output_image_path = os.path.join(input_dir, f"histogram-{image_name}-{y_scale}-bins-{num_bins}-percentile-{percentile}.png")
print("saving histogram into file: %s" % output_image_path)
# Save the plot as an image
plt.savefig(output_image_path, bbox_inches='tight')
# plt.legend()
# plt.show()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Generate a histogram from an image.')
# Positional argument for image_path
parser.add_argument('image_path', help='Path to the image file')
# Optional named argument for num_bins with a default value of 255
parser.add_argument('--bins', type=int, default=255, help='Number of bins in the histogram')
# Optional named argument for y_scale with choices 'linear' or 'log', defaulting to 'linear'
parser.add_argument('--scale', choices=['linear', 'log'], default='linear', help='Y-axis scale for the histogram')
parser.add_argument('--percentile', type=int, default=None, help='What percent of the data to display (useful, when some buckets go way out)')
args = parser.parse_args()
# Call your function with the parsed arguments
generate_histogram(args.image_path, args.bins, args.scale, args.percentile)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment