Last active
May 1, 2016 00:37
-
-
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 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
# 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 | |
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
# 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 | |
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
# 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