Skip to content

Instantly share code, notes, and snippets.

@soswow
Last active August 11, 2024 11:03
Show Gist options
  • 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, x_min, x_max):
# Load the image
image = cv2.imread(image_path, cv2.IMREAD_ANYCOLOR | cv2.IMREAD_ANYDEPTH)
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 = np.nanmin(pixels)
max_value = np.nanmax(pixels)
print("%f - %f range of values" % (min_value, max_value))
# If x_min and x_max are not provided, use the actual min and max values
if x_min is None:
x_min = min_value
if x_max is None:
x_max = max_value
# Ensure x_min and x_max are within the valid range
x_min = max(x_min, min_value)
x_max = min(x_max, max_value)
print("X-axis range: %f to %f" % (x_min, x_max))
# BGR channels
blue_pixels = pixels[0::3]
green_pixels = pixels[1::3]
red_pixels = pixels[2::3]
# Create the histogram using NumPy
red_hist, bins = np.histogram(red_pixels, bins=num_bins, range=(x_min, x_max))
green_hist, bins = np.histogram(green_pixels, bins=num_bins, range=(x_min, x_max))
blue_hist, bins = np.histogram(blue_pixels, bins=num_bins, range=(x_min, x_max))
# 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:
y_limit = int(max(np.percentile(red_hist, percentile), np.percentile(blue_hist, percentile), np.percentile(green_hist, percentile)))
plt.ylim(0, y_limit)
bin_width = (x_max - x_min) / num_bins
plt.ylabel('Num of pixels per %f wide bin' % bin_width)
step = int(num_bins / 25)
x_ticks = np.arange(0, num_bins, step=step)
x_ticks = np.append(x_ticks, num_bins)
x_tick_labels = [x_min + i * bin_width for i in x_ticks]
plt.xticks(x_ticks, x_tick_labels, rotation=90)
# Add text annotations for parameters
info_text = f'# Bins: {num_bins}\nScale: {y_scale}\nX-range: {x_min} to {x_max}\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, image_extension = os.path.splitext(os.path.basename(image_path))
output_image_path = os.path.join(input_dir, f"histogram-{image_name}_{image_extension[1:]}-{y_scale}-bins-{num_bins}-xrange-{x_min}-{x_max}-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')
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 arguments
parser.add_argument('--bins', type=int, default=255, help='Number of bins in the histogram')
parser.add_argument('--scale', choices=['linear', 'log'], default='linear', help='Y-axis scale for the histogram')
parser.add_argument('--percentile', type=float, default=None, help='What percent of the data to display (useful, when some buckets go way out)')
# New arguments for x-axis range
parser.add_argument('--x_min', type=float, default=None, help='Minimum value of the x-axis range')
parser.add_argument('--x_max', type=float, default=None, help='Maximum value of the x-axis range')
args = parser.parse_args()
# Call your function with the parsed arguments
generate_histogram(args.image_path, args.bins, args.scale, args.percentile, args.x_min, args.x_max)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment