Skip to content

Instantly share code, notes, and snippets.

@gleicon
Last active May 1, 2016 00:37
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 gleicon/aa8fe6a315761edcd32d05e4d37b1f86 to your computer and use it in GitHub Desktop.
Save gleicon/aa8fe6a315761edcd32d05e4d37b1f86 to your computer and use it in GitHub Desktop.
pre-historic 2006 code for my post graduation studies using ruby and imagemagick. fepo.
# This routine needs the color_histogram method
# from either ImageMagick 6.0.0 or GraphicsMagick 1.1
# Specify an image filename as an argument.
require 'RMagick'
class PixelColumn < Array
def initialize(size)
super
fill {Magick::Pixel.new}
end
def reset(bg)
each {|pixel| pixel.reset(bg)}
end
end
module Magick
class Pixel
def reset(bg)
self.red = bg.red
self.green = bg.green
self.blue = bg.blue
self.opacity = bg.opacity
end
end
class Image
private
HISTOGRAM_COLS = 256
HISTOGRAM_ROWS = 200
MAX_QUANTUM = 255
AIR_FACTOR = 1.025
# The alpha frequencies are shown as white dots.
def alpha_hist(freqs, scale, fg, bg)
histogram = Image.new(HISTOGRAM_COLS, HISTOGRAM_ROWS) {
self.background_color = bg
self.border_color = fg
}
gc = Draw.new
gc.affine(1, 0, 0, -scale, 0, HISTOGRAM_ROWS)
gc.fill('white')
HISTOGRAM_COLS.times do |x|
gc.point(x, freqs[x])
end
gc.draw(histogram)
histogram['Label'] = 'Alpha'
return histogram
end
def channel_histograms(red, green, blue, int, scale, fg, bg)
rgb_histogram = Image.new(HISTOGRAM_COLS, HISTOGRAM_ROWS) {
self.background_color = bg
self.border_color = fg
}
rgb_histogram['Label'] = 'RGB'
red_histogram = rgb_histogram.copy
red_histogram['Label'] = 'Red'
green_histogram = rgb_histogram.copy
green_histogram['Label'] = 'Green'
blue_histogram = rgb_histogram.copy
blue_histogram['Label'] = 'Blue'
int_histogram = rgb_histogram.copy
int_histogram['Label'] = 'Intensity'
int_histogram.matte = true
rgb_column = PixelColumn.new(HISTOGRAM_ROWS)
red_column = PixelColumn.new(HISTOGRAM_ROWS)
green_column = PixelColumn.new(HISTOGRAM_ROWS)
blue_column = PixelColumn.new(HISTOGRAM_ROWS)
int_column = PixelColumn.new(HISTOGRAM_ROWS)
HISTOGRAM_COLS.times do |x|
HISTOGRAM_ROWS.times do |y|
yf = Float(y)
if yf >= HISTOGRAM_ROWS - (red[x] * scale)
red_column[y].red = MaxRGB
red_column[y].green = 0
red_column[y].blue = 0
rgb_column[y].red = MaxRGB
end
if yf >= HISTOGRAM_ROWS - (green[x] * scale)
green_column[y].green = MaxRGB
green_column[y].red = 0
green_column[y].blue = 0
rgb_column[y].green = MaxRGB
end
if yf >= HISTOGRAM_ROWS - (blue[x] * scale)
blue_column[y].blue = MaxRGB
blue_column[y].red = 0
blue_column[y].green = 0
rgb_column[y].blue = MaxRGB
end
if yf >= HISTOGRAM_ROWS - (int[x] * scale)
int_column[y].opacity = TransparentOpacity
end
end
rgb_histogram.store_pixels( x, 0, 1, HISTOGRAM_ROWS, rgb_column)
red_histogram.store_pixels( x, 0, 1, HISTOGRAM_ROWS, red_column)
green_histogram.store_pixels(x, 0, 1, HISTOGRAM_ROWS, green_column)
blue_histogram.store_pixels( x, 0, 1, HISTOGRAM_ROWS, blue_column)
int_histogram.store_pixels( x, 0, 1, HISTOGRAM_ROWS, int_column)
rgb_column.reset(bg)
red_column.reset(bg)
green_column.reset(bg)
blue_column.reset(bg)
int_column.reset(bg)
end
return red_histogram, green_histogram, blue_histogram, rgb_histogram, int_histogram
end
# Make the color histogram. Quantize the image to 256 colors if necessary.
def color_hist(fg, bg)
img = number_colors > 256 ? quantize(256) : self
begin
hist = img.color_histogram
rescue NotImplementedError
$stderr.puts "The color_histogram method is not supported by this version "+
"of ImageMagick/GraphicsMagick"
else
pixels = hist.keys.sort_by {|pixel| hist[pixel] }
scale = HISTOGRAM_ROWS / (hist.values.max*AIR_FACTOR)
histogram = Image.new(HISTOGRAM_COLS, HISTOGRAM_ROWS) {
self.background_color = bg
self.border_color = fg
}
x = 0
pixels.each do |pixel|
column = Array.new(HISTOGRAM_ROWS).fill {Pixel.new}
HISTOGRAM_ROWS.times do |y|
if y >= HISTOGRAM_ROWS - (hist[pixel] * scale)
column[y] = pixel;
end
end
histogram.store_pixels(x, 0, 1, HISTOGRAM_ROWS, column)
x = x.succ
end
histogram['Label'] = 'Color Frequency'
return histogram
end
end
# Use AnnotateImage to write the stats.
def info_text(fg, bg)
klass = class_type == DirectClass ? "DirectClass" : "PsuedoClass"
text = <<-END_TEXT
Format: #{format}
Geometry: #{columns}x#{rows}
Class: #{klass}
Depth: #{depth} bits-per-pixel component
Colors: #{number_colors}
END_TEXT
info = Image.new(HISTOGRAM_COLS, HISTOGRAM_ROWS) {
self.background_color = bg
self.border_color = fg
}
gc = Draw.new
gc.annotate(info, 0, 0, 0, 0, text) {
self.stroke = 'transparent'
self.fill = fg
self.gravity = CenterGravity
}
info['Label'] = 'Info'
return info
end
def intensity_hist(int_histogram)
gradient = (Image.read("gradient:#ffff80-#ff9000") { self.size="#{HISTOGRAM_COLS}x#{HISTOGRAM_ROWS}" }).first
int_histogram = gradient.composite(int_histogram, CenterGravity, OverCompositeOp)
int_histogram['Label'] = 'Intensity'
return int_histogram
end
# Returns a value between 0 and MAX_QUANTUM. Same as the PixelIntensity macro.
def pixel_intensity(pixel)
(306*(pixel.red & MAX_QUANTUM) + 601*(pixel.green & MAX_QUANTUM) + 117*(pixel.blue & MAX_QUANTUM))/1024
end
public
# Create the histogram montage.
def histogram(fg='white', bg='black')
red = Array.new(HISTOGRAM_COLS, 0)
green = Array.new(HISTOGRAM_COLS, 0)
blue = Array.new(HISTOGRAM_COLS, 0)
alpha = Array.new(HISTOGRAM_COLS, 0)
int = Array.new(HISTOGRAM_COLS, 0)
rows.times do |row|
pixels = get_pixels(0, row, columns, 1)
pixels.each do |pixel|
red[pixel.red & MAX_QUANTUM] += 1
green[pixel.green & MAX_QUANTUM] += 1
blue[pixel.blue & MAX_QUANTUM] += 1
# Only count opacity channel if some pixels are not opaque.
if !opaque?
alpha[pixel.opacity & MAX_QUANTUM] += 1
end
v = pixel_intensity(pixel)
int[v] += 1
end
end
# Scale to chart size. When computing the scale, add some "air" between
# the max frequency and the top of the histogram. This makes a prettier chart.
# The RGBA and intensity histograms are all drawn to the same scale.
max = [red.max, green.max, blue.max, alpha.max, int.max].max
scale = HISTOGRAM_ROWS / (max*AIR_FACTOR)
charts = ImageList.new
# Add the thumbnail.
thumb = copy
thumb['Label'] = File.basename(filename)
charts << thumb
# Compute the channel and intensity histograms.
channel_hists = channel_histograms(red, green, blue, int, scale, fg, bg)
# Add the red, green, and blue histograms to the list
charts << channel_hists.shift
charts << channel_hists.shift
charts << channel_hists.shift
# Add Alpha channel or image stats
if !opaque?
charts << alpha_hist(alpha, scale, fg, bg)
else
charts << info_text(fg, bg)
end
# Add the RGB histogram
charts << channel_hists.shift
# Add the intensity histogram.
charts << intensity_hist(channel_hists.shift)
# Add the color frequency histogram.
charts << color_hist(fg, bg)
# Make a montage.
histogram = charts.montage {
self.background_color = bg
self.stroke = 'transparent'
self.fill = fg
self.border_width = 1
self.tile = "4x2"
self.geometry = "#{HISTOGRAM_COLS}x#{HISTOGRAM_ROWS}+10+10"
}
return histogram
end
end
end
puts <<END_INFO
This example shows how to get pixel-level access to an image.
Usage: histogram.rb <image-filename>
END_INFO
# Get filename from command line.
if !ARGV[0] then
puts "No filename argument. Defaulting to Flower_Hat.jpg"
filename = '../doc/ex/images/Flower_Hat.jpg'
else
filename = ARGV[0]
end
# Only process first frame if multi-frame image
image = Magick::Image.read(filename)
if image.length > 1
puts "Charting 1st image"
end
image = image.first
# Give the user something to look at while we're working.
name = File.basename(filename).sub(/\..*?$/,'')
$defout.sync = true
printf "Creating #{name}_Histogram.miff"
timer = Thread.new do
loop do
sleep(1)
printf "."
end
end
# Generate the histograms
histogram = image.histogram(Magick::Pixel.from_color('white'), Magick::Pixel.from_color('black'))
# Write output file
histogram.compression = Magick::ZipCompression
histogram.write("./#{name}_Histogram.miff")
Thread.kill(timer)
puts "Done!"
exit
# image_analysis.rb by Gleicon Moraes (gleicon@gmail.com) 01/2007
# Specify two images as argument.
# will compare both images and print comparative data about both images
require 'RMagick'
module Magick
# compute psnr and add to ImageList
# (diff_img, dbDist) = compare_channel(mod, PeakSignalToNoiseRatioMetric)
# diff_img['Label']="Pixels diferentes"
# charts << diff_img
end
# main
# Get filename from command line.
if !ARGV[1] then
puts "No arguments."
puts "Usage: image_analysis.rb image1 image2 [image3 image4...]"
exit
else
orig_filename = ARGV[0]
mod_filename = ARGV[1]
end
# Only process first frame if multi-frame image
orig_image = Magick::Image.read(orig_filename)
orig_image = orig_image.first
$defout.sync = true
printf "Image data\n"
printf "name\t\t\t\tsize(b)\tred(%%)\tPSNR(Db)\tRMSE\n"
orig_size=File.size(orig_filename)
ARGV.each {|mod_filename|
mod_size = File.size(mod_filename)
reduc = 100.00 - ((mod_size.to_f/orig_size.to_f)*100.0 )
mod_image = Magick::Image.read(mod_filename)
mod_image = mod_image.first
printf "%s\t%d\t%.2f\t%.2f\t\t%.2f\n", mod_filename, mod_size, reduc, orig_image.distortion_channel(mod_image, Magick::PeakSignalToNoiseRatioMetric), orig_image.distortion_channel(mod_image, Magick::RootMeanSquaredErrorMetric)
}
#
#MeanAbsoluteErrorMetric
#MeanSquaredErrorMetric
#PeakAbsoluteErrorMetric
#PeakSignalToNoiseRatioMetric
#RootMeanSquaredErrorMetric
#
exit
# pnsr.rb by Gleicon Moraes (gleicon@gmail.com) 01/2007
# based on histogram.rb from RMagick package
# Specify two images as argument.
# psnr.rb will compare both images and draw a comparative chart with both images, info
# about signal-to-noise rate and the diff image
# almost the same as compare -metric PSNR
#
require 'RMagick'
class PixelColumn < Array
def initialize(size)
super
fill {Magick::Pixel.new}
end
def reset(bg)
each {|pixel| pixel.reset(bg)}
end
end
module Magick
class Pixel
def reset(bg)
self.red = bg.red
self.green = bg.green
self.blue = bg.blue
self.opacity = bg.opacity
end
end
class Image
private
HISTOGRAM_COLS = 320 #256
HISTOGRAM_ROWS = 240 #200
MAX_QUANTUM = 255
AIR_FACTOR = 1.025
def info_text(fg, bg, db)
if (db.to_s.eql?("Infinity"))
sdb = "Inf."
else
sdb = "%.2f" % db.to_s
end
text = <<-END_TEXT
Peak Signal-to-Noise Ratio (PSNR): #{sdb} db
Geometry: #{columns}x#{rows}
Depth: #{depth} bits-per-pixel component
Colors: #{number_colors}
END_TEXT
info = Image.new(HISTOGRAM_COLS, HISTOGRAM_ROWS) {
self.background_color = bg
self.border_color = fg
}
gc = Draw.new
gc.annotate(info, 0, 0, 0, 0, text) {
self.stroke = 'transparent'
self.fill = fg
self.gravity = CenterGravity
}
info['Label'] = 'Info'
return info
end
public
def channel_histograms(fg, bg)
# data
red = Array.new(HISTOGRAM_COLS, 0)
green = Array.new(HISTOGRAM_COLS, 0)
blue = Array.new(HISTOGRAM_COLS, 0)
alpha = Array.new(HISTOGRAM_COLS, 0)
int = Array.new(HISTOGRAM_COLS, 0)
rows.times do |row|
pixels = get_pixels(0, row, columns, 1)
pixels.each do |pixel|
red[pixel.red & MAX_QUANTUM] += 1
green[pixel.green & MAX_QUANTUM] += 1
blue[pixel.blue & MAX_QUANTUM] += 1
# Only count opacity channel if some pixels are not opaque.
if !opaque?
alpha[pixel.opacity & MAX_QUANTUM] += 1
end
v = pixel_intensity(pixel)
int[v] += 1
end
end
# Scale to chart size. When computing the scale, add some "air" between
# the max frequency and the top of the histogram. This makes a prettier chart.
# The RGBA and intensity histograms are all drawn to the same scale.
max = [red.max, green.max, blue.max, alpha.max, int.max].max
scale = HISTOGRAM_ROWS / (max*AIR_FACTOR)
rgb_histogram = Image.new(HISTOGRAM_COLS, HISTOGRAM_ROWS) {
self.background_color = bg
self.border_color = fg
}
rgb_histogram['Label'] = 'RGB'
red_histogram = rgb_histogram.copy
red_histogram['Label'] = 'Red'
green_histogram = rgb_histogram.copy
green_histogram['Label'] = 'Green'
blue_histogram = rgb_histogram.copy
blue_histogram['Label'] = 'Blue'
int_histogram = rgb_histogram.copy
int_histogram['Label'] = 'Intensity'
int_histogram.matte = true
rgb_column = PixelColumn.new(HISTOGRAM_ROWS)
red_column = PixelColumn.new(HISTOGRAM_ROWS)
green_column = PixelColumn.new(HISTOGRAM_ROWS)
blue_column = PixelColumn.new(HISTOGRAM_ROWS)
int_column = PixelColumn.new(HISTOGRAM_ROWS)
HISTOGRAM_COLS.times do |x|
HISTOGRAM_ROWS.times do |y|
yf = Float(y)
if yf >= HISTOGRAM_ROWS - (red[x] * scale)
red_column[y].red = MaxRGB
red_column[y].green = 0
red_column[y].blue = 0
rgb_column[y].red = MaxRGB
end
if yf >= HISTOGRAM_ROWS - (green[x] * scale)
green_column[y].green = MaxRGB
green_column[y].red = 0
green_column[y].blue = 0
rgb_column[y].green = MaxRGB
end
if yf >= HISTOGRAM_ROWS - (blue[x] * scale)
blue_column[y].blue = MaxRGB
blue_column[y].red = 0
blue_column[y].green = 0
rgb_column[y].blue = MaxRGB
end
if yf >= HISTOGRAM_ROWS - (int[x] * scale)
int_column[y].opacity = TransparentOpacity
end
end
rgb_histogram.store_pixels( x, 0, 1, HISTOGRAM_ROWS, rgb_column)
red_histogram.store_pixels( x, 0, 1, HISTOGRAM_ROWS, red_column)
green_histogram.store_pixels(x, 0, 1, HISTOGRAM_ROWS, green_column)
blue_histogram.store_pixels( x, 0, 1, HISTOGRAM_ROWS, blue_column)
int_histogram.store_pixels( x, 0, 1, HISTOGRAM_ROWS, int_column)
rgb_column.reset(bg)
red_column.reset(bg)
green_column.reset(bg)
blue_column.reset(bg)
int_column.reset(bg)
end
return red_histogram, green_histogram, blue_histogram, rgb_histogram, int_histogram
end
def pixel_intensity(pixel)
(306*(pixel.red & MAX_QUANTUM) + 601*(pixel.green & MAX_QUANTUM) + 117*(pixel.blue & MAX_QUANTUM))/1024
end
# Make the color histogram. Quantize the image to 256 colors if necessary.
def color_hist(fg, bg)
img = number_colors > 256 ? quantize(256) : self
begin
hist = img.color_histogram
rescue NotImplementedError
$stderr.puts "The color_histogram method is not supported by this version "+
"of ImageMagick/GraphicsMagick"
else
pixels = hist.keys.sort_by {|pixel| hist[pixel] }
scale = HISTOGRAM_ROWS / (hist.values.max*AIR_FACTOR)
histogram = Image.new(HISTOGRAM_COLS, HISTOGRAM_ROWS) {
self.background_color = bg
self.border_color = fg
}
x = 0
pixels.each do |pixel|
column = Array.new(HISTOGRAM_ROWS).fill {Pixel.new()}
HISTOGRAM_ROWS.times do |y|
column[y].reset(bg)
if y >= HISTOGRAM_ROWS - (hist[pixel] * scale)
column[y] = pixel;
end
end
histogram.store_pixels(x, 0, 1, HISTOGRAM_ROWS, column)
x = x.succ
end
return histogram
end
end
def psnrchart(fg, bg, mod)
charts = ImageList.new
# Add the thumbnail for original image.
#thumb = copy
#thumb['Label'] = 'Imagem original: \n'+File.basename(thumb.filename)
#charts << thumb
# Add the thumbnail for mod image.
#mod['Label'] = "Imagem modificada: \n"+File.basename(mod.filename)
#charts << mod
# compute psnr and add to ImageList
(diff_img, dbDist) = compare_channel(mod, PeakSignalToNoiseRatioMetric)
diff_img['Label']="Pixels diferentes"
charts << diff_img
# generate info image
info_img = info_text( fg, bg,dbDist);
charts << info_img
# generate color histogram for orig image
orig_hist = color_hist( fg, bg);
orig_hist['Label'] = 'Histograma de freqüencia de cores\n Imagem original'
charts << orig_hist
# generate color histogram for mod
mod_hist = mod.color_hist( fg, bg);
mod_hist['Label'] = 'Histograma de freqüencia de cores\n Imagem modificada'
charts << mod_hist
# Compute the channel and intensity histograms.
(red_hist, green_hist, blue_hist, rgb_hist, int_hist) = channel_histograms(fg, bg)
(m_red_hist, m_green_hist, m_blue_hist, m_rgb_hist, m_int_hist) = mod.channel_histograms(fg, bg)
# Add the RGB histogram
#charts << rgb_hist
charts << int_hist
charts << m_int_hist
# Make a montage.
histogram = charts.montage {
self.background_color = bg
self.stroke = 'transparent'
self.fill = fg
self.border_width = 1
self.tile = "2x3"
self.geometry = "#{HISTOGRAM_COLS}x#{HISTOGRAM_ROWS}+10+10"
}
return histogram
end
end
end
# main
# Get filename from command line.
if !ARGV[1] then
puts "No arguments."
puts "Usage: psnr.rb image1 image2."
exit
else
orig_filename = ARGV[0]
mod_filename = ARGV[1]
end
# Only process first frame if multi-frame image
orig_image = Magick::Image.read(orig_filename)
mod_image = Magick::Image.read(mod_filename)
if orig_image.length > 1 or mod_image.length > 1
puts "Charting 1st image from both files"
end
orig_image = orig_image.first
mod_image = mod_image.first
# Give the user something to look at while we're working.
name = File.basename(mod_filename).sub(/\..*?$/,'')
$defout.sync = true
printf "Creating #{name}_psnr_chart.miff"
timer = Thread.new do
loop do
sleep(1)
printf "."
end
end
# Generate psnr chart
psnrchart=orig_image.psnrchart(Magick::Pixel.from_color('black'), Magick::Pixel.from_color('white'), mod_image)
# Write output file
psnrchart.compression = Magick::ZipCompression
psnrchart.write("./#{name}_psnr_chart.miff")
Thread.kill(timer)
puts "Done!"
exit
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment