Last active
August 11, 2024 11:03
-
-
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, 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