Last active
December 16, 2023 23:18
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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